feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
@@ -112,3 +112,131 @@ test('3. team/brand-kit locale 必须包含 menu.team.brandKit 与中英文案',
|
||||
assert.equal(defaultI18n['en-US']['menu.team.brandKit'], 'Brand Kit');
|
||||
assert.equal(defaultI18n['zh-CN']['menu.team.brandKit'], '品牌规范');
|
||||
});
|
||||
|
||||
test('4. 版权页脚:无真实保存字段时不得提交/假装已生效,须禁用或提示暂不可保存', () => {
|
||||
const content = readPage();
|
||||
const localeContent = fs.readFileSync(brandKitLocalePath, 'utf-8');
|
||||
|
||||
// 当前 brand 保存契约仅有 companyName / logoUrl / primaryColor,无 footer|copyright|legalNotice
|
||||
const savePayloadMatch = content.match(
|
||||
/interface BrandSavePayload\s*\{[\s\S]*?\}/
|
||||
);
|
||||
const savePayloadBlock = savePayloadMatch?.[0] ?? '';
|
||||
const hasFooterInSavePayload = /\b(footer|copyright|legalNotice)\b/.test(
|
||||
savePayloadBlock
|
||||
);
|
||||
const hasFooterAssignedToBody =
|
||||
/body\.(footer|copyright|legalNotice)\s*=/.test(content) ||
|
||||
/(?:const|let)\s+body\s*[:=][^{]*\{[\s\S]*?\b(footer|copyright|legalNotice)\s*:/.test(
|
||||
content
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
localeContent.includes("'teamBrandKit.footer.unavailable'") ||
|
||||
localeContent.includes("'teamBrandKit.copyright.unavailable'") ||
|
||||
localeContent.includes("'teamBrandKit.legalNotice.unavailable'"),
|
||||
'locale 必须包含页脚/版权暂不可保存词条'
|
||||
);
|
||||
assert.ok(
|
||||
localeContent.includes("'teamBrandKit.form.footer'") ||
|
||||
localeContent.includes("'teamBrandKit.form.copyright'") ||
|
||||
localeContent.includes("'teamBrandKit.form.legalNotice'"),
|
||||
'locale 必须包含页脚输入标签文案'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes("t['teamBrandKit.footer.unavailable']") ||
|
||||
content.includes("t['teamBrandKit.copyright.unavailable']") ||
|
||||
content.includes("t['teamBrandKit.legalNotice.unavailable']") ||
|
||||
content.includes('teamBrandKit.footer.unavailable') ||
|
||||
content.includes('teamBrandKit.copyright.unavailable') ||
|
||||
content.includes('teamBrandKit.legalNotice.unavailable'),
|
||||
'页面必须引用页脚暂不可保存文案'
|
||||
);
|
||||
|
||||
// 禁止把写死版权句当成已保存生效内容
|
||||
assert.ok(
|
||||
!/©\s*20\d{2}|All rights reserved|版权所有|保留所有权利/i.test(content),
|
||||
'禁止页面写死版权声明并当成已生效'
|
||||
);
|
||||
assert.ok(
|
||||
!hasFooterInSavePayload && !hasFooterAssignedToBody,
|
||||
'无后端页脚字段时禁止把 footer/copyright/legalNotice 写入保存 payload'
|
||||
);
|
||||
|
||||
// 空值不得提交假声明(保存体不得硬编码版权句子)
|
||||
assert.ok(
|
||||
!/BrandSavePayload[\s\S]{0,400}(©|All rights reserved|版权所有)/i.test(
|
||||
content
|
||||
),
|
||||
'空值不得向保存接口提交假版权声明'
|
||||
);
|
||||
|
||||
const hasFooterSaveField = hasFooterInSavePayload || hasFooterAssignedToBody;
|
||||
if (!hasFooterSaveField) {
|
||||
const footerInputShown =
|
||||
/field=["'](footer|copyright|legalNotice)["']/.test(content) ||
|
||||
content.includes('teamBrandKit.form.footer') ||
|
||||
content.includes('teamBrandKit.form.copyright') ||
|
||||
content.includes('teamBrandKit.form.legalNotice');
|
||||
assert.ok(footerInputShown, '可展示页脚输入,但不得当作已保存字段');
|
||||
|
||||
const footerDisabled =
|
||||
/disabled[\s\S]{0,160}(footer|copyright|legalNotice)|(footer|copyright|legalNotice)[\s\S]{0,200}disabled/i.test(
|
||||
content
|
||||
) ||
|
||||
content.includes('disabled={!FOOTER_SAVE_SUPPORTED}') ||
|
||||
content.includes('disabled={true}');
|
||||
const footerUnavailableHint =
|
||||
content.includes("t['teamBrandKit.footer.unavailable']") ||
|
||||
content.includes("t['teamBrandKit.copyright.unavailable']") ||
|
||||
content.includes("t['teamBrandKit.legalNotice.unavailable']") ||
|
||||
content.includes('teamBrandKit.footer.unavailable') ||
|
||||
content.includes('teamBrandKit.copyright.unavailable') ||
|
||||
content.includes('teamBrandKit.legalNotice.unavailable');
|
||||
assert.ok(
|
||||
footerDisabled || footerUnavailableHint,
|
||||
'无页脚保存字段时必须禁用输入/保存或提示暂不可保存'
|
||||
);
|
||||
}
|
||||
|
||||
// Message.success 仅允许出现在 code === 1000 成功分支
|
||||
const successMatches = [
|
||||
...content.matchAll(/Message\.success\s*\([^)]*\)/g),
|
||||
];
|
||||
for (const match of successMatches) {
|
||||
const idx = match.index ?? 0;
|
||||
const windowBefore = content.slice(Math.max(0, idx - 280), idx);
|
||||
assert.ok(
|
||||
/code\s*===\s*1000/.test(windowBefore),
|
||||
'保存失败不得 Message.success;成功提示必须在 code === 1000 分支内'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('5. locale 页脚词条须说明暂不可保存,且中英文齐全', () => {
|
||||
const localeContent = fs.readFileSync(brandKitLocalePath, 'utf-8');
|
||||
const unavailableKey =
|
||||
localeContent.includes("'teamBrandKit.footer.unavailable'")
|
||||
? 'teamBrandKit.footer.unavailable'
|
||||
: localeContent.includes("'teamBrandKit.copyright.unavailable'")
|
||||
? 'teamBrandKit.copyright.unavailable'
|
||||
: 'teamBrandKit.legalNotice.unavailable';
|
||||
|
||||
assert.ok(
|
||||
localeContent.includes(`'${unavailableKey}'`),
|
||||
'必须包含暂不可保存词条'
|
||||
);
|
||||
assert.ok(
|
||||
/暂不可保存|暂不可用|not available|cannot be saved|unavailable/i.test(
|
||||
localeContent
|
||||
),
|
||||
'暂不可保存文案须明确不可落库'
|
||||
);
|
||||
assert.ok(!/\bany\b/.test(localeContent), 'locale 禁止使用 any');
|
||||
assert.ok(
|
||||
!/[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u.test(
|
||||
localeContent
|
||||
),
|
||||
'locale 禁止使用 emoji'
|
||||
);
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户