基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"会员": [
|
||||
{
|
||||
"手机号": "17625166407",
|
||||
"密码": "wujun2000",
|
||||
"会员类型": "永久会员",
|
||||
"有效期": "永久",
|
||||
"注册时间": "2026-09-09",
|
||||
"备注": ""
|
||||
},
|
||||
{
|
||||
"手机号": "18209918985",
|
||||
"密码": "Wujun2000.",
|
||||
"会员类型": "永久会员",
|
||||
"有效期": "永久",
|
||||
"注册时间": "2026-09-09",
|
||||
"备注": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
// 会员系统服务器(零依赖纯 node http,照 cadbt server.cjs 模式)
|
||||
// · 端口 12701,只绑 127.0.0.1(外部访问不到)
|
||||
// · 数据存 huiyuan.json(本目录): { "会员": [ {手机号,密码,会员类型,有效期,注册时间,备注} ] }
|
||||
// · API:
|
||||
// POST /api/login {手机号,密码} → {ok,会员类型,有效期} | {ok:false,msg}
|
||||
// POST /api/register {手机号,密码} → {ok} 注册为"试用会员",有效期=今天+30天
|
||||
// GET /api/member?手机号=xxx → {ok,手机号,会员类型,有效期}
|
||||
// POST /api/changepwd {手机号,旧密码,新密码} → {ok}
|
||||
// GET /api/list → {ok,会员:[...]}(管理页用)
|
||||
// POST /api/save {会员:[...]} → {ok}(管理页整表保存)
|
||||
// · 项目云 API(2026-09-16,数据落 项目云\<手机号安全化>\<项目编号安全化>\项目.zip):
|
||||
// POST /api/proj/upload body=JSON头 + \n\n + zip二进制 → {ok,编号,大小,时间}(覆盖式)
|
||||
// GET /api/proj/list?手机号= → {ok,项目:[{编号,名称,时间,大小}]}
|
||||
// GET /api/proj/download?手机号=&编号= → zip 二进制流
|
||||
// POST /api/proj/delete {手机号,编号} → {ok}
|
||||
// 上传前校验会员已注册(huiyuan.json);手机号/编号文件名安全化+防路径穿越;
|
||||
// 元数据存各项目目录 meta.json(项目名/上传时间/大小)。
|
||||
// · 管理页: GET / 或 /admin.html → www/admin.html
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// · 监听地址/端口可用环境变量覆盖(Windows 本地默认 127.0.0.1:12701;
|
||||
// Linux 服务器部署用: MEMBER_HOST=0.0.0.0 MEMBER_PORT=12701 node server.cjs)
|
||||
var PORT = parseInt(process.env.MEMBER_PORT || '12701', 10);
|
||||
var HOST = process.env.MEMBER_HOST || '127.0.0.1';
|
||||
var ROOT = __dirname;
|
||||
var DB_FILE = path.join(ROOT, 'huiyuan.json');
|
||||
var WWW = path.join(ROOT, 'www');
|
||||
|
||||
var MIME = {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.js': 'application/javascript; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.ico': 'image/x-icon'
|
||||
};
|
||||
|
||||
// ---- 数据读写(原子写:临时文件+rename) ----
|
||||
function 读库() {
|
||||
try {
|
||||
var d = JSON.parse(fs.readFileSync(DB_FILE, 'utf8'));
|
||||
if (!Array.isArray(d['会员'])) d['会员'] = [];
|
||||
return d;
|
||||
} catch (e) { return { '会员': [] }; }
|
||||
}
|
||||
function 写库(data) {
|
||||
var tmp = DB_FILE + '.tmp';
|
||||
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');
|
||||
fs.renameSync(tmp, DB_FILE);
|
||||
}
|
||||
function 今天加天数(n) {
|
||||
var d = new Date(Date.now() + n * 86400000);
|
||||
return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);
|
||||
}
|
||||
|
||||
// ---- 业务 ----
|
||||
function 登录(b) {
|
||||
var db = 读库();
|
||||
for (var i = 0; i < db['会员'].length; i++) {
|
||||
var m = db['会员'][i];
|
||||
if (m['手机号'] === b['手机号']) {
|
||||
if (m['密码'] !== b['密码']) return { ok: false, msg: '密码错误' };
|
||||
return { ok: true, 会员类型: m['会员类型'], 有效期: m['有效期'] };
|
||||
}
|
||||
}
|
||||
return { ok: false, msg: '该手机号未注册' };
|
||||
}
|
||||
function 注册(b) {
|
||||
if (!b['手机号'] || !b['密码']) return { ok: false, msg: '手机号和密码不能为空' };
|
||||
var db = 读库();
|
||||
for (var i = 0; i < db['会员'].length; i++)
|
||||
if (db['会员'][i]['手机号'] === b['手机号']) return { ok: false, msg: '该手机号已注册' };
|
||||
db['会员'].push({
|
||||
'手机号': b['手机号'], '密码': b['密码'],
|
||||
'会员类型': '试用会员', '有效期': 今天加天数(30),
|
||||
'注册时间': 今天加天数(0), '备注': ''
|
||||
});
|
||||
写库(db);
|
||||
return { ok: true, msg: '注册成功' };
|
||||
}
|
||||
function 查会员(b) {
|
||||
var db = 读库();
|
||||
for (var i = 0; i < db['会员'].length; i++) {
|
||||
var m = db['会员'][i];
|
||||
if (m['手机号'] === b['手机号'])
|
||||
return { ok: true, 手机号: m['手机号'], 会员类型: m['会员类型'], 有效期: m['有效期'] };
|
||||
}
|
||||
return { ok: false, msg: '该手机号未注册' };
|
||||
}
|
||||
function 改密(b) {
|
||||
var db = 读库();
|
||||
for (var i = 0; i < db['会员'].length; i++) {
|
||||
var m = db['会员'][i];
|
||||
if (m['手机号'] === b['手机号']) {
|
||||
if (m['密码'] !== b['旧密码']) return { ok: false, msg: '旧密码错误' };
|
||||
if (!b['新密码']) return { ok: false, msg: '新密码不能为空' };
|
||||
m['密码'] = b['新密码'];
|
||||
写库(db);
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
return { ok: false, msg: '该手机号未注册' };
|
||||
}
|
||||
|
||||
// ---- 项目云(2026-09-16):数据落 项目云\<手机号安全化>\<编号安全化>\{项目.zip,meta.json} ----
|
||||
var PROJ_ROOT = path.join(ROOT, '项目云');
|
||||
function 安全名(s) { // 文件名安全化:去掉路径分隔/非法字符,限长 80
|
||||
var t = String(s || '').replace(/[\\\/:*?"<>|]/g, '_').replace(/\.\./g, '_').trim();
|
||||
if (t.length > 80) t = t.slice(0, 80);
|
||||
return t;
|
||||
}
|
||||
function 会员存在(手机号) {
|
||||
var db = 读库();
|
||||
for (var i = 0; i < db['会员'].length; i++)
|
||||
if (db['会员'][i]['手机号'] === 手机号) return true;
|
||||
return false;
|
||||
}
|
||||
function 项目目录(手机号, 编号) { // 防穿越:拼好后必须仍在 项目云 下
|
||||
var d = path.join(PROJ_ROOT, 安全名(手机号), 安全名(编号));
|
||||
return d.indexOf(PROJ_ROOT) === 0 ? d : null;
|
||||
}
|
||||
function 列项目(手机号) {
|
||||
if (!手机号 || !会员存在(手机号)) return { ok: false, msg: '该手机号未注册' };
|
||||
var dir = 项目目录(手机号, '');
|
||||
var 出 = [];
|
||||
try {
|
||||
fs.readdirSync(dir).forEach(function (编号) {
|
||||
var p = path.join(dir, 编号);
|
||||
try {
|
||||
var meta = JSON.parse(fs.readFileSync(path.join(p, 'meta.json'), 'utf8'));
|
||||
出.push({ 编号: meta['编号'] || 编号, 名称: meta['名称'] || '', 时间: meta['时间'] || '', 大小: meta['大小'] || 0 });
|
||||
} catch (e) { }
|
||||
});
|
||||
} catch (e) { } // 目录不存在=无项目
|
||||
return { ok: true, '项目': 出 };
|
||||
}
|
||||
|
||||
// ---- HTTP ----
|
||||
function sendJson(res, obj) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
||||
res.end(JSON.stringify(obj));
|
||||
}
|
||||
function readBody(req, cb) {
|
||||
var chunks = [];
|
||||
req.on('data', function (c) { chunks.push(c); });
|
||||
req.on('end', function () {
|
||||
try { cb(null, JSON.parse(Buffer.concat(chunks).toString('utf8'))); }
|
||||
catch (e) { cb(e, null); }
|
||||
});
|
||||
}
|
||||
|
||||
var server = http.createServer(function (req, res) {
|
||||
var urlPath = decodeURIComponent(req.url.split('?')[0]);
|
||||
var query = {};
|
||||
(req.url.split('?')[1] || '').split('&').forEach(function (kv) {
|
||||
var p = kv.split('=');
|
||||
if (p[0]) query[decodeURIComponent(p[0])] = decodeURIComponent((p[1] || '').replace(/\+/g, ' '));
|
||||
});
|
||||
|
||||
// API 路由
|
||||
if (urlPath === '/api/login') return readBody(req, function (e, b) { sendJson(res, e ? { ok: false, msg: '参数错误' } : 登录(b || {})); });
|
||||
if (urlPath === '/api/register') return readBody(req, function (e, b) { sendJson(res, e ? { ok: false, msg: '参数错误' } : 注册(b || {})); });
|
||||
if (urlPath === '/api/member') return sendJson(res, 查会员(query));
|
||||
if (urlPath === '/api/changepwd') return readBody(req, function (e, b) { sendJson(res, e ? { ok: false, msg: '参数错误' } : 改密(b || {})); });
|
||||
if (urlPath === '/api/list') {
|
||||
var db = 读库();
|
||||
return sendJson(res, { ok: true, '会员': db['会员'] });
|
||||
}
|
||||
if (urlPath === '/api/save') return readBody(req, function (e, b) {
|
||||
if (e || !b || !Array.isArray(b['会员'])) return sendJson(res, { ok: false, msg: '参数错误' });
|
||||
写库(b);
|
||||
return sendJson(res, { ok: true });
|
||||
});
|
||||
|
||||
// ---- 项目云路由 ----
|
||||
// 上传:body = JSON 文本(项目编号/名称/手机号) + "\n\n" + zip 二进制
|
||||
if (urlPath === '/api/proj/upload') {
|
||||
var chunks = [];
|
||||
req.on('data', function (c) { chunks.push(c); });
|
||||
req.on('end', function () {
|
||||
var buf = Buffer.concat(chunks);
|
||||
var sep = buf.indexOf('\n\n');
|
||||
if (sep < 0) return sendJson(res, { ok: false, msg: '缺少 JSON 头' });
|
||||
var 头 = {};
|
||||
try { 头 = JSON.parse(buf.slice(0, sep).toString('utf8')); } catch (e) { return sendJson(res, { ok: false, msg: 'JSON 头解析失败' }); }
|
||||
var 手机号 = 头['手机号'], 编号 = 头['编号'];
|
||||
if (!手机号 || !编号) return sendJson(res, { ok: false, msg: '缺少手机号或项目编号' });
|
||||
if (!会员存在(手机号)) return sendJson(res, { ok: false, msg: '该手机号未注册' });
|
||||
var zip = buf.slice(sep + 2);
|
||||
if (zip.length < 100) return sendJson(res, { ok: false, msg: 'zip 数据为空或过小' });
|
||||
var d = 项目目录(手机号, 编号);
|
||||
if (!d) return sendJson(res, { ok: false, msg: '路径非法' });
|
||||
try {
|
||||
fs.mkdirSync(d, { recursive: true });
|
||||
var tmp = path.join(d, '项目.zip.tmp');
|
||||
fs.writeFileSync(tmp, zip);
|
||||
var 目标 = path.join(d, '项目.zip');
|
||||
try { fs.unlinkSync(目标); } catch (e) { }
|
||||
fs.renameSync(tmp, 目标);
|
||||
var meta = { 编号: 编号, 名称: 头['名称'] || '', 时间: 头['时间'] || 今天加天数(0), 大小: zip.length };
|
||||
var mt = path.join(d, 'meta.json.tmp');
|
||||
fs.writeFileSync(mt, JSON.stringify(meta), 'utf8');
|
||||
fs.renameSync(mt, path.join(d, 'meta.json'));
|
||||
return sendJson(res, { ok: true, 编号: 编号, 大小: zip.length, 时间: meta['时间'] });
|
||||
} catch (e) { return sendJson(res, { ok: false, msg: '写盘失败: ' + e.message }); }
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (urlPath === '/api/proj/list') {
|
||||
return sendJson(res, 列项目(query['手机号']));
|
||||
}
|
||||
if (urlPath === '/api/proj/download') {
|
||||
var 手机号 = query['手机号'], 编号 = query['编号'];
|
||||
var d2 = 手机号 && 编号 ? 项目目录(手机号, 编号) : null;
|
||||
var zp = d2 ? path.join(d2, '项目.zip') : '';
|
||||
if (!d2 || !fs.existsSync(zp)) return sendJson(res, { ok: false, msg: '云上没有该项目' });
|
||||
res.writeHead(200, { 'Content-Type': 'application/zip', 'Content-Length': fs.statSync(zp).size });
|
||||
fs.createReadStream(zp).pipe(res);
|
||||
return;
|
||||
}
|
||||
if (urlPath === '/api/proj/delete') return readBody(req, function (e, b) {
|
||||
b = b || {};
|
||||
var d3 = b['手机号'] && b['编号'] ? 项目目录(b['手机号'], b['编号']) : null;
|
||||
if (!d3) return sendJson(res, { ok: false, msg: '参数错误' });
|
||||
try { fs.rmSync(d3, { recursive: true, force: true }); return sendJson(res, { ok: true }); }
|
||||
catch (e2) { return sendJson(res, { ok: false, msg: '删除失败: ' + e2.message }); }
|
||||
});
|
||||
|
||||
// 静态文件(管理页)
|
||||
if (urlPath === '/' || urlPath === '/admin.html') urlPath = '/admin.html';
|
||||
var file = path.join(WWW, path.normalize(urlPath).replace(/^([A-Za-z]:)?[\\\/]+/, ''));
|
||||
if (file.indexOf(WWW) !== 0) { res.writeHead(403); res.end('Forbidden'); return; }
|
||||
fs.readFile(file, function (err, data) {
|
||||
if (err) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('Not Found: ' + urlPath); return; }
|
||||
var ext = path.extname(file).toLowerCase();
|
||||
res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
|
||||
res.end(data);
|
||||
});
|
||||
}).listen(PORT, HOST, function () {
|
||||
console.log('会员系统服务 on http://' + HOST + ':' + PORT + ' root=' + ROOT);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{"手机号": "17625166407", "密码": "123456"}
|
||||
@@ -0,0 +1,162 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>会员管理</title>
|
||||
<style>
|
||||
body { font-family: "Microsoft YaHei UI", "Microsoft YaHei", sans-serif; font-size: 14px; margin: 20px; background: #f5f6f8; }
|
||||
h2 { margin: 0 0 14px; }
|
||||
table { border-collapse: collapse; width: 100%; background: #fff; }
|
||||
th, td { border: 1px solid #d0d4da; padding: 6px 10px; text-align: left; }
|
||||
th { background: #eef1f5; }
|
||||
tr:hover td { background: #f4f8ff; }
|
||||
.bar { margin-bottom: 12px; }
|
||||
button { padding: 5px 14px; cursor: pointer; }
|
||||
input { padding: 4px 8px; }
|
||||
.op button { margin-right: 4px; padding: 2px 8px; font-size: 12px; }
|
||||
#tip { color: #2a7; margin-left: 10px; }
|
||||
dialog { border: 1px solid #99a; border-radius: 6px; padding: 18px; }
|
||||
dialog::backdrop { background: rgba(0,0,0,.3); }
|
||||
dialog div { margin-bottom: 8px; }
|
||||
dialog label { display: inline-block; width: 80px; }
|
||||
dialog input { width: 200px; }
|
||||
.nav { margin-bottom: 16px; }
|
||||
.nav a { margin-right: 14px; color: #247; text-decoration: none; font-weight: 700; }
|
||||
.nav a.cur { color: #f60; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav"><a class="cur" href="/admin.html">会员管理</a><a href="/xiangmu-cloud.html">项目云管理</a></div>
|
||||
<h2>会员管理</h2>
|
||||
<div class="bar">
|
||||
手机号: <input id="q手机号" style="width:130px">
|
||||
<button onclick="查询()">查询</button>
|
||||
<button 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>密码</th><th>会员类型</th><th>有效期</th><th>注册时间</th><th>备注</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<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 style="text-align:right;margin-top:10px">
|
||||
<button onclick="dlg新增.close()">取消</button>
|
||||
<button onclick="确定新增()">确定</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<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 value="'+(m['密码']||'')+'" 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.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));
|
||||
}
|
||||
function 查询() { 渲染(); }
|
||||
function 新增() { document.getElementById('dlg新增').showModal(); }
|
||||
function 确定新增() {
|
||||
var 手机号 = document.getElementById('n手机号').value.trim();
|
||||
var 密码 = document.getElementById('n密码').value.trim();
|
||||
if (!手机号 || !密码) { alert('手机号和密码不能为空'); return; }
|
||||
fetch('/api/register', { method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ 手机号: 手机号, 密码: 密码 }) })
|
||||
.then(r => r.json()).then(function (j) {
|
||||
if (!j.ok) { alert(j.msg); return; }
|
||||
// 注册默认"试用会员",把用户在弹窗选的类型/有效期补上
|
||||
var 类型 = document.getElementById('n类型').value;
|
||||
var 有效期 = document.getElementById('n有效期').value.trim();
|
||||
加载后设置(手机号, 类型, 有效期);
|
||||
document.getElementById('dlg新增').close();
|
||||
});
|
||||
}
|
||||
function 加载后设置(手机号, 类型, 有效期) {
|
||||
fetch('/api/list').then(r => r.json()).then(function (j) {
|
||||
var list = j['会员'] || [];
|
||||
list.forEach(function (m) {
|
||||
if (m['手机号'] === 手机号) {
|
||||
if (类型) m['会员类型'] = 类型;
|
||||
if (有效期) m['有效期'] = 有效期;
|
||||
}
|
||||
});
|
||||
fetch('/api/save', { method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ '会员': list }) }).then(function () { 加载(); });
|
||||
});
|
||||
}
|
||||
加载();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>库备份管理</title>
|
||||
<style>
|
||||
body { font-family: "Microsoft YaHei UI", "Microsoft YaHei", sans-serif; font-size: 14px; margin: 20px; background: #f5f6f8; }
|
||||
h2 { margin: 0 0 14px; }
|
||||
table { border-collapse: collapse; width: 100%; background: #fff; }
|
||||
th, td { border: 1px solid #d0d4da; padding: 6px 10px; text-align: left; }
|
||||
th { background: #eef1f5; }
|
||||
tr:hover td { background: #f4f8ff; }
|
||||
.bar { margin-bottom: 12px; }
|
||||
button { padding: 5px 14px; cursor: pointer; }
|
||||
input, select { padding: 4px 8px; }
|
||||
.op button { margin-right: 4px; padding: 2px 8px; font-size: 12px; }
|
||||
.del { color: #c33; }
|
||||
#tip { color: #2a7; margin-left: 10px; }
|
||||
.nav { margin-bottom: 16px; }
|
||||
.nav a { margin-right: 14px; color: #247; text-decoration: none; font-weight: 700; }
|
||||
.nav a.cur { color: #f60; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav"><a href="/admin.html">会员管理</a><a href="/xiangmu-cloud.html">项目云管理</a><a class="cur" href="/lib-cloud.html">库备份管理</a></div>
|
||||
<h2>库备份管理(方案库/用户库/柜体库/铜排铝排库/企业信息,按手机号)</h2>
|
||||
<div class="bar">
|
||||
手机号: <select id="c手机号sel" onchange="同步手机号()" style="width:150px"><option value="">-- 选择会员 --</option></select>
|
||||
或输入: <input id="c手机号" style="width:130px" placeholder="手动输入">
|
||||
<button onclick="云查询()">查看云上库备份</button>
|
||||
<span id="tip"></span>
|
||||
</div>
|
||||
<table id="云tbl">
|
||||
<thead>
|
||||
<tr><th>库名</th><th>原始路径</th><th>备份时间</th><th>大小</th><th>操作</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
function 提示(s, ok) {
|
||||
var t = document.getElementById('tip');
|
||||
t.textContent = s; t.style.color = ok ? '#2a7' : '#c33';
|
||||
}
|
||||
function 加载会员下拉() {
|
||||
fetch('/api/list').then(r => r.json()).then(function (j) {
|
||||
var sel = document.getElementById('c手机号sel');
|
||||
(j['会员'] || []).forEach(function (m) {
|
||||
var p = m['手机号'] || '';
|
||||
if (!p) return;
|
||||
var op = document.createElement('option');
|
||||
op.value = p;
|
||||
op.textContent = p + (m['会员类型'] ? ' (' + m['会员类型'] + ')' : '');
|
||||
sel.appendChild(op);
|
||||
});
|
||||
}).catch(function () {});
|
||||
}
|
||||
function 同步手机号() {
|
||||
var v = document.getElementById('c手机号sel').value;
|
||||
if (v) document.getElementById('c手机号').value = v;
|
||||
}
|
||||
function 格式大小(b) {
|
||||
if (b >= 1048576) return (b / 1048576).toFixed(1) + 'M';
|
||||
if (b >= 1024) return (b / 1024).toFixed(1) + 'K';
|
||||
return b + 'B';
|
||||
}
|
||||
function 云查询() {
|
||||
var p = document.getElementById('c手机号').value.trim();
|
||||
if (!p) { 提示('请选择或输入手机号', false); return; }
|
||||
fetch('/api/proj/list?手机号=' + encodeURIComponent(p)).then(r => r.json()).then(function (j) {
|
||||
var tb = document.getElementById('云tbl').getElementsByTagName('tbody')[0];
|
||||
tb.innerHTML = '';
|
||||
if (!j.ok) { 提示(j.msg || '查询失败', false); return; }
|
||||
// 只显示 LIB_ 开头的库条目(项目在 xiangmu-cloud.html)
|
||||
var list = (j['项目'] || []).filter(function (pr) { return (pr.编号 || '').indexOf('LIB_') === 0; });
|
||||
if (!list.length) { 提示('该手机号云上暂无库备份', true); return; }
|
||||
提示('共 ' + list.length + ' 个库备份', true);
|
||||
list.forEach(function (pr) {
|
||||
var 名称 = pr.名称 || '', 显示名 = 名称, 路径 = '';
|
||||
var 竖 = 名称.indexOf('|');
|
||||
if (竖 >= 0) { 显示名 = 名称.slice(0, 竖); 路径 = 名称.slice(竖 + 1); }
|
||||
var tr = tb.insertRow();
|
||||
tr.insertCell().textContent = 显示名;
|
||||
tr.insertCell().textContent = 路径;
|
||||
tr.insertCell().textContent = pr.时间 || '';
|
||||
tr.insertCell().textContent = 格式大小(pr.大小 || 0);
|
||||
var op = tr.insertCell(); op.className = 'op';
|
||||
var del = document.createElement('button');
|
||||
del.textContent = '删除'; del.className = 'del';
|
||||
del.onclick = function () {
|
||||
if (!confirm('确定删除云端库备份 ' + 显示名 + ' ?')) return;
|
||||
fetch('/api/proj/delete', { method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ 手机号: p, 编号: pr.编号 }) })
|
||||
.then(r => r.json()).then(function (d) { 提示(d.ok ? '已删除' : '删除失败:'+(d.msg||''), d.ok); 云查询(); });
|
||||
};
|
||||
op.appendChild(del);
|
||||
var dl = document.createElement('button');
|
||||
dl.textContent = '下载';
|
||||
dl.onclick = function () {
|
||||
location.href = '/api/proj/download?手机号=' + encodeURIComponent(p) + '&编号=' + encodeURIComponent(pr.编号);
|
||||
};
|
||||
op.appendChild(dl);
|
||||
});
|
||||
}).catch(function (e) { 提示('请求失败: ' + e, false); });
|
||||
}
|
||||
加载会员下拉();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>项目云管理</title>
|
||||
<style>
|
||||
body { font-family: "Microsoft YaHei UI", "Microsoft YaHei", sans-serif; font-size: 14px; margin: 20px; background: #f5f6f8; }
|
||||
h2 { margin: 0 0 14px; }
|
||||
table { border-collapse: collapse; width: 100%; background: #fff; }
|
||||
th, td { border: 1px solid #d0d4da; padding: 6px 10px; text-align: left; }
|
||||
th { background: #eef1f5; }
|
||||
tr:hover td { background: #f4f8ff; }
|
||||
.bar { margin-bottom: 12px; }
|
||||
button { padding: 5px 14px; cursor: pointer; }
|
||||
input { padding: 4px 8px; }
|
||||
.op button { margin-right: 4px; padding: 2px 8px; font-size: 12px; }
|
||||
.del { color: #c33; }
|
||||
#tip { color: #2a7; margin-left: 10px; }
|
||||
.nav { margin-bottom: 16px; }
|
||||
.nav a { margin-right: 14px; color: #247; text-decoration: none; font-weight: 700; }
|
||||
.nav a.cur { color: #f60; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav"><a href="/admin.html">会员管理</a><a class="cur" href="/xiangmu-cloud.html">项目云管理</a><a href="/lib-cloud.html">库备份管理</a></div>
|
||||
<h2>项目云管理(按手机号查看名下云上项目)</h2>
|
||||
<div class="bar">
|
||||
手机号: <select id="c手机号sel" onchange="同步手机号()" style="width:150px"><option value="">-- 选择会员 --</option></select>
|
||||
或输入: <input id="c手机号" style="width:130px" placeholder="手动输入">
|
||||
<button onclick="云查询()">查看云上项目</button>
|
||||
<span id="tip"></span>
|
||||
</div>
|
||||
<table id="云tbl">
|
||||
<thead>
|
||||
<tr><th style="width:40px"></th><th>项目编号</th><th>项目名称</th><th>上传时间</th><th>大小</th><th>操作</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
function 提示(s, ok) {
|
||||
var t = document.getElementById('tip');
|
||||
t.textContent = s; t.style.color = ok ? '#2a7' : '#c33';
|
||||
}
|
||||
// 下拉选会员:从 /api/list 拉全部手机号填充;选中即同步到输入框(输入框仍可手改)
|
||||
function 加载会员下拉() {
|
||||
fetch('/api/list').then(r => r.json()).then(function (j) {
|
||||
var sel = document.getElementById('c手机号sel');
|
||||
var cur = sel.value;
|
||||
sel.innerHTML = '<option value="">-- 选择会员 --</option>';
|
||||
(j['会员'] || []).forEach(function (m) {
|
||||
var p = m['手机号'] || '';
|
||||
if (!p) return;
|
||||
var op = document.createElement('option');
|
||||
op.value = p;
|
||||
op.textContent = p + (m['会员类型'] ? ' (' + m['会员类型'] + ')' : '');
|
||||
sel.appendChild(op);
|
||||
});
|
||||
if (cur) sel.value = cur;
|
||||
}).catch(function () {});
|
||||
}
|
||||
function 同步手机号() {
|
||||
var v = document.getElementById('c手机号sel').value;
|
||||
if (v) document.getElementById('c手机号').value = v;
|
||||
}
|
||||
function 格式大小(b) {
|
||||
if (b >= 1048576) return (b / 1048576).toFixed(1) + 'M';
|
||||
if (b >= 1024) return (b / 1024).toFixed(1) + 'K';
|
||||
return b + 'B';
|
||||
}
|
||||
function 云查询() {
|
||||
var p = document.getElementById('c手机号').value.trim();
|
||||
if (!p) { 提示('请输入手机号', false); return; }
|
||||
fetch('/api/proj/list?手机号=' + encodeURIComponent(p)).then(r => r.json()).then(function (j) {
|
||||
var tb = document.getElementById('云tbl').getElementsByTagName('tbody')[0];
|
||||
tb.innerHTML = '';
|
||||
if (!j.ok) { 提示(j.msg || '查询失败', false); return; }
|
||||
var list = (j['项目'] || []).filter(function (pr) { return (pr.编号 || '').indexOf('LIB_') !== 0; }); // 库备份在 lib-cloud.html,这里只显示项目
|
||||
if (!list.length) { 提示('该手机号云上暂无项目', true); return; }
|
||||
提示('共 ' + list.length + ' 个项目', true);
|
||||
list.forEach(function (pr) {
|
||||
var tr = tb.insertRow();
|
||||
tr.insertCell().innerHTML = '<input type="checkbox" class="云sel">';
|
||||
tr.insertCell().textContent = pr.编号 || '';
|
||||
tr.insertCell().textContent = pr.名称 || '';
|
||||
tr.insertCell().textContent = pr.时间 || '';
|
||||
tr.insertCell().textContent = 格式大小(pr.大小 || 0);
|
||||
var op = tr.insertCell(); op.className = 'op';
|
||||
var del = document.createElement('button');
|
||||
del.textContent = '删除'; del.className = 'del';
|
||||
del.onclick = function () {
|
||||
if (!confirm('确定删除云端项目 ' + (pr.名称 || pr.编号) + ' ?')) return;
|
||||
fetch('/api/proj/delete', { method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ 手机号: p, 编号: pr.编号 }) })
|
||||
.then(r => r.json()).then(function (d) { 提示(d.ok ? '已删除' : '删除失败:'+(d.msg||''), d.ok); 云查询(); });
|
||||
};
|
||||
op.appendChild(del);
|
||||
var dl = document.createElement('button');
|
||||
dl.textContent = '下载';
|
||||
dl.onclick = function () {
|
||||
location.href = '/api/proj/download?手机号=' + encodeURIComponent(p) + '&编号=' + encodeURIComponent(pr.编号);
|
||||
};
|
||||
op.appendChild(dl);
|
||||
});
|
||||
}).catch(function (e) { 提示('请求失败: ' + e, false); });
|
||||
}
|
||||
加载会员下拉();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
rem 会员服务器停止脚本 - 按端口定位进程,不误杀其他 node - GBK 编码,勿加 chcp
|
||||
setlocal enabledelayedexpansion
|
||||
set "FOUND="
|
||||
for /f "tokens=5" %%p in ('netstat -ano ^| findstr ":12701" ^| findstr "LISTENING"') do (
|
||||
set "FOUND=%%p"
|
||||
)
|
||||
if not defined FOUND (
|
||||
echo [提示] 会员服务当前没有在运行。
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
echo [停止] 结束监听 12701 端口的进程 PID=!FOUND!
|
||||
taskkill /F /PID !FOUND! >nul 2>nul
|
||||
if !errorlevel!==0 (
|
||||
echo [完成] 会员服务已停止。
|
||||
) else (
|
||||
echo [错误] 停止失败,请手动结束 PID !FOUND!
|
||||
)
|
||||
pause
|
||||
@@ -0,0 +1,26 @@
|
||||
@echo off
|
||||
rem 会员服务器启动脚本(双击运行) - GBK 编码,勿加 chcp
|
||||
setlocal
|
||||
set "DIR=%~dp0"
|
||||
set "NODEEXE="
|
||||
if exist "%DIR%node\node.exe" set "NODEEXE=%DIR%node\node.exe"
|
||||
if not defined NODEEXE if exist "%DIR%..\cadbt\node\node.exe" set "NODEEXE=%DIR%..\cadbt\node\node.exe"
|
||||
if not defined NODEEXE if exist "%DIR%..\cadbt\cadbt\bin\Debug\node\node.exe" set "NODEEXE=%DIR%..\cadbt\cadbt\bin\Debug\node\node.exe"
|
||||
if not defined NODEEXE (
|
||||
echo [错误] 未找到 node.exe,请确认 node 目录存在。
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
powershell -NoProfile -Command "try{$c=New-Object Net.Sockets.TcpClient;$r=$c.BeginConnect('127.0.0.1',12701,$null,$null);if($r.AsyncWaitHandle.WaitOne(500)){$c.EndConnect($r);exit 0}}catch{};exit 1" >nul 2>nul
|
||||
if %errorlevel%==0 (
|
||||
echo [提示] 会员服务已在运行: http://127.0.0.1:12701
|
||||
start "" "http://127.0.0.1:12701/"
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo [启动] 会员服务器 http://127.0.0.1:12701 数据文件: %DIR%huiyuan.json
|
||||
start "" "http://127.0.0.1:12701/"
|
||||
"%NODEEXE%" "%DIR%server.cjs"
|
||||
pause
|
||||
@@ -0,0 +1 @@
|
||||
{"编号":"20260916001","名称":"32131风华绝代","时间":"2026-09-16","大小":1034}
|
||||
@@ -0,0 +1 @@
|
||||
{"编号":"LIB_fak方案库(含公式表)","名称":"fak方案库(含公式表)|sjk\\fak.db","时间":"2026-09-16","大小":1594}
|
||||
@@ -0,0 +1 @@
|
||||
{"编号":"LIB_企业信息.config","名称":"企业信息.config|shezhi\\企业信息.config.xml","时间":"2026-09-16","大小":328}
|
||||
@@ -0,0 +1 @@
|
||||
{"编号":"LIB_柜体库","名称":"柜体库|sjk\\advancedDataGridView10Table.db","时间":"2026-09-16","大小":444}
|
||||
@@ -0,0 +1 @@
|
||||
{"编号":"LIB_用户库","名称":"用户库|sjk\\advancedDataGridView7Table.db","时间":"2026-09-16","大小":8113}
|
||||
@@ -0,0 +1 @@
|
||||
{"编号":"LIB_铜排库","名称":"铜排库|sjk\\advancedDataGridView8Table.db","时间":"2026-09-16","大小":1148}
|
||||
@@ -0,0 +1 @@
|
||||
{"编号":"LIB_铝排库","名称":"铝排库|sjk\\advancedDataGridView8Table_LMY.db","时间":"2026-09-16","大小":1152}
|
||||
Reference in New Issue
Block a user