mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-05 21:15:52 +08:00
510 lines
22 KiB
HTML
510 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>会员管理 - RainOpsMini</title>
|
||
<link rel="stylesheet" href="/lib/css/bootstrap.min.css">
|
||
<link rel="stylesheet" href="/lib/css/font-awesome.min.css">
|
||
<style>
|
||
:root {
|
||
--primary-color: #0056b3;
|
||
--bg-color: #f0f2f5;
|
||
--card-bg: #ffffff;
|
||
--text-color: #2c3e50;
|
||
--border-color: #d1d5db;
|
||
--danger-color: #d93025;
|
||
--success-color: #107c10;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background-color: var(--bg-color);
|
||
color: var(--text-color);
|
||
margin: 0;
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 1.5rem;
|
||
font-weight: bold;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
transition: background-color 0.2s;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
}
|
||
.btn-primary:hover { background-color: #005a9e; }
|
||
|
||
.btn-success {
|
||
background-color: var(--success-color);
|
||
color: white;
|
||
}
|
||
.btn-success:hover { background-color: #0e6a0e; }
|
||
|
||
.btn-danger {
|
||
background-color: white;
|
||
color: var(--danger-color);
|
||
border: 1px solid var(--danger-color);
|
||
}
|
||
.btn-danger:hover { background-color: #fff0f0; }
|
||
|
||
.btn-back {
|
||
background-color: transparent;
|
||
color: #666;
|
||
border: 1px solid #ccc;
|
||
}
|
||
.btn-back:hover { background-color: #eee; }
|
||
|
||
.btn-info {
|
||
background-color: #17a2b8;
|
||
color: white;
|
||
}
|
||
.btn-info:hover { background-color: #138496; }
|
||
|
||
.container {
|
||
background: var(--card-bg);
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
padding: 20px;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
||
th { background-color: #f9f9f9; font-weight: 600; color: #666; }
|
||
|
||
/* Modal */
|
||
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); justify-content: center; align-items: center; z-index: 1000; }
|
||
.modal.active { display: flex; }
|
||
.modal-content { background-color: white; padding: 25px; border-radius: 8px; width: 500px; max-width: 90%; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
|
||
.modal-header { font-size: 1.2rem; font-weight: bold; margin-bottom: 20px; display: flex; justify-content: space-between; }
|
||
.close-btn { cursor: pointer; font-size: 1.5rem; line-height: 1; }
|
||
.form-group { margin-bottom: 15px; }
|
||
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
|
||
.form-control { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
|
||
.hint { font-size: 0.8em; color: #888; margin-top: 4px; }
|
||
|
||
.badge { padding: 4px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; }
|
||
.badge-vip { background-color: #e3f2fd; color: #0d47a1; }
|
||
.badge-svip { background-color: #fce4ec; color: #880e4f; }
|
||
|
||
.status-valid { color: #28a745; }
|
||
.status-expired { color: #dc3545; font-weight: bold; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<div class="title">
|
||
<span><i class="fas fa-crown"></i> 会员管理</span>
|
||
</div>
|
||
<div style="display: flex; gap: 10px;">
|
||
<button class="btn btn-primary" id="btn-add" onclick="showAddModal()"><i class="fas fa-plus"></i> 添加会员</button>
|
||
<button class="btn btn-success" id="btn-sync" onclick="syncToConfig()"><i class="fas fa-save"></i> 写入配置文件 (Admin.cfg)</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>SteamID</th>
|
||
<th>名称</th>
|
||
<th>组别</th>
|
||
<th>到期时间</th>
|
||
<th>备注/原因</th>
|
||
<th>欢迎语</th>
|
||
<th>状态</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="vip-list">
|
||
<tr><td colspan="8" style="text-align:center;">加载中...</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Add/Edit Modal -->
|
||
<div id="add-modal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<span id="modal-title">添加新会员</span>
|
||
<span class="close-btn" onclick="closeModal()">×</span>
|
||
</div>
|
||
<form onsubmit="handleSubmit(event)">
|
||
<input type="hidden" id="edit-mode" value="false">
|
||
<div class="form-group">
|
||
<label>SteamID64 *</label>
|
||
<input type="text" id="steamId" class="form-control" required placeholder="7656119xxxxxxxxxx" pattern="\d{17}" title="请输入17位 SteamID64">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>玩家名称</label>
|
||
<input type="text" id="name" class="form-control" placeholder="仅作备注用途">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>会员组 *</label>
|
||
<select id="group" class="form-control" required>
|
||
<option value="VIP_RainOpsMini">VIP (权限: reserve)</option>
|
||
<option value="SVIP_RainOpsMini">SVIP (权限: reserve, teamchange)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>到期时间 *</label>
|
||
<input type="date" id="expiryDate" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>自定义欢迎语 (VIP)</label>
|
||
<input type="text" id="welcomeMessage" class="form-control" placeholder="留空则使用默认配置。支持变量 {Name}">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>备注/原因</label>
|
||
<textarea id="remark" class="form-control" rows="3" placeholder="例如:赞助/活动奖励"></textarea>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary" style="width: 100%" id="modal-submit-btn">确认添加</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Welcome Settings Modal -->
|
||
<div id="welcomeSettingsModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<span><i class="fa fa-bullhorn"></i> 欢迎语插件配置</span>
|
||
<span class="close-btn" onclick="closeWelcomeSettings()">×</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>启用插件</label>
|
||
<select id="welcomeEnabled" class="form-control">
|
||
<option value="true">启用</option>
|
||
<option value="false">禁用</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>普通玩家欢迎语 (启用)</label>
|
||
<select id="generalWelcomeEnabled" class="form-control">
|
||
<option value="true">启用</option>
|
||
<option value="false">禁用</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>普通玩家欢迎语内容</label>
|
||
<input type="text" id="generalWelcomeMessage" class="form-control" placeholder="欢迎 {Name} 进入服务器...">
|
||
<small style="color: #666;">可用变量: {Name}</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>默认 VIP 欢迎语</label>
|
||
<input type="text" id="defaultVipWelcomeMessage" class="form-control" placeholder="尊贵的VIP {Name} 进入了服务器!">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>默认 SVIP 欢迎语</label>
|
||
<input type="text" id="defaultSvipWelcomeMessage" class="form-control" placeholder="尊贵的SVIP {Name} 进入了服务器!">
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-back" onclick="closeWelcomeSettings()">取消</button>
|
||
<button class="btn btn-primary" onclick="saveWelcomeSettings()">保存配置</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Permission Check
|
||
window.checkPermission = function(key) {
|
||
const isSuper = localStorage.getItem('isSuperAdmin') === 'true';
|
||
if (isSuper) return true;
|
||
try {
|
||
const perms = JSON.parse(localStorage.getItem('permissions') || '[]');
|
||
return perms.includes('all') || perms.includes(key);
|
||
} catch { return false; }
|
||
};
|
||
|
||
(function() {
|
||
const required = 'vips.view';
|
||
if (!checkPermission(required)) {
|
||
document.body.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;color:red;font-size:1.5rem;">Access Denied: Permission "vips.view" required.</div>';
|
||
throw new Error("Access Denied");
|
||
}
|
||
})();
|
||
|
||
async function loadVips() {
|
||
try {
|
||
const res = await fetch('/api/vip');
|
||
if (res.status === 401) {
|
||
window.location.href = '/login.html';
|
||
return;
|
||
}
|
||
const vips = await res.json();
|
||
renderVips(vips);
|
||
} catch (err) {
|
||
console.error(err);
|
||
document.getElementById('vip-list').innerHTML = `<tr><td colspan="7" style="color:red">加载失败: ${err.message}</td></tr>`;
|
||
}
|
||
}
|
||
|
||
function renderVips(vips) {
|
||
const tbody = document.getElementById('vip-list');
|
||
if (vips.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;color:#888;">暂无会员数据</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = vips.map(v => {
|
||
const isExpired = new Date(v.expiryDate) < new Date();
|
||
const status = isExpired ? '<span class="status-expired">已过期</span>' : '<span class="status-valid">生效中</span>';
|
||
const badgeClass = v.group.includes('SVIP') ? 'badge-svip' : 'badge-vip';
|
||
const groupName = v.group.replace('_RainOpsMini', '');
|
||
|
||
return `
|
||
<tr>
|
||
<td>${v.steamId}</td>
|
||
<td>${v.name || '-'}</td>
|
||
<td><span class="badge ${badgeClass}">${groupName}</span></td>
|
||
<td>${new Date(v.expiryDate).toLocaleDateString()}</td>
|
||
<td>${v.remark || '-'}</td>
|
||
<td>${v.welcomeMessage || '-'}</td>
|
||
<td>${status}</td>
|
||
<td>
|
||
${checkPermission('vips.edit') ?
|
||
`<button class="btn btn-primary btn-sm" onclick="showEditModal('${v.steamId}', '${v.name || ''}', '${v.group}', '${v.expiryDate}', '${v.remark || ''}', '${v.welcomeMessage || ''}')" style="margin-right:5px;">编辑</button>` : ''}
|
||
${checkPermission('vips.delete') ?
|
||
`<button class="btn btn-danger btn-sm" onclick="deleteVip('${v.steamId}')">删除</button>` : ''}
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function showAddModal() {
|
||
document.getElementById('add-modal').classList.add('active');
|
||
document.getElementById('modal-title').innerText = "添加新会员";
|
||
document.getElementById('edit-mode').value = "false";
|
||
document.getElementById('steamId').disabled = false;
|
||
document.getElementById('modal-submit-btn').innerText = "确认添加";
|
||
|
||
// Clear form
|
||
document.getElementById('steamId').value = '';
|
||
document.getElementById('name').value = '';
|
||
document.getElementById('remark').value = '';
|
||
document.getElementById('welcomeMessage').value = '';
|
||
document.getElementById('group').value = 'VIP_RainOpsMini';
|
||
|
||
// Default expiry to 30 days later
|
||
const date = new Date();
|
||
date.setDate(date.getDate() + 30);
|
||
document.getElementById('expiryDate').valueAsDate = date;
|
||
}
|
||
|
||
function showEditModal(steamId, name, group, expiryDate, remark, welcomeMessage) {
|
||
document.getElementById('add-modal').classList.add('active');
|
||
document.getElementById('modal-title').innerText = "编辑会员";
|
||
document.getElementById('edit-mode').value = "true";
|
||
document.getElementById('modal-submit-btn').innerText = "保存修改";
|
||
|
||
const steamIdInput = document.getElementById('steamId');
|
||
steamIdInput.value = steamId;
|
||
steamIdInput.disabled = true;
|
||
|
||
document.getElementById('name').value = name;
|
||
document.getElementById('group').value = group;
|
||
document.getElementById('remark').value = remark;
|
||
document.getElementById('welcomeMessage').value = welcomeMessage || '';
|
||
|
||
// Handle date format
|
||
const date = new Date(expiryDate);
|
||
document.getElementById('expiryDate').valueAsDate = date;
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('add-modal').classList.remove('active');
|
||
}
|
||
|
||
async function handleSubmit(e) {
|
||
e.preventDefault();
|
||
|
||
const isEdit = document.getElementById('edit-mode').value === "true";
|
||
const permission = isEdit ? 'vips.edit' : 'vips.add';
|
||
|
||
if (!checkPermission(permission)) {
|
||
alert('没有权限执行此操作');
|
||
return;
|
||
}
|
||
|
||
const data = {
|
||
steamId: document.getElementById('steamId').value,
|
||
name: document.getElementById('name').value,
|
||
group: document.getElementById('group').value,
|
||
expiryDate: document.getElementById('expiryDate').value,
|
||
remark: document.getElementById('remark').value,
|
||
welcomeMessage: document.getElementById('welcomeMessage').value
|
||
};
|
||
|
||
try {
|
||
const url = '/api/vip';
|
||
const method = isEdit ? 'PUT' : 'POST';
|
||
|
||
const res = await fetch(url, {
|
||
method: method,
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (res.ok) {
|
||
closeModal();
|
||
loadVips();
|
||
alert(isEdit ? '修改成功' : '添加成功');
|
||
} else {
|
||
const err = await res.json();
|
||
alert((isEdit ? '修改失败: ' : '添加失败: ') + (err.error || '未知错误'));
|
||
}
|
||
} catch (err) {
|
||
alert('请求失败: ' + err.message);
|
||
}
|
||
}
|
||
|
||
async function deleteVip(steamId) {
|
||
if (!checkPermission('vips.delete')) return;
|
||
if (!confirm(`确定要删除会员 ${steamId} 吗?`)) return;
|
||
|
||
try {
|
||
const res = await fetch(`/api/vip/${steamId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
if (res.ok) {
|
||
loadVips();
|
||
} else {
|
||
alert('删除失败');
|
||
}
|
||
} catch (err) {
|
||
alert('请求失败: ' + err.message);
|
||
}
|
||
}
|
||
|
||
async function syncToConfig() {
|
||
if (!checkPermission('vips.edit')) return;
|
||
if (!confirm('确定要将当前未过期的会员信息覆盖写入到 Admin.cfg 吗?\n注意:这将更新 VIP_RainOpsMini 和 SVIP_RainOpsMini 组的配置。')) return;
|
||
|
||
try {
|
||
const res = await fetch('/api/vip/sync', {
|
||
method: 'POST'
|
||
});
|
||
|
||
if (res.ok) {
|
||
alert('写入成功!配置文件已更新。');
|
||
} else {
|
||
const err = await res.json();
|
||
alert('写入失败: ' + (err.error || '未知错误'));
|
||
}
|
||
} catch (err) {
|
||
alert('请求失败: ' + err.message);
|
||
}
|
||
}
|
||
|
||
function openWelcomeSettings() {
|
||
// Load config
|
||
fetch('/api/plugins')
|
||
.then(res => res.json())
|
||
.then(plugins => {
|
||
// Try finding by Chinese name first (default), then fallback to English if needed
|
||
const plugin = plugins.find(p => p.name === '欢迎语插件' || p.name === 'WelcomePlugin');
|
||
|
||
if (plugin && plugin.config) {
|
||
const config = plugin.config;
|
||
document.getElementById('welcomeEnabled').value = config.Enabled !== false ? 'true' : 'false';
|
||
document.getElementById('generalWelcomeEnabled').value = config.GeneralWelcomeEnabled !== false ? 'true' : 'false';
|
||
document.getElementById('generalWelcomeMessage').value = config.GeneralWelcomeMessage || '';
|
||
document.getElementById('defaultVipWelcomeMessage').value = config.DefaultVipWelcomeMessage || '';
|
||
document.getElementById('defaultSvipWelcomeMessage').value = config.DefaultSvipWelcomeMessage || '';
|
||
|
||
// Store plugin name for save function
|
||
document.getElementById('welcomeSettingsModal').dataset.pluginName = plugin.name;
|
||
|
||
document.getElementById('welcomeSettingsModal').classList.add('active');
|
||
} else {
|
||
const pluginNames = plugins.map(p => p.name).join(', ');
|
||
alert(`未找到欢迎语插件配置,请确认插件已加载。\n当前已加载插件: ${pluginNames}`);
|
||
console.log('Available plugins:', plugins.map(p => p.name));
|
||
}
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
alert('加载配置失败: ' + err.message);
|
||
});
|
||
}
|
||
|
||
function closeWelcomeSettings() {
|
||
document.getElementById('welcomeSettingsModal').classList.remove('active');
|
||
}
|
||
|
||
function saveWelcomeSettings() {
|
||
const pluginName = document.getElementById('welcomeSettingsModal').dataset.pluginName || '欢迎语插件';
|
||
|
||
const config = {
|
||
Enabled: document.getElementById('welcomeEnabled').value === 'true',
|
||
GeneralWelcomeEnabled: document.getElementById('generalWelcomeEnabled').value === 'true',
|
||
GeneralWelcomeMessage: document.getElementById('generalWelcomeMessage').value,
|
||
DefaultVipWelcomeMessage: document.getElementById('defaultVipWelcomeMessage').value,
|
||
DefaultSvipWelcomeMessage: document.getElementById('defaultSvipWelcomeMessage').value
|
||
};
|
||
|
||
fetch(`/api/plugins/${encodeURIComponent(pluginName)}/config`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(config)
|
||
})
|
||
.then(res => {
|
||
if (res.ok) {
|
||
alert('配置已保存');
|
||
closeWelcomeSettings();
|
||
} else {
|
||
alert('保存失败');
|
||
}
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
alert('保存出错');
|
||
});
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
loadVips();
|
||
|
||
// Apply button permissions
|
||
if (!checkPermission('vips.add')) {
|
||
const btnAdd = document.getElementById('btn-add');
|
||
if (btnAdd) btnAdd.style.display = 'none';
|
||
}
|
||
if (!checkPermission('vips.edit')) {
|
||
const btnSync = document.getElementById('btn-sync');
|
||
if (btnSync) btnSync.style.display = 'none';
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|