管理页新增:员工删除功能 + 直接选择 CSV 文件导入(不用复制粘贴)

- server.js 新增 POST /api/admin/delete:移除本地账号并逐平台 force_logout
- admin.html 员工行新增删除按钮(带确认);导入区新增选择文件导入,自动去 BOM
This commit is contained in:
KIMI
2026-08-09 00:54:52 +08:00
parent dfcecaaff6
commit 07f018c280
5 changed files with 40 additions and 0 deletions
+1
View File
@@ -39,6 +39,7 @@ POST /api/login
| POST | `/api/admin/disable` | 停用 `{staffNo}`,立即无法登录并被踢下线 |
| POST | `/api/admin/enable` | 启用 `{staffNo}` |
| POST | `/api/admin/reset_password` | 重置密码 `{staffNo, password}` |
| POST | `/api/admin/delete` | 删除 `{staffNo}`,立即无法登录并被踢下线(OpenIM 侧同名用户保留但本服务不再为其签发令牌,等于不可用) |
管理页:浏览器打开 `http://<服务器IP>:10010/`,输入管理口令即可操作,无需写接口。
+27
View File
@@ -54,7 +54,9 @@
<div class="hint">支持逗号或 Tab 分隔(Excel 复制出来直接粘贴即可);重复工号会覆盖原账号(姓名与密码重置、状态恢复为启用)。</div>
<div class="row" style="margin-top:10px">
<button onclick="doImport()">导入</button>
<button class="ghost" onclick="$('fileInput').click()">选择文件导入</button>
<button class="ghost" onclick="downloadTemplate()">下载导入模板</button>
<input id="fileInput" type="file" accept=".csv,.txt" style="display:none" onchange="importFile(this)">
</div>
</div>
@@ -110,6 +112,7 @@ async function loadList() {
? `<button class="danger" onclick="toggle('${e.staffNo}', false)">停用</button>`
: `<button class="ghost" onclick="toggle('${e.staffNo}', true)">启用</button>`}
<button class="ghost" onclick="resetPwd('${e.staffNo}')">重置密码</button>
<button class="danger" onclick="deleteEmp('${e.staffNo}')">删除</button>
</td>
</tr>`).join('') || '<tr><td colspan="5" style="color:#98a2b3">暂无员工,请先导入</td></tr>';
} catch (e) { show(e.message, false); }
@@ -151,6 +154,30 @@ async function doImport() {
} catch (e) { show(e.message, false); }
}
// 直接选择模板 CSV 文件导入,不用复制粘贴
async function importFile(input) {
const file = input.files[0];
input.value = '';
if (!file) return;
try {
let text = await file.text();
if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1); // 去 BOM
const d = await api('/api/admin/import', { csv: text });
const bad = d.results.filter(r => !r.ok);
show('文件「' + file.name + '」导入完成:成功 ' + d.imported + ' / ' + d.total + (bad.length ? '\n失败明细:\n' + bad.map(b => b.line + ' → ' + b.msg).join('\n') : ''), bad.length === 0);
loadList();
} catch (e) { show(e.message, false); }
}
async function deleteEmp(staffNo) {
if (!confirm('确定删除 ' + staffNo + ' 吗?删除后该员工立即无法登录,需要重新添加才能恢复。')) return;
try {
await api('/api/admin/delete', { staffNo });
show('已删除 ' + staffNo, true);
loadList();
} catch (e) { show(e.message, false); }
}
// 下载 CSV 导入模板(加 BOM 让 Excel 正确识别中文)
function downloadTemplate() {
const csv = '\uFEFF工号,姓名,初始密码\n10001,张三,abc123456\n10002,李四,abc123456\n';
+11
View File
@@ -260,6 +260,17 @@ const routes = {
ok(res, publicView(emp, body.staffNo));
},
// 删除员工:本地账号直接移除(立即无法再登录并被踢下线)。
// 注意:OpenIM 侧的同名用户不删(其公开 API 不提供删除),但删除后本服务不再为其签发令牌,等于不可用。
'POST /api/admin/delete': async (req, res, body) => {
const emp = employees[body.staffNo];
if (!emp) return fail(res, '员工不存在');
delete employees[body.staffNo];
saveStore();
forceLogoutAll(body.staffNo); // 异步踢下线,不等结果
ok(res, { staffNo: body.staffNo, deleted: true });
},
'POST /api/admin/reset_password': async (req, res, body) => {
const emp = employees[body.staffNo];
if (!emp) return fail(res, '员工不存在');
+1
View File
@@ -0,0 +1 @@
{}
View File