feat: 神秘箱子系统完整实现 + 婚姻状态弹窗 + 工具栏优化
## 新功能 - 神秘箱子系统(MysteryBox)完整实现: - 新增 MysteryBox / MysteryBoxClaim 模型及迁移文件 - DropMysteryBoxJob / ExpireMysteryBoxJob 队列作业 - MysteryBoxController(/mystery-box/status + /mystery-box/claim) - 支持三种类型:普通箱(500~2000金)/ 稀有箱(5000~20000金)/ 黑化箱(陷阱扣200~1000金) - 调度器自动投放 + 管理员手动投放 - CurrencySource 新增 MYSTERY_BOX / MYSTERY_BOX_TRAP 枚举 - 婚姻状态弹窗(工具栏「婚姻」按钮): - 工具栏「呼叫」改为「婚姻」,点击打开婚姻状态弹窗 - 动态渲染三种状态:单身 / 求婚中 / 已婚 - 被求婚方可直接「答应 / 婉拒」;已婚可申请离婚(含二次确认) ## 优化修复 - frame.blade.php:Alpine.js CDN 补加 defer,修复所有组件初始化报错 - scripts.blade.php:神秘箱子暗号主动拦截(不依赖轮询),领取成功后弹 chatDialog 展示结果,更新金币余额 - MysteryBoxController:claim() 时 change() 补传 room_id 记录来源房间 - 后台游戏管理页(game-configs):投放箱子按钮颜色修复;弹窗替换为 window.adminDialog - admin/layouts:新增全局 adminDialog 弹窗组件(替代原生 alert/confirm) - baccarat-panel:FAB 拖动重构为 Alpine.js baccaratFab() 组件,与 slotFab 一致 - GAMES_TODO.md:神秘箱子移入已完成区,补全修复记录
This commit is contained in:
@@ -96,6 +96,12 @@ enum CurrencySource: string
|
||||
/** 领取礼包红包——经验(用户抢到经验礼包时收入) */
|
||||
case RED_PACKET_RECV_EXP = 'red_packet_recv_exp';
|
||||
|
||||
/** 神秘箱子——领取奖励(普通箱/稀有箱,正数金币) */
|
||||
case MYSTERY_BOX = 'mystery_box';
|
||||
|
||||
/** 神秘箱子——黑化陷阱(倒扣金币,负数) */
|
||||
case MYSTERY_BOX_TRAP = 'mystery_box_trap';
|
||||
|
||||
/**
|
||||
* 返回该来源的中文名称,用于后台统计展示。
|
||||
*/
|
||||
@@ -127,6 +133,8 @@ enum CurrencySource: string
|
||||
self::SLOT_CURSE => '老虎机诅咒',
|
||||
self::RED_PACKET_RECV => '领取礼包红包(金币)',
|
||||
self::RED_PACKET_RECV_EXP => '领取礼包红包(经验)',
|
||||
self::MYSTERY_BOX => '神秘箱子奖励',
|
||||
self::MYSTERY_BOX_TRAP => '神秘箱子陷阱',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,4 +69,36 @@ class GameConfigController extends Controller
|
||||
|
||||
return back()->with('success', "「{$gameConfig->name}」参数已保存!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员手动投放神秘箱子。
|
||||
*
|
||||
* 立即分发 DropMysteryBoxJob 到队列,由 Horizon 执行箱子投放和公屏广播。
|
||||
*/
|
||||
public function dropMysteryBox(Request $request): JsonResponse
|
||||
{
|
||||
if (! \App\Models\GameConfig::isEnabled('mystery_box')) {
|
||||
return response()->json(['ok' => false, 'message' => '神秘箱子功能未开放,请先开启。']);
|
||||
}
|
||||
|
||||
$boxType = $request->input('box_type', 'normal');
|
||||
|
||||
if (! in_array($boxType, ['normal', 'rare', 'trap'], true)) {
|
||||
return response()->json(['ok' => false, 'message' => '无效的箱子类型。']);
|
||||
}
|
||||
|
||||
// 检查是否有正在开放的箱子(避免同时多个)
|
||||
if (\App\Models\MysteryBox::currentOpenBox()) {
|
||||
return response()->json(['ok' => false, 'message' => '当前已有一个神秘箱子正在等待领取,请等它结束后再投放。']);
|
||||
}
|
||||
|
||||
\App\Jobs\DropMysteryBoxJob::dispatch($boxType, 1, null, (int) auth()->id());
|
||||
|
||||
$typeNames = ['normal' => '普通箱', 'rare' => '稀有箱', 'trap' => '黑化箱'];
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => "✅ 已投放「{$typeNames[$boxType]}」到 #1 房间,暗号将实时发送到公屏!",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:神秘箱子前台控制器
|
||||
*
|
||||
* 提供神秘箱子相关接口:
|
||||
* - /mystery-box/status 查询当前可领取的箱子(给前端轮询)
|
||||
* - /mystery-box/claim 用户发送暗号领取箱子
|
||||
*
|
||||
* 领取流程:
|
||||
* 1. 用户在聊天框发送暗号(前端拦截后调用此接口)
|
||||
* 2. 验证暗号匹配、箱子未过期、未已领取
|
||||
* 3. 随机奖励金额(trap=扣,其余=加)
|
||||
* 4. 写货币流水日志
|
||||
* 5. 公屏广播结果(中奖/踩雷)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Events\MessageSent;
|
||||
use App\Jobs\SaveMessageJob;
|
||||
use App\Models\GameConfig;
|
||||
use App\Models\MysteryBox;
|
||||
use App\Models\MysteryBoxClaim;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\UserCurrencyService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MysteryBoxController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserCurrencyService $currency,
|
||||
private readonly ChatStateService $chatState,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 查询当前可领取的箱子状态(给前端轮询/显示用)。
|
||||
*/
|
||||
public function status(): JsonResponse
|
||||
{
|
||||
if (! GameConfig::isEnabled('mystery_box')) {
|
||||
return response()->json(['active' => false]);
|
||||
}
|
||||
|
||||
$box = MysteryBox::currentOpenBox();
|
||||
|
||||
if (! $box) {
|
||||
return response()->json(['active' => false]);
|
||||
}
|
||||
|
||||
// 计算剩余时间
|
||||
$secondsLeft = $box->expires_at ? max(0, now()->diffInSeconds($box->expires_at, false)) : null;
|
||||
|
||||
return response()->json([
|
||||
'active' => true,
|
||||
'box_id' => $box->id,
|
||||
'box_type' => $box->box_type,
|
||||
'type_name' => $box->typeName(),
|
||||
'type_emoji' => $box->typeEmoji(),
|
||||
'passcode' => $box->passcode,
|
||||
'seconds_left' => $secondsLeft,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户用暗号领取箱子。
|
||||
*/
|
||||
public function claim(Request $request): JsonResponse
|
||||
{
|
||||
if (! GameConfig::isEnabled('mystery_box')) {
|
||||
return response()->json(['ok' => false, 'message' => '神秘箱子功能未开放。']);
|
||||
}
|
||||
|
||||
$passcode = strtoupper(trim((string) $request->input('passcode', '')));
|
||||
|
||||
if ($passcode === '') {
|
||||
return response()->json(['ok' => false, 'message' => '请输入暗号。']);
|
||||
}
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
return DB::transaction(function () use ($user, $passcode): JsonResponse {
|
||||
// 查找匹配暗号的可领取箱子(加锁防并发)
|
||||
$box = MysteryBox::query()
|
||||
->where('passcode', $passcode)
|
||||
->where('status', 'open')
|
||||
->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
if (! $box) {
|
||||
return response()->json(['ok' => false, 'message' => '暗号不正确,或箱子已被领走/已过期。']);
|
||||
}
|
||||
|
||||
// ① 随机奖励金额
|
||||
$reward = $box->rollReward();
|
||||
|
||||
// ② 货币变更
|
||||
$source = $reward >= 0 ? CurrencySource::MYSTERY_BOX : CurrencySource::MYSTERY_BOX_TRAP;
|
||||
$remark = $reward >= 0
|
||||
? "神秘箱子【{$box->typeName()}】奖励"
|
||||
: "神秘箱子【黑化箱】陷阱扣除";
|
||||
|
||||
$this->currency->change($user, 'gold', $reward, $source, $remark, $box->room_id);
|
||||
|
||||
// ③ 写领取记录 + 更新箱子状态
|
||||
MysteryBoxClaim::create([
|
||||
'mystery_box_id' => $box->id,
|
||||
'user_id' => $user->id,
|
||||
'reward_amount' => $reward,
|
||||
]);
|
||||
|
||||
$box->update(['status' => 'claimed']);
|
||||
|
||||
// ④ 公屏广播结果
|
||||
$user->refresh();
|
||||
$this->broadcastResult($box, $user->username, $reward);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'reward' => $reward,
|
||||
'balance' => $user->jjb ?? 0,
|
||||
'message' => $reward >= 0
|
||||
? "🎉 恭喜!开箱获得 +{$reward} 金币!"
|
||||
: "☠️ 中了黑化陷阱!扣除 " . abs($reward) . ' 金币!',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 公屏广播开箱结果。
|
||||
*
|
||||
* @param MysteryBox $box 箱子实例
|
||||
* @param string $username 领取者用户名
|
||||
* @param int $reward 奖励金额(正/负)
|
||||
*/
|
||||
private function broadcastResult(MysteryBox $box, string $username, int $reward): void
|
||||
{
|
||||
$emoji = $box->typeEmoji();
|
||||
$typeName = $box->typeName();
|
||||
|
||||
if ($reward >= 0) {
|
||||
$content = "{$emoji}【开箱播报】恭喜 【{$username}】 抢到了神秘{$typeName}!"
|
||||
. "获得 🪙" . number_format($reward) . " 金币!";
|
||||
$color = $box->box_type === 'rare' ? '#c4b5fd' : '#34d399';
|
||||
} else {
|
||||
$content = "☠️【黑化陷阱】haha!【{$username}】 中了神秘黑化箱的陷阱!"
|
||||
. "被扣除 🪙" . number_format(abs($reward)) . " 金币!点背~";
|
||||
$color = '#f87171';
|
||||
}
|
||||
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId(1),
|
||||
'room_id' => 1,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => '大家',
|
||||
'content' => $content,
|
||||
'is_secret' => false,
|
||||
'font_color' => $color,
|
||||
'action' => '大声宣告',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage(1, $msg);
|
||||
broadcast(new MessageSent(1, $msg));
|
||||
SaveMessageJob::dispatch($msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:投放神秘箱子队列任务
|
||||
*
|
||||
* 由调度器或管理员手动触发,执行以下操作:
|
||||
* 1. 创建神秘箱记录(含暗号、类型、奖励范围)
|
||||
* 2. 公屏广播暗号提示(全服可见)
|
||||
* 3. 设定定时关闭任务(windows_seconds 秒后过期)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Events\MessageSent;
|
||||
use App\Models\GameConfig;
|
||||
use App\Models\MysteryBox;
|
||||
use App\Services\ChatStateService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DropMysteryBoxJob implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* 最大重试次数。
|
||||
*/
|
||||
public int $tries = 1;
|
||||
|
||||
/**
|
||||
* @param string $boxType 箱子类型:normal | rare | trap
|
||||
* @param int|null $roomId 投放目标房间,null 时默认 1
|
||||
* @param string|null $passcode 手动指定暗号(null=自动生成)
|
||||
* @param int|null $droppedBy 投放者用户ID(null=系统自动)
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $boxType = 'normal',
|
||||
public readonly ?int $roomId = 1,
|
||||
public readonly ?string $passcode = null,
|
||||
public readonly ?int $droppedBy = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 执行投放逻辑。
|
||||
*/
|
||||
public function handle(ChatStateService $chatState): void
|
||||
{
|
||||
// 检查游戏是否开启
|
||||
if (! GameConfig::isEnabled('mystery_box')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = GameConfig::forGame('mystery_box')?->params ?? [];
|
||||
$claimWindow = (int) ($config['claim_window_seconds'] ?? 120);
|
||||
$targetRoom = $this->roomId ?? 1;
|
||||
|
||||
// 自动生成随机暗号(若未指定)
|
||||
$passcode = $this->passcode ?? strtoupper(Str::random(6));
|
||||
|
||||
// 根据类型确定奖励范围
|
||||
[$rewardMin, $rewardMax] = match ($this->boxType) {
|
||||
'rare' => [
|
||||
(int) ($config['rare_reward_min'] ?? 5000),
|
||||
(int) ($config['rare_reward_max'] ?? 20000),
|
||||
],
|
||||
'trap' => [
|
||||
(int) ($config['trap_penalty_min'] ?? 200),
|
||||
(int) ($config['trap_penalty_max'] ?? 1000),
|
||||
],
|
||||
default => [
|
||||
(int) ($config['normal_reward_min'] ?? 500),
|
||||
(int) ($config['normal_reward_max'] ?? 2000),
|
||||
],
|
||||
};
|
||||
|
||||
// 创建箱子记录
|
||||
$box = MysteryBox::create([
|
||||
'box_type' => $this->boxType,
|
||||
'passcode' => $passcode,
|
||||
'reward_min' => $rewardMin,
|
||||
'reward_max' => $rewardMax,
|
||||
'status' => 'open',
|
||||
'expires_at' => now()->addSeconds($claimWindow),
|
||||
'dropped_by' => $this->droppedBy,
|
||||
]);
|
||||
|
||||
// 公屏广播暗号提示
|
||||
$emoji = $box->typeEmoji();
|
||||
$typeName = $box->typeName();
|
||||
$source = $this->droppedBy ? '管理员' : '系统';
|
||||
|
||||
$content = "{$emoji}【{$typeName}】{$source}投放了一个神秘箱子!"
|
||||
. "发送暗号「{$passcode}」即可开箱!限时 {$claimWindow} 秒,先到先得!";
|
||||
|
||||
$msg = [
|
||||
'id' => $chatState->nextMessageId($targetRoom),
|
||||
'room_id' => $targetRoom,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => '大家',
|
||||
'content' => $content,
|
||||
'is_secret' => false,
|
||||
'font_color' => match ($this->boxType) {
|
||||
'rare' => '#c4b5fd',
|
||||
'trap' => '#f87171',
|
||||
default => '#34d399',
|
||||
},
|
||||
'action' => '大声宣告',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$chatState->pushMessage($targetRoom, $msg);
|
||||
broadcast(new MessageSent($targetRoom, $msg));
|
||||
SaveMessageJob::dispatch($msg);
|
||||
|
||||
// 定时关闭任务:到期后将箱子标记为 expired
|
||||
ExpireMysteryBoxJob::dispatch($box->id)->delay(now()->addSeconds($claimWindow + 5));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:过期神秘箱子队列任务
|
||||
*
|
||||
* 在箱子到期后将其状态更新为 expired(若尚未被领取),并向公屏广播过期通知。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Events\MessageSent;
|
||||
use App\Models\MysteryBox;
|
||||
use App\Services\ChatStateService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class ExpireMysteryBoxJob implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* 最大重试次数。
|
||||
*/
|
||||
public int $tries = 1;
|
||||
|
||||
/**
|
||||
* @param int $boxId 要过期的箱子ID
|
||||
*/
|
||||
public function __construct(public readonly int $boxId) {}
|
||||
|
||||
/**
|
||||
* 执行过期逻辑。
|
||||
*/
|
||||
public function handle(ChatStateService $chatState): void
|
||||
{
|
||||
$box = MysteryBox::find($this->boxId);
|
||||
|
||||
// 箱子不存在或已经被领取/过期,跳过
|
||||
if (! $box || $box->status !== 'open') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 标记为过期
|
||||
$box->update(['status' => 'expired']);
|
||||
|
||||
// 公屏广播过期通知
|
||||
$msg = [
|
||||
'id' => $chatState->nextMessageId(1),
|
||||
'room_id' => 1,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => '大家',
|
||||
'content' => "⏰ 神秘箱子(暗号:{$box->passcode})已超时,箱子消失了!下次要快哦~",
|
||||
'is_secret' => false,
|
||||
'font_color' => '#9ca3af',
|
||||
'action' => '大声宣告',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$chatState->pushMessage(1, $msg);
|
||||
broadcast(new MessageSent(1, $msg));
|
||||
SaveMessageJob::dispatch($msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:神秘箱子模型
|
||||
*
|
||||
* 管理聊天室内投放的神秘箱记录,提供领取状态管理及类型标签工具方法。
|
||||
* 对应表:mystery_boxes
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
class MysteryBox extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'box_type',
|
||||
'passcode',
|
||||
'reward_min',
|
||||
'reward_max',
|
||||
'status',
|
||||
'expires_at',
|
||||
'dropped_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* 属性类型转换。
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'reward_min' => 'integer',
|
||||
'reward_max' => 'integer',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
// ─── 关联关系 ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 领取记录(一个箱子只能被一人领取,但关联为 HasOne)
|
||||
*/
|
||||
public function claim(): HasOne
|
||||
{
|
||||
return $this->hasOne(MysteryBoxClaim::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有领取记录(逻辑上只有一条,保留 HasMany 供统计使用)
|
||||
*/
|
||||
public function claims(): HasMany
|
||||
{
|
||||
return $this->hasMany(MysteryBoxClaim::class);
|
||||
}
|
||||
|
||||
// ─── 查询作用域 ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 当前可领取(open 状态 + 未过期)的箱子。
|
||||
*/
|
||||
public static function currentOpenBox(): ?static
|
||||
{
|
||||
return static::query()
|
||||
->where('status', 'open')
|
||||
->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))
|
||||
->latest()
|
||||
->first();
|
||||
}
|
||||
|
||||
// ─── 工具方法 ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 返回箱子类型的 emoji 前缀。
|
||||
*/
|
||||
public function typeEmoji(): string
|
||||
{
|
||||
return match ($this->box_type) {
|
||||
'normal' => '📦',
|
||||
'rare' => '💎',
|
||||
'trap' => '☠️',
|
||||
default => '📦',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回箱子类型中文名称。
|
||||
*/
|
||||
public function typeName(): string
|
||||
{
|
||||
return match ($this->box_type) {
|
||||
'normal' => '普通箱',
|
||||
'rare' => '稀有箱',
|
||||
'trap' => '黑化箱',
|
||||
default => '神秘箱',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机生成奖励金额(trap 类型为负数)。
|
||||
*/
|
||||
public function rollReward(): int
|
||||
{
|
||||
$amount = random_int(
|
||||
min(abs($this->reward_min), abs($this->reward_max)),
|
||||
max(abs($this->reward_min), abs($this->reward_max)),
|
||||
);
|
||||
|
||||
// trap 类型:倒扣金币(负数)
|
||||
return $this->box_type === 'trap' ? -$amount : $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断箱子是否已过期
|
||||
*/
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->expires_at !== null && $this->expires_at->isPast();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:神秘箱子领取记录模型
|
||||
*
|
||||
* 记录每次神秘箱被用户发送暗号成功领取后的详情(关联箱子 + 领取者 + 实际奖励)。
|
||||
* 对应表:mystery_box_claims
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MysteryBoxClaim extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'mystery_box_id',
|
||||
'user_id',
|
||||
'reward_amount',
|
||||
];
|
||||
|
||||
/**
|
||||
* 属性类型转换。
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'reward_amount' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
// ─── 关联关系 ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 关联神秘箱子。
|
||||
*/
|
||||
public function mysteryBox(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(MysteryBox::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联领取用户。
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user