Files
squad-rain-ops-mini/wwwroot/vip-manager.html

510 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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()">&times;</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()">&times;</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>