[CMS-VISUAL] F6: 站点设置「扩展参数」分区退役(7→6 分区),未知配置键改静默保留不丢失(vitest 164 绿)
This commit is contained in:
+11
-60
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 站点配置结构化表单纯函数模块(docs/09 §3.1)。
|
||||
* 已知键与模板/Java 实际消费方对齐(docs/09 §0 键清单表);未知字符串键 → 可编辑扩展行
|
||||
* (Projman「添加项」模式);未知非字符串键(theme_params 等系统托管键)原样保留无损回写。
|
||||
* 站点配置结构化表单纯函数模块(docs/09 §3.1;docs/20 F6 扩展参数分区退役)。
|
||||
* 已知键与模板/Java 实际消费方对齐(docs/09 §0 键清单表);
|
||||
* 未知键(字符串与非字符串)一律无损入 preserved 静默保留——不再提供「扩展参数」编辑区
|
||||
* (后端对未知键的宽容策略不变:不拦截、不渲染、不丢失)。
|
||||
* vitest 单测对象:siteConfigForm.spec.ts,不依赖组件挂载基建。
|
||||
*/
|
||||
|
||||
@@ -121,28 +122,22 @@ export const RESERVED_SITE_CONFIG_KEYS = ["theme_params", "custom_css"];
|
||||
*/
|
||||
export const ABSORBED_LEGACY_KEYS = ["gallery_title", "gallery_subtitle", "news_bg_image"];
|
||||
|
||||
/** 扩展键名约束(与后端 CONFIG_KEY_PATTERN 一致) */
|
||||
/** 扩展键名约束(与后端 CONFIG_KEY_PATTERN 一致;供后续功能键落位时复用) */
|
||||
export const EXTRA_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
||||
|
||||
export const HOST_RE = /^(\*\.)?[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+$/;
|
||||
export const URL_RE = /^https?:\/\/\S+$/;
|
||||
export const ASSET_RE = /^(\/.+|https?:\/\/\S+)$/;
|
||||
|
||||
export interface SiteConfigExtraRow {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface SiteConfigFormModel {
|
||||
fields: Record<string, string>;
|
||||
extras: SiteConfigExtraRow[];
|
||||
/** 系统托管键 + 非字符串未知键,无损保留 */
|
||||
/** 非已知键(含系统托管键与未知键)无损保留 */
|
||||
preserved: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** configJson → 表单模型:已知键入 fields、未知字符串键入扩展行、其余无损入 preserved */
|
||||
/** configJson → 表单模型:已知键入 fields,其余(系统托管键/旧键/未知键)无损入 preserved */
|
||||
export function parseSiteConfig(configJson: string | undefined | null): SiteConfigFormModel {
|
||||
const model: SiteConfigFormModel = { fields: {}, extras: [], preserved: {} };
|
||||
const model: SiteConfigFormModel = { fields: {}, preserved: {} };
|
||||
if (!configJson || !configJson.trim()) {
|
||||
return model;
|
||||
}
|
||||
@@ -159,23 +154,18 @@ export function parseSiteConfig(configJson: string | undefined | null): SiteConf
|
||||
return model;
|
||||
}
|
||||
const known = new Set(SITE_CONFIG_FIELD_DEFS.map(f => f.key));
|
||||
const reserved = new Set([...RESERVED_SITE_CONFIG_KEYS, ...ABSORBED_LEGACY_KEYS]);
|
||||
Object.keys(parsed).forEach(k => {
|
||||
const v = parsed![k];
|
||||
if (typeof v !== "string") {
|
||||
model.preserved[k] = v;
|
||||
} else if (known.has(k)) {
|
||||
if (known.has(k) && typeof v === "string") {
|
||||
model.fields[k] = v;
|
||||
} else if (reserved.has(k)) {
|
||||
model.preserved[k] = v;
|
||||
} else {
|
||||
model.extras.push({ key: k, value: v });
|
||||
model.preserved[k] = v;
|
||||
}
|
||||
});
|
||||
return model;
|
||||
}
|
||||
|
||||
/** 表单模型 → configJson:空值剔除;扩展行空键/空值剔除;preserved 原样回写(永不丢失) */
|
||||
/** 表单模型 → configJson:空值剔除;preserved 原样回写(永不丢失) */
|
||||
export function serializeSiteConfig(model: SiteConfigFormModel): string {
|
||||
const out: Record<string, unknown> = {};
|
||||
SITE_CONFIG_FIELD_DEFS.forEach(def => {
|
||||
@@ -184,13 +174,6 @@ export function serializeSiteConfig(model: SiteConfigFormModel): string {
|
||||
out[def.key] = v;
|
||||
}
|
||||
});
|
||||
(model.extras || []).forEach(row => {
|
||||
const k = (row.key || "").trim();
|
||||
const v = (row.value || "").trim();
|
||||
if (k && v) {
|
||||
out[k] = v;
|
||||
}
|
||||
});
|
||||
Object.keys(model.preserved || {}).forEach(k => {
|
||||
out[k] = model.preserved[k];
|
||||
});
|
||||
@@ -214,35 +197,3 @@ export function validateFieldValue(def: SiteConfigFieldDef, value: string): stri
|
||||
return def.maxLength && v.length > def.maxLength ? `不能超过 ${def.maxLength} 字` : "";
|
||||
}
|
||||
}
|
||||
|
||||
/** 扩展行校验:键名格式 / 与内置键或系统键冲突 / 行间重复;返回错误消息,空串=通过(空键行视为待填) */
|
||||
export function extraRowError(rows: SiteConfigExtraRow[], index: number): string {
|
||||
const row = rows[index];
|
||||
const key = (row?.key || "").trim();
|
||||
if (!key) {
|
||||
return "";
|
||||
}
|
||||
if (!EXTRA_KEY_RE.test(key)) {
|
||||
return "键名仅限字母/数字/下划线,且以字母或下划线开头";
|
||||
}
|
||||
const known = new Set(SITE_CONFIG_FIELD_DEFS.map(f => f.key));
|
||||
if (known.has(key) || RESERVED_SITE_CONFIG_KEYS.includes(key)) {
|
||||
return "扩展键不能与内置键/系统键重名";
|
||||
}
|
||||
const dup = rows.findIndex(r => (r.key || "").trim() === key);
|
||||
if (dup >= 0 && dup !== index) {
|
||||
return "扩展键重复";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/** 保存前兜底:返回第一条扩展行错误,空串=全部通过 */
|
||||
export function firstExtraError(rows: SiteConfigExtraRow[]): string {
|
||||
for (let i = 0; i < (rows || []).length; i++) {
|
||||
const msg = extraRowError(rows, i);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user