优化 会员页面;

This commit is contained in:
2026-04-12 17:06:38 +08:00
parent 1e64d2d5e2
commit 77c17f87f9
7 changed files with 508 additions and 11 deletions
+69 -8
View File
@@ -23,7 +23,7 @@ class VipCenterController extends Controller
*
* @param Request $request 当前请求对象
*/
public function index(Request $request): View
public function index(Request $request): View|\Illuminate\Http\JsonResponse
{
$user = $request->user();
@@ -47,10 +47,46 @@ class VipCenterController extends Controller
->where('status', 'paid')
->sum('amount');
return view('vip.center', [
'user' => $user,
'vipLevels' => $vipLevels,
'paymentLogs' => $paymentLogs,
$data = [
'user' => [
'id' => $user->id,
'username' => $user->username,
'is_vip' => $user->isVip(),
'vip_name' => $user->vipName(),
'hy_time' => $user->hy_time?->format('Y-m-d H:i'),
'vip_level_id' => $user->vip_level_id,
'can_customize' => $user->canCustomizeVipPresence(),
'custom_join_message' => $user->custom_join_message,
'custom_leave_message' => $user->custom_leave_message,
'custom_join_effect' => $user->custom_join_effect,
'custom_leave_effect' => $user->custom_leave_effect,
'vip_level' => $user->vipLevel ? [
'id' => $user->vipLevel->id,
'name' => $user->vipLevel->name,
'icon' => $user->vipLevel->icon,
'color' => $user->vipLevel->color,
'join_effect' => $user->vipLevel->joinEffectKey(),
'join_banner' => $user->vipLevel->joinBannerStyleKey(),
'leave_effect' => $user->vipLevel->leaveEffectKey(),
'leave_banner' => $user->vipLevel->leaveBannerStyleKey(),
'join_templates' => $user->vipLevel->join_templates_array,
'leave_templates' => $user->vipLevel->leave_templates_array,
] : null,
],
'vipLevels' => $vipLevels->map(function ($vip) {
return [
'id' => $vip->id,
'name' => $vip->name,
'icon' => $vip->icon,
'color' => $vip->color,
'price' => $vip->price,
'duration_days' => $vip->duration_days,
'exp_multiplier' => $vip->exp_multiplier,
'jjb_multiplier' => $vip->jjb_multiplier,
'description' => $vip->description,
];
}),
'paymentLogs' => $paymentLogs->items(),
'vipPaymentEnabled' => Sysparam::getValue('vip_payment_enabled', '0') === '1',
'paidOrders' => $paidOrders,
'totalAmount' => $totalAmount,
@@ -74,18 +110,34 @@ class VipCenterController extends Controller
'cosmic' => '星穹幻彩',
'farewell' => '告别暮光',
],
]);
];
if ($request->expectsJson()) {
return response()->json([
'status' => 'success',
'data' => $data,
]);
}
return view('vip.center', $data);
}
/**
* 保存会员个人自定义欢迎语与离开语。
*/
public function updatePresenceSettings(UpdateVipPresenceSettingsRequest $request): RedirectResponse
public function updatePresenceSettings(UpdateVipPresenceSettingsRequest $request): RedirectResponse|\Illuminate\Http\JsonResponse
{
$user = $request->user();
// 只有有效会员且当前等级允许自定义时,才允许保存专属语句。
if (! $user->canCustomizeVipPresence()) {
if ($request->expectsJson()) {
return response()->json([
'status' => 'error',
'message' => '当前会员等级暂不支持自定义欢迎语和离开语。',
], 403);
}
return redirect()
->route('vip.center')
->with('error', '当前会员等级暂不支持自定义欢迎语和离开语。');
@@ -97,11 +149,20 @@ class VipCenterController extends Controller
$user->update([
'custom_join_message' => $this->sanitizeNullableMessage($data['custom_join_message'] ?? null),
'custom_leave_message' => $this->sanitizeNullableMessage($data['custom_leave_message'] ?? null),
'custom_join_effect' => $data['custom_join_effect'] ?? null,
'custom_leave_effect' => $data['custom_leave_effect'] ?? null,
]);
if ($request->expectsJson()) {
return response()->json([
'status' => 'success',
'message' => '设置已保存。',
]);
}
return redirect()
->route('vip.center')
->with('success', '会员专属欢迎语和离开语已保存。');
->with('success', '设置已保存。');
}
/**
@@ -27,6 +27,8 @@ class UpdateVipPresenceSettingsRequest extends FormRequest
return [
'custom_join_message' => ['nullable', 'string', 'max:255'],
'custom_leave_message' => ['nullable', 'string', 'max:255'],
'custom_join_effect' => ['nullable', 'string', 'max:30'],
'custom_leave_effect' => ['nullable', 'string', 'max:30'],
];
}
+2
View File
@@ -37,6 +37,8 @@ class User extends Authenticatable
'sign',
'custom_join_message',
'custom_leave_message',
'custom_join_effect',
'custom_leave_effect',
'user_level',
'inviter_id',
'room_id',
+10 -1
View File
@@ -76,12 +76,21 @@ class VipPresenceService
? $this->vipService->getJoinMessage($user)
: $this->vipService->getLeaveMessage($user);
// 只有在等级允许自定义时,才允许覆盖默认特效。
$customEffect = $type === 'join'
? $user->custom_join_effect
: $user->custom_leave_effect;
if (! $user->canCustomizeVipPresence()) {
$customEffect = null;
}
return [
'enabled' => true,
'type' => $type,
'text' => $customMessage ?: $templateMessage,
'color' => $vipLevel->color ?: '#f59e0b',
'effect' => $type === 'join' ? $this->normalizeEffect($vipLevel->joinEffectKey()) : $this->normalizeEffect($vipLevel->leaveEffectKey()),
'effect' => $this->normalizeEffect($customEffect ?: ($type === 'join' ? $vipLevel->joinEffectKey() : $vipLevel->leaveEffectKey())),
'banner_style' => $type === 'join' ? $vipLevel->joinBannerStyleKey() : $vipLevel->leaveBannerStyleKey(),
'level_name' => $vipLevel->name,
'icon' => $vipLevel->icon,
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('custom_join_effect', 30)->nullable()->after('custom_leave_message')->comment('用户自定义入场特效');
$table->string('custom_leave_effect', 30)->nullable()->after('custom_join_effect')->comment('用户自定义离场特效');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['custom_join_effect', 'custom_leave_effect']);
});
}
};
@@ -32,7 +32,7 @@
</div>
<div class="mobile-drawer-body">
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openShopModal();">🛒<br>商店</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();window.open('{{ route('vip.center') }}','_blank');">👑<br>会员</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openVipModal();">👑<br>会员</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();saveExp();">💾<br>存点</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openGameHall();">🎮<br>娱乐</div>
<div class="mobile-tool-btn" onclick="closeMobileDrawer();openBankModal();">🏦<br>银行</div>
@@ -16,7 +16,7 @@
{{-- ═══════════ 竖向工具条按钮 ═══════════ --}}
<div class="chat-toolbar" id="toolbar-strip">
<div class="tool-btn" onclick="openShopModal()" title="购买道具">商店</div>
<div class="tool-btn" onclick="window.open('{{ route('vip.center') }}', '_blank')" title="会员中心">会员</div>
<div class="tool-btn" onclick="openVipModal()" title="会员中心">会员</div>
<div class="tool-btn" onclick="saveExp()" title="手动存经验点">存点</div>
<div class="tool-btn" onclick="openGameHall()" title="娱乐游戏大厅">娱乐</div>
<div class="tool-btn" onclick="window.dispatchEvent(new CustomEvent('open-earn-panel'))" title="看视频赚金币">赚钱</div>
@@ -1434,6 +1434,400 @@ async function generateWechatBindCode() {
})();
</script>
{{-- ═══════════ 会员中心弹窗 ═══════════ --}}
<style>
#vip-modal { display:none; position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:9999; justify-content:center; align-items:center; }
#vip-modal-inner { width:760px; max-width:96vw; max-height:90vh; background:#fff; border-radius:12px; overflow:hidden; display:flex; flex-direction:column; box-shadow:0 12px 40px rgba(0,0,0,.4); font-family:'Microsoft YaHei',SimSun,sans-serif; }
#vip-modal-header { background:linear-gradient(135deg,#1e293b,#334155); color:#fff; padding:12px 18px; display:flex; align-items:center; flex-shrink:0; }
.vip-tab-btn { background:rgba(255,255,255,.1); border:none; color:#cbd5e1; border-radius:8px; padding:6px 16px; font-size:13px; cursor:pointer; font-weight:bold; transition:all .2s; }
.vip-tab-btn.active { background:#f59e0b; color:#fff; box-shadow:0 2px 4px rgba(0,0,0,.1); }
.vip-view-pane { display:none; flex-direction:column; flex:1; overflow-y:auto; background:#f8fafc; padding:20px; }
.vip-view-pane.active { display:flex; }
/* 会员卡片 */
.vip-level-card { background:#fff; border:1px solid #e2e8f0; border-radius:12px; padding:18px; display:flex; flex-direction:column; gap:10px; transition:all .2s; position:relative; overflow:hidden; }
.vip-level-card.current { border-color:#f59e0b; background:#fffdf5; box-shadow:0 0 0 1px #f59e0b; }
.vip-level-badge { position:absolute; top:10px; right:10px; background:#f59e0b; color:#fff; font-size:10px; font-weight:bold; padding:2px 8px; border-radius:10px; }
/* 设置表单 */
.vip-setting-group { margin-bottom:15px; }
.vip-setting-label { display:block; font-size:13px; font-weight:bold; color:#475569; margin-bottom:6px; }
.vip-setting-input { width:100%; border:1px solid #cbd5e1; border-radius:8px; padding:10px 12px; font-size:13px; outline:none; transition:border-color .2s; background:#fff; }
.vip-setting-input:focus { border-color:#f59e0b; }
.vip-setting-textarea { min-height:80px; resize:vertical; }
.vip-btn-save { background:#1e293b; color:#fff; border:none; border-radius:8px; padding:10px 20px; font-size:13px; font-weight:bold; cursor:pointer; transition:opacity .2s; }
.vip-btn-save:hover { opacity:.9; }
/* 会员特权图标 */
.vip-feature-item { display:flex; align-items:center; gap:8px; font-size:12px; color:#64748b; }
.vip-feature-icon { color:#10b981; font-weight:bold; }
</style>
<div id="vip-modal">
<div id="vip-modal-inner">
<div id="vip-modal-header">
<div style="font-size:16px; font-weight:bold; margin-right:24px;">👑 会员中心</div>
<div style="display:flex; gap:10px; flex:1;">
<button id="vip-tabbtn-center" class="vip-tab-btn active" onclick="switchVipTab('center')">会员中心</button>
<button id="vip-tabbtn-settings" class="vip-tab-btn" onclick="switchVipTab('settings')" style="display:none;">会员相关设置</button>
<button id="vip-tabbtn-logs" class="vip-tab-btn" onclick="switchVipTab('logs')">购买记录</button>
</div>
<span style="cursor:pointer; font-size:24px; opacity:.7; transition:opacity .15s;" onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=.7" onclick="closeVipModal()">&times;</span>
</div>
{{-- 会员中心面板 --}}
<div id="vip-view-center" class="vip-view-pane active">
<div id="vip-status-bar" style="background:#fff; border:1px solid #fef3c7; border-radius:12px; padding:16px; margin-bottom:20px; display:flex; justify-content:space-between; align-items:center;">
<div>
<div style="font-size:12px; font-weight:bold; color:#b45309;">我的会员状态</div>
<div id="vip-current-name" style="font-size:18px; font-weight:900; color:#1e293b; margin-top:4px;">加载中...</div>
<div id="vip-expire-time" style="font-size:12px; color:#64748b; margin-top:4px;"></div>
</div>
<div style="text-align:right;">
<div style="font-size:12px; font-weight:bold; color:#059669;">累计已支付</div>
<div id="vip-total-amount" style="font-size:18px; font-weight:900; color:#064e3b; margin-top:4px;">¥0.00</div>
</div>
</div>
<div style="font-size:14px; font-weight:bold; color:#1e293b; margin-bottom:12px;">会员等级列表</div>
<div id="vip-levels-grid" style="display:grid; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); gap:15px;">
<!-- 动态渲染会员等级 -->
</div>
</div>
{{-- 会员相关设置面板 --}}
<div id="vip-view-settings" class="vip-view-pane">
<div style="display:grid; grid-template-columns:1.2fr 0.8fr; gap:20px;">
<div>
<div style="background:#fff; border:1px solid #e2e8f0; border-radius:12px; padding:18px; margin-bottom:15px;">
<div style="display:flex; align-items:center; gap:10px; margin-bottom:15px;">
<div id="vip-theme-icon" style="width:40px; height:40px; background:#1e293b; border-radius:10px; display:flex; align-items:center; justify-content:center; font-size:20px; color:#fff;"></div>
<div>
<div style="font-size:12px; color:#64748b;">当前主题预览</div>
<div id="vip-theme-name" style="font-size:15px; font-weight:bold; color:#1e293b;">普通用户</div>
</div>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
<div style="background:#f8fafc; border:1px solid #e2e8f0; border-radius:10px; padding:12px;">
<div style="font-size:10px; font-weight:bold; color:#94a3b8; text-transform:uppercase;">入场特效</div>
<div id="vip-join-effect" style="font-size:14px; font-weight:bold; color:#1e293b; margin-top:4px;"></div>
<div id="vip-join-banner" style="font-size:11px; color:#64748b; margin-top:4px;"></div>
</div>
<div style="background:#f8fafc; border:1px solid #e2e8f0; border-radius:10px; padding:12px;">
<div style="font-size:10px; font-weight:bold; color:#94a3b8; text-transform:uppercase;">离场特效</div>
<div id="vip-leave-effect" style="font-size:14px; font-weight:bold; color:#1e293b; margin-top:4px;"></div>
<div id="vip-leave-banner" style="font-size:11px; color:#64748b; margin-top:4px;"></div>
</div>
</div>
</div>
<div style="background:#fffdf5; border:1px solid #fef3c7; border-radius:12px; padding:18px;">
<div style="font-size:13px; font-weight:bold; color:#b45309; margin-bottom:12px;">等级默认语句</div>
<div style="display:grid; gap:10px;">
<div style="background:#fff; border:1px solid #fef3c7; border-radius:10px; padding:12px;">
<div style="font-size:11px; font-weight:bold; color:#f59e0b;">默认欢迎语</div>
<div id="vip-default-join" style="font-size:12px; color:#475569; margin-top:6px; line-height:1.6;"></div>
</div>
<div style="background:#fff; border:1px solid #fef3c7; border-radius:10px; padding:12px;">
<div style="font-size:11px; font-weight:bold; color:#f59e0b;">默认离开语</div>
<div id="vip-default-leave" style="font-size:12px; color:#475569; margin-top:6px; line-height:1.6;"></div>
</div>
</div>
</div>
</div>
<div style="background:#fff; border:1px solid #e2e8f0; border-radius:12px; padding:18px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px;">
<div style="font-size:15px; font-weight:bold; color:#1e293b;">我的个性化设置</div>
<span id="vip-customize-badge" style="font-size:10px; font-weight:bold; padding:2px 8px; border-radius:10px;"></span>
</div>
<div id="vip-customize-form" style="display:none;">
<div class="vip-setting-group">
<label class="vip-setting-label">入场特效选择</label>
<select id="vip-custom-join-effect" class="vip-setting-input">
<!-- 动态渲染 -->
</select>
</div>
<div class="vip-setting-group">
<label class="vip-setting-label">离场特效选择</label>
<select id="vip-custom-leave-effect" class="vip-setting-input">
<!-- 动态渲染 -->
</select>
</div>
<div class="vip-setting-group">
<label class="vip-setting-label">我的欢迎语</label>
<textarea id="vip-custom-join" class="vip-setting-input vip-setting-textarea" placeholder="例:{username} 闪耀登场..."></textarea>
<p style="font-size:10px; color:#94a3b8; margin-top:6px;">支持使用 {username} 占位符</p>
</div>
<div class="vip-setting-group">
<label class="vip-setting-label">我的离开语</label>
<textarea id="vip-custom-leave" class="vip-setting-input vip-setting-textarea" placeholder="例:{username} 优雅离场..."></textarea>
</div>
<button onclick="saveVipPresenceSettings()" class="vip-btn-save" style="width:100%;">保存我的专属设置</button>
</div>
<div id="vip-customize-notice" style="display:none; padding:15px; background:#f8fafc; border:1px dashed #cbd5e1; border-radius:10px; font-size:12px; color:#64748b; line-height:1.8;">
<!-- 动态提示 -->
</div>
</div>
</div>
</div>
{{-- 购买记录面板 --}}
<div id="vip-view-logs" class="vip-view-pane">
<div style="overflow-x:auto;">
<table style="width:100%; border-collapse:collapse; font-size:12px;">
<thead>
<tr style="background:#f1f5f9; color:#475569; text-align:left;">
<th style="padding:10px; border-bottom:1px solid #e2e8f0;">订单号</th>
<th style="padding:10px; border-bottom:1px solid #e2e8f0;">等级</th>
<th style="padding:10px; border-bottom:1px solid #e2e8f0;">金额</th>
<th style="padding:10px; border-bottom:1px solid #e2e8f0;">状态</th>
<th style="padding:10px; border-bottom:1px solid #e2e8f0;">开通时间</th>
</tr>
</thead>
<tbody id="vip-logs-body">
<!-- 动态渲染购买记录 -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
(function() {
const CSRF = () => document.querySelector('meta[name="csrf-token"]')?.content ?? '';
let vipData = null;
window.openVipModal = function() {
document.getElementById('vip-modal').style.display = 'flex';
fetchVipData();
};
window.closeVipModal = function() {
document.getElementById('vip-modal').style.display = 'none';
};
window.switchVipTab = function(tabName) {
document.querySelectorAll('.vip-tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.vip-view-pane').forEach(pane => pane.classList.remove('active'));
document.getElementById('vip-tabbtn-' + tabName).classList.add('active');
document.getElementById('vip-view-' + tabName).classList.add('active');
};
async function fetchVipData() {
try {
const res = await fetch('{{ route('vip.center') }}', {
headers: { 'Accept': 'application/json' }
});
const json = await res.json();
if (json.status === 'success') {
vipData = json.data;
renderVipModal();
}
} catch (e) {
console.error('Failed to fetch VIP data', e);
}
}
function renderVipModal() {
const d = vipData;
// 状态栏
document.getElementById('vip-current-name').textContent = d.user.is_vip ? (d.user.vip_name || '尊贵会员') : '普通用户';
document.getElementById('vip-current-name').style.color = d.user.vip_level?.color || '#1e293b';
document.getElementById('vip-expire-time').textContent = d.user.is_vip ? `到期时间:${d.user.hy_time}` : '开通会员享特权';
document.getElementById('vip-total-amount').textContent = `¥${Number(d.totalAmount).toFixed(2)}`;
// 会员等级
const grid = document.getElementById('vip-levels-grid');
grid.innerHTML = d.vipLevels.map(v => {
const isCurrent = d.user.vip_level_id === v.id && d.user.is_vip;
return `
<div class="vip-level-card ${isCurrent ? 'current' : ''}">
${isCurrent ? '<span class="vip-level-badge">当前档位</span>' : ''}
<div style="display:flex; align-items:center; gap:10px;">
<span style="font-size:24px;">${v.icon}</span>
<span style="font-size:16px; font-weight:bold; color:${v.color}">${v.name}</span>
</div>
<div style="font-size:11px; color:#64748b; line-height:1.5; min-height:33px;">${v.description || ''}</div>
<div style="margin-top:5px; space-y:6px;">
<div class="vip-feature-item"><span class="vip-feature-icon"></span> 经验获取 <b>${v.exp_multiplier}x</b></div>
<div class="vip-feature-item"><span class="vip-feature-icon"></span> 金币获取 <b>${v.jjb_multiplier}x</b></div>
<div class="vip-feature-item"><span class="vip-feature-icon"></span> 专属入场特效 & 横幅</div>
</div>
<div style="margin-top:auto; padding-top:10px;">
<div style="font-size:18px; font-weight:900; color:#e11d48; margin-bottom:10px;">¥${Number(v.price).toFixed(2)} <span style="font-size:11px; font-weight:normal; color:#94a3b8;">/ ${v.duration_days}</span></div>
<button onclick="buyVip(${v.id})" ${!d.vipPaymentEnabled ? 'disabled' : ''}
style="width:100%; border:none; border-radius:8px; padding:10px; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s;
background:${isCurrent ? '#fef3c7' : '#1e293b'}; color:${isCurrent ? '#b45309' : '#fff'};">
${!d.vipPaymentEnabled ? '支付暂未开启' : (isCurrent ? '立即续费' : '立即购买')}
</button>
</div>
</div>
`;
}).join('');
// 会员设置 Tab 显隐
const settingsBtn = document.getElementById('vip-tabbtn-settings');
if (d.user.is_vip) {
settingsBtn.style.display = 'block';
renderVipSettings();
} else {
settingsBtn.style.display = 'none';
}
// 购买记录
const logsBody = document.getElementById('vip-logs-body');
if (d.paymentLogs.length === 0) {
logsBody.innerHTML = '<tr><td colspan="5" style="padding:40px; text-align:center; color:#94a3b8;">暂无记录</td></tr>';
} else {
logsBody.innerHTML = d.paymentLogs.map(log => {
const statusMap = {
'created': { text: '待创建', class: 'background:#f1f5f9;color:#475569' },
'pending': { text: '待支付', class: 'background:#fef3c7;color:#b45309' },
'paid': { text: '已支付', class: 'background:#dcfce7;color:#166534' },
'closed': { text: '已关闭', class: 'background:#f1f5f9;color:#64748b' },
'failed': { text: '失败', class: 'background:#fee2e2;color:#991b1b' },
};
const s = statusMap[log.status] || { text: log.status, class: 'background:#f1f5f9' };
return `
<tr style="border-bottom:1px solid #f1f5f9;">
<td style="padding:10px; font-family:monospace; color:#64748b;">${log.order_no}</td>
<td style="padding:10px; font-weight:bold;">${log.vip_name}</td>
<td style="padding:10px; font-weight:bold; color:#e11d48;">¥${Number(log.amount).toFixed(2)}</td>
<td style="padding:10px;"><span style="display:inline-block; padding:2px 8px; border-radius:10px; font-size:10px; font-weight:bold; ${s.class}">${s.text}</span></td>
<td style="padding:10px; color:#64748b;">${log.opened_vip_at ? log.opened_vip_at.substring(0, 16).replace('T', ' ') : '—'}</td>
</tr>
`;
}).join('');
}
}
function renderVipSettings() {
const d = vipData;
const u = d.user;
const vl = u.vip_level;
if (!vl) return;
document.getElementById('vip-theme-icon').textContent = vl.icon || '✨';
document.getElementById('vip-theme-name').textContent = vl.name;
document.getElementById('vip-join-effect').textContent = d.effectOptions[u.custom_join_effect || vl.join_effect] || '无';
document.getElementById('vip-join-banner').textContent = `风格:${d.bannerStyleOptions[vl.join_banner] || '默认'}`;
document.getElementById('vip-leave-effect').textContent = d.effectOptions[u.custom_leave_effect || vl.leave_effect] || '无';
document.getElementById('vip-leave-banner').textContent = `风格:${d.bannerStyleOptions[vl.leave_banner] || '默认'}`;
document.getElementById('vip-default-join').textContent = vl.join_templates?.[0] || '当前档位尚未配置默认欢迎语。';
document.getElementById('vip-default-leave').textContent = vl.leave_templates?.[0] || '当前档位尚未配置默认离开语。';
const badge = document.getElementById('vip-customize-badge');
const form = document.getElementById('vip-customize-form');
const notice = document.getElementById('vip-customize-notice');
if (u.can_customize) {
badge.textContent = '已开启';
badge.style.background = '#dcfce7';
badge.style.color = '#166534';
form.style.display = 'block';
notice.style.display = 'none';
// 填充特效选项
const joinSelect = document.getElementById('vip-custom-join-effect');
const leaveSelect = document.getElementById('vip-custom-leave-effect');
let optionsHtml = '';
for (const [key, label] of Object.entries(d.effectOptions)) {
optionsHtml += `<option value="${key}">${label}</option>`;
}
joinSelect.innerHTML = optionsHtml;
leaveSelect.innerHTML = optionsHtml;
// 设置当前值
joinSelect.value = u.custom_join_effect || vl.join_effect || 'none';
leaveSelect.value = u.custom_leave_effect || vl.leave_effect || 'none';
document.getElementById('vip-custom-join').value = u.custom_join_message || '';
document.getElementById('vip-custom-leave').value = u.custom_leave_message || '';
} else {
badge.textContent = '未开放';
badge.style.background = '#f1f5f9';
badge.style.color = '#64748b';
form.style.display = 'none';
notice.style.display = 'block';
notice.textContent = u.is_vip
? '当前会员档位暂未开放个人自定义功能,不过你仍会自动使用本等级配置的专属欢迎语、离开语和华丽特效。'
: '开通会员后,这里会解锁对应等级的专属进退场主题;若等级允许,还能设置你自己的欢迎语和离开语。';
}
}
window.buyVip = function(levelId) {
// 这里我们模拟提交表单,因为支付逻辑通常需要页面跳转
const form = document.createElement('form');
form.method = 'POST';
form.action = '{{ route('vip.payment.store') }}';
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = '_token';
csrfInput.value = CSRF();
form.appendChild(csrfInput);
const idInput = document.createElement('input');
idInput.type = 'hidden';
idInput.name = 'vip_level_id';
idInput.value = levelId;
form.appendChild(idInput);
document.body.appendChild(form);
form.submit();
};
window.saveVipPresenceSettings = async function() {
const joinMsg = document.getElementById('vip-custom-join').value;
const leaveMsg = document.getElementById('vip-custom-leave').value;
const joinEffect = document.getElementById('vip-custom-join-effect').value;
const leaveEffect = document.getElementById('vip-custom-leave-effect').value;
try {
const res = await fetch('{{ route('vip.center.presence.update') }}', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': CSRF()
},
body: JSON.stringify({
custom_join_message: joinMsg,
custom_leave_message: leaveMsg,
custom_join_effect: joinEffect,
custom_leave_effect: leaveEffect
})
});
const json = await res.json();
if (json.status === 'success') {
window.chatDialog?.alert(json.message, '提示', '#10b981');
fetchVipData();
} else {
window.chatDialog?.alert(json.message || '保存失败', '提示', '#f59e0b');
}
} catch (e) {
window.chatDialog?.alert('网络异常,请重试', '错误', '#ef4444');
}
};
document.getElementById('vip-modal').addEventListener('click', function(e) {
if (e.target === this) closeVipModal();
});
})();
</script>
{{-- ═══════════ 婚姻状态弹窗 ═══════════ --}}
<div id="marriage-status-modal"
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.5);