feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口

补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
wikiboss
2026-09-23 18:01:47 +08:00
父节点 8ea9c25386
当前提交 62e0ae4435
共有 227 个文件被更改,包括 21855 次插入 和 5264 次删除

查看文件

@@ -114,3 +114,89 @@ test('4. settings/audit-logs 语言包必须包含中英文案且零 emoji、零
'语言包严禁包含 emoji'
);
});
test('5. R18: 必须支持按 action 筛选,并将所选动作作为 page 请求参数发出', () => {
const content = readPage();
const localeContent = fs.readFileSync(auditLocalePath, 'utf-8');
assert.ok(
content.includes('selectedAction'),
'必须具备 selectedAction 筛选状态'
);
assert.ok(
content.includes('ACTION_OPTIONS') ||
(content.includes('nfc_freeze') && content.includes('lead_batch_export')),
'必须提供可选动作列表'
);
assert.ok(
/if\s*\(\s*filters\.action\s*\)\s*\{[\s\S]*?payload\.action\s*=\s*filters\.action/.test(
content
),
'仅当已选动作时写入 payload.action;清空筛选时不带空动作'
);
assert.ok(
!/payload\.action\s*=\s*['"]['"]/.test(content),
'禁止把空字符串硬编码进 payload.action'
);
assert.ok(
content.includes('setSelectedAction'),
'重置/变更筛选必须能更新 selectedAction'
);
assert.ok(
localeContent.includes("'auditLogs.filter.action'"),
'locale 必须包含 auditLogs.filter.action'
);
assert.ok(
localeContent.includes('auditLogs.actionType.nfc_freeze') &&
localeContent.includes('auditLogs.actionType.lead_batch_export'),
'locale 必须覆盖动作选项文案'
);
assert.ok(
content.includes("t['auditLogs.filter.action']"),
'action 筛选 placeholder 必须走 locale'
);
});
test('6. R18: 请求失败或 code 非 1000 必须清空表格,禁止残留示例日志', () => {
const content = readPage();
assert.ok(
/useState\s*<\s*BaseAuditLog\s*\[\s*\]\s*>\s*\(\s*\[\s*\]\s*\)/.test(
content
),
'初始 dataList 必须为空数组'
);
// else(非 1000)与 catch 两处都必须清空
const clearCalls = content.match(/setDataList\(\[\]\)/g) || [];
assert.ok(
clearCalls.length >= 2,
'code 非 1000 与 catch 均须 setDataList([]) 清空表格'
);
assert.ok(
content.includes('setTotal(0)'),
'失败时 total 也须归零'
);
assert.ok(
content.includes("t['auditLogs.request.fail']"),
'失败提示必须走 locale,禁止硬编码'
);
assert.ok(
!content.includes('Mock.js') &&
!content.includes('mockList') &&
!content.includes('示例日志') &&
!content.includes('sampleLogs') &&
!content.includes('fakeLogs'),
'禁止写死示例日志行或 mock 列表'
);
assert.ok(
!/operatorName:\s*['"][^'"]+['"]/.test(content) &&
!/detail:\s*['"][^'"]+['"]/.test(content) &&
!/ip:\s*['"]\d+\.\d+\.\d+\.\d+['"]/.test(content),
'禁止内联写死审计日志行字段'
);
// 成功分支才写入 list;失败不得回退到本地示例数组
assert.ok(
/res\.data\.list/.test(content) || /data\.list/.test(content),
'表格数据必须来自接口 list'
);
});