You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
6.2 KiB

5 months ago
/**
* 将设计文档中的 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<string, unknown> {
return v !== null && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : {}
}
/** 去掉零宽字符与首尾空白 */
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<string, unknown>, 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<string, unknown> | null {
const out: Record<string, unknown> = {}
if (form.documentTitle.trim()) out.documentTitle = form.documentTitle.trim()
const login: Record<string, string> = {}
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<string, string> = {}
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<string, string> = {}
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<string, string> = {}
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
}