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 次删除

查看文件

@@ -124,6 +124,15 @@ test('4. team/members locale 必须包含中英多语言且零 emoji、零 any',
assert.ok(content.includes('teamMembers.memberTable.title'), '必须包含成员列表标题 key');
assert.ok(content.includes('teamMembers.modal.assignTitle'), '必须包含名片分配弹窗 key');
assert.ok(content.includes('teamMembers.modal.moveTitle'), '必须包含批量调部弹窗 key');
assert.ok(
content.includes("'teamMembers.invite.unavailable'"),
'locale 必须包含邀请暂不可用词条'
);
assert.ok(
content.includes("'teamMembers.memberTable.invite'") ||
content.includes("'teamMembers.invite.action'"),
'locale 必须包含邀请成员按钮文案'
);
assert.ok(!/\bany\b/.test(content), 'locale 禁止使用 any');
assert.ok(
@@ -131,3 +140,95 @@ test('4. team/members locale 必须包含中英多语言且零 emoji、零 any',
'locale 禁止使用 emoji'
);
});
test('5. 无真实邀请接口时不得假 Message.success;须禁用或提示暂不可邀请', () => {
const content = readPage();
const localeContent = fs.readFileSync(membersLocalePath, 'utf-8');
const hasInviteApi =
/admin\/team\/members\/invite/i.test(content) ||
/admin\/team\/members\/createInvite/i.test(content) ||
/admin\/team\/members\/generateInvite/i.test(content);
assert.ok(
localeContent.includes("'teamMembers.invite.unavailable'"),
'locale 必须包含 teamMembers.invite.unavailable'
);
assert.ok(
content.includes("t['teamMembers.invite.unavailable']") ||
content.includes('teamMembers.invite.unavailable'),
'页面必须引用 invite.unavailable 文案'
);
// 不得在无真实邀请接口时假装邀请成功
const hasFakeInviteSuccess =
/Message\.success\s*\(\s*t\[['"]teamMembers\.invite\.success['"]\]/.test(
content
) ||
(/handleInvite[\s\S]{0,240}Message\.success/.test(content) && !hasInviteApi);
assert.ok(!hasFakeInviteSuccess, '无邀请接口时禁止 Message.success 假装邀请成功');
// 禁止前端伪造邀请链接 / 邀请码资源
assert.ok(
!/inviteUrl|inviteLink|invitationUrl|invitationLink/i.test(content) ||
hasInviteApi,
'无真实接口时禁止生成假邀请链接字段'
);
assert.ok(
!/\bQRCode\b|from ['"][^'"]*qrcode|qrcode\.react|qr-code/i.test(content) ||
hasInviteApi,
'无真实接口时禁止生成假邀请二维码'
);
assert.ok(
!content.includes('createObjectURL') || hasInviteApi,
'无真实接口时禁止 createObjectURL 伪造邀请资源'
);
if (!hasInviteApi) {
const inviteDisabled =
/disabled[\s\S]{0,120}invite|invite[\s\S]{0,160}disabled/i.test(content) ||
content.includes('disabled={true}') ||
/Button[\s\S]{0,400}invite[\s\S]{0,200}disabled/i.test(content) ||
/disabled[\s\S]{0,200}teamMembers\.(memberTable\.invite|invite\.action)/i.test(
content
);
const inviteUnavailableHint =
content.includes("t['teamMembers.invite.unavailable']") ||
content.includes('teamMembers.invite.unavailable');
assert.ok(
inviteDisabled || inviteUnavailableHint,
'无邀请接口时必须禁用邀请按钮或提示暂不可邀请'
);
assert.ok(
content.includes('Message.warning') ||
content.includes('Message.info') ||
content.includes('Message.error'),
'无邀请接口时必须通过 Message 提示不可邀请'
);
}
});
test('6. 已接真实接口的成员操作失败时不得 Message.success', () => {
const content = readPage();
// 每个 Message.success 必须出现在 code === 1000 成功分支内
const successMatches = [
...content.matchAll(/Message\.success\s*\([^)]*\)/g),
];
assert.ok(successMatches.length > 0, '真实操作成功时仍应使用 Message.success');
for (const match of successMatches) {
const idx = match.index ?? 0;
const windowStart = Math.max(0, idx - 280);
const preceding = content.slice(windowStart, idx);
assert.ok(
/code\s*===\s*1000/.test(preceding),
`Message.success 必须位于 code === 1000 分支内,附近片段: ${preceding.slice(-80)}`
);
}
assert.ok(
content.includes('Message.error'),
'失败必须使用 Message.error,不得报成功'
);
});