[CMS-VISUAL] F6: 站点设置「扩展参数」分区退役(7→6 分区),未知配置键改静默保留不丢失(vitest 164 绿)

This commit is contained in:
chenxing
2026-09-28 12:33:23 +08:00
parent 649a08629b
commit 3a0f1e0cc9
5 changed files with 34 additions and 204 deletions
+7 -73
View File
@@ -1,15 +1,11 @@
<script setup lang="ts">
import { reactive, ref, watch } from "vue";
import { Message } from "view-ui-plus";
import {
SITE_CONFIG_FIELD_DEFS,
SITE_CONFIG_GROUPS,
SiteConfigExtraRow,
SiteConfigFieldDef,
SiteConfigFormModel,
SiteConfigGroup,
extraRowError,
firstExtraError,
parseSiteConfig,
serializeSiteConfig,
validateFieldValue,
@@ -17,19 +13,16 @@ import {
import ImageAssetField from "@/components/ImageAssetField.vue";
/**
* 站点配置结构化表单(docs/09 §3.1;docs/16 §3.3 分区作用域扩展):
* 站点配置结构化表单(docs/09 §3.1;docs/16 §3.3 分区作用域;docs/20 F6 扩展参数区退役):
* v-model 绑定整页共享的 configJson 字符串(单一事实源)。
* props.groups 声明本分区渲染的分组;props.showExtras 控制扩展参数编辑区(每页仅一处)。
* 模型仍完整解析全部键(fields/extras/preserved 三通道),仅渲染限定分组——
* 序列化时未渲染分区的值原样回写,保证多分区实例绑定同一 configJson 互不踩踏。
* 保存前由父级调用 validate()(含扩展行兜底校验)。
* props.groups 声明本分区渲染的分组。模型仍完整解析全部键(fields/preserved 双通道),
* 仅渲染限定分组——序列化时未渲染分区的值原样回写,保证多分区实例绑定同一 configJson 互不踩踏。
* 保存前由父级调用 validate()。
*/
const props = defineProps<{
modelValue: string;
/** 渲染的配置分组(缺省=全部分组,兼容旧整表单用法) */
groups?: SiteConfigGroup[];
/** 是否渲染扩展参数编辑区(docs/16:仅「扩展参数」分区开启) */
showExtras?: boolean;
/** 图片类字段上传归属站点(附件场景引用) */
siteId?: number;
}>();
@@ -40,9 +33,8 @@ const groupSet = (() => new Set(props.groups || SITE_CONFIG_GROUPS.map(g => g.na
const visibleGroups = SITE_CONFIG_GROUPS.filter(g => groupSet.has(g.name));
const formRef = ref();
const formModel = reactive<{ fields: Record<string, string>; extras: SiteConfigExtraRow[] }>({
const formModel = reactive<{ fields: Record<string, string> }>({
fields: {},
extras: [],
});
let preserved: Record<string, unknown> = {};
let lastEmitted: string | null = null;
@@ -51,7 +43,6 @@ function parseIn(v: string) {
const m: SiteConfigFormModel = parseSiteConfig(v);
Object.keys(formModel.fields).forEach(k => delete formModel.fields[k]);
Object.assign(formModel.fields, m.fields);
formModel.extras.splice(0, formModel.extras.length, ...m.extras.map(r => ({ ...r })));
preserved = m.preserved;
}
@@ -67,33 +58,14 @@ watch(
function emitUp() {
lastEmitted = serializeSiteConfig({
fields: { ...formModel.fields },
extras: formModel.extras.map(r => ({ ...r })),
preserved,
});
emit("update:modelValue", lastEmitted);
}
function addExtra() {
formModel.extras.push({ key: "", value: "" });
}
function removeExtra(i: number) {
formModel.extras.splice(i, 1);
emitUp();
}
/** 保存前校验:内置字段 Form 规则 + 扩展行兜底(仅渲染扩展区的实例校验,避免越区拦截);通过返回 true */
/** 保存前校验:内置字段 Form 规则;通过返回 true */
async function validate(): Promise<boolean> {
const valid = await formRef.value?.validate();
if (!valid) return false;
if (props.showExtras) {
const extraMsg = firstExtraError(formModel.extras);
if (extraMsg) {
Message.error(`扩展参数有误:${extraMsg}`);
return false;
}
}
return true;
return !!(await formRef.value?.validate());
}
defineExpose({ validate });
@@ -150,22 +122,6 @@ function defsOf(group: string): SiteConfigFieldDef[] {
<p v-if="def.tip" class="cfg-tip">{{ def.tip }}</p>
</FormItem>
</template>
<template v-if="showExtras">
<div class="group-title">扩展参数</div>
<div v-for="(row, i) in formModel.extras" :key="i" class="extra-row">
<Input v-model="row.key" placeholder="参数名" style="width: 200px" @on-change="emitUp" />
<Input v-model="row.value" placeholder="参数值" style="flex: 1" @on-change="emitUp" />
<Button type="text" class="extra-del" icon="md-trash" @click="removeExtra(i)" />
<p v-if="extraRowError(formModel.extras, i)" class="cfg-error">
{{ extraRowError(formModel.extras, i) }}
</p>
</div>
<Button type="dashed" long icon="md-add" class="extra-add" @click="addExtra">新增参数</Button>
<p class="cfg-tip">
面向后续扩展的预留配置项(键名仅字母/数字/下划线);系统托管参数(如 theme_params)不在此显示,保存时自动保留。
</p>
</template>
</Form>
</div>
</template>
@@ -190,27 +146,5 @@ function defsOf(group: string): SiteConfigFieldDef[] {
line-height: 1.6;
margin-top: 4px;
}
.cfg-error {
color: #ed4014;
font-size: 12px;
line-height: 1.6;
margin-top: 4px;
}
.extra-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
flex-wrap: wrap;
.extra-del {
color: #999;
&:hover {
color: #ed4014;
}
}
}
.extra-add {
margin: 4px 0 8px;
}
}
</style>