[CMS-VISUAL] F6: 站点设置「扩展参数」分区退役(7→6 分区),未知配置键改静默保留不丢失(vitest 164 绿)
This commit is contained in:
@@ -41,7 +41,8 @@ const row = ref<SiteVo | null>(null);
|
||||
const sites = ref<SiteVo[]>([]);
|
||||
const themes = ref<ThemeVo[]>([]);
|
||||
|
||||
/** 分区清单(锚点导航;locked = 创建模式锁定) */
|
||||
/** 分区清单(锚点导航;locked = 创建模式锁定)。F6(docs/20):「扩展参数」分区退役——
|
||||
* 未知配置键由后端宽容保留,前端不再提供编辑区。 */
|
||||
const sections = [
|
||||
{ key: "basic", title: "基本信息", desc: "编码 / 名称 / 归属 / 状态" },
|
||||
{ key: "theme", title: "皮肤与外观", desc: "选皮肤 / 自定义 CSS" },
|
||||
@@ -49,7 +50,6 @@ const sections = [
|
||||
{ key: "display", title: "基础展示", desc: "品牌标识与图片" },
|
||||
{ key: "footer", title: "页脚信息", desc: "联系方式与社媒" },
|
||||
{ key: "seo", title: "SEO 与域名", desc: "收录与独立域名" },
|
||||
{ key: "extras", title: "扩展参数", desc: "预留键位", locked: true },
|
||||
];
|
||||
const activeSection = ref("basic");
|
||||
let observer: IntersectionObserver | null = null;
|
||||
@@ -76,12 +76,11 @@ const customCss = ref("");
|
||||
const previewVisible = ref(false);
|
||||
const editTheme = computed(() => themes.value.find(t => t.name === theme.value));
|
||||
|
||||
/** ③~⑦ 分区表单 ref(分区独立校验) */
|
||||
/** ③~⑥ 分区表单 ref(分区独立校验) */
|
||||
const layoutEditorRef = ref();
|
||||
const displayFormRef = ref();
|
||||
const footerFormRef = ref();
|
||||
const seoFormRef = ref();
|
||||
const extrasFormRef = ref();
|
||||
|
||||
const saving = reactive<Record<string, boolean>>({});
|
||||
|
||||
@@ -301,14 +300,6 @@ async function saveSeo() {
|
||||
await submitSection("seo", {});
|
||||
}
|
||||
|
||||
async function saveExtras() {
|
||||
if (!(await extrasFormRef.value?.validate())) {
|
||||
Message.warning("请填写红色星号内容!"); // bsch §4
|
||||
return;
|
||||
}
|
||||
await submitSection("extras", {});
|
||||
}
|
||||
|
||||
async function saveLayout() {
|
||||
const err = layoutEditorRef.value?.validate();
|
||||
if (err) {
|
||||
@@ -557,22 +548,6 @@ watch(() => store.currentSchool?.schId, () => loadSites());
|
||||
<span class="card-hint">仅保存本分区;其他分区未保存的修改仍在页面上,下次任一分区保存会一并带上</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ⑦ 扩展参数 -->
|
||||
<div id="sec-extras" class="card setting-card" :class="{ locked: isCreate }" data-section="extras">
|
||||
<div class="card-header">
|
||||
<span class="card-title">扩展参数</span>
|
||||
<span class="card-subtitle">面向后续扩展的预留配置键</span>
|
||||
</div>
|
||||
<Alert v-if="isCreate" type="info" show-icon class="ss-lock-tip">创建站点后开放</Alert>
|
||||
<div class="card-body">
|
||||
<SiteConfigForm ref="extrasFormRef" v-model="configJson" show-extras />
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<Button type="primary" :loading="saving.extras" :disabled="isCreate" @click="saveExtras">仅保存扩展参数</Button>
|
||||
<span class="card-hint">仅保存本分区;其他分区未保存的修改仍在页面上,下次任一分区保存会一并带上</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user