新增老虎机游戏:①slot_machine_logs表+模型(8种权重图案/判奖) ②SlotMachineController(扣费/随机/赔付/诅咒/三7全服广播) ③前台面板(三列滚轮动画/逐列停止/赔率说明/历史记录) ④CurrencySource三个枚举
This commit is contained in:
@@ -81,6 +81,15 @@ enum CurrencySource: string
|
|||||||
/** 星海小博士随机事件(好运/坏运/经验/金币奖惩) */
|
/** 星海小博士随机事件(好运/坏运/经验/金币奖惩) */
|
||||||
case AUTO_EVENT = 'auto_event';
|
case AUTO_EVENT = 'auto_event';
|
||||||
|
|
||||||
|
/** 老虎机转动消耗金币 */
|
||||||
|
case SLOT_SPIN = 'slot_spin';
|
||||||
|
|
||||||
|
/** 老虎机中奖赔付(含本金返还) */
|
||||||
|
case SLOT_WIN = 'slot_win';
|
||||||
|
|
||||||
|
/** 老虎机诅咒额外扣除 */
|
||||||
|
case SLOT_CURSE = 'slot_curse';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回该来源的中文名称,用于后台统计展示。
|
* 返回该来源的中文名称,用于后台统计展示。
|
||||||
*/
|
*/
|
||||||
@@ -107,6 +116,9 @@ enum CurrencySource: string
|
|||||||
self::BACCARAT_BET => '百家乐下注',
|
self::BACCARAT_BET => '百家乐下注',
|
||||||
self::BACCARAT_WIN => '百家乐赢钱',
|
self::BACCARAT_WIN => '百家乐赢钱',
|
||||||
self::AUTO_EVENT => '随机事件(星海小博士)',
|
self::AUTO_EVENT => '随机事件(星海小博士)',
|
||||||
|
self::SLOT_SPIN => '老虎机转动',
|
||||||
|
self::SLOT_WIN => '老虎机中奖',
|
||||||
|
self::SLOT_CURSE => '老虎机诅咒',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件功能:老虎机游戏前台控制器
|
||||||
|
*
|
||||||
|
* 提供老虎机转动 API:
|
||||||
|
* - 检查游戏开关、每日限制
|
||||||
|
* - 扣除金币、生成三列图案
|
||||||
|
* - 判断结果、赔付金币、写流水
|
||||||
|
* - 三个7时全服公屏广播
|
||||||
|
* - 返回结果供前端播放动画
|
||||||
|
*
|
||||||
|
* @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\SlotMachineLog;
|
||||||
|
use App\Services\ChatStateService;
|
||||||
|
use App\Services\UserCurrencyService;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class SlotMachineController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UserCurrencyService $currency,
|
||||||
|
private readonly ChatStateService $chatState,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取老虎机配置信息(图案表、赔率、今日剩余次数)。
|
||||||
|
*/
|
||||||
|
public function info(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
if (! GameConfig::isEnabled('slot_machine')) {
|
||||||
|
return response()->json(['enabled' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = GameConfig::forGame('slot_machine')?->params ?? [];
|
||||||
|
$user = $request->user();
|
||||||
|
$dailyLimit = (int) ($config['daily_limit'] ?? 0);
|
||||||
|
$usedToday = 0;
|
||||||
|
|
||||||
|
if ($dailyLimit > 0) {
|
||||||
|
$usedToday = SlotMachineLog::query()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->whereDate('created_at', today())
|
||||||
|
->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'enabled' => true,
|
||||||
|
'cost_per_spin' => (int) ($config['cost_per_spin'] ?? 100),
|
||||||
|
'daily_limit' => $dailyLimit,
|
||||||
|
'used_today' => $usedToday,
|
||||||
|
'remaining' => $dailyLimit > 0 ? max(0, $dailyLimit - $usedToday) : null,
|
||||||
|
'symbols' => collect(SlotMachineLog::symbols())->map(fn ($s) => $s['emoji']),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一次转动。
|
||||||
|
*
|
||||||
|
* 流程:检查可玩 → 扣费 → 摇号 → 赔付 → 写日志 → 全服广播(三7)→ 返回结果
|
||||||
|
*/
|
||||||
|
public function spin(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
if (! GameConfig::isEnabled('slot_machine')) {
|
||||||
|
return response()->json(['ok' => false, 'message' => '老虎机未开放。']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = GameConfig::forGame('slot_machine')?->params ?? [];
|
||||||
|
$cost = (int) ($config['cost_per_spin'] ?? 100);
|
||||||
|
$dailyLimit = (int) ($config['daily_limit'] ?? 0);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// 金币余额检查
|
||||||
|
if (($user->jjb ?? 0) < $cost) {
|
||||||
|
return response()->json(['ok' => false, 'message' => "金币不足,每次转动需 {$cost} 金币。"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每日次数限制检查
|
||||||
|
if ($dailyLimit > 0) {
|
||||||
|
$usedToday = SlotMachineLog::query()
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->whereDate('created_at', today())
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if ($usedToday >= $dailyLimit) {
|
||||||
|
return response()->json(['ok' => false, 'message' => "今日已转动 {$dailyLimit} 次,明日再来!"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($user, $cost, $config): JsonResponse {
|
||||||
|
// ① 扣费
|
||||||
|
$this->currency->change(
|
||||||
|
$user,
|
||||||
|
'gold',
|
||||||
|
-$cost,
|
||||||
|
CurrencySource::SLOT_SPIN,
|
||||||
|
'老虎机转动消耗',
|
||||||
|
);
|
||||||
|
|
||||||
|
// ② 摇号
|
||||||
|
$r1 = SlotMachineLog::randomSymbol();
|
||||||
|
$r2 = SlotMachineLog::randomSymbol();
|
||||||
|
$r3 = SlotMachineLog::randomSymbol();
|
||||||
|
|
||||||
|
$resultType = SlotMachineLog::judgeResult($r1, $r2, $r3);
|
||||||
|
$symbols = SlotMachineLog::symbols();
|
||||||
|
|
||||||
|
// ③ 计算赔付金额
|
||||||
|
$payout = $this->calcPayout($resultType, $cost, $config);
|
||||||
|
|
||||||
|
// ④ 赔付金币
|
||||||
|
if ($payout > 0) {
|
||||||
|
$this->currency->change(
|
||||||
|
$user,
|
||||||
|
'gold',
|
||||||
|
$payout,
|
||||||
|
CurrencySource::SLOT_WIN,
|
||||||
|
"老虎机 {$resultType} 中奖",
|
||||||
|
);
|
||||||
|
} elseif ($resultType === 'curse' && ($config['curse_enabled'] ?? true)) {
|
||||||
|
// 诅咒:再扣一倍本金
|
||||||
|
$cursePenalty = $cost;
|
||||||
|
if (($user->jjb ?? 0) >= $cursePenalty) {
|
||||||
|
$this->currency->change(
|
||||||
|
$user,
|
||||||
|
'gold',
|
||||||
|
-$cursePenalty,
|
||||||
|
CurrencySource::SLOT_CURSE,
|
||||||
|
'老虎机三骷髅诅咒额外扣除',
|
||||||
|
);
|
||||||
|
$payout = -$cursePenalty; // 净损失 = 本金+惩罚
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⑤ 写游戏日志
|
||||||
|
SlotMachineLog::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'reel1' => $r1,
|
||||||
|
'reel2' => $r2,
|
||||||
|
'reel3' => $r3,
|
||||||
|
'result_type' => $resultType,
|
||||||
|
'cost' => $cost,
|
||||||
|
'payout' => $payout,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ⑥ 三个7:全服公屏广播
|
||||||
|
if ($resultType === 'jackpot') {
|
||||||
|
$this->broadcastJackpot($user->username, $payout, $cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'ok' => true,
|
||||||
|
'reels' => [$r1, $r2, $r3],
|
||||||
|
'emojis' => [
|
||||||
|
$symbols[$r1]['emoji'],
|
||||||
|
$symbols[$r2]['emoji'],
|
||||||
|
$symbols[$r3]['emoji'],
|
||||||
|
],
|
||||||
|
'result_type' => $resultType,
|
||||||
|
'result_label' => SlotMachineLog::resultLabel($resultType),
|
||||||
|
'payout' => $payout, // 净变化(正=赢,负=额外亏)
|
||||||
|
'net_change' => $payout - $cost, // 相对本金的盈亏(debug 用)
|
||||||
|
'balance' => $user->jjb ?? 0,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询最近10条个人记录。
|
||||||
|
*/
|
||||||
|
public function history(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$logs = SlotMachineLog::query()
|
||||||
|
->where('user_id', $request->user()->id)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->limit(10)
|
||||||
|
->get(['id', 'reel1', 'reel2', 'reel3', 'result_type', 'cost', 'payout', 'created_at']);
|
||||||
|
|
||||||
|
$symbols = SlotMachineLog::symbols();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'history' => $logs->map(fn ($l) => [
|
||||||
|
'emojis' => [$symbols[$l->reel1]['emoji'], $symbols[$l->reel2]['emoji'], $symbols[$l->reel3]['emoji']],
|
||||||
|
'result_label' => SlotMachineLog::resultLabel($l->result_type),
|
||||||
|
'payout' => $l->payout,
|
||||||
|
'created_at' => $l->created_at->format('H:i'),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算赔付金额(赢时返还 = 本金 × 赔率)。
|
||||||
|
*
|
||||||
|
* @return int 正数=赢得金额(含本金返还),0=不赔付
|
||||||
|
*/
|
||||||
|
private function calcPayout(string $resultType, int $cost, array $config): int
|
||||||
|
{
|
||||||
|
$multiplier = match ($resultType) {
|
||||||
|
'jackpot' => (int) ($config['jackpot_payout'] ?? 100),
|
||||||
|
'triple_gem' => (int) ($config['triple_payout'] ?? 50),
|
||||||
|
'triple' => (int) ($config['same_payout'] ?? 10),
|
||||||
|
'pair' => (int) ($config['pair_payout'] ?? 2),
|
||||||
|
default => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return $multiplier > 0 ? $cost * $multiplier : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三个7全服公屏广播。
|
||||||
|
*/
|
||||||
|
private function broadcastJackpot(string $username, int $payout, int $cost): void
|
||||||
|
{
|
||||||
|
$net = $payout - $cost;
|
||||||
|
$content = "🎰🎉【老虎机大奖】恭喜 【{$username}】 转出三个7️⃣!"
|
||||||
|
.'狂揽 🪙'.number_format($net).' 金币!全服见证奇迹!';
|
||||||
|
|
||||||
|
$msg = [
|
||||||
|
'id' => $this->chatState->nextMessageId(1),
|
||||||
|
'room_id' => 1,
|
||||||
|
'from_user' => '系统传音',
|
||||||
|
'to_user' => '大家',
|
||||||
|
'content' => $content,
|
||||||
|
'is_secret' => false,
|
||||||
|
'font_color' => '#f59e0b',
|
||||||
|
'action' => '大声宣告',
|
||||||
|
'sent_at' => now()->toDateTimeString(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->chatState->pushMessage(1, $msg);
|
||||||
|
broadcast(new MessageSent(1, $msg));
|
||||||
|
SaveMessageJob::dispatch($msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件功能:老虎机游戏记录模型
|
||||||
|
*
|
||||||
|
* 记录每次转动的三列图案、结果类型和赔付情况。
|
||||||
|
*
|
||||||
|
* @author ChatRoom Laravel
|
||||||
|
*
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class SlotMachineLog extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id', 'reel1', 'reel2', 'reel3',
|
||||||
|
'result_type', 'cost', 'payout',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 属性类型转换。
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cost' => 'integer',
|
||||||
|
'payout' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联玩家。
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部图案符号定义(key => emoji)。
|
||||||
|
* 顺序影响权重:越靠前出现概率越高(摇奖时按权重随机)。
|
||||||
|
*
|
||||||
|
* @return array<string, array{emoji: string, weight: int}>
|
||||||
|
*/
|
||||||
|
public static function symbols(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cherry' => ['emoji' => '🍒', 'weight' => 30],
|
||||||
|
'lemon' => ['emoji' => '🍋', 'weight' => 25],
|
||||||
|
'orange' => ['emoji' => '🍊', 'weight' => 20],
|
||||||
|
'grape' => ['emoji' => '🍇', 'weight' => 15],
|
||||||
|
'bell' => ['emoji' => '🔔', 'weight' => 8],
|
||||||
|
'gem' => ['emoji' => '💎', 'weight' => 4],
|
||||||
|
'skull' => ['emoji' => '💀', 'weight' => 3],
|
||||||
|
'seven' => ['emoji' => '7️⃣', 'weight' => 1],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按权重随机抽取一个图案 key。
|
||||||
|
*/
|
||||||
|
public static function randomSymbol(): string
|
||||||
|
{
|
||||||
|
$symbols = static::symbols();
|
||||||
|
$totalWeight = array_sum(array_column($symbols, 'weight'));
|
||||||
|
$rand = random_int(1, $totalWeight);
|
||||||
|
$cumulative = 0;
|
||||||
|
|
||||||
|
foreach ($symbols as $key => $item) {
|
||||||
|
$cumulative += $item['weight'];
|
||||||
|
if ($rand <= $cumulative) {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'cherry'; // fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据三列图案判断结果类型。
|
||||||
|
*
|
||||||
|
* @return string 'jackpot' | 'triple_gem' | 'triple' | 'pair' | 'curse' | 'miss'
|
||||||
|
*/
|
||||||
|
public static function judgeResult(string $r1, string $r2, string $r3): string
|
||||||
|
{
|
||||||
|
// 三个7 → 大奖
|
||||||
|
if ($r1 === 'seven' && $r2 === 'seven' && $r3 === 'seven') {
|
||||||
|
return 'jackpot';
|
||||||
|
}
|
||||||
|
// 三个💎 → 豪华奖
|
||||||
|
if ($r1 === 'gem' && $r2 === 'gem' && $r3 === 'gem') {
|
||||||
|
return 'triple_gem';
|
||||||
|
}
|
||||||
|
// 三个💀 → 诅咒(亏双倍)
|
||||||
|
if ($r1 === 'skull' && $r2 === 'skull' && $r3 === 'skull') {
|
||||||
|
return 'curse';
|
||||||
|
}
|
||||||
|
// 三同(其他)
|
||||||
|
if ($r1 === $r2 && $r2 === $r3) {
|
||||||
|
return 'triple';
|
||||||
|
}
|
||||||
|
// 两同
|
||||||
|
if ($r1 === $r2 || $r2 === $r3 || $r1 === $r3) {
|
||||||
|
return 'pair';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'miss';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结果类型中文标签。
|
||||||
|
*/
|
||||||
|
public static function resultLabel(string $type): string
|
||||||
|
{
|
||||||
|
return match ($type) {
|
||||||
|
'jackpot' => '🎉 三个7大奖!',
|
||||||
|
'triple_gem' => '💎 三钻豪华奖',
|
||||||
|
'triple' => '✨ 三同奖',
|
||||||
|
'pair' => '🎁 两同小奖',
|
||||||
|
'curse' => '☠️ 三骷髅诅咒',
|
||||||
|
'miss' => '😔 未中奖',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件功能:老虎机游戏记录表迁移
|
||||||
|
*
|
||||||
|
* 每次转动老虎机产生一条记录,包含三列图案、结果类型、赔付金额。
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 创建 slot_machine_logs 表。
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('slot_machine_logs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('user_id')->comment('玩家 ID');
|
||||||
|
$table->string('reel1', 10)->comment('第一列图案 key');
|
||||||
|
$table->string('reel2', 10)->comment('第二列图案 key');
|
||||||
|
$table->string('reel3', 10)->comment('第三列图案 key');
|
||||||
|
$table->enum('result_type', ['jackpot', 'triple_gem', 'triple', 'pair', 'curse', 'miss'])
|
||||||
|
->comment('结果类型');
|
||||||
|
$table->unsignedInteger('cost')->comment('消耗金币');
|
||||||
|
$table->integer('payout')->default(0)->comment('净赔付金额(正=赢,负=输)');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index(['user_id', 'created_at']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回滚迁移。
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('slot_machine_logs');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -139,6 +139,8 @@
|
|||||||
@include('chat.partials.holiday-modal')
|
@include('chat.partials.holiday-modal')
|
||||||
{{-- ═══════════ 百家乐游戏面板 ═══════════ --}}
|
{{-- ═══════════ 百家乐游戏面板 ═══════════ --}}
|
||||||
@include('chat.partials.baccarat-panel')
|
@include('chat.partials.baccarat-panel')
|
||||||
|
{{-- ═══════════ 老虎机游戏面板 ═══════════ --}}
|
||||||
|
@include('chat.partials.slot-machine')
|
||||||
|
|
||||||
{{-- 全屏特效系统:管理员烟花/下雨/雷电/下雪 --}}
|
{{-- 全屏特效系统:管理员烟花/下雨/雷电/下雪 --}}
|
||||||
<script src="/js/effects/effect-sounds.js"></script>
|
<script src="/js/effects/effect-sounds.js"></script>
|
||||||
|
|||||||
@@ -0,0 +1,460 @@
|
|||||||
|
{{--
|
||||||
|
文件功能:老虎机游戏前台面板组件
|
||||||
|
|
||||||
|
聊天室内老虎机游戏:
|
||||||
|
- 悬浮按钮 🎰 入口(游戏开启时显示)
|
||||||
|
- 三列滚轮动画(CSS 逐列延迟停止)
|
||||||
|
- 权重随机图案、多种赔率(三7全服广播)
|
||||||
|
- 每日次数限制、金币余额显示
|
||||||
|
- 最近记录展示
|
||||||
|
--}}
|
||||||
|
|
||||||
|
{{-- ─── 老虎机悬浮按钮 ─── --}}
|
||||||
|
<div id="slot-fab" x-data="slotFab()" x-show="visible" x-cloak
|
||||||
|
style="position:fixed; bottom:150px; right:18px; z-index:9900;">
|
||||||
|
<button x-on:click="openPanel()"
|
||||||
|
style="width:52px; height:52px; border-radius:50%; border:none; cursor:pointer;
|
||||||
|
background:linear-gradient(135deg,#d97706,#f59e0b);
|
||||||
|
box-shadow:0 4px 20px rgba(245,158,11,.5);
|
||||||
|
font-size:22px; display:flex; align-items:center; justify-content:center;
|
||||||
|
animation:slot-pulse 2s infinite;"
|
||||||
|
title="老虎机">🎰</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ─── 老虎机主面板 ─── --}}
|
||||||
|
<div id="slot-panel" x-data="slotPanel()" x-show="show" x-cloak>
|
||||||
|
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
style="position:fixed; inset:0; background:rgba(0,0,0,.75); z-index:9950;
|
||||||
|
display:flex; align-items:center; justify-content:center;">
|
||||||
|
|
||||||
|
<div
|
||||||
|
style="width:400px; max-width:96vw; border-radius:24px; overflow:hidden;
|
||||||
|
box-shadow:0 24px 80px rgba(245,158,11,.4); font-family:system-ui,sans-serif;">
|
||||||
|
|
||||||
|
{{-- ─── 顶部标题 ─── --}}
|
||||||
|
<div style="background:linear-gradient(135deg,#78350f,#b45309,#d97706); padding:16px 20px 12px;">
|
||||||
|
<div style="display:flex; align-items:center; justify-content:space-between;">
|
||||||
|
<div>
|
||||||
|
<div style="color:#fff; font-weight:900; font-size:18px;">🎰 老虎机</div>
|
||||||
|
<div style="color:rgba(255,255,255,.6); font-size:11px; margin-top:2px;">
|
||||||
|
每次消耗 <span x-text="costPerSpin" style="color:#fbbf24; font-weight:bold;"></span> 金币
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align:right;">
|
||||||
|
<div style="color:#fbbf24; font-size:18px; font-weight:900;">🪙 <span
|
||||||
|
x-text="Number(balance).toLocaleString()"></span></div>
|
||||||
|
<div x-show="dailyLimit > 0" style="color:rgba(255,255,255,.5); font-size:11px;"
|
||||||
|
x-text="'今日剩余 ' + remaining + ' 次'"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ─── 滚轮区域 ─── --}}
|
||||||
|
<div style="background:linear-gradient(180deg,#1c1007,#292012); padding:20px;">
|
||||||
|
|
||||||
|
{{-- 三列转轮 --}}
|
||||||
|
<div
|
||||||
|
style="background:#0f0a02; border-radius:16px; padding:16px 12px; margin-bottom:16px;
|
||||||
|
border:2px solid rgba(245,158,11,.3); box-shadow:inset 0 0 30px rgba(0,0,0,.5);">
|
||||||
|
<div style="display:flex; gap:8px; justify-content:center; align-items:center;">
|
||||||
|
{{-- 第一列 --}}
|
||||||
|
<div
|
||||||
|
style="flex:1; background:linear-gradient(180deg,#1a1000,#2d1f00); border-radius:12px;
|
||||||
|
height:90px; display:flex; align-items:center; justify-content:center;
|
||||||
|
border:1px solid rgba(245,158,11,.2); overflow:hidden; position:relative;">
|
||||||
|
<div id="slot-reel-0" style="font-size:44px; transition:all .15s; user-select:none;"
|
||||||
|
x-text="spinning ? spinEmojis[0] : resultEmojis[0]"
|
||||||
|
:style="spinning && !reel1Stopped ? 'animation:reel-spin .1s linear infinite' : ''">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{-- 分隔 --}}
|
||||||
|
<div style="color:rgba(245,158,11,.4); font-size:20px; font-weight:900;">|</div>
|
||||||
|
{{-- 第二列 --}}
|
||||||
|
<div
|
||||||
|
style="flex:1; background:linear-gradient(180deg,#1a1000,#2d1f00); border-radius:12px;
|
||||||
|
height:90px; display:flex; align-items:center; justify-content:center;
|
||||||
|
border:1px solid rgba(245,158,11,.2); overflow:hidden; position:relative;">
|
||||||
|
<div id="slot-reel-1" style="font-size:44px; transition:all .15s; user-select:none;"
|
||||||
|
x-text="spinning ? spinEmojis[1] : resultEmojis[1]"
|
||||||
|
:style="spinning && !reel2Stopped ? 'animation:reel-spin .12s linear infinite' : ''">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{-- 分隔 --}}
|
||||||
|
<div style="color:rgba(245,158,11,.4); font-size:20px; font-weight:900;">|</div>
|
||||||
|
{{-- 第三列 --}}
|
||||||
|
<div
|
||||||
|
style="flex:1; background:linear-gradient(180deg,#1a1000,#2d1f00); border-radius:12px;
|
||||||
|
height:90px; display:flex; align-items:center; justify-content:center;
|
||||||
|
border:1px solid rgba(245,158,11,.2); overflow:hidden; position:relative;">
|
||||||
|
<div id="slot-reel-2" style="font-size:44px; transition:all .15s; user-select:none;"
|
||||||
|
x-text="spinning ? spinEmojis[2] : resultEmojis[2]"
|
||||||
|
:style="spinning && !reel3Stopped ? 'animation:reel-spin .14s linear infinite' : ''">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 中间射线指示条 --}}
|
||||||
|
<div
|
||||||
|
style="height:2px; background:linear-gradient(90deg,transparent,rgba(245,158,11,.6),transparent);
|
||||||
|
margin-top:8px; border-radius:1px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 结果提示 --}}
|
||||||
|
<div style="text-align:center; min-height:36px; margin-bottom:12px;">
|
||||||
|
<div x-show="!spinning && resultLabel" x-transition
|
||||||
|
style="display:inline-block; padding:5px 18px; border-radius:20px; font-weight:bold; font-size:14px;"
|
||||||
|
:style="resultType === 'jackpot' ?
|
||||||
|
'background:linear-gradient(135deg,#fbbf24,#f59e0b); color:#1c1007; box-shadow:0 0 20px rgba(251,191,36,.5);' :
|
||||||
|
resultType === 'triple_gem' ?
|
||||||
|
'background:rgba(167,139,250,.2); color:#c4b5fd; border:1px solid rgba(167,139,250,.3);' :
|
||||||
|
resultType === 'triple' ?
|
||||||
|
'background:rgba(52,211,153,.15); color:#6ee7b7; border:1px solid rgba(52,211,153,.25);' :
|
||||||
|
resultType === 'pair' ?
|
||||||
|
'background:rgba(96,165,250,.15); color:#93c5fd; border:1px solid rgba(96,165,250,.25);' :
|
||||||
|
resultType === 'curse' ?
|
||||||
|
'background:rgba(239,68,68,.15); color:#f87171; border:1px solid rgba(239,68,68,.25);' :
|
||||||
|
'background:rgba(255,255,255,.06); color:rgba(255,255,255,.4); '"
|
||||||
|
x-text="resultLabel">
|
||||||
|
</div>
|
||||||
|
<div x-show="spinning"
|
||||||
|
style="color:rgba(255,255,255,.4); font-size:13px; animation:blink .6s infinite;">
|
||||||
|
正在转动中…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 盈亏显示 --}}
|
||||||
|
<div x-show="!spinning && resultType" style="text-align:center; margin-bottom:12px;">
|
||||||
|
<div x-show="netChange > 0" style="color:#34d399; font-size:20px; font-weight:bold;"
|
||||||
|
x-text="'+' + Number(netChange).toLocaleString() + ' 🪙'">
|
||||||
|
</div>
|
||||||
|
<div x-show="netChange < 0" style="color:#f87171; font-size:16px; font-weight:bold;"
|
||||||
|
x-text="Number(netChange).toLocaleString() + ' 🪙'">
|
||||||
|
</div>
|
||||||
|
<div x-show="netChange === 0 && resultType === 'miss'"
|
||||||
|
style="color:rgba(255,255,255,.3); font-size:13px;">
|
||||||
|
损失 <span x-text="costPerSpin"></span> 金币
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 转动按钮 --}}
|
||||||
|
<button x-on:click="doSpin()" :disabled="spinning || (dailyLimit > 0 && remaining <= 0)"
|
||||||
|
style="width:100%; border:none; border-radius:14px; padding:14px; font-size:16px;
|
||||||
|
font-weight:900; cursor:pointer; transition:all .2s; letter-spacing:2px;"
|
||||||
|
:style="(spinning || (dailyLimit > 0 && remaining <= 0)) ? {
|
||||||
|
background: '#292012',
|
||||||
|
color: 'rgba(255,255,255,.3)',
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
} : {
|
||||||
|
background: 'linear-gradient(135deg,#b45309,#d97706,#f59e0b)',
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 20px rgba(245,158,11,.5)',
|
||||||
|
transform: 'scale(1)'
|
||||||
|
}">
|
||||||
|
<span
|
||||||
|
x-text="spinning ? '🎰 转动中…' :
|
||||||
|
(dailyLimit > 0 && remaining <= 0) ? '今日次数已用完 🔒' :
|
||||||
|
'🎰 SPIN'"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{{-- 赔率说明 --}}
|
||||||
|
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:4px; margin-top:10px;">
|
||||||
|
<div
|
||||||
|
style="background:rgba(245,158,11,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||||
|
<div>7️⃣7️⃣7️⃣</div>
|
||||||
|
<div style="color:#fbbf24; font-weight:bold;">×100</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="background:rgba(167,139,250,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||||
|
<div>💎💎💎</div>
|
||||||
|
<div style="color:#c4b5fd; font-weight:bold;">×50</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="background:rgba(52,211,153,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||||
|
<div>三同</div>
|
||||||
|
<div style="color:#6ee7b7; font-weight:bold;">×10</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="background:rgba(96,165,250,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||||
|
<div>两同</div>
|
||||||
|
<div style="color:#93c5fd; font-weight:bold;">×2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 历史记录 --}}
|
||||||
|
<div x-show="history.length > 0" style="margin-top:10px;">
|
||||||
|
<div style="color:rgba(255,255,255,.3); font-size:10px; margin-bottom:4px;">最近记录</div>
|
||||||
|
<div style="display:flex; gap:4px; flex-wrap:wrap;">
|
||||||
|
<template x-for="h in history" :key="h.created_at + h.result_label">
|
||||||
|
<div style="background:rgba(255,255,255,.06); border-radius:6px; padding:3px 8px;
|
||||||
|
font-size:11px; display:flex; align-items:center; gap:4px;"
|
||||||
|
:title="h.result_label + ' ' + (h.payout > 0 ? '+' : '') + h.payout">
|
||||||
|
<span x-text="h.emojis.join('')"></span>
|
||||||
|
<span :style="h.payout > 0 ? 'color:#4ade80' : 'color:#f87171'"
|
||||||
|
x-text="(h.payout > 0 ? '+' : '') + h.payout"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ─── 底部关闭 ─── --}}
|
||||||
|
<div style="background:rgba(15,8,0,.95); padding:8px 20px; display:flex; justify-content:center;">
|
||||||
|
<button x-on:click="close()"
|
||||||
|
style="padding:6px 28px; background:rgba(255,255,255,.06); border:none; border-radius:20px;
|
||||||
|
font-size:12px; color:rgba(255,255,255,.4); cursor:pointer; transition:all .15s;"
|
||||||
|
onmouseover="this.style.background='rgba(255,255,255,.12)'"
|
||||||
|
onmouseout="this.style.background='rgba(255,255,255,.06)'">
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes slot-pulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 4px 20px rgba(245, 158, 11, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 4px 30px rgba(245, 158, 11, .9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reel-spin {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 老虎机悬浮按钮 Alpine 组件(检查游戏是否开启)
|
||||||
|
*/
|
||||||
|
function slotFab() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/slot/info');
|
||||||
|
const data = await res.json();
|
||||||
|
this.visible = data.enabled === true;
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
openPanel() {
|
||||||
|
const panel = document.getElementById('slot-panel');
|
||||||
|
if (panel) Alpine.$data(panel).open();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 老虎机主面板 Alpine 组件
|
||||||
|
*/
|
||||||
|
function slotPanel() {
|
||||||
|
// 所有图案的 emoji 数组,与服务端权重一致(用于转动时随机展示)
|
||||||
|
const ALL_EMOJIS = ['🍒', '🍋', '🍊', '🍇', '🔔', '💎', '💀', '7️⃣'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
|
||||||
|
// 配置
|
||||||
|
costPerSpin: 100,
|
||||||
|
dailyLimit: 0,
|
||||||
|
remaining: null,
|
||||||
|
balance: 0,
|
||||||
|
|
||||||
|
// 转轮状态
|
||||||
|
spinning: false,
|
||||||
|
reel1Stopped: false,
|
||||||
|
reel2Stopped: false,
|
||||||
|
reel3Stopped: false,
|
||||||
|
spinEmojis: ['🎰', '🎰', '🎰'],
|
||||||
|
resultEmojis: ['❓', '❓', '❓'],
|
||||||
|
|
||||||
|
// 结果
|
||||||
|
resultType: '',
|
||||||
|
resultLabel: '',
|
||||||
|
netChange: 0,
|
||||||
|
|
||||||
|
// 历史
|
||||||
|
history: [],
|
||||||
|
|
||||||
|
// 动画定时器
|
||||||
|
_spinInterval: null,
|
||||||
|
_stopTimers: [],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开面板并加载数据
|
||||||
|
*/
|
||||||
|
async open() {
|
||||||
|
this.show = true;
|
||||||
|
await this.loadInfo();
|
||||||
|
await this.loadHistory();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载游戏配置和余额
|
||||||
|
*/
|
||||||
|
async loadInfo() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/slot/info');
|
||||||
|
const data = await res.json();
|
||||||
|
if (!data.enabled) {
|
||||||
|
this.show = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.costPerSpin = data.cost_per_spin;
|
||||||
|
this.dailyLimit = data.daily_limit;
|
||||||
|
this.remaining = data.remaining;
|
||||||
|
// 从聊天室全局变量读取余额
|
||||||
|
this.balance = window.__chatUser?.jjb ?? 0;
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载历史记录
|
||||||
|
*/
|
||||||
|
async loadHistory() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/slot/history');
|
||||||
|
const data = await res.json();
|
||||||
|
this.history = data.history || [];
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行转动
|
||||||
|
*/
|
||||||
|
async doSpin() {
|
||||||
|
if (this.spinning) return;
|
||||||
|
if (this.dailyLimit > 0 && this.remaining <= 0) return;
|
||||||
|
|
||||||
|
this.spinning = true;
|
||||||
|
this.resultType = '';
|
||||||
|
this.resultLabel = '';
|
||||||
|
this.netChange = 0;
|
||||||
|
this.reel1Stopped = false;
|
||||||
|
this.reel2Stopped = false;
|
||||||
|
this.reel3Stopped = false;
|
||||||
|
|
||||||
|
// 开始随机滚动动画
|
||||||
|
this._spinInterval = setInterval(() => {
|
||||||
|
this.spinEmojis = [
|
||||||
|
ALL_EMOJIS[Math.floor(Math.random() * ALL_EMOJIS.length)],
|
||||||
|
ALL_EMOJIS[Math.floor(Math.random() * ALL_EMOJIS.length)],
|
||||||
|
ALL_EMOJIS[Math.floor(Math.random() * ALL_EMOJIS.length)],
|
||||||
|
];
|
||||||
|
}, 80);
|
||||||
|
|
||||||
|
// 请求后端
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/slot/spin', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]')?.content,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
data = await res.json();
|
||||||
|
} catch {
|
||||||
|
clearInterval(this._spinInterval);
|
||||||
|
this.spinning = false;
|
||||||
|
window.chatDialog?.alert('网络异常,请稍后重试', '错误', '#ef4444');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.ok) {
|
||||||
|
clearInterval(this._spinInterval);
|
||||||
|
this.spinning = false;
|
||||||
|
window.chatDialog?.alert(data.message || '转动失败', '提示', '#ef4444');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐列停止(延迟效果)
|
||||||
|
const stopReel = (reelIndex, emoji, delay) => {
|
||||||
|
return new Promise(resolve => setTimeout(() => {
|
||||||
|
clearInterval(this._spinInterval);
|
||||||
|
this['reel' + (reelIndex + 1) + 'Stopped'] = true;
|
||||||
|
this.spinEmojis = [...this.spinEmojis];
|
||||||
|
this.spinEmojis[reelIndex] = emoji;
|
||||||
|
resolve();
|
||||||
|
}, delay));
|
||||||
|
};
|
||||||
|
|
||||||
|
await stopReel(0, data.emojis[0], 600);
|
||||||
|
this._spinInterval = setInterval(() => {
|
||||||
|
this.spinEmojis = [data.emojis[0],
|
||||||
|
ALL_EMOJIS[Math.floor(Math.random() * ALL_EMOJIS.length)],
|
||||||
|
ALL_EMOJIS[Math.floor(Math.random() * ALL_EMOJIS.length)]
|
||||||
|
];
|
||||||
|
}, 80);
|
||||||
|
await stopReel(1, data.emojis[1], 500);
|
||||||
|
this._spinInterval = setInterval(() => {
|
||||||
|
this.spinEmojis = [data.emojis[0], data.emojis[1],
|
||||||
|
ALL_EMOJIS[Math.floor(Math.random() * ALL_EMOJIS.length)]
|
||||||
|
];
|
||||||
|
}, 80);
|
||||||
|
await stopReel(2, data.emojis[2], 400);
|
||||||
|
|
||||||
|
clearInterval(this._spinInterval);
|
||||||
|
|
||||||
|
// 显示结果
|
||||||
|
this.resultEmojis = data.emojis;
|
||||||
|
this.resultType = data.result_type;
|
||||||
|
this.resultLabel = data.result_label;
|
||||||
|
this.netChange = data.payout > 0 ? data.payout - this.costPerSpin : -this.costPerSpin + (data
|
||||||
|
.payout < 0 ? data.payout : 0);
|
||||||
|
this.balance = data.balance;
|
||||||
|
this.spinning = false;
|
||||||
|
|
||||||
|
// 更新全局余额(让聊天界面同步)
|
||||||
|
if (window.__chatUser) window.__chatUser.jjb = data.balance;
|
||||||
|
|
||||||
|
if (this.dailyLimit > 0 && this.remaining !== null) {
|
||||||
|
this.remaining = Math.max(0, this.remaining - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.loadHistory();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭面板
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
clearInterval(this._spinInterval);
|
||||||
|
this.spinning = false;
|
||||||
|
this.show = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
+10
-3
@@ -117,14 +117,21 @@ Route::middleware(['chat.auth'])->group(function () {
|
|||||||
|
|
||||||
// ── 百家乐(前台)────────────────────────────────────────────────
|
// ── 百家乐(前台)────────────────────────────────────────────────
|
||||||
Route::prefix('baccarat')->name('baccarat.')->group(function () {
|
Route::prefix('baccarat')->name('baccarat.')->group(function () {
|
||||||
// 获取当前局次信息
|
|
||||||
Route::get('/current', [\App\Http\Controllers\BaccaratController::class, 'currentRound'])->name('current');
|
Route::get('/current', [\App\Http\Controllers\BaccaratController::class, 'currentRound'])->name('current');
|
||||||
// 提交下注
|
|
||||||
Route::post('/bet', [\App\Http\Controllers\BaccaratController::class, 'bet'])->name('bet');
|
Route::post('/bet', [\App\Http\Controllers\BaccaratController::class, 'bet'])->name('bet');
|
||||||
// 查询历史记录
|
|
||||||
Route::get('/history', [\App\Http\Controllers\BaccaratController::class, 'history'])->name('history');
|
Route::get('/history', [\App\Http\Controllers\BaccaratController::class, 'history'])->name('history');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── 老虎机(前台)────────────────────────────────────────────────
|
||||||
|
Route::prefix('slot')->name('slot.')->group(function () {
|
||||||
|
// 获取配置及今日剩余次数
|
||||||
|
Route::get('/info', [\App\Http\Controllers\SlotMachineController::class, 'info'])->name('info');
|
||||||
|
// 执行一次转动
|
||||||
|
Route::post('/spin', [\App\Http\Controllers\SlotMachineController::class, 'spin'])->name('spin');
|
||||||
|
// 个人历史记录
|
||||||
|
Route::get('/history', [\App\Http\Controllers\SlotMachineController::class, 'history'])->name('history');
|
||||||
|
});
|
||||||
|
|
||||||
// ---- 第五阶段:具体房间内部聊天核心 ----
|
// ---- 第五阶段:具体房间内部聊天核心 ----
|
||||||
// 进入具体房间界面的初始化
|
// 进入具体房间界面的初始化
|
||||||
Route::get('/room/{id}', [ChatController::class, 'init'])->name('chat.room');
|
Route::get('/room/{id}', [ChatController::class, 'init'])->name('chat.room');
|
||||||
|
|||||||
Reference in New Issue
Block a user