含 app/server/server.cjs 服务端、app/www 网页(admin/auth/项目云/库备份/settings)、 app/ui 图标、cmd 安装脚本、config、wizard、manifest、图标与对接文档。 按仓库规则排除构建产物 *.fpk 与 .mimosa 缓存。
157 lines
6.8 KiB
HTML
Executable File
157 lines
6.8 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>会员管理</title>
|
|
<link rel="stylesheet" href="/style.css">
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
<header>
|
|
<h2>会员管理</h2>
|
|
<span class="badge" id="h-ver"></span>
|
|
<nav class="nav">
|
|
<a class="cur" href="/admin.html">会员管理</a>
|
|
<a href="/xiangmu-cloud.html">项目云管理</a>
|
|
<a href="/lib-cloud.html">库备份管理</a>
|
|
<a href="/settings.html">设置</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<div class="card card--table">
|
|
<div class="bar">
|
|
手机号: <input id="q手机号" style="width:130px">
|
|
<button onclick="查询()">查询</button>
|
|
<button class="primary" onclick="新增()">新增会员</button>
|
|
<button onclick="加载()">刷新列表</button>
|
|
<button onclick="保存()">保存修改</button>
|
|
<span id="tip"></span>
|
|
</div>
|
|
<table id="tbl">
|
|
<thead>
|
|
<tr><th style="width:40px"></th><th>手机号</th><th title="密码加密存储不可查看;输入新值即重置该会员密码,留空保持不变">密码</th><th>会员类型</th><th>有效期</th><th>注册时间</th><th>备注</th><th>操作</th></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<dialog id="dlg新增">
|
|
<h3>新增会员</h3>
|
|
<div><label>手机号:</label><input id="n手机号"></div>
|
|
<div><label>密码:</label><input id="n密码"></div>
|
|
<div><label>会员类型:</label>
|
|
<select id="n类型" onchange="n类型改()">
|
|
<option>试用会员</option><option>月会员</option><option>季会员</option><option>年会员</option><option>永久会员</option>
|
|
</select>
|
|
</div>
|
|
<div><label>有效期:</label><input id="n有效期" placeholder="2027-05-13"></div>
|
|
<div class="dlg-actions">
|
|
<button onclick="dlg新增.close()">取消</button>
|
|
<button class="primary" onclick="确定新增()">确定</button>
|
|
</div>
|
|
</dialog>
|
|
|
|
<script src="/auth.js"></script>
|
|
<script>
|
|
var 数据 = [];
|
|
|
|
function 提示(s, ok) {
|
|
var t = document.getElementById('tip');
|
|
t.textContent = s; t.style.color = ok ? '#2a7' : '#c33';
|
|
setTimeout(function(){ t.textContent=''; }, 3000);
|
|
}
|
|
// 会员类型 → 有效期天数(永久会员=永久);改类型时自动算有效期
|
|
var 类型天数 = { '试用会员': 30, '月会员': 1, '季会员': 3, '年会员': 12, '永久会员': 0 };
|
|
function 今天加月(n) {
|
|
var d = new Date(); d.setMonth(d.getMonth() + n);
|
|
return d.getFullYear() + '-' + ('0'+(d.getMonth()+1)).slice(-2) + '-' + ('0'+d.getDate()).slice(-2);
|
|
}
|
|
function 改类型(i, v) {
|
|
if (!数据[i]) return;
|
|
数据[i]['会员类型'] = v;
|
|
var n = 类型天数[v];
|
|
if (n === undefined) return; // 自定义类型不动有效期
|
|
数据[i]['有效期'] = n === 0 ? '永久' : 今天加月(n);
|
|
渲染(); // 重渲染让有效期输入框同步显示新值
|
|
}
|
|
function 加载() {
|
|
fetch('/api/list').then(r => r.json()).then(j => {
|
|
数据 = j['会员'] || [];
|
|
渲染();
|
|
});
|
|
}
|
|
function 渲染() {
|
|
var q = document.getElementById('q手机号').value.trim();
|
|
var tb = document.getElementById('tbl').getElementsByTagName('tbody')[0];
|
|
tb.innerHTML = '';
|
|
数据.forEach(function (m, i) {
|
|
if (q && (m['手机号']||'').indexOf(q) < 0) return;
|
|
var tr = tb.insertRow();
|
|
tr.insertCell().innerHTML = '<input type="checkbox" data-i="'+i+'" class="sel">';
|
|
tr.insertCell().innerHTML = '<input value="'+(m['手机号']||'')+'" onchange="改('+i+',\'手机号\',this.value)" style="width:120px">';
|
|
tr.insertCell().innerHTML = '<input type="password" value="" placeholder="留空不变" title="密码加密存储不可查看;输入新值即重置,留空保持不变" onchange="改('+i+',\'密码\',this.value)" style="width:100px">';
|
|
tr.insertCell().innerHTML = '<select onchange="改类型('+i+',this.value)" style="width:90px">'
|
|
+ ['试用会员','月会员','季会员','年会员','永久会员'].map(function(t){ return '<option'+((m['会员类型']||'')===t?' selected':'')+'>'+t+'</option>'; }).join('')
|
|
+ '</select>';
|
|
tr.insertCell().innerHTML = '<input value="'+(m['有效期']||'')+'" onchange="改('+i+',\'有效期\',this.value)" style="width:100px">';
|
|
tr.insertCell().textContent = m['注册时间'] || '';
|
|
tr.insertCell().innerHTML = '<input value="'+(m['备注']||'')+'" onchange="改('+i+',\'备注\',this.value)" style="width:120px">';
|
|
var op = tr.insertCell(); op.className = 'op';
|
|
var del = document.createElement('button');
|
|
del.textContent = '删除';
|
|
del.className = 'del';
|
|
del.onclick = function () {
|
|
if (!confirm('确定删除会员 ' + m['手机号'] + ' ?')) return;
|
|
数据.splice(i, 1); 渲染(); 保存();
|
|
};
|
|
op.appendChild(del);
|
|
});
|
|
}
|
|
function 改(i, k, v) { if (数据[i]) { 数据[i][k] = v; } }
|
|
// 新增弹窗:选类型 → 自动填有效期(手改过则以手改为准,切类型才覆盖)
|
|
function n类型改() {
|
|
var v = document.getElementById('n类型').value;
|
|
var n = 类型天数[v];
|
|
if (n === undefined) return;
|
|
document.getElementById('n有效期').value = n === 0 ? '永久' : 今天加月(n);
|
|
}
|
|
function 保存() {
|
|
fetch('/api/save', { method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({ '会员': 数据 }) })
|
|
.then(r => r.json()).then(j => 提示(j.ok ? '已保存' : '保存失败:'+(j.msg||''), j.ok))
|
|
.catch(function (e) { 提示('请求失败: ' + e, false); });
|
|
}
|
|
function 查询() { 渲染(); }
|
|
function 新增() { document.getElementById('dlg新增').showModal(); }
|
|
function 确定新增() {
|
|
var 手机号 = document.getElementById('n手机号').value.trim();
|
|
var 密码 = document.getElementById('n密码').value.trim();
|
|
if (!手机号 || !密码) { alert('手机号和密码不能为空'); return; }
|
|
var 类型 = document.getElementById('n类型').value;
|
|
var 有效期 = document.getElementById('n有效期').value.trim();
|
|
fetch('/api/admin/addmember', { method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({ 手机号: 手机号, 密码: 密码, 会员类型: 类型, 有效期: 有效期 || n类型初值(类型) }) })
|
|
.then(r => r.json())
|
|
.then(function (j) {
|
|
if (!j.ok) { alert(j.msg || '创建失败'); return; }
|
|
document.getElementById('dlg新增').close();
|
|
提示('已创建会员 ' + 手机号, true);
|
|
加载();
|
|
})
|
|
.catch(function (e) { alert('请求失败: ' + e); });
|
|
}
|
|
function n类型初值(类型) {
|
|
var n = 类型天数[类型];
|
|
return n === undefined || n === 0 ? '永久' : 今天加月(n);
|
|
}
|
|
fetch('/api/version').then(r => r.json()).then(function (j) {
|
|
var b = document.getElementById('h-ver');
|
|
if (b && j.version) b.textContent = 'v' + j.version;
|
|
}).catch(function () {});
|
|
加载();
|
|
</script>
|
|
</body>
|
|
</html>
|