feat(auth): 注册服务协议 - 名称与条款改为后台可配置并同步注册页展示
后端: - RegistrationSettingRequest 协议字段改为可选指针,注册开关单独保存不再覆盖协议正文 - 新增 RegistrationAgreement / AgreementTitleForMessage:标题未配置时跟随品牌名生成,读取失败返回空标题而非猜一个不存在的协议名 - PublicAuthSettings 下发 agreementTitle / agreementContent,首用户分支同样携带 - 新增 registration_agreement_test.go 覆盖品牌名兜底、存储合并与失败降级 前端: - 注册页协议标题与条款改为消费后台配置;读取失败时隐藏协议入口并支持重新读取 - 验证码获取按钮内联到输入框右侧,联系方式为空即禁用;sms_email 双通道合并为单输入框 - 协议弹窗重排:条目标题独立成行加重、空态虚线提示、底部补充同意说明;深浅主题适配 - 协议勾选行改为状态化区域,复选框适配 AntD 6 结构(方框在 .ant-checkbox 本体,修正深色主题白底白勾) - 后台「服务协议名称与条款」区块补齐表单内边距并对齐区块头/页脚,协议名称限宽 文档:http-api.mdx 更新协议字段语义与失败降级说明;pending-test.mdx 增加手工验证清单
This commit is contained in:
@@ -25,17 +25,45 @@ describe("注册服务协议", () => {
|
||||
|
||||
expect(page).toContain("acceptedTerms: agreementAccepted");
|
||||
expect(page).toContain("agreementAccepted ? linuxDOLoginURL(next, true) : undefined");
|
||||
expect(page).toContain("请先同意影策服务协议");
|
||||
expect(page).toContain("message.warning(`请先同意${agreementTitle}`)");
|
||||
expect(login).toContain("linuxDOLoginURL(next)");
|
||||
expect(api).toContain("acceptedTerms: boolean");
|
||||
expect(api).toContain("acceptedTerms?: boolean");
|
||||
});
|
||||
|
||||
test("协议使用影策标题并提供正文承载区域", () => {
|
||||
test("协议标题与条款来自后台配置而不是硬编码常量", () => {
|
||||
const page = readFileSync(resolve(import.meta.dir, "../src/pages/auth/register.tsx"), "utf8");
|
||||
|
||||
expect(page).toContain('title="影策服务协议"');
|
||||
expect(page).toContain("SERVICE_AGREEMENT.map");
|
||||
expect(page).toContain("服务协议内容待补充。");
|
||||
expect(page).not.toContain("SERVICE_AGREEMENT");
|
||||
expect(page).toContain("settings?.agreementTitle");
|
||||
expect(page).toContain("settings?.agreementContent");
|
||||
expect(page).toContain("title={agreementTitle}");
|
||||
expect(page).toContain("agreementParagraphs.map");
|
||||
// 条款留空时的占位提示;断言关键词而非整句,允许后续补充指引文案。
|
||||
expect(page).toContain("服务协议内容待补充");
|
||||
});
|
||||
|
||||
test("读取设置失败时不猜协议标题,改为提示并允许重新读取", () => {
|
||||
const page = readFileSync(resolve(import.meta.dir, "../src/pages/auth/register.tsx"), "utf8");
|
||||
|
||||
// 失败必须留下状态位,不能静默降级成品牌名拼接的协议名。
|
||||
expect(page).toContain("[settingsFailed, setSettingsFailed] = useState(false)");
|
||||
expect(page).toContain("setSettingsFailed(true)");
|
||||
expect(page).toContain("读取注册设置失败,暂时无法确认服务协议名称与条款。");
|
||||
expect(page).toContain("setSettingsReloadKey((value) => value + 1)");
|
||||
// 协议入口只在拿到设置后渲染,读不到配置就不展示协议名。
|
||||
expect(page).toContain("{settings ? (");
|
||||
});
|
||||
|
||||
test("公开认证设置暴露协议字段,管理接口可回传协议正文", () => {
|
||||
const auth = readFileSync(resolve(import.meta.dir, "../src/services/api/auth.ts"), "utf8");
|
||||
const wallet = readFileSync(resolve(import.meta.dir, "../src/services/api/wallet.ts"), "utf8");
|
||||
const panel = readFileSync(resolve(import.meta.dir, "../src/pages/admin/components/access-settings-panel.tsx"), "utf8");
|
||||
|
||||
expect(auth).toContain("agreementTitle?: string; agreementContent?: string");
|
||||
expect(wallet).toContain("agreementTitle?: string");
|
||||
expect(wallet).toContain("agreementContent?: string");
|
||||
expect(panel).toContain("服务协议名称与条款");
|
||||
expect(panel).toContain("saveAgreement");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,9 @@ const source = (path: string) => readFileSync(new URL(`../src/${path}`, import.m
|
||||
|
||||
describe("authentication follows the application theme", () => {
|
||||
test("classic login surfaces have distinct light and dark palettes", () => {
|
||||
const css = source("styles/globals.css");
|
||||
// globals.css 在不同平台上可能是 LF 或 CRLF;先归一化再分段,
|
||||
// 否则按字面换行查找 .dark 段会返回 -1,把断言变成永远失败。
|
||||
const css = source("styles/globals.css").replace(/\r\n/g, "\n");
|
||||
const dark = css.slice(css.indexOf(".dark {\n --background:"));
|
||||
for (const [mode, background, panel, card] of [
|
||||
["light", "#ffffff", "#f7f7f7", "#ffffff"],
|
||||
|
||||
Reference in New Issue
Block a user