功能:商店完善戒指板块

迁移:
- 2026_03_01_153959:shop_items 增加 intimacy_bonus/charm_bonus 字段

Seeder(RingItemsSeeder):
- 银质戒指 500金  亲密+10 魅力+30
- 黄金戒指 2000金 亲密+30 魅力+80
- 红宝石戒指 8000金 亲密+80 魅力+200
- 钻石戒指 30000金 亲密+200 魅力+500
- 传说神戒 100000金 亲密+500 魅力+1000

ShopService:
- buyItem() 分支加 ring 类型
- buyRing():扣金币 + 写入 active UserPurchase(背包持有)

ShopController::items():
- 返回 intimacy_bonus/charm_bonus
- 统计 ring_counts(各戒指持有数量)

shop-panel.blade.php:
- 新增「💍 求婚戒指」分组(排在最后)
- 图标右上角红色数字徽章(持有时)
- 卡片下方显示亲密度/魅力加成
- 购买按钮与现有逻辑复用
This commit is contained in:
2026-03-01 15:42:25 +08:00
parent 1f33013216
commit 29e43507ac
5 changed files with 209 additions and 2 deletions

View File

@@ -42,13 +42,26 @@ class ShopController extends Controller
'price' => $item->price,
'type' => $item->type,
'duration_days' => $item->duration_days,
'intimacy_bonus' => $item->intimacy_bonus,
'charm_bonus' => $item->charm_bonus,
]);
// 统计背包中各戒指持有数量
$ringCounts = \App\Models\UserPurchase::query()
->where('user_id', $user->id)
->where('status', 'active')
->whereHas('item', fn ($q) => $q->where('type', 'ring'))
->selectRaw('shop_item_id, count(*) as qty')
->groupBy('shop_item_id')
->pluck('qty', 'shop_item_id')
->toArray();
return response()->json([
'items' => $items,
'user_jjb' => $user->jjb ?? 0,
'active_week_effect' => $this->shopService->getActiveWeekEffect($user),
'has_rename_card' => $this->shopService->hasRenameCard($user),
'ring_counts' => $ringCounts, // [item_id => qty]
]);
}

View File

@@ -32,6 +32,7 @@ class ShopService
'instant' => $this->buyInstant($user, $item),
'duration' => $this->buyWeekCard($user, $item),
'one_time' => $this->buyRenameCard($user, $item),
'ring' => $this->buyRing($user, $item),
default => ['ok' => false, 'message' => '未知商品类型'],
};
}
@@ -209,6 +210,31 @@ class ShopService
return $purchase->shopItem->effectKey();
}
/**
* 购买结婚戒指扣金币、写入背包active 状态,等待结婚时消耗)。
*
* @return array{ok:bool, message:string}
*/
public function buyRing(User $user, ShopItem $item): array
{
DB::transaction(function () use ($user, $item): void {
// 扣除金币
$user->decrement('jjb', $item->price);
// 写入背包active = 未使用,求婚时变为 used_pending→used 或 lost
UserPurchase::create([
'user_id' => $user->id,
'shop_item_id' => $item->id,
'status' => 'active',
'price_paid' => $item->price,
]);
});
return [
'ok' => true,
'message' => "💍 {$item->name} 购买成功!已放入背包,可用于求婚。",
];
}
/**
* 获取用户当前激活的改名卡(是否持有未用改名卡)
*/

View File

@@ -0,0 +1,37 @@
<?php
/**
* 文件功能:为 shop_items 表添加戒指属性字段
*
* intimacy_bonus 结婚时初始亲密度加成
* charm_bonus 结婚时双方魅力加成
* 对非戒指类商品均为 NULL,不影响现有数据。
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 添加戒指加成字段。
*/
public function up(): void
{
Schema::table('shop_items', function (Blueprint $table): void {
$table->unsignedSmallInteger('intimacy_bonus')->default(0)->after('duration_days')->comment('结婚初始亲密度加成(戒指专用)');
$table->unsignedSmallInteger('charm_bonus')->default(0)->after('intimacy_bonus')->comment('结婚魅力加成(戒指专用)');
});
}
/**
* 回滚:删除字段。
*/
public function down(): void
{
Schema::table('shop_items', function (Blueprint $table): void {
$table->dropColumn(['intimacy_bonus', 'charm_bonus']);
});
}
};

View File

