优化项目结构

This commit is contained in:
eafonyang
2026-08-13 14:23:01 +08:00
parent e40d9d6b27
commit d01b0c8f47
29 changed files with 620 additions and 872 deletions
@@ -2,6 +2,7 @@
import { computed, h, onMounted, reactive, ref } from 'vue';
import { NButton, NSpace, NSwitch, NTag, type DataTableColumns, type FormInst, type SelectOption, type SelectRenderOption } from 'naive-ui';
import ColorInput from '../page/home/components/ColorInput.vue';
import GradientInput from './GradientInput.vue';
import MediaSelectInput from './MediaSelectInput.vue';
import RichTextEditor from './RichTextEditor.vue';
import SpecManager from './SpecManager.vue';
@@ -89,8 +90,6 @@ const formData = reactive({
body_color: '',
bg_type: 'color',
bg_value: '',
overlay_enabled: false,
overlay_opacity: 0,
overline_zh: '',
overline_en: '',
title_zh: '',
@@ -349,8 +348,6 @@ function handleAdd() {
formData.body_color = '';
formData.bg_type = 'color';
formData.bg_value = '';
formData.overlay_enabled = false;
formData.overlay_opacity = 0;
formData.overline_zh = '';
formData.overline_en = '';
formData.title_zh = '';
@@ -382,8 +379,6 @@ function fillFormFromRow(row: Api.Www.SectionBlock) {
formData.body_color = row.body_color || '';
formData.bg_type = row.bg_type;
formData.bg_value = row.bg_value;
formData.overlay_enabled = Boolean(row.overlay_enabled);
formData.overlay_opacity = Number(row.overlay_opacity);
formData.overline_zh = row.overline_zh;
formData.overline_en = row.overline_en;
formData.title_zh = row.title_zh;
@@ -519,146 +514,170 @@ onMounted(loadData);
>
<NForm ref="formRef" :model="formData" label-placement="left" label-width="120">
<NDivider>基础配置</NDivider>
<NFormItem label="布局类型">
<NSelect v-model:value="formData.layout" :options="layoutOptions" :render-label="renderLayoutLabel" :render-option="renderLayoutOption" />
</NFormItem>
<NFormItem label="主题">
<NRadioGroup v-model:value="formData.theme">
<NRadio v-for="opt in themeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</NRadio>
</NRadioGroup>
</NFormItem>
<NFormItem label="区块宽度">
<div class="w-full flex flex-col gap-8px">
<NInputNumber
:value="(formData.config.width_percent as number) ?? 100"
:min="30"
:max="100"
:step="5"
class="w-full"
@update:value="v => (formData.config.width_percent = v ?? 100)"
>
<template #suffix>%</template>
</NInputNumber>
<span class="text-12px opacity-60">占视口宽度百分比居中显示100 = 全宽移动端小屏始终全宽</span>
</div>
</NFormItem>
<NFormItem label="内容区宽度">
<div class="w-full flex flex-col gap-8px">
<NSelect
:value="(formData.config.container_width as string) ?? '1200'"
:options="containerWidthOptions"
@update:value="v => (formData.config.container_width = v)"
/>
<span class="text-12px opacity-60">区块内容容器的最大宽度居中于区块内全宽 = 内容占满整个区块</span>
</div>
</NFormItem>
<NFormItem label="区块圆角">
<div class="w-full flex flex-col gap-8px">
<NInputNumber
:value="(formData.config.border_radius as number) ?? 0"
:min="0"
:max="60"
:step="4"
class="w-full"
@update:value="v => (formData.config.border_radius = v ?? 0)"
>
<template #suffix>px</template>
</NInputNumber>
<span class="text-12px opacity-60">区块四角圆角大小0 = 直角默认建议与区块宽度小于 100 搭配使用全宽时圆角不可见</span>
</div>
</NFormItem>
<NFormItem label="上下内边距">
<div class="w-full flex flex-col gap-8px">
<NInputNumber
:value="(formData.config.padding_y as number | null) ?? null"
:min="0"
:max="240"
:step="8"
class="w-full"
clearable
placeholder="留空 = 布局默认值"
@update:value="v => (formData.config.padding_y = v)"
>
<template #suffix>px</template>
</NInputNumber>
<span class="text-12px opacity-60">内容区与区块上下边缘的间距留空 = 布局默认多数 96px / 全幅横幅 120px / 主视觉分屏 64px0 = 完全贴边</span>
</div>
</NFormItem>
<NFormItem label="上下外边距">
<div class="w-full flex flex-col gap-8px">
<NInputNumber
:value="(formData.config.margin_y as number | null) ?? null"
:min="0"
:max="240"
:step="8"
class="w-full"
clearable
placeholder="留空 = 紧贴相邻区块"
@update:value="v => (formData.config.margin_y = v)"
>
<template #suffix>px</template>
</NInputNumber>
<span class="text-12px opacity-60">区块与上下相邻区块之间的间隙露出页面背景色适合区块宽度 &lt; 100 + 圆角的卡片式区块留空/0 = 紧贴默认</span>
</div>
</NFormItem>
<NFormItem label="标题字号">
<div class="w-full flex flex-col gap-8px">
<NInputNumber
:value="(formData.config.title_font_size as number | null) ?? null"
:min="16"
:max="96"
:step="2"
class="w-full"
clearable
placeholder="留空 = 36px(兜底)"
@update:value="v => (formData.config.title_font_size = v)"
>
<template #suffix>px</template>
</NInputNumber>
<span class="text-12px opacity-60">桌面端标题字号移动端自动按约 62.5% 缩小如填 48 移动端 30px留空 = 兜底 36px引言布局无标题字段不受影响</span>
</div>
</NFormItem>
<NFormItem v-if="formData.layout === 'hero_split' || formData.layout === 'mosaic'" :label="formData.layout === 'mosaic' ? '网格高度' : '最小高度'">
<div class="w-full flex flex-col gap-8px">
<NInputNumber
:value="(formData.config.min_height as number | null) ?? null"
:min="0"
:max="1200"
:step="20"
class="w-full"
clearable
:placeholder="formData.layout === 'mosaic' ? '留空 = 默认 16:9 宽高比' : '留空 = 首屏高度(默认)'"
@update:value="v => (formData.config.min_height = v)"
>
<template #suffix>px</template>
</NInputNumber>
<span class="text-12px opacity-60">
<div class="grid grid-cols-2 gap-x-16px">
<NFormItem label="布局类型" label-placement="top">
<NSelect v-model:value="formData.layout" :options="layoutOptions" :render-label="renderLayoutLabel" :render-option="renderLayoutOption" />
</NFormItem>
<NFormItem label="主题" label-placement="top">
<NRadioGroup v-model:value="formData.theme">
<NRadio v-for="opt in themeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</NRadio>
</NRadioGroup>
</NFormItem>
</div>
<div class="grid grid-cols-3 gap-x-16px">
<NFormItem label="区块宽度" label-placement="top">
<NTooltip trigger="focus" placement="top">
<template #trigger>
<NInputNumber
:value="(formData.config.width_percent as number) ?? 100"
:min="30"
:max="100"
:step="5"
class="w-full"
@update:value="v => (formData.config.width_percent = v ?? 100)"
>
<template #suffix>%</template>
</NInputNumber>
</template>
占视口宽度百分比居中显示100 = 全宽移动端小屏始终全宽
</NTooltip>
</NFormItem>
<NFormItem label="内容区宽度" label-placement="top">
<NTooltip trigger="focus" placement="top">
<template #trigger>
<NSelect
:value="(formData.config.container_width as string) ?? '1200'"
:options="containerWidthOptions"
@update:value="v => (formData.config.container_width = v)"
/>
</template>
区块内容容器的最大宽度居中于区块内全宽 = 内容占满整个区块
</NTooltip>
</NFormItem>
<NFormItem label="区块圆角" label-placement="top">
<NTooltip trigger="focus" placement="top">
<template #trigger>
<NInputNumber
:value="(formData.config.border_radius as number) ?? 0"
:min="0"
:max="60"
:step="4"
class="w-full"
@update:value="v => (formData.config.border_radius = v ?? 0)"
>
<template #suffix>px</template>
</NInputNumber>
</template>
区块四角圆角大小0 = 直角默认建议与区块宽度小于 100 搭配使用全宽时圆角不可见
</NTooltip>
</NFormItem>
</div>
<div class="grid grid-cols-3 gap-x-16px">
<NFormItem label="上下内边距" label-placement="top">
<NTooltip trigger="focus" placement="top">
<template #trigger>
<NInputNumber
:value="(formData.config.padding_y as number | null) ?? null"
:min="0"
:max="240"
:step="8"
class="w-full"
clearable
placeholder="留空 = 布局默认值"
@update:value="v => (formData.config.padding_y = v)"
>
<template #suffix>px</template>
</NInputNumber>
</template>
内容区与区块上下边缘的间距留空 = 布局默认多数 96px / 全幅横幅 120px / 主视觉分屏 64px0 = 完全贴边
</NTooltip>
</NFormItem>
<NFormItem label="上下外边距" label-placement="top">
<NTooltip trigger="focus" placement="top">
<template #trigger>
<NInputNumber
:value="(formData.config.margin_y as number | null) ?? null"
:min="0"
:max="240"
:step="8"
class="w-full"
clearable
placeholder="留空 = 紧贴相邻区块"
@update:value="v => (formData.config.margin_y = v)"
>
<template #suffix>px</template>
</NInputNumber>
</template>
区块与上下相邻区块之间的间隙露出页面背景色适合区块宽度 &lt; 100 + 圆角的卡片式区块留空/0 = 紧贴默认
</NTooltip>
</NFormItem>
<NFormItem v-if="formData.layout === 'hero_split' || formData.layout === 'mosaic'" :label="formData.layout === 'mosaic' ? '网格高度' : '最小高度'" label-placement="top">
<NTooltip trigger="focus" placement="top">
<template #trigger>
<NInputNumber
:value="(formData.config.min_height as number | null) ?? null"
:min="0"
:max="1200"
:step="20"
class="w-full"
clearable
:placeholder="formData.layout === 'mosaic' ? '留空 = 默认 16:9 宽高比' : '留空 = 首屏高度(默认)'"
@update:value="v => (formData.config.min_height = v)"
>
<template #suffix>px</template>
</NInputNumber>
</template>
<template v-if="formData.layout === 'mosaic'">仅桌面端生效留空 = 默认 16:9 宽高比填数值 = 网格固定高度px素材自动裁剪填充可调低区块高度移动端保持单列堆叠不变</template>
<template v-else>仅主视觉分屏布局生效留空 = 沿用首屏高度至少 680px 约一屏0 = 完全自适应内容去上下内边距区块高度严格等于内容高度填数值 = 自定义最小高度</template>
</span>
</div>
</NFormItem>
<NDivider>文案颜色留空=跟随主题默认色</NDivider>
<NFormItem label="Overline 颜色">
<ColorInput v-model:value="formData.overline_color" />
</NFormItem>
<NFormItem label="标题颜色">
<ColorInput v-model:value="formData.title_color" />
</NFormItem>
<NFormItem label="副标题/正文颜色">
<ColorInput v-model:value="formData.body_color" />
</NFormItem>
<NFormItem label="背景类型">
<NRadioGroup v-model:value="formData.bg_type">
<NRadioButton value="color">纯色</NRadioButton>
<NRadioButton value="image">图片</NRadioButton>
<NRadioButton value="gradient">渐变</NRadioButton>
<NRadioButton value="video">视频</NRadioButton>
</NRadioGroup>
</NFormItem>
<NFormItem label="背景值">
<MediaSelectInput v-model:value="formData.bg_value" placeholder="颜色值或图片/视频路径(支持 /uploads/... 相对路径)" />
</NFormItem>
</NTooltip>
</NFormItem>
</div>
<div class="flex items-start gap-x-16px">
<NFormItem label="背景类型" label-placement="top" class="shrink-0">
<NRadioGroup v-model:value="formData.bg_type">
<NRadioButton value="color">纯色</NRadioButton>
<NRadioButton value="gradient">渐变</NRadioButton>
<NRadioButton value="image">图片</NRadioButton>
<NRadioButton value="video">视频</NRadioButton>
</NRadioGroup>
</NFormItem>
<NFormItem label="背景值" label-placement="top" class="min-w-0 flex-1">
<ColorInput v-if="formData.bg_type === 'color'" v-model:value="formData.bg_value" placeholder="留空则无背景色" />
<GradientInput v-else-if="formData.bg_type === 'gradient'" v-model:value="formData.bg_value" />
<MediaSelectInput v-else v-model:value="formData.bg_value" placeholder="图片/视频路径(支持 /uploads/... 相对路径)" />
</NFormItem>
</div>
<NDivider>文案样式</NDivider>
<div class="grid grid-cols-4 gap-x-16px">
<NFormItem label="标题字号" label-placement="top">
<NTooltip trigger="focus" placement="top">
<template #trigger>
<NInputNumber
:value="(formData.config.title_font_size as number | null) ?? null"
:min="16"
:max="96"
:step="2"
class="w-full"
clearable
placeholder="留空 = 36px(兜底)"
@update:value="v => (formData.config.title_font_size = v)"
>
<template #suffix>px</template>
</NInputNumber>
</template>
桌面端标题字号移动端自动按约 62.5% 缩小如填 48 移动端 30px留空 = 兜底 36px引言布局无标题字段不受影响
</NTooltip>
</NFormItem>
<NFormItem label="Overline 颜色" label-placement="top">
<ColorInput v-model:value="formData.overline_color" />
</NFormItem>
<NFormItem label="标题颜色" label-placement="top">
<ColorInput v-model:value="formData.title_color" />
</NFormItem>
<NFormItem label="副标题/正文颜色" label-placement="top">
<ColorInput v-model:value="formData.body_color" />
</NFormItem>
</div>
<NFormItem v-if="formData.bg_type === 'image' || formData.bg_type === 'video'" label="背景显示模式">
<NRadioGroup :value="(formData.config.bg_fit as string) ?? 'cover'" @update:value="v => (formData.config.bg_fit = v)">
<NRadio v-for="opt in bgFitOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</NRadio>
@@ -704,14 +723,6 @@ onMounted(loadData);
<span class="text-12px opacity-60">固定版式切换版式会同步下方槽位数量已填素材按顺序保留</span>
</div>
</NFormItem>
<NFormItem label="遮罩">
<NSpace align="center">
<NSwitch v-model:value="formData.overlay_enabled" />
<span>透明度</span>
<NSlider v-model:value="formData.overlay_opacity" :min="0" :max="1" :step="0.05" class="w-200px" />
<span>{{ formData.overlay_opacity.toFixed(2) }}</span>
</NSpace>
</NFormItem>
<NDivider>文案内容</NDivider>
<NFormItem label="Overline(中)">