优化 会员页面;
This commit is contained in:
@@ -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'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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()">×</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);
|
||||
|
||||
Reference in New Issue
Block a user