@@ -0,0 +1,92 @@
<?php
/**
* 文件功能:初始化商店戒指商品数据
*
* 5 档戒指,对应不同价格、亲密度加成、魅力加成。
* 可重复运行updateOrCreate 幂等)。
*/
namespace Database\Seeders;
use App\Models\ShopItem;
use Illuminate\Database\Seeder;
class RingItemsSeeder extends Seeder
{
/**
* 写入 5 档结婚戒指数据。
*/
public function run(): void
{
$rings = [
[
'slug' => 'ring_silver',
'name' => '银质戒指',
'icon' => '💍',
'description' => '朴素银戒,见证两心相许。',
'price' => 500,
'type' => 'ring',
'intimacy_bonus' => 10,
'charm_bonus' => 30,
'sort_order' => 101,
],
[
'slug' => 'ring_gold',
'name' => '黄金戒指',
'icon' => '💛',
'description' => '18K黄金情比金坚。',
'price' => 2000,
'type' => 'ring',
'intimacy_bonus' => 30,
'charm_bonus' => 80,
'sort_order' => 102,
],
[
'slug' => 'ring_ruby',
'name' => '红宝石戒指',
'icon' => '❤️',
'description' => '鸽血红宝,热烈如初恋。',
'price' => 8000,
'type' => 'ring',
'intimacy_bonus' => 80,
'charm_bonus' => 200,
'sort_order' => 103,
],
[
'slug' => 'ring_diamond',
'name' => '钻石戒指',
'icon' => '💎',
'description' => '一克拉钻石,誓言永恒。',
'price' => 30000,
'type' => 'ring',
'intimacy_bonus' => 200,
'charm_bonus' => 500,
'sort_order' => 104,
],
[
'slug' => 'ring_legendary',
'name' => '传说神戒',
'icon' => '🌟',
'description' => '世间仅此一款,天命之环,天作之合。',
'price' => 100000,
'type' => 'ring',
'intimacy_bonus' => 500,
'charm_bonus' => 1000,
'sort_order' => 105,
],
];
foreach ($rings as $ring) {
ShopItem::updateOrCreate(
['slug' => $ring['slug']],
array_merge($ring, [
'is_active' => true,
'duration_days' => null,
])
);
}
$this->command->info('✅ 5 枚结婚戒指已写入 shop_items。');
}
}

View File

@@ -318,6 +318,8 @@
badge.style.display = 'inline-block';
}
const ringCounts = data.ring_counts || {};
const groups = [{
label: '⚡ 单次特效卡',
desc: '立即播放一次,仅自己可见',
@@ -333,6 +335,11 @@
desc: '',
type: 'one_time'
},
{
label: '💍 求婚戒指',
desc: '购买后存入背包,求婚时消耗(若被拒绝则遗失)',
type: 'ring'
},
];
const itemsEl = document.getElementById('shop-items-list');
@@ -361,6 +368,8 @@
items.forEach(item => {
const isRename = item.slug === 'rename_card';
const canUseRename = isRename && data.has_rename_card;
const isRing = item.type === 'ring';
const ownedQty = isRing ? (ringCounts[item.id] || 0) : 0;
const card = document.createElement('div');
card.className = 'shop-card';
@@ -369,11 +378,22 @@
const row = document.createElement('div');
row.className = 'shop-card-row';
// 图标
// 图标区(戒指加持有数徽标)
const iconWrap = document.createElement('span');
iconWrap.style.cssText =
'position:relative; flex-shrink:0; width:28px; text-align:center;';
const icon = document.createElement('span');
icon.className = 'shop-card-icon';
icon.textContent = item.icon;
row.appendChild(icon);
iconWrap.appendChild(icon);
if (isRing && ownedQty > 0) {
const badge = document.createElement('span');
badge.style.cssText =
'position:absolute; top:-4px; right:-4px; background:#f43f5e; color:#fff; font-size:8px; font-weight:800; min-width:14px; height:14px; border-radius:7px; text-align:center; line-height:14px; padding:0 2px;';
badge.textContent = ownedQty;
iconWrap.appendChild(badge);
}
row.appendChild(iconWrap);
// 名称
const name = document.createElement('span');
@@ -404,6 +424,25 @@
card.appendChild(desc);
}
// 戒指:加成信息行
if (isRing && (item.intimacy_bonus > 0 || item.charm_bonus > 0)) {
const bonus = document.createElement('div');
bonus.style.cssText =
'font-size:9px; color:#f43f5e; margin-top:3px; display:flex; gap:8px;';
if (item.intimacy_bonus > 0) {
const b1 = document.createElement('span');
b1.textContent = `💞 亲密 +${item.intimacy_bonus}`;
bonus.appendChild(b1);
}
if (item.charm_bonus > 0) {
const b2 = document.createElement('span');
b2.style.color = '#a855f7';
b2.textContent = `✨ 魅力 +${item.charm_bonus}`;
bonus.appendChild(b2);
}
card.appendChild(bonus);
}
section.appendChild(card);
});