功能:VIP 赞助会员系统
- 新建 vip_levels 表(名称、图标、颜色、经验/金币倍率、专属进入/离开模板) - 默认4个等级种子:白银🥈(×1.5)、黄金🥇(×2.0)、钻石💎(×3.0)、至尊👑(×5.0) - 后台 VIP 等级 CRUD 管理(新增/编辑/删除,配置模板和倍率) - 后台用户编辑弹窗支持设置 VIP 等级和到期时间 - ChatController 心跳经验按 VIP 倍率加成 - FishingController 正向奖励按 VIP 倍率加成(负面惩罚不变) - 在线名单显示 VIP 图标和管理员🛡️标识 - VIP 用户进入/离开使用专属颜色和标题 - 后台侧栏新增「👑 VIP 会员等级」入口
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
{{--
|
||||
文件功能:AI 厂商配置管理页面
|
||||
|
||||
提供 AI 厂商的完整 CRUD 管理:
|
||||
- 列表展示所有配置(名称、模型、状态等)
|
||||
- 新增/编辑厂商配置弹窗
|
||||
- 启用/禁用切换、设为默认、删除
|
||||
- 全局开关控制聊天机器人是否启用
|
||||
|
||||
@author ChatRoom Laravel
|
||||
@version 1.0.0
|
||||
--}}
|
||||
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', 'AI 厂商配置')
|
||||
|
||||
@section('content')
|
||||
<div x-data="{
|
||||
showForm: false,
|
||||
editId: null,
|
||||
form: {
|
||||
provider: '',
|
||||
name: '',
|
||||
api_key: '',
|
||||
api_endpoint: '',
|
||||
model: '',
|
||||
temperature: 0.3,
|
||||
max_tokens: 2048,
|
||||
sort_order: 0,
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开新增弹窗,重置表单
|
||||
*/
|
||||
openNew() {
|
||||
this.editId = null;
|
||||
this.form = {
|
||||
provider: '',
|
||||
name: '',
|
||||
api_key: '',
|
||||
api_endpoint: '',
|
||||
model: '',
|
||||
temperature: 0.3,
|
||||
max_tokens: 2048,
|
||||
sort_order: 0,
|
||||
};
|
||||
this.showForm = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开编辑弹窗,填充现有数据
|
||||
*/
|
||||
openEdit(provider) {
|
||||
this.editId = provider.id;
|
||||
this.form = {
|
||||
provider: provider.provider,
|
||||
name: provider.name,
|
||||
api_key: '', // 编辑时不回填 API Key(已加密)
|
||||
api_endpoint: provider.api_endpoint,
|
||||
model: provider.model,
|
||||
temperature: provider.temperature,
|
||||
max_tokens: provider.max_tokens,
|
||||
sort_order: provider.sort_order,
|
||||
};
|
||||
this.showForm = true;
|
||||
},
|
||||
}">
|
||||
|
||||
{{-- 全局开关 + 操作栏 --}}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mb-6">
|
||||
<div class="p-6 flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<h2 class="text-lg font-bold text-gray-800">🤖 AI 聊天机器人</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-500">全局开关:</span>
|
||||
<button id="chatbot-toggle-btn" onclick="toggleChatBot()"
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {{ $chatbotEnabled ? 'bg-emerald-500' : 'bg-gray-300' }}">
|
||||
<span
|
||||
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {{ $chatbotEnabled ? 'translate-x-6' : 'translate-x-1' }}"></span>
|
||||
</button>
|
||||
<span id="chatbot-status-text"
|
||||
class="text-sm font-bold {{ $chatbotEnabled ? 'text-emerald-600' : 'text-gray-400' }}">
|
||||
{{ $chatbotEnabled ? '已开启' : '已关闭' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button x-on:click="openNew()"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition font-bold text-sm">
|
||||
+ 添加 AI 厂商
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 厂商列表 --}}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-gray-600">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left font-bold">厂商</th>
|
||||
<th class="px-6 py-3 text-left font-bold">模型</th>
|
||||
<th class="px-6 py-3 text-left font-bold">API 端点</th>
|
||||
<th class="px-6 py-3 text-center font-bold">参数</th>
|
||||
<th class="px-6 py-3 text-center font-bold">排序</th>
|
||||
<th class="px-6 py-3 text-center font-bold">状态</th>
|
||||
<th class="px-6 py-3 text-center font-bold">默认</th>
|
||||
<th class="px-6 py-3 text-center font-bold">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
@forelse ($providers as $provider)
|
||||
<tr class="hover:bg-gray-50 transition {{ !$provider->is_enabled ? 'opacity-50' : '' }}">
|
||||
<td class="px-6 py-4">
|
||||
<div class="font-bold text-gray-800">{{ $provider->name }}</div>
|
||||
<div class="text-xs text-gray-400">{{ $provider->provider }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<code class="bg-gray-100 px-2 py-1 rounded text-xs">{{ $provider->model }}</code>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="text-xs text-gray-500 truncate block max-w-[200px]"
|
||||
title="{{ $provider->api_endpoint }}">
|
||||
{{ $provider->api_endpoint }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center text-xs text-gray-500">
|
||||
T={{ $provider->temperature }} / {{ $provider->max_tokens }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center text-gray-500">
|
||||
{{ $provider->sort_order }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<button onclick="toggleProvider({{ $provider->id }}, this)"
|
||||
class="px-3 py-1 rounded-full text-xs font-bold {{ $provider->is_enabled ? 'bg-emerald-100 text-emerald-700' : 'bg-gray-100 text-gray-500' }}">
|
||||
{{ $provider->is_enabled ? '已启用' : '已禁用' }}
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
@if ($provider->is_default)
|
||||
<span class="px-3 py-1 rounded-full text-xs font-bold bg-amber-100 text-amber-700">★
|
||||
默认</span>
|
||||
@else
|
||||
<button onclick="setDefault({{ $provider->id }})"
|
||||
class="px-3 py-1 rounded-full text-xs text-gray-400 hover:text-amber-600 hover:bg-amber-50 transition">
|
||||
设为默认
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<button x-on:click="openEdit({{ $provider->toJson() }})"
|
||||
class="text-indigo-600 hover:text-indigo-800 text-xs font-bold">编辑</button>
|
||||
<form action="{{ route('admin.ai-providers.destroy', $provider->id) }}" method="POST"
|
||||
onsubmit="return confirm('确定要删除 {{ $provider->name }} 吗?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="text-red-500 hover:text-red-700 text-xs font-bold">删除</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="px-6 py-12 text-center text-gray-400">
|
||||
暂无 AI 厂商配置,请点击上方"添加 AI 厂商"按钮。
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- 新增/编辑弹窗 --}}
|
||||
<div x-show="showForm" x-cloak style="display: none;"
|
||||
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" x-on:click.self="showForm = false">
|
||||
<div class="bg-white rounded-xl shadow-xl w-[560px] max-h-[90vh] overflow-y-auto" x-transition>
|
||||
<div class="p-6 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 class="text-lg font-bold" x-text="editId ? '编辑 AI 厂商' : '添加 AI 厂商'"></h3>
|
||||
<button x-on:click="showForm = false" class="text-gray-400 hover:text-gray-600 text-xl">×</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
:action="editId ? '{{ url('admin/ai-providers') }}/' + editId : '{{ route('admin.ai-providers.store') }}'"
|
||||
method="POST" class="p-6 space-y-4">
|
||||
@csrf
|
||||
<template x-if="editId">
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
</template>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{{-- 厂商标识 --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">厂商标识 <span
|
||||
class="text-red-500">*</span></label>
|
||||
<input type="text" name="provider" x-model="form.provider" required
|
||||
placeholder="如:deepseek, qwen, openai"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
{{-- 显示名称 --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">显示名称 <span
|
||||
class="text-red-500">*</span></label>
|
||||
<input type="text" name="name" x-model="form.name" required placeholder="如:DeepSeek、通义千问"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- API Key --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">
|
||||
API Key
|
||||
<span x-show="!editId" class="text-red-500">*</span>
|
||||
<span x-show="editId" class="text-gray-400 font-normal">(留空表示不修改)</span>
|
||||
</label>
|
||||
<input type="password" name="api_key" x-model="form.api_key" :required="!editId"
|
||||
placeholder="sk-xxxxxxxxxxxx"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500 font-mono">
|
||||
</div>
|
||||
|
||||
{{-- API 端点 --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">API 端点 <span
|
||||
class="text-red-500">*</span></label>
|
||||
<input type="url" name="api_endpoint" x-model="form.api_endpoint" required
|
||||
placeholder="https://api.deepseek.com"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<p class="text-xs text-gray-400 mt-1">系统会自动拼接 /v1/chat/completions</p>
|
||||
</div>
|
||||
|
||||
{{-- 模型 --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">模型名称 <span
|
||||
class="text-red-500">*</span></label>
|
||||
<input type="text" name="model" x-model="form.model" required
|
||||
placeholder="如:deepseek-chat, qwen-turbo"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
{{-- Temperature --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">Temperature</label>
|
||||
<input type="number" name="temperature" x-model="form.temperature" step="0.1"
|
||||
min="0" max="2"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
{{-- Max Tokens --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">最大 Tokens</label>
|
||||
<input type="number" name="max_tokens" x-model="form.max_tokens" min="100"
|
||||
max="32000"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
{{-- Sort Order --}}
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">排序</label>
|
||||
<input type="number" name="sort_order" x-model="form.sort_order" min="0"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<p class="text-xs text-gray-400 mt-1">故障转移时按此排序</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-4 border-t flex justify-end gap-3">
|
||||
<button type="button" x-on:click="showForm = false"
|
||||
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md text-sm transition">取消</button>
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-indigo-600 text-white rounded-md font-bold hover:bg-indigo-700 text-sm transition">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 切换全局聊天机器人开关
|
||||
*/
|
||||
async function toggleChatBot() {
|
||||
try {
|
||||
const res = await fetch('{{ route('admin.ai-providers.toggle-chatbot') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
// 更新按钮样式
|
||||
const btn = document.getElementById('chatbot-toggle-btn');
|
||||
const text = document.getElementById('chatbot-status-text');
|
||||
if (data.enabled) {
|
||||
btn.className = btn.className.replace('bg-gray-300', 'bg-emerald-500');
|
||||
btn.firstElementChild.className = btn.firstElementChild.className.replace('translate-x-1',
|
||||
'translate-x-6');
|
||||
text.textContent = '已开启';
|
||||
text.className = text.className.replace('text-gray-400', 'text-emerald-600');
|
||||
} else {
|
||||
btn.className = btn.className.replace('bg-emerald-500', 'bg-gray-300');
|
||||
btn.firstElementChild.className = btn.firstElementChild.className.replace('translate-x-6',
|
||||
'translate-x-1');
|
||||
text.textContent = '已关闭';
|
||||
text.className = text.className.replace('text-emerald-600', 'text-gray-400');
|
||||
}
|
||||
alert(data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('操作失败:' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换厂商启用/禁用状态
|
||||
*/
|
||||
async function toggleProvider(id, btn) {
|
||||
try {
|
||||
const res = await fetch('/admin/ai-providers/' + id + '/toggle', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
location.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('操作失败:' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设为默认 AI 厂商
|
||||
*/
|
||||
async function setDefault(id) {
|
||||
try {
|
||||
const res = await fetch('/admin/ai-providers/' + id + '/default', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
location.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('操作失败:' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -37,6 +37,14 @@
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.autoact.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
🎲 随机事件
|
||||
</a>
|
||||
<a href="{{ route('admin.vip.index') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.vip.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
👑 VIP 会员等级
|
||||
</a>
|
||||
<a href="{{ route('admin.ai-providers.index') }}"
|
||||
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.ai-providers.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
|
||||
🤖 AI 厂商配置
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/10">
|
||||
<a href="{{ route('rooms.index') }}"
|
||||
|
||||
@@ -44,6 +44,10 @@
|
||||
<img src="/images/headface/{{ $user->headface ?? '01.gif' }}"
|
||||
class="w-8 h-8 rounded border object-cover">
|
||||
<span class="font-bold text-gray-800">{{ $user->username }}</span>
|
||||
@if ($user->isVip())
|
||||
<span title="{{ $user->vipName() }}"
|
||||
style="color: {{ $user->vipLevel?->color ?? '#f59e0b' }}">{{ $user->vipIcon() }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4 text-sm">{{ [0 => '保密', 1 => '男', 2 => '女'][$user->sex] ?? '保密' }}</td>
|
||||
@@ -70,6 +74,8 @@
|
||||
sex: '{{ $user->sex }}',
|
||||
qianming: '{{ addslashes($user->qianming ?? '') }}',
|
||||
visit_num: {{ $user->visit_num ?? 0 }},
|
||||
vip_level_id: '{{ $user->vip_level_id ?? '' }}',
|
||||
hy_time: '{{ $user->hy_time ? $user->hy_time->format('Y-m-d') : '' }}',
|
||||
requestUrl: '{{ route('admin.users.update', $user->id) }}'
|
||||
}; showEditModal = true"
|
||||
class="text-xs bg-indigo-50 text-indigo-600 font-bold px-3 py-1.5 rounded hover:bg-indigo-600 hover:text-white transition cursor-pointer">
|
||||
@@ -168,6 +174,28 @@
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 p-2 border text-sm">
|
||||
</div>
|
||||
|
||||
{{-- VIP 会员设置 --}}
|
||||
<div class="mt-4 grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">VIP 会员等级</label>
|
||||
<select name="vip_level_id" x-model="editingUser.vip_level_id"
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 p-2 border text-sm">
|
||||
<option value="">无(普通用户)</option>
|
||||
@foreach ($vipLevels as $vl)
|
||||
<option value="{{ $vl->id }}">{{ $vl->icon }}
|
||||
{{ $vl->name }}(×{{ $vl->exp_multiplier }}经验
|
||||
×{{ $vl->jjb_multiplier }}金币)</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">会员到期时间
|
||||
<span class="font-normal text-gray-400">(留空=永久)</span></label>
|
||||
<input type="date" name="hy_time" x-model="editingUser.hy_time"
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 p-2 border text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 密码 --}}
|
||||
<div class="mt-4">
|
||||
<label
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
{{--
|
||||
文件功能:后台 VIP 会员等级管理页面
|
||||
提供会员等级的新增、编辑、删除功能
|
||||
后台可自由管理所有会员等级配置
|
||||
|
||||
@author ChatRoom Laravel
|
||||
@version 1.0.0
|
||||
--}}
|
||||
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', 'VIP 会员等级管理')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div x-data="{
|
||||
showForm: false,
|
||||
editing: null,
|
||||
form: {
|
||||
name: '',
|
||||
icon: '⭐',
|
||||
color: '#f59e0b',
|
||||
exp_multiplier: 1.5,
|
||||
jjb_multiplier: 1.2,
|
||||
sort_order: 0,
|
||||
price: 10,
|
||||
duration_days: 30,
|
||||
join_templates: '',
|
||||
leave_templates: '',
|
||||
},
|
||||
|
||||
openCreate() {
|
||||
this.editing = null;
|
||||
this.form = {
|
||||
name: '',
|
||||
icon: '⭐',
|
||||
color: '#f59e0b',
|
||||
exp_multiplier: 1.5,
|
||||
jjb_multiplier: 1.2,
|
||||
sort_order: 0,
|
||||
price: 10,
|
||||
duration_days: 30,
|
||||
join_templates: '',
|
||||
leave_templates: '',
|
||||
};
|
||||
this.showForm = true;
|
||||
},
|
||||
|
||||
openEdit(level) {
|
||||
this.editing = level;
|
||||
this.form = {
|
||||
name: level.name,
|
||||
icon: level.icon,
|
||||
color: level.color,
|
||||
exp_multiplier: level.exp_multiplier,
|
||||
jjb_multiplier: level.jjb_multiplier,
|
||||
sort_order: level.sort_order,
|
||||
price: level.price,
|
||||
duration_days: level.duration_days,
|
||||
join_templates: level.join_templates_text,
|
||||
leave_templates: level.leave_templates_text,
|
||||
};
|
||||
this.showForm = true;
|
||||
}
|
||||
}">
|
||||
|
||||
{{-- 头部操作栏 --}}
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800">会员等级列表</h2>
|
||||
<p class="text-sm text-gray-500">管理赞助会员等级,配置名称、图标、倍率和专属提示语</p>
|
||||
</div>
|
||||
<button @click="openCreate()"
|
||||
class="bg-indigo-600 text-white px-5 py-2.5 rounded-lg font-bold hover:bg-indigo-700 transition shadow-sm">
|
||||
+ 新增等级
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- 等级卡片列表 --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
@foreach ($levels as $level)
|
||||
<div class="bg-white rounded-xl shadow-sm border overflow-hidden hover:shadow-md transition">
|
||||
<div class="p-5">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-2xl">{{ $level->icon }}</span>
|
||||
<span class="font-bold text-lg"
|
||||
style="color: {{ $level->color }}">{{ $level->name }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs bg-gray-100 px-2 py-0.5 rounded text-gray-500">排序:{{ $level->sort_order }}</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">经验倍率</span>
|
||||
<span class="font-bold text-green-600">×{{ $level->exp_multiplier }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">金币倍率</span>
|
||||
<span class="font-bold text-yellow-600">×{{ $level->jjb_multiplier }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">赞助金额</span>
|
||||
<span class="font-bold text-gray-700">¥{{ $level->price }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">有效天数</span>
|
||||
<span class="font-bold">{{ $level->duration_days ?: '永久' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">当前会员</span>
|
||||
<span class="font-bold text-indigo-600">{{ $level->users()->count() }} 人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t bg-gray-50 px-5 py-3 flex justify-end space-x-2">
|
||||
<button
|
||||
@click="openEdit({
|
||||
id: {{ $level->id }},
|
||||
name: '{{ addslashes($level->name) }}',
|
||||
icon: '{{ $level->icon }}',
|
||||
color: '{{ $level->color }}',
|
||||
exp_multiplier: {{ $level->exp_multiplier }},
|
||||
jjb_multiplier: {{ $level->jjb_multiplier }},
|
||||
sort_order: {{ $level->sort_order }},
|
||||
price: {{ $level->price }},
|
||||
duration_days: {{ $level->duration_days }},
|
||||
join_templates_text: `{{ str_replace('`', '', implode("\n", $level->join_templates_array)) }}`,
|
||||
leave_templates_text: `{{ str_replace('`', '', implode("\n", $level->leave_templates_array)) }}`,
|
||||
requestUrl: '{{ route('admin.vip.update', $level->id) }}'
|
||||
})"
|
||||
class="text-xs bg-indigo-50 text-indigo-600 font-bold px-3 py-1.5 rounded hover:bg-indigo-600 hover:text-white transition">
|
||||
编辑
|
||||
</button>
|
||||
<form action="{{ route('admin.vip.destroy', $level->id) }}" method="POST"
|
||||
onsubmit="return confirm('确定删除等级 [{{ $level->name }}] 吗?关联用户会变为普通用户。')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit"
|
||||
class="text-xs bg-red-50 text-red-600 font-bold px-3 py-1.5 rounded hover:bg-red-600 hover:text-white transition">
|
||||
删除
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if ($levels->isEmpty())
|
||||
<div class="text-center py-16 text-gray-400">
|
||||
<p class="text-lg">暂无会员等级,点击右上角"新增等级"创建</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 新增/编辑弹窗 --}}
|
||||
<div x-show="showForm" style="display: none;"
|
||||
class="fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4">
|
||||
<div @click.away="showForm = false"
|
||||
class="bg-white rounded-xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto" x-transition>
|
||||
<div class="bg-indigo-900 px-6 py-4 flex justify-between items-center rounded-t-xl text-white">
|
||||
<h3 class="font-bold text-lg" x-text="editing ? '编辑等级:' + editing.name : '新增会员等级'"></h3>
|
||||
<button @click="showForm = false" class="text-gray-400 hover:text-white text-xl">×</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<form :action="editing ? editing.requestUrl : '{{ route('admin.vip.store') }}'" method="POST">
|
||||
@csrf
|
||||
<template x-if="editing"><input type="hidden" name="_method" value="PUT"></template>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">等级名称</label>
|
||||
<input type="text" name="name" x-model="form.name" required maxlength="50"
|
||||
class="w-full border rounded-md p-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">图标 (Emoji)</label>
|
||||
<input type="text" name="icon" x-model="form.icon" required maxlength="20"
|
||||
class="w-full border rounded-md p-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">颜色</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="color" name="color" x-model="form.color"
|
||||
class="w-10 h-8 border rounded cursor-pointer">
|
||||
<input type="text" x-model="form.color" maxlength="10"
|
||||
class="flex-1 border rounded-md p-2 text-sm font-mono">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">排序 (越大越高级)</label>
|
||||
<input type="number" name="sort_order" x-model="form.sort_order" required min="0"
|
||||
class="w-full border rounded-md p-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">经验倍率</label>
|
||||
<input type="number" name="exp_multiplier" x-model="form.exp_multiplier" required
|
||||
min="1" step="0.1" class="w-full border rounded-md p-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">金币倍率</label>
|
||||
<input type="number" name="jjb_multiplier" x-model="form.jjb_multiplier" required
|
||||
min="1" step="0.1" class="w-full border rounded-md p-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">赞助金额 (元)</label>
|
||||
<input type="number" name="price" x-model="form.price" required min="0"
|
||||
class="w-full border rounded-md p-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">有效天数 (0=永久)</label>
|
||||
<input type="number" name="duration_days" x-model="form.duration_days" required
|
||||
min="0" class="w-full border rounded-md p-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 专属模板 --}}
|
||||
<div class="mt-4">
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">进入欢迎语模板
|
||||
<span class="font-normal text-gray-400">(每行一条,用 {username} 代替用户名)</span></label>
|
||||
<textarea name="join_templates" x-model="form.join_templates" rows="3" placeholder="例:贵宾{username}驾到!全场起立!"
|
||||
class="w-full border rounded-md p-2 text-sm"></textarea>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<label class="block text-xs font-bold text-gray-600 mb-1">离开提示语模板
|
||||
<span class="font-normal text-gray-400">(每行一条)</span></label>
|
||||
<textarea name="leave_templates" x-model="form.leave_templates" rows="3" placeholder="例:贵宾{username}潇洒离去..."
|
||||
class="w-full border rounded-md p-2 text-sm"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4 mt-4 border-t">
|
||||
<button type="button" @click="showForm = false"
|
||||
class="px-4 py-2 border rounded font-medium text-gray-600 hover:bg-gray-50">取消</button>
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded font-bold hover:bg-indigo-700 shadow-sm"
|
||||
x-text="editing ? '保存修改' : '创建等级'"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user