功能:商店完善戒指板块
迁移:
- 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:
@@ -42,13 +42,26 @@ class ShopController extends Controller
|
|||||||
'price' => $item->price,
|
'price' => $item->price,
|
||||||
'type' => $item->type,
|
'type' => $item->type,
|
||||||
'duration_days' => $item->duration_days,
|
'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([
|
return response()->json([
|
||||||
'items' => $items,
|
'items' => $items,
|
||||||
'user_jjb' => $user->jjb ?? 0,
|
'user_jjb' => $user->jjb ?? 0,
|
||||||
'active_week_effect' => $this->shopService->getActiveWeekEffect($user),
|
'active_week_effect' => $this->shopService->getActiveWeekEffect($user),
|
||||||
'has_rename_card' => $this->shopService->hasRenameCard($user),
|
'has_rename_card' => $this->shopService->hasRenameCard($user),
|
||||||
|
'ring_counts' => $ringCounts, // [item_id => qty]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class ShopService
|
|||||||
'instant' => $this->buyInstant($user, $item),
|
'instant' => $this->buyInstant($user, $item),
|
||||||
'duration' => $this->buyWeekCard($user, $item),
|
'duration' => $this->buyWeekCard($user, $item),
|
||||||
'one_time' => $this->buyRenameCard($user, $item),
|
'one_time' => $this->buyRenameCard($user, $item),
|
||||||
|
'ring' => $this->buyRing($user, $item),
|
||||||
default => ['ok' => false, 'message' => '未知商品类型'],
|
default => ['ok' => false, 'message' => '未知商品类型'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -209,6 +210,31 @@ class ShopService
|
|||||||
return $purchase->shopItem->effectKey();
|
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} 购买成功!已放入背包,可用于求婚。",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户当前激活的改名卡(是否持有未用改名卡)
|
* 获取用户当前激活的改名卡(是否持有未用改名卡)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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。');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -318,6 +318,8 @@
|
|||||||
badge.style.display = 'inline-block';
|
badge.style.display = 'inline-block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ringCounts = data.ring_counts || {};
|
||||||
|
|
||||||
const groups = [{
|
const groups = [{
|
||||||
label: '⚡ 单次特效卡',
|
label: '⚡ 单次特效卡',
|
||||||
desc: '立即播放一次,仅自己可见',
|
desc: '立即播放一次,仅自己可见',
|
||||||
@@ -333,6 +335,11 @@
|
|||||||
desc: '',
|
desc: '',
|
||||||
type: 'one_time'
|
type: 'one_time'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '💍 求婚戒指',
|
||||||
|
desc: '购买后存入背包,求婚时消耗(若被拒绝则遗失)',
|
||||||
|
type: 'ring'
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const itemsEl = document.getElementById('shop-items-list');
|
const itemsEl = document.getElementById('shop-items-list');
|
||||||
@@ -361,6 +368,8 @@
|
|||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const isRename = item.slug === 'rename_card';
|
const isRename = item.slug === 'rename_card';
|
||||||
const canUseRename = isRename && data.has_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');
|
const card = document.createElement('div');
|
||||||
card.className = 'shop-card';
|
card.className = 'shop-card';
|
||||||
@@ -369,11 +378,22 @@
|
|||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'shop-card-row';
|
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');
|
const icon = document.createElement('span');
|
||||||
icon.className = 'shop-card-icon';
|
icon.className = 'shop-card-icon';
|
||||||
icon.textContent = item.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');
|
const name = document.createElement('span');
|
||||||
@@ -404,6 +424,25 @@
|
|||||||
card.appendChild(desc);
|
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);
|
section.appendChild(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user