feat(admin): 方案A—消息铃铛接真实待办聚合 + 清理空壳接口与空卡片

【消息铃铛(真实数据)】
- POST /api/message/list 改为待办聚合:待接单订单/待审核大神/待审核提现/待处理举报/待仲裁工单(复用 getDashboardTodos + 新增 pendingOrdersCount)
- POST /api/message/read 持久化已读:新表 admin_message_reads 记录"已读时的数量",待办数量增加会重新变未读
- 两个接口均加 requireAdmin(待办数量属运营数据,不再匿名可见)

【删除空壳/死接口(前端已无调用方)】
- /api/user/save-info、/api/chat/list、/api/user/my-team/list、/api/user/latest-activity、/api/user/my-project/list

【后台页面清理】
- 「用户与个人中心 → 管理员资料」移除 我的项目/最近动态/我的团队/站内通知 四张空卡(保留真实的管理员资料头部)
- 删除对应 4 个组件;api/user-center.ts 移除 queryMyProjectList/queryMyTeamList/queryLatestActivity/saveUserInfo;api/message.ts 移除 queryChatList

【验证】
- 新增 admin-todo-bell.spec.ts;更新 admin-demo-data-cleanup.spec.ts
- 全量 101 套件 / 690 用例全绿;冒烟 8/8(未登录50008、待办数量、已读、新增后重新未读)
- 后台 pnpm build 通过
这个提交包含在:
xcbyy
2026-10-20 15:52:04 +08:00
父节点 05815b4708
当前提交 f3ea26d161
共有 16 个文件被更改,包括 250 次插入 和 455 次删除

查看文件

@@ -586,34 +586,6 @@ app.all('/api/user/certification', (_req: Request, res: Response) => {
});
});
// 平台未接入多项目管理:返回空数组,不再返回伪造项目与成员
app.all('/api/user/my-project/list', (_req: Request, res: Response) => {
res.json({
code: 20000,
data: []
});
});
// 后台个人中心:我的团队(真实团队表尚未建立,返回空数组,不再返回静态演示成员)
app.all('/api/user/my-team/list', (_req: Request, res: Response) => {
res.json({
code: 20000,
data: []
});
});
// 后台个人中心:最新动态(暂无审计动态数据源,返回空数组,不再返回静态演示内容)
app.all('/api/user/latest-activity', (_req: Request, res: Response) => {
res.json({
code: 20000,
data: []
});
});
app.all('/api/user/save-info', (_req: Request, res: Response) => {
res.json({ code: 20000, data: null, message: '个人设置保存成功' });
});
// 真实文件与图片对象存储上传接口 (支持单图上传并持久化至 public/uploads)
const handleFileUpload = (req: Request, res: Response) => {
// 生产环境要求登录后上传,避免匿名上传被滥用(刷盘/托管非法内容)
@@ -734,20 +706,59 @@ app.get('/api/message/subscribe-templates', async (_req: Request, res: Response)
}
});
// 通知中心列表(暂无真实站内消息数据源,返回空数组,不再返回静态演示消息)
app.all('/api/message/list', (_req: Request, res: Response) => {
res.json({
code: 20000,
data: []
});
// 管理端消息铃铛:真实待办聚合(待接单 / 待审核大神 / 待审核提现 / 待处理举报 / 待仲裁)
function buildAdminTodoItems(todos: any, readMap: Record<string, number>): any[] {
const defs = [
{ key: 'pending_orders', count: Number(todos?.pendingOrdersCount || 0), title: '待接单订单', content: '有订单等待大神接单', link: '/order/list' },
{ key: 'pending_audits', count: Number(todos?.pendingAuditsCount || 0), title: '待审核大神', content: '有大神入驻申请待审核', link: '/companion/audit' },
{ key: 'pending_withdrawals', count: Number(todos?.pendingWithdrawalsCount || 0), title: '待审核提现', content: `待审核提现合计 ¥${Number(todos?.pendingWithdrawalAmount || 0).toFixed(2)}`, link: '/finance/withdraw' },
{ key: 'pending_reports', count: Number(todos?.pendingReportsCount || 0), title: '待处理举报', content: '有违规举报工单待处置', link: '/safety/reports' },
{ key: 'pending_arbitrations', count: Number(todos?.pendingArbitrationsCount || 0), title: '待仲裁工单', content: '有争议仲裁待平台介入', link: '/order/arbitration' },
];
return defs
.filter((d) => d.count > 0)
.map((d, i) => {
const readCount = Number(readMap[d.key] || 0);
return {
id: i + 1,
key: d.key,
type: 'todo',
title: d.title,
subTitle: `待处理 ${d.count} 条`,
content: d.content,
time: formatCnTime(),
status: d.count > readCount ? 0 : 1,
messageType: 1,
count: d.count,
link: d.link,
};
});
}
app.post('/api/message/list', requireAdmin, async (_req: Request, res: Response) => {
try {
const todos = await repo.getDashboardTodos();
const readMap = await repo.getAdminTodoReadCounts();
res.json({ code: 20000, data: buildAdminTodoItems(todos, readMap) });
} catch (err: any) {
res.status(500).json({ code: 50000, message: err.message });
}
});
app.all('/api/message/read', (_req: Request, res: Response) => {
res.json({ code: 20000, data: true });
});
app.all('/api/chat/list', (_req: Request, res: Response) => {
res.json({ code: 20000, data: [] });
app.post('/api/message/read', requireAdmin, async (req: Request, res: Response) => {
try {
const ids = Array.isArray(req.body?.ids) ? req.body.ids.map((x: any) => Number(x)) : [];
const keys = Array.isArray(req.body?.keys) ? req.body.keys.map((x: any) => String(x)) : [];
const todos = await repo.getDashboardTodos();
const readMap = await repo.getAdminTodoReadCounts();
const items = buildAdminTodoItems(todos, readMap);
const targets = items.filter((it: any) => ids.includes(it.id) || keys.includes(it.key));
const toMark = (targets.length ? targets : items).map((it: any) => ({ key: it.key, count: it.count }));
const affected = await repo.markAdminTodosRead(toMark);
res.json({ code: 20000, data: { affected }, message: '已标记为已读' });
} catch (err: any) {
res.status(500).json({ code: 50000, message: err.message });
}
});
// ===== 站内消息通知中心(用户端真实数据源)=====