迁移聊天室上下文注入脚本
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
// 聊天室上下文初始化模块,从 Blade 输出的 JSON 数据节点恢复 window.chatContext。
|
||||||
|
|
||||||
|
const CHAT_CONTEXT_ELEMENT_ID = "chat-context-data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取并解析 Blade 注入的聊天室上下文 JSON。
|
||||||
|
*
|
||||||
|
* @returns {Record<string, any>}
|
||||||
|
*/
|
||||||
|
function readChatContextPayload() {
|
||||||
|
const element = document.getElementById(CHAT_CONTEXT_ELEMENT_ID);
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(element.textContent || "{}");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[chatContext] 初始化数据解析失败", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用模板生成带 ID 的 URL。
|
||||||
|
*
|
||||||
|
* @param {string} template URL 模板
|
||||||
|
* @param {number|string} id 资源 ID
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function fillIdTemplate(template, id) {
|
||||||
|
return String(template || "").replace("__ID__", encodeURIComponent(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复婚姻系统 URL 工厂函数,兼容存量调用方式。
|
||||||
|
*
|
||||||
|
* @param {Record<string, any>} context 聊天室上下文
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function hydrateMarriageUrlFactories(context) {
|
||||||
|
const marriage = context.marriage || {};
|
||||||
|
|
||||||
|
marriage.acceptUrl = (id) => fillIdTemplate(marriage.acceptUrlTemplate, id);
|
||||||
|
marriage.rejectUrl = (id) => fillIdTemplate(marriage.rejectUrlTemplate, id);
|
||||||
|
marriage.divorceUrl = (id) => fillIdTemplate(marriage.divorceUrlTemplate, id);
|
||||||
|
marriage.confirmDivorceUrl = (id) => fillIdTemplate(marriage.confirmDivorceUrlTemplate, id);
|
||||||
|
marriage.rejectDivorceUrl = (id) => fillIdTemplate(marriage.rejectDivorceUrlTemplate, id);
|
||||||
|
marriage.weddingSetupUrl = (id) => fillIdTemplate(marriage.weddingSetupUrlTemplate, id);
|
||||||
|
marriage.claimEnvelopeUrl = (id, ceremonyId) => fillIdTemplate(
|
||||||
|
fillIdTemplate(marriage.claimEnvelopeUrlTemplate, id),
|
||||||
|
ceremonyId,
|
||||||
|
);
|
||||||
|
marriage.envelopeStatusUrl = (id) => fillIdTemplate(marriage.envelopeStatusUrlTemplate, id);
|
||||||
|
|
||||||
|
context.marriage = marriage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 window.chatContext,供后续聊天室模块和存量 Blade 脚本读取。
|
||||||
|
*
|
||||||
|
* @returns {Record<string, any>}
|
||||||
|
*/
|
||||||
|
export function initializeChatContext() {
|
||||||
|
// Blade 底部仍有同步脚本会立即读取 chatContext;若兼容桥已初始化,这里只做函数补齐。
|
||||||
|
const context = window.chatContext || readChatContextPayload();
|
||||||
|
hydrateMarriageUrlFactories(context);
|
||||||
|
|
||||||
|
window.chatContext = context;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeChatContext();
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import "./bootstrap";
|
import "./bootstrap";
|
||||||
|
import "./chat-room/context.js";
|
||||||
import "./chat-room.js";
|
import "./chat-room.js";
|
||||||
|
|
||||||
// 这个文件负责处理浏览器与 Laravel Reverb WebSocket 服务器的通信。
|
// 这个文件负责处理浏览器与 Laravel Reverb WebSocket 服务器的通信。
|
||||||
|
|||||||
@@ -36,114 +36,112 @@
|
|||||||
$fishingCooldownSeconds = \Illuminate\Support\Facades\Redis::ttl($fishingCooldownKey);
|
$fishingCooldownSeconds = \Illuminate\Support\Facades\Redis::ttl($fishingCooldownKey);
|
||||||
$fishingCooldownSeconds = $fishingCooldownSeconds > 0 ? $fishingCooldownSeconds : 0;
|
$fishingCooldownSeconds = $fishingCooldownSeconds > 0 ? $fishingCooldownSeconds : 0;
|
||||||
@endphp
|
@endphp
|
||||||
<script>
|
@php
|
||||||
window.chatContext = {
|
$chatbotEnabledState = \App\Models\Sysparam::getValue('chatbot_enabled', '0') === '1';
|
||||||
roomId: {{ $room->id }},
|
$botUserData = null;
|
||||||
userId: {{ $user->id }},
|
if ($chatbotEnabledState) {
|
||||||
username: "{{ $user->username }}",
|
$botUser = \App\Models\User::query()->where('username', 'AI小班长')->first();
|
||||||
userSex: "{{ match ((int) $user->sex) {1 => '男',2 => '女',default => ''} }}",
|
if ($botUser) {
|
||||||
userLevel: {{ $user->user_level }},
|
$botUserData = app(\App\Services\ChatUserPresenceService::class)->build($botUser);
|
||||||
superLevel: {{ $superLevel }},
|
$botUserData['headfaceUrl'] = $botUser->headfaceUrl;
|
||||||
levelKick: {{ $levelKick }},
|
|
||||||
levelMute: {{ $levelMute }},
|
|
||||||
levelBan: {{ $levelBan }},
|
|
||||||
levelBanip: {{ $levelBanip }},
|
|
||||||
sendUrl: "{{ route('chat.send', $room->id) }}",
|
|
||||||
leaveUrl: "{{ route('chat.leave', $room->id) }}",
|
|
||||||
expiredLeaveUrl: "{{ \Illuminate\Support\Facades\URL::temporarySignedRoute('chat.leave.expired', now()->addHours(12), ['id' => $room->id, 'user' => $user->id]) }}",
|
|
||||||
heartbeatUrl: "{{ route('chat.heartbeat', $room->id) }}",
|
|
||||||
fishCastUrl: "{{ route('fishing.cast', $room->id) }}",
|
|
||||||
fishReelUrl: "{{ route('fishing.reel', $room->id) }}",
|
|
||||||
autoFishingMinutesLeft: {{ (int) $autoFishingMinutesLeft }},
|
|
||||||
fishingCooldownSeconds: {{ (int) $fishingCooldownSeconds }},
|
|
||||||
chatBotUrl: "{{ route('chatbot.chat') }}",
|
|
||||||
chatBotClearUrl: "{{ route('chatbot.clear') }}",
|
|
||||||
@php
|
|
||||||
$chatbotEnabledState = \App\Models\Sysparam::getValue('chatbot_enabled', '0') === '1';
|
|
||||||
$botUserData = null;
|
|
||||||
if ($chatbotEnabledState) {
|
|
||||||
$botUser = \App\Models\User::where('username', 'AI小班长')->first();
|
|
||||||
if ($botUser) {
|
|
||||||
$botUserData = app(\App\Services\ChatUserPresenceService::class)->build($botUser);
|
|
||||||
$botUserData['headfaceUrl'] = $botUser->headfaceUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
chatBotEnabled: {{ $chatbotEnabledState ? 'true' : 'false' }},
|
|
||||||
botUser: @json($botUserData),
|
|
||||||
hasPosition: {{ Auth::user()->activePosition || Auth::user()->user_level >= $superLevel ? 'true' : 'false' }},
|
|
||||||
hasRoomManagementPermission: {{ (! empty($hasRoomManagementPermission) || Auth::id() === 1) ? 'true' : 'false' }},
|
|
||||||
isSiteOwner: {{ Auth::id() === 1 ? 'true' : 'false' }},
|
|
||||||
positionPermissions: @json($positionPermissions),
|
|
||||||
positionPermissionMap: @json($roomPermissionMap ?? []),
|
|
||||||
@php
|
|
||||||
$activePos = Auth::user()->activePosition;
|
|
||||||
$deptName = $activePos?->position?->department?->name ?? '';
|
|
||||||
$posName = $activePos?->position?->name ?? '';
|
|
||||||
@endphp
|
|
||||||
welcomePrefix: "{{ $deptName ? "{$deptName} {$posName} {$user->username}" : $user->username }}",
|
|
||||||
operatorDepartmentRank: {{ $operatorDepartmentRank }},
|
|
||||||
operatorPositionRank: {{ $operatorPositionRank }},
|
|
||||||
myMaxReward: @php
|
|
||||||
if (Auth::id() === 1) {
|
|
||||||
// 超级管理员(id=1)无需职务,直接拥有不限量奖励权
|
|
||||||
echo -1;
|
|
||||||
} else {
|
|
||||||
$pos = Auth::user()->activePosition?->position;
|
|
||||||
// -1 = 有权限但无上限(null),0 = 禁止,正整数 = 有具体上限
|
|
||||||
echo $pos ? ($pos->max_reward === null ? -1 : (int) $pos->max_reward) : 0;
|
|
||||||
}
|
|
||||||
@endphp,
|
|
||||||
appointPositionsUrl: "{{ route('chat.appoint.positions') }}",
|
|
||||||
appointUrl: "{{ route('chat.appoint.appoint') }}",
|
|
||||||
revokeUrl: "{{ route('chat.appoint.revoke') }}",
|
|
||||||
rewardUrl: "{{ route('command.reward') }}",
|
|
||||||
rewardQuotaUrl: "{{ route('command.reward_quota') }}",
|
|
||||||
refreshAllUrl: "{{ route('command.refresh_all') }}",
|
|
||||||
chatPreferencesUrl: "{{ route('user.update_chat_preferences') }}",
|
|
||||||
dailyStatusUpdateUrl: "{{ route('user.update_daily_status') }}",
|
|
||||||
dailySignInStatusUrl: @json(\Illuminate\Support\Facades\Route::has('daily-sign-in.status') ? route('daily-sign-in.status') : null),
|
|
||||||
dailySignInCalendarUrl: @json(\Illuminate\Support\Facades\Route::has('daily-sign-in.calendar') ? route('daily-sign-in.calendar') : null),
|
|
||||||
dailySignInClaimUrl: @json(\Illuminate\Support\Facades\Route::has('daily-sign-in.claim') ? route('daily-sign-in.claim') : null),
|
|
||||||
dailySignInMakeupUrl: @json(\Illuminate\Support\Facades\Route::has('daily-sign-in.makeup') ? route('daily-sign-in.makeup') : null),
|
|
||||||
userJjb: {{ (int) $user->jjb }}, // 当前用户金币(求婚前金额预检查用)
|
|
||||||
myGold: {{ (int) $user->jjb }}, // 赠金币面板显示余额用(赠送成功后前端更新)
|
|
||||||
profileHeadface: @json(Auth::user()->usersf ?: '1.gif'),
|
|
||||||
profileSign: @json(Auth::user()->sign ?? ''),
|
|
||||||
chatPreferences: @json($user->chat_preferences ?? []),
|
|
||||||
currentDailyStatus: @json($activeDailyStatus),
|
|
||||||
dailyStatusCatalog: @json($dailyStatusCatalog),
|
|
||||||
|
|
||||||
// ─── 婚姻系统 ──────────────────────────────
|
|
||||||
minWeddingCost: {{ (int) \App\Models\WeddingTier::where('is_active', true)->orderBy('amount')->value('amount') ?? 0 }},
|
|
||||||
marriage: {
|
|
||||||
proposeUrl: "{{ route('marriage.propose') }}",
|
|
||||||
statusUrl: "{{ route('marriage.status') }}",
|
|
||||||
targteStatusUrl: "/marriage/target",
|
|
||||||
myRingsUrl: "{{ route('marriage.rings') }}",
|
|
||||||
acceptUrl: (id) => `/marriage/${id}/accept`,
|
|
||||||
rejectUrl: (id) => `/marriage/${id}/reject`,
|
|
||||||
divorceUrl: (id) => `/marriage/${id}/divorce`,
|
|
||||||
confirmDivorceUrl: (id) => `/marriage/${id}/confirm-divorce`,
|
|
||||||
rejectDivorceUrl: (id) => `/marriage/${id}/reject-divorce`,
|
|
||||||
divorceConfigUrl: '/marriage/divorce-config',
|
|
||||||
weddingTiersUrl: "/wedding/tiers",
|
|
||||||
weddingSetupUrl: (id) => `/wedding/${id}/setup`,
|
|
||||||
claimEnvelopeUrl: (id, ceremonyId) => `/wedding/${id}/claim`,
|
|
||||||
envelopeStatusUrl: (id) => `/wedding/${id}/envelope-status`,
|
|
||||||
},
|
|
||||||
earnRewardUrl: "{{ route('earn.video_reward') }}",
|
|
||||||
chatImageRetentionDays: 3,
|
|
||||||
initialState: {
|
|
||||||
historyMessages: @json($historyMessages ?? []),
|
|
||||||
localClearStorageKey: "local_clear_msg_id_{{ $room->id }}",
|
|
||||||
welcomeMessage: @json($initialWelcomeMessage ?? null),
|
|
||||||
entryEffect: @json($newbieEffect ?: ($initialPresenceTheme['presence_effect'] ?? ($weekEffect ?? null))),
|
|
||||||
presenceTheme: @json($initialPresenceTheme ?? null),
|
|
||||||
pendingProposal: @json($pendingProposal ?? null),
|
|
||||||
pendingDivorce: @json($pendingDivorce ?? null),
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
$activePos = Auth::user()->activePosition;
|
||||||
|
$deptName = $activePos?->position?->department?->name ?? '';
|
||||||
|
$posName = $activePos?->position?->name ?? '';
|
||||||
|
if (Auth::id() === 1) {
|
||||||
|
$myMaxReward = -1;
|
||||||
|
} else {
|
||||||
|
$pos = Auth::user()->activePosition?->position;
|
||||||
|
$myMaxReward = $pos ? ($pos->max_reward === null ? -1 : (int) $pos->max_reward) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chatContext = [
|
||||||
|
'roomId' => $room->id,
|
||||||
|
'userId' => $user->id,
|
||||||
|
'username' => $user->username,
|
||||||
|
'userSex' => match ((int) $user->sex) {1 => '男', 2 => '女', default => ''},
|
||||||
|
'userLevel' => $user->user_level,
|
||||||
|
'superLevel' => $superLevel,
|
||||||
|
'levelKick' => $levelKick,
|
||||||
|
'levelMute' => $levelMute,
|
||||||
|
'levelBan' => $levelBan,
|
||||||
|
'levelBanip' => $levelBanip,
|
||||||
|
'sendUrl' => route('chat.send', $room->id),
|
||||||
|
'leaveUrl' => route('chat.leave', $room->id),
|
||||||
|
'expiredLeaveUrl' => \Illuminate\Support\Facades\URL::temporarySignedRoute('chat.leave.expired', now()->addHours(12), ['id' => $room->id, 'user' => $user->id]),
|
||||||
|
'heartbeatUrl' => route('chat.heartbeat', $room->id),
|
||||||
|
'fishCastUrl' => route('fishing.cast', $room->id),
|
||||||
|
'fishReelUrl' => route('fishing.reel', $room->id),
|
||||||
|
'autoFishingMinutesLeft' => (int) $autoFishingMinutesLeft,
|
||||||
|
'fishingCooldownSeconds' => (int) $fishingCooldownSeconds,
|
||||||
|
'chatBotUrl' => route('chatbot.chat'),
|
||||||
|
'chatBotClearUrl' => route('chatbot.clear'),
|
||||||
|
'chatBotEnabled' => $chatbotEnabledState,
|
||||||
|
'botUser' => $botUserData,
|
||||||
|
'hasPosition' => (bool) (Auth::user()->activePosition || Auth::user()->user_level >= $superLevel),
|
||||||
|
'hasRoomManagementPermission' => (bool) (! empty($hasRoomManagementPermission) || Auth::id() === 1),
|
||||||
|
'isSiteOwner' => Auth::id() === 1,
|
||||||
|
'positionPermissions' => $positionPermissions,
|
||||||
|
'positionPermissionMap' => $roomPermissionMap ?? [],
|
||||||
|
'welcomePrefix' => $deptName ? "{$deptName} {$posName} {$user->username}" : $user->username,
|
||||||
|
'operatorDepartmentRank' => $operatorDepartmentRank,
|
||||||
|
'operatorPositionRank' => $operatorPositionRank,
|
||||||
|
'myMaxReward' => $myMaxReward,
|
||||||
|
'appointPositionsUrl' => route('chat.appoint.positions'),
|
||||||
|
'appointUrl' => route('chat.appoint.appoint'),
|
||||||
|
'revokeUrl' => route('chat.appoint.revoke'),
|
||||||
|
'rewardUrl' => route('command.reward'),
|
||||||
|
'rewardQuotaUrl' => route('command.reward_quota'),
|
||||||
|
'refreshAllUrl' => route('command.refresh_all'),
|
||||||
|
'chatPreferencesUrl' => route('user.update_chat_preferences'),
|
||||||
|
'dailyStatusUpdateUrl' => route('user.update_daily_status'),
|
||||||
|
'dailySignInStatusUrl' => \Illuminate\Support\Facades\Route::has('daily-sign-in.status') ? route('daily-sign-in.status') : null,
|
||||||
|
'dailySignInCalendarUrl' => \Illuminate\Support\Facades\Route::has('daily-sign-in.calendar') ? route('daily-sign-in.calendar') : null,
|
||||||
|
'dailySignInClaimUrl' => \Illuminate\Support\Facades\Route::has('daily-sign-in.claim') ? route('daily-sign-in.claim') : null,
|
||||||
|
'dailySignInMakeupUrl' => \Illuminate\Support\Facades\Route::has('daily-sign-in.makeup') ? route('daily-sign-in.makeup') : null,
|
||||||
|
'userJjb' => (int) $user->jjb,
|
||||||
|
'myGold' => (int) $user->jjb,
|
||||||
|
'profileHeadface' => Auth::user()->usersf ?: '1.gif',
|
||||||
|
'profileSign' => Auth::user()->sign ?? '',
|
||||||
|
'chatPreferences' => $user->chat_preferences ?? [],
|
||||||
|
'currentDailyStatus' => $activeDailyStatus,
|
||||||
|
'dailyStatusCatalog' => $dailyStatusCatalog,
|
||||||
|
'minWeddingCost' => (int) (\App\Models\WeddingTier::query()->where('is_active', true)->orderBy('amount')->value('amount') ?? 0),
|
||||||
|
'marriage' => [
|
||||||
|
'proposeUrl' => route('marriage.propose'),
|
||||||
|
'statusUrl' => route('marriage.status'),
|
||||||
|
'targteStatusUrl' => '/marriage/target',
|
||||||
|
'myRingsUrl' => route('marriage.rings'),
|
||||||
|
'acceptUrlTemplate' => '/marriage/__ID__/accept',
|
||||||
|
'rejectUrlTemplate' => '/marriage/__ID__/reject',
|
||||||
|
'divorceUrlTemplate' => '/marriage/__ID__/divorce',
|
||||||
|
'confirmDivorceUrlTemplate' => '/marriage/__ID__/confirm-divorce',
|
||||||
|
'rejectDivorceUrlTemplate' => '/marriage/__ID__/reject-divorce',
|
||||||
|
'divorceConfigUrl' => '/marriage/divorce-config',
|
||||||
|
'weddingTiersUrl' => '/wedding/tiers',
|
||||||
|
'weddingSetupUrlTemplate' => '/wedding/__ID__/setup',
|
||||||
|
'claimEnvelopeUrlTemplate' => '/wedding/__ID__/claim',
|
||||||
|
'envelopeStatusUrlTemplate' => '/wedding/__ID__/envelope-status',
|
||||||
|
],
|
||||||
|
'earnRewardUrl' => route('earn.video_reward'),
|
||||||
|
'chatImageRetentionDays' => 3,
|
||||||
|
'initialState' => [
|
||||||
|
'historyMessages' => $historyMessages ?? [],
|
||||||
|
'localClearStorageKey' => "local_clear_msg_id_{$room->id}",
|
||||||
|
'welcomeMessage' => $initialWelcomeMessage ?? null,
|
||||||
|
'entryEffect' => $newbieEffect ?: ($initialPresenceTheme['presence_effect'] ?? ($weekEffect ?? null)),
|
||||||
|
'presenceTheme' => $initialPresenceTheme ?? null,
|
||||||
|
'pendingProposal' => $pendingProposal ?? null,
|
||||||
|
'pendingDivorce' => $pendingDivorce ?? null,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
@endphp
|
||||||
|
<script type="application/json" id="chat-context-data">@json($chatContext)</script>
|
||||||
|
<script>
|
||||||
|
// 兼容底部存量同步脚本,完整 URL 工厂由 Vite 的 chat-room/context.js 继续补齐。
|
||||||
|
window.chatContext = JSON.parse(document.getElementById('chat-context-data')?.textContent || '{}');
|
||||||
</script>
|
</script>
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js', 'resources/js/chat.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js', 'resources/js/chat.js'])
|
||||||
<script defer src="/js/alpinejs.min.js"></script>
|
<script defer src="/js/alpinejs.min.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user