官网开发 0810
This commit is contained in:
@@ -4,6 +4,7 @@ import { NButton, NSpace, NSwitch, NTag, type DataTableColumns, type FormInst, t
|
||||
import ColorInput from '../page/home/components/ColorInput.vue';
|
||||
import MediaSelectInput from './MediaSelectInput.vue';
|
||||
import RichTextEditor from './RichTextEditor.vue';
|
||||
import SpecManager from './SpecManager.vue';
|
||||
import {
|
||||
fetchCreateSection,
|
||||
fetchDeleteSection,
|
||||
@@ -110,7 +111,7 @@ const formData = reactive({
|
||||
media_url_en: '',
|
||||
video_url: '',
|
||||
is_visible: true,
|
||||
config: { text_position: 'middle_left', width_percent: 100, container_width: '1200', border_radius: 0, padding_y: null, title_font_size: null, bg_fit: 'cover', media_position: 'left', mosaic_pattern: 'big_3', mosaic_items: emptyMosaicItems(4), min_height: null } as Record<string, unknown>
|
||||
config: { text_position: 'middle_left', width_percent: 100, container_width: '1200', border_radius: 0, padding_y: null, margin_y: null, title_font_size: null, bg_fit: 'cover', media_position: 'left', split_ratio: 50, mosaic_pattern: 'big_3', mosaic_items: emptyMosaicItems(4), min_height: null } as Record<string, unknown>
|
||||
});
|
||||
|
||||
// 背景图/视频显示模式选项(bg_type 为图片/视频时生效)
|
||||
@@ -128,6 +129,20 @@ const containerWidthOptions = [
|
||||
{ label: '全宽', value: 'full' }
|
||||
];
|
||||
|
||||
// 图文分栏比例选项(值为素材栏宽占比);标签顺序随「媒体位置」联动:
|
||||
// 素材在左 → 「素材 : 文案」,素材在右 → 「文案 : 素材」(与视觉左右顺序一致,底层存值不变)
|
||||
const splitRatioOptions = computed(() => {
|
||||
const mediaRight = formData.config.media_position === 'right';
|
||||
const label = (media: number) => (mediaRight ? `${100 - media} : ${media}` : `${media} : ${100 - media}`);
|
||||
return [
|
||||
{ label: `${label(30)}`, value: 30 },
|
||||
{ label: `${label(40)}`, value: 40 },
|
||||
{ label: `${label(50)}(默认)`, value: 50 },
|
||||
{ label: `${label(60)}`, value: 60 },
|
||||
{ label: `${label(70)}`, value: 70 }
|
||||
];
|
||||
});
|
||||
|
||||
// ===== 马赛克拼贴(layout=mosaic):固定版式 + 槽位素材,存 config.mosaic_pattern / config.mosaic_items =====
|
||||
interface MosaicItemForm {
|
||||
type: 'image' | 'video';
|
||||
@@ -247,11 +262,15 @@ const columns = computed((): DataTableColumns<Api.Www.SectionBlock> => [
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 240,
|
||||
width: 290,
|
||||
fixed: 'right',
|
||||
render(row) {
|
||||
return h('div', { class: 'flex-y-center gap-8px' }, [
|
||||
h(NButton, { size: 'small', type: 'primary', onClick: () => handleEdit(row) }, { default: () => '编辑' }),
|
||||
// spec_table 布局:管理参数分组与参数项
|
||||
row.layout === 'spec_table'
|
||||
? h(NButton, { size: 'small', type: 'info', onClick: () => handleSpecManage(row) }, { default: () => '参数' })
|
||||
: null,
|
||||
h(NButton, { size: 'small', onClick: () => handleCopy(row) }, { default: () => '复制' }),
|
||||
h(NButton, { size: 'small', type: 'error', onClick: () => handleDelete(row) }, { default: () => '删除' })
|
||||
]);
|
||||
@@ -315,7 +334,7 @@ function handleAdd() {
|
||||
formData.media_url_en = '';
|
||||
formData.video_url = '';
|
||||
formData.is_visible = true;
|
||||
formData.config = { text_position: 'middle_left', width_percent: 100, container_width: '1200', border_radius: 0, padding_y: null, title_font_size: null, bg_fit: 'cover', media_position: 'left', mosaic_pattern: 'big_3', mosaic_items: emptyMosaicItems(4), min_height: null };
|
||||
formData.config = { text_position: 'middle_left', width_percent: 100, container_width: '1200', border_radius: 0, padding_y: null, margin_y: null, title_font_size: null, bg_fit: 'cover', media_position: 'left', split_ratio: 50, mosaic_pattern: 'big_3', mosaic_items: emptyMosaicItems(4), min_height: null };
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
@@ -356,9 +375,11 @@ function fillFormFromRow(row: Api.Www.SectionBlock) {
|
||||
container_width: ['1400', '1600', 'full'].includes(String(cfg.container_width)) ? String(cfg.container_width) : '1200',
|
||||
border_radius: typeof cfg.border_radius === 'number' && cfg.border_radius > 0 ? cfg.border_radius : 0,
|
||||
padding_y: typeof cfg.padding_y === 'number' && cfg.padding_y >= 0 ? cfg.padding_y : null,
|
||||
margin_y: typeof cfg.margin_y === 'number' && cfg.margin_y >= 0 ? cfg.margin_y : null,
|
||||
title_font_size: typeof cfg.title_font_size === 'number' && cfg.title_font_size > 0 ? cfg.title_font_size : null,
|
||||
bg_fit: cfg.bg_fit === 'contain' || cfg.bg_fit === 'auto' ? cfg.bg_fit : 'cover',
|
||||
media_position: cfg.media_position === 'right' ? 'right' : 'left',
|
||||
split_ratio: [30, 40, 50, 60, 70].includes(Number(cfg.split_ratio)) ? Number(cfg.split_ratio) : 50,
|
||||
min_height: typeof cfg.min_height === 'number' && cfg.min_height >= 0 ? cfg.min_height : null,
|
||||
mosaic_pattern: ['big_3', 'half_2', 'big_4', 'big_2'].includes(String(cfg.mosaic_pattern)) ? String(cfg.mosaic_pattern) : 'big_3'
|
||||
};
|
||||
@@ -404,7 +425,14 @@ function handleDelete(row: Api.Www.SectionBlock) {
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
// 参数管理(spec_table 布局):唤起 SpecManager
|
||||
const specManagerRef = ref<InstanceType<typeof SpecManager> | null>(null);
|
||||
|
||||
function handleSpecManage(row: Api.Www.SectionBlock) {
|
||||
specManagerRef.value?.open(row);
|
||||
}
|
||||
|
||||
async function handleSubmit(closeAfter = true) {
|
||||
submitLoading.value = true;
|
||||
const { id, ...payload } = formData;
|
||||
const reqData = {
|
||||
@@ -412,13 +440,15 @@ async function handleSubmit() {
|
||||
page_type: props.pageType,
|
||||
product_id: props.productId || null
|
||||
};
|
||||
const { error } = id
|
||||
const { data, error } = id
|
||||
? await fetchUpdateSection(id, reqData)
|
||||
: await fetchCreateSection(reqData);
|
||||
submitLoading.value = false;
|
||||
if (!error) {
|
||||
// 「应用」新建后回填新 id,后续保存走更新接口,避免重复创建
|
||||
if (!id && data?.id) formData.id = data.id;
|
||||
window.$message?.success(id ? '更新成功' : '创建成功');
|
||||
dialogVisible.value = false;
|
||||
if (closeAfter) dialogVisible.value = false;
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
@@ -447,6 +477,8 @@ onMounted(loadData);
|
||||
:title="dialogTitle"
|
||||
class="w-1000px"
|
||||
:mask-closable="false"
|
||||
:segmented="{ content: true, footer: 'soft' }"
|
||||
content-style="max-height: calc(85vh - 130px); overflow-y: auto;"
|
||||
>
|
||||
<NForm ref="formRef" :model="formData" label-placement="left" label-width="120">
|
||||
<NDivider>基础配置</NDivider>
|
||||
@@ -515,6 +547,23 @@ onMounted(loadData);
|
||||
<span class="text-12px opacity-60">内容区与区块上下边缘的间距。留空 = 布局默认(多数 96px / 全幅横幅 120px / 主视觉分屏 64px);0 = 完全贴边</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">区块与上下相邻区块之间的间隙,露出页面背景色;适合「区块宽度 < 100 + 圆角」的卡片式区块。留空/0 = 紧贴(默认)</span>
|
||||
</div>
|
||||
</NFormItem>
|
||||
<NFormItem label="标题字号">
|
||||
<div class="w-full flex flex-col gap-8px">
|
||||
<NInputNumber
|
||||
@@ -596,10 +645,20 @@ onMounted(loadData);
|
||||
</NFormItem>
|
||||
<NFormItem v-if="formData.layout === 'split_content'" label="媒体位置">
|
||||
<NRadioGroup :value="(formData.config.media_position as string) ?? 'left'" @update:value="v => (formData.config.media_position = v)">
|
||||
<NRadio value="left">图片/视频在左</NRadio>
|
||||
<NRadio value="right">图片/视频在右</NRadio>
|
||||
<NRadio value="left">素材|文案</NRadio>
|
||||
<NRadio value="right">文案|素材</NRadio>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem v-if="formData.layout === 'split_content'" label="分栏比例">
|
||||
<div class="w-full flex flex-col gap-8px">
|
||||
<NSelect
|
||||
:value="(formData.config.split_ratio as number) ?? 50"
|
||||
:options="splitRatioOptions"
|
||||
@update:value="v => (formData.config.split_ratio = v)"
|
||||
/>
|
||||
<span class="text-12px opacity-60">按页面视觉左右顺序标注({{ formData.config.media_position === 'right' ? '文案 : 素材' : '素材 : 文案' }}),默认五五等分;移动端(小屏)始终单列堆叠不受影响</span>
|
||||
</div>
|
||||
</NFormItem>
|
||||
<NFormItem v-if="formData.layout === 'mosaic'" label="拼贴版式">
|
||||
<div class="w-full flex flex-col gap-8px">
|
||||
<NRadioGroup :value="(formData.config.mosaic_pattern as string) ?? 'big_3'" @update:value="handleMosaicPatternChange">
|
||||
@@ -700,10 +759,14 @@ onMounted(loadData);
|
||||
<template #footer>
|
||||
<NSpace justify="end">
|
||||
<NButton @click="dialogVisible = false">取消</NButton>
|
||||
<NButton type="primary" :loading="submitLoading" @click="handleSubmit">确定</NButton>
|
||||
<NButton type="success" secondary :loading="submitLoading" @click="handleSubmit(false)">应用</NButton>
|
||||
<NButton type="primary" :loading="submitLoading" @click="handleSubmit()">确定</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
|
||||
<!-- 参数规格管理(spec_table 布局专用) -->
|
||||
<SpecManager ref="specManagerRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user