/** * 将设计文档中的 branding_json 拆为表单字段,供运营可视化编辑。 */ export interface BrandingForm { documentTitle: string login: { markLine: string headline: string slogan: string cardWelcome: string footerCopyright: string logoUrl: string faviconUrl: string themePrimary: string } apply: { pageTitle: string headerSubtitle: string } review: { pageTitle: string footerCopyright: string } admin: { loginTitle: string footerCopyright: string sidebarProductName: string } } export function emptyBrandingForm(): BrandingForm { return { documentTitle: '', login: { markLine: '', headline: '', slogan: '', cardWelcome: '', footerCopyright: '', logoUrl: '', faviconUrl: '', themePrimary: '', }, apply: { pageTitle: '', headerSubtitle: '' }, review: { pageTitle: '', footerCopyright: '' }, admin: { loginTitle: '', footerCopyright: '', sidebarProductName: '' }, } } function asRecord(v: unknown): Record { return v !== null && typeof v === 'object' && !Array.isArray(v) ? (v as Record) : {} } /** 去掉零宽字符与首尾空白 */ export function normalizeDisplayWhitespace(s: string): string { return s.replace(/[\u200B-\u200D\uFEFF]/gu, '').trim() } /** 是否有可见字符(用于未配置、仅空格或零宽符时不覆盖端上默认文案) */ export function hasVisibleText(s: string): boolean { return /\S/u.test(normalizeDisplayWhitespace(s)) } function str(r: Record, key: string): string { const x = r[key] if (x == null) return '' if (typeof x === 'string') return x if (typeof x === 'number' && Number.isFinite(x)) return String(x) return '' } export function brandingFormFromApi(json: unknown | null | undefined): BrandingForm { const b = emptyBrandingForm() if (json == null) return b let parsed: unknown = json if (typeof parsed === 'string') { const t = parsed.trim() if (!t) return b try { parsed = JSON.parse(t) as unknown } catch { return b } } const root = asRecord(parsed) b.documentTitle = normalizeDisplayWhitespace(str(root, 'documentTitle')) const login = asRecord(root.login) b.login.markLine = normalizeDisplayWhitespace(str(login, 'markLine')) b.login.headline = normalizeDisplayWhitespace(str(login, 'headline')) b.login.slogan = normalizeDisplayWhitespace(str(login, 'slogan')) b.login.cardWelcome = normalizeDisplayWhitespace(str(login, 'cardWelcome')) b.login.footerCopyright = normalizeDisplayWhitespace(str(login, 'footerCopyright')) b.login.logoUrl = normalizeDisplayWhitespace(str(login, 'logoUrl')) b.login.faviconUrl = normalizeDisplayWhitespace(str(login, 'faviconUrl')) b.login.themePrimary = normalizeDisplayWhitespace(str(login, 'themePrimary')) const apply = asRecord(root.apply) b.apply.pageTitle = normalizeDisplayWhitespace(str(apply, 'pageTitle')) b.apply.headerSubtitle = normalizeDisplayWhitespace(str(apply, 'headerSubtitle')) const review = asRecord(root.review) b.review.pageTitle = normalizeDisplayWhitespace(str(review, 'pageTitle')) b.review.footerCopyright = normalizeDisplayWhitespace(str(review, 'footerCopyright')) const admin = asRecord(root.admin) b.admin.loginTitle = normalizeDisplayWhitespace(str(admin, 'loginTitle')) b.admin.footerCopyright = normalizeDisplayWhitespace(str(admin, 'footerCopyright')) b.admin.sidebarProductName = normalizeDisplayWhitespace(str(admin, 'sidebarProductName')) return b } /** 选手端报名页主标题(导航栏 / 表单大标题共用) */ export function participantApplyPageTitle(brand: BrandingForm, competitionName: string): string { const a = brand.apply?.pageTitle if (hasVisibleText(a)) return normalizeDisplayWhitespace(a) const n = normalizeDisplayWhitespace(competitionName) return n ? `${n} · 赛事报名` : '赛事报名' } /** 评审工作台顶栏标题(对齐 branding.review.pageTitle) */ export function reviewWorkspacePageTitle(brand: BrandingForm, competitionName: string): string { const r = brand.review?.pageTitle if (hasVisibleText(r)) return normalizeDisplayWhitespace(r) const n = normalizeDisplayWhitespace(competitionName) return n ? `${n} · 项目评审` : '项目评审' } /** 去掉空字符串键,减少存储体积 */ export function brandingJsonFromForm(form: BrandingForm): Record | null { const out: Record = {} if (form.documentTitle.trim()) out.documentTitle = form.documentTitle.trim() const login: Record = {} if (form.login.markLine.trim()) login.markLine = form.login.markLine.trim() if (form.login.headline.trim()) login.headline = form.login.headline.trim() if (form.login.slogan.trim()) login.slogan = form.login.slogan.trim() if (form.login.cardWelcome.trim()) login.cardWelcome = form.login.cardWelcome.trim() if (form.login.footerCopyright.trim()) login.footerCopyright = form.login.footerCopyright.trim() if (form.login.logoUrl.trim()) login.logoUrl = form.login.logoUrl.trim() if (form.login.faviconUrl.trim()) login.faviconUrl = form.login.faviconUrl.trim() if (form.login.themePrimary.trim()) login.themePrimary = form.login.themePrimary.trim() if (Object.keys(login).length) out.login = login const apply: Record = {} if (form.apply.pageTitle.trim()) apply.pageTitle = form.apply.pageTitle.trim() if (form.apply.headerSubtitle.trim()) apply.headerSubtitle = form.apply.headerSubtitle.trim() if (Object.keys(apply).length) out.apply = apply const review: Record = {} if (form.review.pageTitle.trim()) review.pageTitle = form.review.pageTitle.trim() if (form.review.footerCopyright.trim()) review.footerCopyright = form.review.footerCopyright.trim() if (Object.keys(review).length) out.review = review const admin: Record = {} if (form.admin.loginTitle.trim()) admin.loginTitle = form.admin.loginTitle.trim() if (form.admin.footerCopyright.trim()) admin.footerCopyright = form.admin.footerCopyright.trim() if (form.admin.sidebarProductName.trim()) admin.sidebarProductName = form.admin.sidebarProductName.trim() if (Object.keys(admin).length) out.admin = admin return Object.keys(out).length ? out : null }