功能更新与UI优化:游戏图标移除、用户名片修复、婚礼红包界面重设计

- 移除聊天室右下角浮动游戏图标(占卜、百家乐、赛马、老虎机)
- 用户名片按钮区:修复已婚/已好友时按钮换行问题,统一单行显示
- 婚礼红包弹窗:重设计为喜庆鲜红背景,领取按钮改为圆形米黄样式
- 新增婚礼红包恢复接口(/wedding/pending-envelopes),刷新后自动恢复领取按钮
- 修复 Alpine :style 字符串覆盖静态 style 导致圆形按钮失效的问题
- 撤职后用户等级改为根据经验值重新计算,不再无条件重置为1
- 管理员修改用户经验值后自动重算等级,有职务用户等级锁定
- 娱乐大厅钓鱼游戏按钮直接调用 startFishing() 简化操作流程
- 新增赛马、占卜、百家乐游戏及相关后端逻辑
This commit is contained in:
2026-03-03 23:19:59 +08:00
parent 602dcd7cf1
commit f45483bcba
32 changed files with 3746 additions and 370 deletions
+224
View File
@@ -0,0 +1,224 @@
<?php
/**
* 文件功能:神秘占卜记录模型
*
* 记录用户每次占卜的签文类型、签文文本和当日加成效果。
* 内嵌 50+ 条签文库,提供当日次数查询和随机抽签方法。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class FortuneLog extends Model
{
protected $fillable = [
'user_id',
'grade',
'text',
'buff_desc',
'is_free',
'cost',
'fortune_date',
];
/**
* 属性类型转换。
*/
protected function casts(): array
{
return [
'is_free' => 'boolean',
'cost' => 'integer',
'fortune_date' => 'date',
];
}
/**
* 占卜用户关联。
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* 查询指定用户今日占卜次数。
*/
public static function todayCount(int $userId): int
{
return static::query()
->where('user_id', $userId)
->where('fortune_date', today())
->count();
}
/**
* 查询指定用户今日最新一条占卜记录。
*/
public static function todayLatest(int $userId): ?static
{
return static::query()
->where('user_id', $userId)
->where('fortune_date', today())
->latest()
->first();
}
/**
* 根据概率配置随机抽取一个签文等级。
*
* @param array $config 游戏配置参数
* @return string 签文等级:jackpot | good | normal | bad | curse
*/
public static function rollGrade(array $config): string
{
$jackpotChance = (int) ($config['jackpot_chance'] ?? 5);
$goodChance = (int) ($config['good_chance'] ?? 20);
$badChance = (int) ($config['bad_chance'] ?? 20);
$curseChance = (int) ($config['curse_chance'] ?? 5);
// normal 占剩余概率
$normalChance = max(0, 100 - $jackpotChance - $goodChance - $badChance - $curseChance);
$rand = random_int(1, 100);
return match (true) {
$rand <= $jackpotChance => 'jackpot',
$rand <= $jackpotChance + $goodChance => 'good',
$rand <= $jackpotChance + $goodChance + $normalChance => 'normal',
$rand <= $jackpotChance + $goodChance + $normalChance + $badChance => 'bad',
default => 'curse',
};
}
/**
* 根据签文等级随机抽取签文内容。
*
* @param string $grade 签文等级
* @return array{text: string, buff_desc: string} 签文文字和加成描述
*/
public static function rollFortune(string $grade): array
{
$library = self::fortuneLibrary();
$pool = $library[$grade] ?? $library['normal'];
$entry = $pool[array_rand($pool)];
return $entry;
}
/**
* 签文库:各等级预设签文(共 55 条)。
*
* @return array<string, array<int, array{text: string, buff_desc: string}>>
*/
private static function fortuneLibrary(): array
{
return [
// ─── 上上签(5条)──────────────────────────────────────
'jackpot' => [
['text' => '龙凤呈祥,万事皆宜。天降鸿运,财源广进!', 'buff_desc' => '✨ 今日金币获取 +30%,经验获取 +20%'],
['text' => '紫气东来,鸿运当头。凡事顺遂,财运亨通!', 'buff_desc' => '✨ 今日金币获取 +30%,魅力增长 +50%'],
['text' => '吉星高照,百事大吉。此乃天赐良机,把握之!', 'buff_desc' => '✨ 今日全属性获取 +25%'],
['text' => '神明庇佑,万难消散。今日出行,无往不利!', 'buff_desc' => '✨ 今日金币获取 +40%'],
['text' => '天时地利人和,三才俱备。大展宏图,正此时也!', 'buff_desc' => '✨ 今日经验获取 +50%,金币 +20%'],
],
// ─── 上签(10条)──────────────────────────────────────
'good' => [
['text' => '春风得意马蹄疾,一日看尽长安花。好运正来,进取可得!', 'buff_desc' => '🌸 今日金币获取 +15%'],
['text' => '风调雨顺,五谷丰登。诸事顺利,喜事临门。', 'buff_desc' => '🌸 今日经验获取 +20%'],
['text' => '柳暗花明又一村,峰回路转现坦途。坚持便是胜利!', 'buff_desc' => '🌸 今日金币及经验各 +10%'],
['text' => '心想事成,万事如意。凡有所求,皆可如愿。', 'buff_desc' => '🌸 今日金币获取 +15%'],
['text' => '鱼跃龙门,一步登天。今日努力,事半功倍!', 'buff_desc' => '🌸 今日经验获取 +25%'],
['text' => '花好月圆,良辰美景。诸事大吉,百福临门。', 'buff_desc' => '🌸 今日魅力增长 +30%'],
['text' => '云开雾散见朝阳,前路光明万里长。好运常伴,前途无量!', 'buff_desc' => '🌸 今日金币获取 +20%'],
['text' => '锦上添花,好事成双。今日诸事皆宜,勇往直前!', 'buff_desc' => '🌸 今日全属性 +10%'],
['text' => '马到成功,旗开得胜。积极行动,收获满满!', 'buff_desc' => '🌸 今日经验获取 +15%'],
['text' => '福星高照,喜气洋洋。和气生财,贵人相助!', 'buff_desc' => '🌸 今日金币 +18%,魅力 +20%'],
],
// ─── 中签(20条)──────────────────────────────────────
'normal' => [
['text' => '守株待兔不如主动出击,时机稍纵即逝,把握当下。', 'buff_desc' => null],
['text' => '平平淡淡才是真,安分守己自太平。此乃中签,宜静不宜动。', 'buff_desc' => null],
['text' => '水至清则无鱼,人至察则无徒。凡事保持平常心。', 'buff_desc' => null],
['text' => '船到桥头自然直,车到山前必有路。莫要忧虑,顺其自然。', 'buff_desc' => null],
['text' => '路漫漫其修远兮,吾将上下而求索。持之以恒,终见曙光。', 'buff_desc' => null],
['text' => '千里之行,始于足下。今日无大凶无大吉,稳中求进。', 'buff_desc' => null],
['text' => '谋事在人,成事在天。尽力而为,其余顺其自然。', 'buff_desc' => null],
['text' => '不以物喜,不以己悲。心态平和,自有一番天地。', 'buff_desc' => null],
['text' => '厚积薄发,积柔成刚。今日蓄力,来日爆发。', 'buff_desc' => null],
['text' => '勤能补拙,笨鸟先飞。平庸亦可,持续努力方为上策。', 'buff_desc' => null],
['text' => '事无大小,做好眼前即是。此签平稳,无风雨亦无彩虹。', 'buff_desc' => null],
['text' => '知足者常乐,贪多者多失。今日知足,便是福气。', 'buff_desc' => null],
['text' => '中庸之道,乃处世良方。不争不抢,随缘自在。', 'buff_desc' => null],
['text' => '晴天要备雨伞,好时要留余地。居安思危,防患未然。', 'buff_desc' => null],
['text' => '静水流深,沉默有力。今日低调行事,暗中蓄势。', 'buff_desc' => null],
['text' => '月有阴晴圆缺,人有悲欢离合。此乃常态,坦然接受。', 'buff_desc' => null],
['text' => '三人行,必有我师。今日宜多学习,少出风头。', 'buff_desc' => null],
['text' => '凡事量力而行,不可强求。今日随缘,顺其自然。', 'buff_desc' => null],
['text' => '忍一时风平浪静,退一步海阔天空。宽容待人,必有回报。', 'buff_desc' => null],
['text' => '心静自然凉,此乃中签之意,平安即是福。', 'buff_desc' => null],
],
// ─── 下签(10条)──────────────────────────────────────
'bad' => [
['text' => '乌云当头,诸事不顺。今日宜静守,切莫轻举妄动。', 'buff_desc' => '😞 今日金币获取 -10%'],
['text' => '时运不济,命途多舛。凡事三思而后行,小心为上。', 'buff_desc' => '😞 今日经验获取 -10%'],
['text' => '逆水行舟,举步维艰。今日诸事宜谨慎,避免重大决策。', 'buff_desc' => '😞 今日金币获取 -10%'],
['text' => '阴云密布,好运暂退。宜韬光养晦,待时机再出。', 'buff_desc' => '😞 今日全属性 -5%'],
['text' => '事与愿违,心力交瘁。今日多加休息,来日再战。', 'buff_desc' => '😞 今日经验获取 -15%'],
['text' => '小人横行,道路坎坷。今日言多必失,祸从口出,慎言!', 'buff_desc' => '😞 今日金币 -10%,魅力 -10%'],
['text' => '财帛散尽,才识运归。今日不宜大额消费,节俭为上。', 'buff_desc' => '😞 今日金币获取 -15%'],
['text' => '阳光总在风雨后,苦尽甘来是常事。今日忍耐,明日可期。', 'buff_desc' => '😞 今日经验获取 -10%'],
['text' => '暗箭难防,处处小心。今日谨慎行事,切勿冒进。', 'buff_desc' => '😞 今日全属性 -8%'],
['text' => '屋漏偏逢连夜雨,行路偏遇顶头风。逆境磨砺,坚持即胜。', 'buff_desc' => '😞 今日金币获取 -12%'],
],
// ─── 大凶签(5条)──────────────────────────────────────
'curse' => [
['text' => '大凶!鬼门大开,诸事皆凶。今日宜闭门避祸,切忌妄动!', 'buff_desc' => '💀 今日金币获取 -25%,经验 -20%'],
['text' => '凶星临头,百事皆忌。今日万事不顺,速速祈神化解!', 'buff_desc' => '💀 今日金币获取 -30%'],
['text' => '阴煞临身,大凶兆也!今日诸事皆休,静待天命。', 'buff_desc' => '💀 今日全属性 -20%'],
['text' => '问此签,凶也大凶。如遇困境,切勿强行克服,顺势而为!', 'buff_desc' => '💀 今日经验获取 -30%,金币 -20%'],
['text' => '天机不可泄露,然此签示警。今日三灾八难,小心谨慎,化凶为吉!', 'buff_desc' => '💀 今日金币获取 -25%,魅力 -15%'],
],
];
}
/**
* 返回签文等级对应的中文名称。
*/
public function gradeLabel(): string
{
return match ($this->grade) {
'jackpot' => '上上签',
'good' => '上签',
'normal' => '中签',
'bad' => '下签',
'curse' => '大凶签',
default => '未知',
};
}
/**
* 返回签文等级对应的颜色(前端展示用)。
*/
public function gradeColor(): string
{
return match ($this->grade) {
'jackpot' => '#f59e0b', // 金色
'good' => '#10b981', // 翠绿
'normal' => '#6b7280', // 灰色
'bad' => '#f97316', // 橙色
'curse' => '#ef4444', // 红色
default => '#6b7280',
};
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
/**
* 文件功能:赛马竞猜下注记录模型
*
* 记录用户在每场赛马中的下注信息、押注马匹、结果和赔付金额。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class HorseBet extends Model
{
protected $fillable = [
'race_id',
'user_id',
'horse_id',
'amount',
'status',
'payout',
];
/**
* 属性类型转换。
*/
protected function casts(): array
{
return [
'horse_id' => 'integer',
'amount' => 'integer',
'payout' => 'integer',
];
}
/**
* 所属场次。
*/
public function race(): BelongsTo
{
return $this->belongsTo(HorseRace::class, 'race_id');
}
/**
* 下注用户。
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
/**
* 文件功能:赛马竞猜局次模型
*
* 代表一场赛马比赛,包含参赛马匹信息、场次状态、
* 注池统计以及赛果信息。提供当前场次查询和赔率计算方法。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class HorseRace extends Model
{
protected $fillable = [
'status',
'bet_opens_at',
'bet_closes_at',
'race_starts_at',
'race_ends_at',
'horses',
'winner_horse_id',
'total_bets',
'total_pool',
'settled_at',
];
/**
* 属性类型转换。
*/
protected function casts(): array
{
return [
'bet_opens_at' => 'datetime',
'bet_closes_at' => 'datetime',
'race_starts_at' => 'datetime',
'race_ends_at' => 'datetime',
'settled_at' => 'datetime',
'horses' => 'array',
'winner_horse_id' => 'integer',
'total_bets' => 'integer',
'total_pool' => 'integer',
];
}
/**
* 本场所有下注记录。
*/
public function bets(): HasMany
{
return $this->hasMany(HorseBet::class, 'race_id');
}
/**
* 判断当前是否在押注时间窗口内。
*/
public function isBettingOpen(): bool
{
return $this->status === 'betting'
&& now()->between($this->bet_opens_at, $this->bet_closes_at);
}
/**
* 查询当前正在进行的场次(状态为 betting 且押注未截止)。
*/
public static function currentRace(): ?static
{
return static::query()
->whereIn('status', ['betting', 'running'])
->latest()
->first();
}
/**
* 生成参赛马匹列表(根据马匹数量随机选名)。
*
* @param int $count 马匹数量
* @return array<int, array{id: int, name: string, emoji: string}>
*/
public static function generateHorses(int $count): array
{
// 可用马匹名池(原版竞技风格)
$namePool = [
['name' => '赤兔', 'emoji' => '🐎'],
['name' => '乌骓', 'emoji' => '🐴'],
['name' => '的卢', 'emoji' => '🎠'],
['name' => '绝影', 'emoji' => '🦄'],
['name' => '紫骍', 'emoji' => '🐎'],
['name' => '爪黄', 'emoji' => '🐴'],
['name' => '汗血', 'emoji' => '🎠'],
['name' => '飞电', 'emoji' => '⚡'],
];
// 随机打乱并取前 N 个
shuffle($namePool);
$selected = array_slice($namePool, 0, $count);
$horses = [];
foreach ($selected as $i => $horse) {
$horses[] = [
'id' => $i + 1,
'name' => $horse['name'],
'emoji' => $horse['emoji'],
];
}
return $horses;
}
/**
* 根据注池计算各马匹实时赔率(彩池制,扣除庄家抽水后按比例分配)。
*
* @param int $horseBetAmounts 各马匹的注额数组 [horse_id => amount]
* @param int $housePercent 庄家抽水百分比
* @return array<int, float> horse_id => 赔率(含本金)
*/
public static function calcOdds(array $horseBetAmounts, int $housePercent): array
{
$totalPool = array_sum($horseBetAmounts);
if ($totalPool <= 0) {
// 尚无下注,返回等额赔率
$count = count($horseBetAmounts);
return array_map(fn () => 1.0, $horseBetAmounts);
}
$netPool = $totalPool * (1 - $housePercent / 100);
$odds = [];
foreach ($horseBetAmounts as $horseId => $amount) {
if ($amount <= 0) {
// 无人押注的马,赔率设为理论最大值
$odds[$horseId] = round($netPool, 2);
} else {
// 赔率 = 净注池 / 该马注额(含本金返还)
$odds[$horseId] = round($netPool / $amount, 2);
}
}
return $odds;
}
}
+60 -1
View File
@@ -1,10 +1,69 @@
<?php
/**
* 文件功能:婚礼红包领取记录模型
*
* 对应 wedding_envelope_claims 表。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class WeddingEnvelopeClaim extends Model
{
//
/**
* 该模型不使用 updated_at 字段。
*/
const UPDATED_AT = null;
/**
* 允许批量赋值的属性。
*
* @var list<string>
*/
protected $fillable = [
'ceremony_id',
'user_id',
'amount',
'claimed',
'claimed_at',
'created_at',
];
/**
* 获取属性类型转换。
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'claimed' => 'boolean',
'claimed_at' => 'datetime',
'created_at' => 'datetime',
'amount' => 'integer',
];
}
/**
* 关联婚礼仪式。
*/
public function ceremony(): BelongsTo
{
return $this->belongsTo(WeddingCeremony::class, 'ceremony_id');
}
/**
* 关联领取用户。
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}