feat(cms): docs/16 站点设置整页化+首页布局编辑器- SiteSetting.vue整页7分区(基本信息/皮肤与外观/首页布局/基础展示/页脚信息/SEO与域名/扩展参数)锚点导航sticky+IntersectionObserver+分区卡片三段式每分区独立保存(configJson单一事实源+submitSection统一提交);创建模式仅①②可编辑其余锁定,创建成功原地replace到site/setting/:id;SiteWizard三步向导退役删除(门户预览收编portalPreviewUrl);SiteManage瘦身表格+行内编辑跳整页(adminPathOf子站带站点段);ImageAssetField图片双通道(上传+路径粘贴折叠);SiteConfigForm支持groups/showExtras/siteId+asset字段接ImageAssetField+三旧键表单不再展示(ABSORBED_LEGACY_KEYS静默保留);homeLayout三件套(homeLayout.ts模型parse/serialize/validate/genFromCategories一键生成+blockTypes.ts注册表showIf条件字段+HomeLayoutEditor.vue序列编辑增删显隐上移下移);routes site/new+site/setting/:id+legacy redirect;CategoryManage home_style提示改口指向首页布局;新增siteSetting.spec+homeLayout.spec,重写siteManage.spec,117用例全绿
build / build (push) Successful in 2m19s
build / build (push) Successful in 2m19s
This commit is contained in:
@@ -7,21 +7,38 @@ import {
|
||||
SiteConfigExtraRow,
|
||||
SiteConfigFieldDef,
|
||||
SiteConfigFormModel,
|
||||
SiteConfigGroup,
|
||||
extraRowError,
|
||||
firstExtraError,
|
||||
parseSiteConfig,
|
||||
serializeSiteConfig,
|
||||
validateFieldValue,
|
||||
} from "@/utils/siteConfigForm";
|
||||
import ImageAssetField from "@/components/ImageAssetField.vue";
|
||||
|
||||
/**
|
||||
* 站点配置结构化表单(docs/09 §3.1):v-model 直接绑定 configJson 字符串。
|
||||
* 内部 parse/serialize 双向映射;已知键分组表单 + 扩展参数动态行;
|
||||
* 站点配置结构化表单(docs/09 §3.1;docs/16 §3.3 分区作用域扩展):
|
||||
* v-model 绑定整页共享的 configJson 字符串(单一事实源)。
|
||||
* props.groups 声明本分区渲染的分组;props.showExtras 控制扩展参数编辑区(每页仅一处)。
|
||||
* 模型仍完整解析全部键(fields/extras/preserved 三通道),仅渲染限定分组——
|
||||
* 序列化时未渲染分区的值原样回写,保证多分区实例绑定同一 configJson 互不踩踏。
|
||||
* 保存前由父级调用 validate()(含扩展行兜底校验)。
|
||||
*/
|
||||
const props = defineProps<{ modelValue: string }>();
|
||||
const props = defineProps<{
|
||||
modelValue: string;
|
||||
/** 渲染的配置分组(缺省=全部分组,兼容旧整表单用法) */
|
||||
groups?: SiteConfigGroup[];
|
||||
/** 是否渲染扩展参数编辑区(docs/16:仅「扩展参数」分区开启) */
|
||||
showExtras?: boolean;
|
||||
/** 图片类字段上传归属站点(附件场景引用) */
|
||||
siteId?: number;
|
||||
}>();
|
||||
const emit = defineEmits<{ (e: "update:modelValue", v: string): void }>();
|
||||
|
||||
/** 本实例渲染的分组集合(空=全部) */
|
||||
const groupSet = (() => new Set(props.groups || SITE_CONFIG_GROUPS.map(g => g.name)))();
|
||||
const visibleGroups = SITE_CONFIG_GROUPS.filter(g => groupSet.has(g.name));
|
||||
|
||||
const formRef = ref();
|
||||
const formModel = reactive<{ fields: Record<string, string>; extras: SiteConfigExtraRow[] }>({
|
||||
fields: {},
|
||||
@@ -65,14 +82,16 @@ function removeExtra(i: number) {
|
||||
emitUp();
|
||||
}
|
||||
|
||||
/** 保存前校验:内置字段 Form 规则 + 扩展行兜底;通过返回 true */
|
||||
/** 保存前校验:内置字段 Form 规则 + 扩展行兜底(仅渲染扩展区的实例校验,避免越区拦截);通过返回 true */
|
||||
async function validate(): Promise<boolean> {
|
||||
const valid = await formRef.value?.validate();
|
||||
if (!valid) return false;
|
||||
const extraMsg = firstExtraError(formModel.extras);
|
||||
if (extraMsg) {
|
||||
Message.error(`扩展参数有误:${extraMsg}`);
|
||||
return false;
|
||||
if (props.showExtras) {
|
||||
const extraMsg = firstExtraError(formModel.extras);
|
||||
if (extraMsg) {
|
||||
Message.error(`扩展参数有误:${extraMsg}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -93,13 +112,12 @@ function rulesFor(def: SiteConfigFieldDef) {
|
||||
|
||||
function defsOf(group: string): SiteConfigFieldDef[] {
|
||||
return SITE_CONFIG_FIELD_DEFS.filter(d => d.group === group);
|
||||
}
|
||||
</script>
|
||||
}</script>
|
||||
|
||||
<template>
|
||||
<div class="site-config-form">
|
||||
<Form ref="formRef" :model="formModel" :label-width="130">
|
||||
<template v-for="g in SITE_CONFIG_GROUPS" :key="g.name">
|
||||
<template v-for="g in visibleGroups" :key="g.name">
|
||||
<div class="group-title">{{ g.title }}</div>
|
||||
<FormItem
|
||||
v-for="def in defsOf(g.name)"
|
||||
@@ -108,8 +126,15 @@ function defsOf(group: string): SiteConfigFieldDef[] {
|
||||
:prop="`fields.${def.key}`"
|
||||
:rules="rulesFor(def)"
|
||||
>
|
||||
<ImageAssetField
|
||||
v-if="def.kind === 'asset'"
|
||||
v-model="formModel.fields[def.key]"
|
||||
:site-id="siteId"
|
||||
:placeholder="def.placeholder"
|
||||
@update:model-value="emitUp"
|
||||
/>
|
||||
<Input
|
||||
v-if="!def.multiline"
|
||||
v-else-if="!def.multiline"
|
||||
v-model="formModel.fields[def.key]"
|
||||
:placeholder="def.placeholder"
|
||||
@on-change="emitUp"
|
||||
@@ -126,19 +151,21 @@ function defsOf(group: string): SiteConfigFieldDef[] {
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<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) }}
|
||||
<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>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user