feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
@@ -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,不得报成功'
|
||||
);
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户