官网开发中 0731
This commit is contained in:
@@ -13,6 +13,7 @@ export function fetchUploadMedia(file: File, onProgress?: (percent: number) => v
|
||||
url: '/www/media/upload',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
onUploadProgress: (e: { loaded: number; total?: number }) => {
|
||||
if (onProgress && e.total) {
|
||||
onProgress(Math.round((e.loaded / e.total) * 100));
|
||||
|
||||
@@ -71,7 +71,7 @@ const columns: DataTableColumns<Api.Www.FormField> = [
|
||||
width: 60,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.is_required,
|
||||
value: Boolean(row.is_required),
|
||||
onUpdateValue: (val: boolean) => handleToggleRequired(row, val)
|
||||
});
|
||||
}
|
||||
@@ -130,7 +130,7 @@ function handleEditField(row: Api.Www.FormField) {
|
||||
fieldFormData.name_zh = row.name_zh;
|
||||
fieldFormData.name_en = row.name_en;
|
||||
fieldFormData.field_type = row.field_type as 'text' | 'email' | 'phone' | 'textarea' | 'select';
|
||||
fieldFormData.is_required = row.is_required;
|
||||
fieldFormData.is_required = Boolean(row.is_required);
|
||||
fieldFormData.placeholder_zh = row.placeholder_zh;
|
||||
fieldFormData.placeholder_en = row.placeholder_en;
|
||||
fieldFormData.options_json = row.options_json || '';
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
fetchDeleteMediaItem,
|
||||
fetchGetMedia,
|
||||
fetchGetMediaTags,
|
||||
fetchUpdateMediaItem,
|
||||
fetchUploadMedia
|
||||
} from '@/service/api';
|
||||
|
||||
@@ -53,6 +54,114 @@ function categoryLabel(val: string) {
|
||||
return categoryOptions.find(o => o.value === val)?.label || val;
|
||||
}
|
||||
|
||||
// ===== 预览 =====
|
||||
const previewVisible = ref(false);
|
||||
const previewRow = ref<Api.Www.MediaItem | null>(null);
|
||||
|
||||
// 预览类型:图片 / 视频 / PDF 可在线预览,其余(Office 等)仅提供下载
|
||||
type PreviewKind = 'image' | 'video' | 'pdf' | 'unsupported';
|
||||
const previewKind = computed<PreviewKind>(() => {
|
||||
const row = previewRow.value;
|
||||
if (!row) return 'unsupported';
|
||||
if (row.file_type === 'image') return 'image';
|
||||
if (row.file_type === 'video') return 'video';
|
||||
const ext = row.file_path.split('.').pop()?.toLowerCase() || '';
|
||||
if (ext === 'pdf' || row.mime_type === 'application/pdf') return 'pdf';
|
||||
return 'unsupported';
|
||||
});
|
||||
|
||||
// 相对路径(用于页面内 img/video/iframe 加载,/uploads 已由 vite/nginx 代理)
|
||||
const previewUrl = computed(() => previewRow.value?.file_path || '');
|
||||
// 完整可访问 URL(用于复制链接)
|
||||
const previewFullUrl = computed(() => (previewRow.value ? window.location.origin + previewRow.value.file_path : ''));
|
||||
|
||||
function handlePreview(row: Api.Www.MediaItem) {
|
||||
previewRow.value = row;
|
||||
previewVisible.value = true;
|
||||
}
|
||||
|
||||
// ===== 复制链接 =====
|
||||
async function copyText(text: string): Promise<boolean> {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch {
|
||||
// 非安全上下文(http)下 clipboard API 不可用,降级为 execCommand
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
const ok = document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCopyLink(row: Api.Www.MediaItem) {
|
||||
const url = window.location.origin + row.file_path;
|
||||
const ok = await copyText(url);
|
||||
if (ok) window.$message?.success('链接已复制到剪贴板');
|
||||
else window.$message?.error('复制失败,请手动复制');
|
||||
}
|
||||
|
||||
async function copyPreviewLink() {
|
||||
if (!previewRow.value) return;
|
||||
await handleCopyLink(previewRow.value);
|
||||
}
|
||||
|
||||
// ===== 编辑(重命名 + 分类 + 标签)=====
|
||||
const editVisible = ref(false);
|
||||
const editLoading = ref(false);
|
||||
const editForm = reactive({
|
||||
id: 0,
|
||||
filename: '',
|
||||
category: 'other',
|
||||
tags: [] as string[]
|
||||
});
|
||||
|
||||
const tagSelectOptions = computed(() => tagOptions.value.map(t => ({ label: t.tag_name, value: t.tag_name })));
|
||||
|
||||
function handleEdit(row: Api.Www.MediaItem) {
|
||||
editForm.id = row.id;
|
||||
editForm.filename = row.filename;
|
||||
editForm.category = row.category;
|
||||
editForm.tags = [...(row.tags || [])];
|
||||
editVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleEditSave() {
|
||||
if (!editForm.filename?.trim()) {
|
||||
window.$message?.warning('文件名不能为空');
|
||||
return;
|
||||
}
|
||||
editLoading.value = true;
|
||||
const { error } = await fetchUpdateMediaItem(editForm.id, {
|
||||
filename: editForm.filename.trim(),
|
||||
category: editForm.category,
|
||||
tags: editForm.tags
|
||||
});
|
||||
editLoading.value = false;
|
||||
if (!error) {
|
||||
window.$message?.success('保存成功');
|
||||
editVisible.value = false;
|
||||
loadData();
|
||||
loadTags(); // 可能新建了标签,刷新下拉选项
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 行点击自动复制链接 =====
|
||||
function handleRowClick(row: Api.Www.MediaItem) {
|
||||
handleCopyLink(row);
|
||||
}
|
||||
|
||||
const rowProps = (row: Api.Www.MediaItem) => ({
|
||||
style: 'cursor: pointer;',
|
||||
onClick: () => handleRowClick(row)
|
||||
});
|
||||
|
||||
// ===== 表格列 =====
|
||||
const columns = computed<DataTableColumns<Api.Www.MediaItem>>(() => [
|
||||
{ title: 'ID', key: 'id', width: 60 },
|
||||
{
|
||||
@@ -60,14 +169,15 @@ const columns = computed<DataTableColumns<Api.Www.MediaItem>>(() => [
|
||||
key: 'thumbnail_url',
|
||||
width: 80,
|
||||
render(row) {
|
||||
if (row.file_type === 'image' && (row.thumbnail_url || row.file_path)) {
|
||||
return h('img', {
|
||||
src: row.thumbnail_url || row.file_path,
|
||||
alt: row.filename,
|
||||
style: 'width: 48px; height: 48px; object-fit: cover; border-radius: 4px;'
|
||||
});
|
||||
}
|
||||
return h(NTag, { size: 'small' }, { default: () => row.file_type });
|
||||
const content
|
||||
= row.file_type === 'image' && (row.thumbnail_url || row.file_path)
|
||||
? h('img', {
|
||||
src: row.thumbnail_url || row.file_path,
|
||||
alt: row.filename,
|
||||
style: 'width: 48px; height: 48px; object-fit: cover; border-radius: 4px;'
|
||||
})
|
||||
: h(NTag, { size: 'small' }, { default: () => row.file_type });
|
||||
return h('div', { style: 'cursor: pointer;', onClick: (e: Event) => { e.stopPropagation(); handlePreview(row); } }, [content]);
|
||||
}
|
||||
},
|
||||
{ title: '文件名', key: 'filename', ellipsis: { tooltip: true } },
|
||||
@@ -95,14 +205,15 @@ const columns = computed<DataTableColumns<Api.Www.MediaItem>>(() => [
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
width: 130,
|
||||
fixed: 'right',
|
||||
render(row) {
|
||||
return h(NButton, {
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
onClick: () => handleDelete(row)
|
||||
}, { default: () => '删除' });
|
||||
return h(NSpace, { size: 8, onClick: (e: Event) => e.stopPropagation() }, {
|
||||
default: () => [
|
||||
h(NButton, { type: 'warning', size: 'small', onClick: () => handleEdit(row) }, { default: () => '编辑' }),
|
||||
h(NButton, { type: 'error', size: 'small', onClick: () => handleDelete(row) }, { default: () => '删除' })
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -191,6 +302,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NSpace vertical :size="16">
|
||||
<NCard :bordered="false" class="card-wrapper" size="small">
|
||||
<NForm inline :model="searchForm" label-placement="left">
|
||||
@@ -228,7 +340,7 @@ onMounted(() => {
|
||||
</NUpload>
|
||||
</template>
|
||||
|
||||
<NDataTable :loading="loading" :columns="columns" :data="tableData" :bordered="false" size="small" />
|
||||
<NDataTable :loading="loading" :columns="columns" :data="tableData" :bordered="false" size="small" :scroll-x="900" :row-props="rowProps" />
|
||||
|
||||
<div class="mt-16px flex justify-end">
|
||||
<NPagination
|
||||
@@ -244,4 +356,96 @@ onMounted(() => {
|
||||
</div>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
|
||||
<!-- 预览弹窗:图片大图 / 视频播放 / PDF 内嵌 / Office 等仅下载 -->
|
||||
<NModal
|
||||
v-model:show="previewVisible"
|
||||
preset="card"
|
||||
:title="previewRow?.filename || '素材预览'"
|
||||
style="width: 780px; max-width: 92vw;"
|
||||
:bordered="false"
|
||||
>
|
||||
<div v-if="previewRow" class="flex flex-col gap-12px">
|
||||
<!-- 图片 -->
|
||||
<div v-if="previewKind === 'image'" class="flex-center">
|
||||
<img
|
||||
:src="previewUrl"
|
||||
:alt="previewRow.filename"
|
||||
style="max-width: 100%; max-height: 68vh; object-fit: contain; border-radius: 6px;"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 视频 -->
|
||||
<div v-else-if="previewKind === 'video'" class="flex-center">
|
||||
<video :src="previewUrl" controls autoplay style="max-width: 100%; max-height: 68vh; border-radius: 6px;" />
|
||||
</div>
|
||||
|
||||
<!-- PDF -->
|
||||
<iframe v-else-if="previewKind === 'pdf'" :src="previewUrl" style="width: 100%; height: 70vh; border: none; border-radius: 6px;" />
|
||||
|
||||
<!-- 暂不支持在线预览(Office 等)-->
|
||||
<div v-else class="flex-col-center py-40px">
|
||||
<SvgIcon icon="mdi:file-document-outline" class="text-48px text-gray-300" />
|
||||
<p class="mt-12px text-14px text-gray-500">
|
||||
该文件类型暂不支持在线预览
|
||||
</p>
|
||||
<p class="mt-4px text-12px text-gray-400">
|
||||
{{ previewRow.mime_type || '未知类型' }} · {{ formatSize(previewRow.file_size) }}
|
||||
</p>
|
||||
<NButton class="mt-16px" tag="a" :href="previewUrl" download>
|
||||
<template #icon><SvgIcon icon="mdi:download" /></template>
|
||||
下载文件
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<!-- 文件信息 + 操作 -->
|
||||
<div class="flex items-center justify-between border-t border-gray-100 pt-12px">
|
||||
<span class="truncate text-12px text-gray-400">{{ previewRow.filename }} · {{ formatSize(previewRow.file_size) }}</span>
|
||||
<NSpace>
|
||||
<NButton size="small" @click="copyPreviewLink">
|
||||
<template #icon><SvgIcon icon="mdi:content-copy" /></template>
|
||||
复制链接
|
||||
</NButton>
|
||||
<NButton size="small" tag="a" :href="previewUrl" download>
|
||||
<template #icon><SvgIcon icon="mdi:download" /></template>
|
||||
下载
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</div>
|
||||
</div>
|
||||
</NModal>
|
||||
|
||||
<!-- 编辑弹窗:重命名 + 分类 + 多标签 -->
|
||||
<NModal v-model:show="editVisible" preset="card" title="编辑素材" style="width: 520px; max-width: 92vw;" :bordered="false">
|
||||
<NForm :model="editForm" label-placement="left" label-width="80">
|
||||
<NFormItem label="文件名">
|
||||
<NInput v-model:value="editForm.filename" placeholder="请输入有意义的文件名" />
|
||||
</NFormItem>
|
||||
<NFormItem label="分类">
|
||||
<NSelect v-model:value="editForm.category" :options="categoryOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="标签">
|
||||
<NSelect
|
||||
v-model:value="editForm.tags"
|
||||
:options="tagSelectOptions"
|
||||
multiple
|
||||
filterable
|
||||
tag
|
||||
clearable
|
||||
placeholder="选择已有标签或输入新标签后回车"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace justify="end">
|
||||
<NButton @click="editVisible = false">
|
||||
取消
|
||||
</NButton>
|
||||
<NButton type="primary" :loading="editLoading" @click="handleEditSave">
|
||||
保存
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -55,7 +55,7 @@ const columns: DataTableColumns<Api.Www.NavItem> = [
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.is_visible,
|
||||
value: Boolean(row.is_visible),
|
||||
onUpdateValue: (val: boolean) => handleToggleVisible(row, val)
|
||||
});
|
||||
}
|
||||
@@ -111,11 +111,11 @@ function handleEdit(row: Api.Www.NavItem) {
|
||||
formData.name_zh = row.name_zh;
|
||||
formData.name_en = row.name_en;
|
||||
formData.link = row.link;
|
||||
formData.is_visible = row.is_visible;
|
||||
formData.is_visible = Boolean(row.is_visible);
|
||||
formData.display_mode = row.display_mode;
|
||||
formData.icon_name = row.icon_name;
|
||||
formData.image_url = row.image_url;
|
||||
formData.open_new_tab = row.open_new_tab;
|
||||
formData.open_new_tab = Boolean(row.open_new_tab);
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { h, onMounted, reactive, ref } from 'vue';
|
||||
import { NButton, NSpace, NSwitch, NTag, type DataTableColumns, type FormInst } from 'naive-ui';
|
||||
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 {
|
||||
fetchCreateSection,
|
||||
fetchDeleteSection,
|
||||
fetchGetSections,
|
||||
fetchSortSections,
|
||||
fetchUpdateSection
|
||||
} from '@/service/api';
|
||||
|
||||
@@ -17,19 +18,43 @@ const dialogTitle = ref('');
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const tableData = ref<Api.Www.SectionBlock[]>([]);
|
||||
|
||||
const layoutOptions = [
|
||||
{ label: 'Hero Split', value: 'hero_split' },
|
||||
{ label: 'Full Width Banner', value: 'full_banner' },
|
||||
{ label: 'Feature Grid', value: 'feature_grid' },
|
||||
{ label: 'Split Content', value: 'split_content' },
|
||||
{ label: 'Gallery', value: 'gallery' },
|
||||
{ label: 'Stats', value: 'stats' },
|
||||
{ label: 'Video Showcase', value: 'video_showcase' },
|
||||
{ label: 'Scroll Narrative', value: 'scroll_narrative' },
|
||||
{ label: 'Quote', value: 'quote' },
|
||||
{ label: 'Spec Table', value: 'spec_table' }
|
||||
type LayoutOption = SelectOption & { desc: string };
|
||||
|
||||
const layoutOptions: LayoutOption[] = [
|
||||
{ label: '主视觉分屏', value: 'hero_split', desc: '左文案右图片,首页首屏大图' },
|
||||
{ label: '通栏大横幅', value: 'full_banner', desc: '全宽背景图,标题文案居中' },
|
||||
{ label: '特性网格', value: 'feature_grid', desc: '多列卡片,展示产品特性卖点' },
|
||||
{ label: '图文分栏', value: 'split_content', desc: '图片与文案左右并排介绍' },
|
||||
{ label: '图片画廊', value: 'gallery', desc: '多张图片网格展示' },
|
||||
{ label: '数据统计', value: 'stats', desc: '大数字展示关键指标数据' },
|
||||
{ label: '视频展示', value: 'video_showcase', desc: '以视频为主的视觉区块' },
|
||||
{ label: '滚动叙事', value: 'scroll_narrative', desc: '随滚动逐步展开的叙事内容' },
|
||||
{ label: '引言评价', value: 'quote', desc: '引用语 / 用户评价展示' },
|
||||
{ label: '参数规格表', value: 'spec_table', desc: '产品技术参数规格表格' }
|
||||
];
|
||||
|
||||
function layoutLabel(value: string) {
|
||||
return (layoutOptions.find(o => o.value === value)?.label as string) || value;
|
||||
}
|
||||
|
||||
function renderLayoutLabel(option: SelectOption, _selected: boolean) {
|
||||
const opt = option as LayoutOption;
|
||||
// 始终渲染 label + desc(收起态的 desc 由 CSS 隐藏,见底部 <style>)
|
||||
return h('div', { class: 'flex flex-col py-2px' }, [
|
||||
h('span', { class: 'text-14px' }, opt.label as string),
|
||||
h('span', { class: 'text-12px opacity-60 layout-desc' }, opt.desc)
|
||||
]);
|
||||
}
|
||||
|
||||
const renderLayoutOption: SelectRenderOption = ({ node, option }) => {
|
||||
const opt = option as LayoutOption;
|
||||
return h('div', {
|
||||
style: opt.value === layoutOptions[layoutOptions.length - 1].value
|
||||
? ''
|
||||
: 'border-bottom: 1px solid rgba(0, 0, 0, 0.06);'
|
||||
}, [node]);
|
||||
};
|
||||
|
||||
const themeOptions = [
|
||||
{ label: '浅色', value: 'light' },
|
||||
{ label: '深色', value: 'dark' }
|
||||
@@ -63,9 +88,29 @@ const formData = reactive({
|
||||
is_visible: true
|
||||
});
|
||||
|
||||
const columns: DataTableColumns<Api.Www.SectionBlock> = [
|
||||
{ title: '排序', key: 'sort_order', width: 60 },
|
||||
{ title: '布局', key: 'layout', width: 130 },
|
||||
const columns = computed((): DataTableColumns<Api.Www.SectionBlock> => [
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort_order',
|
||||
width: 110,
|
||||
render(row) {
|
||||
const idx = tableData.value.findIndex(item => item.id === row.id);
|
||||
const isFirst = idx === 0;
|
||||
const isLast = idx === tableData.value.length - 1;
|
||||
return h('div', { class: 'flex-y-center gap-4px' }, [
|
||||
h('span', { class: 'text-12px text-gray-400 w-20px text-center' }, { default: () => String(row.sort_order) }),
|
||||
h(NButton, {
|
||||
text: true, size: 'tiny', type: 'primary', disabled: isFirst,
|
||||
onClick: () => handleMove(idx, -1)
|
||||
}, { default: () => '↑' }),
|
||||
h(NButton, {
|
||||
text: true, size: 'tiny', type: 'primary', disabled: isLast,
|
||||
onClick: () => handleMove(idx, 1)
|
||||
}, { default: () => '↓' })
|
||||
]);
|
||||
}
|
||||
},
|
||||
{ title: '布局', key: 'layout', width: 130, render: row => layoutLabel(row.layout) },
|
||||
{
|
||||
title: '主题',
|
||||
key: 'theme',
|
||||
@@ -84,7 +129,7 @@ const columns: DataTableColumns<Api.Www.SectionBlock> = [
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.is_visible,
|
||||
value: Boolean(row.is_visible),
|
||||
onUpdateValue: (val: boolean) => handleToggleVisible(row, val)
|
||||
});
|
||||
}
|
||||
@@ -101,7 +146,22 @@ const columns: DataTableColumns<Api.Www.SectionBlock> = [
|
||||
]);
|
||||
}
|
||||
}
|
||||
];
|
||||
]);
|
||||
|
||||
async function handleMove(index: number, direction: -1 | 1) {
|
||||
const swapIndex = index + direction;
|
||||
if (swapIndex < 0 || swapIndex >= tableData.value.length) return;
|
||||
|
||||
const newData = [...tableData.value];
|
||||
[newData[index], newData[swapIndex]] = [newData[swapIndex], newData[index]];
|
||||
const sortPayload = newData.map((item, i) => ({ id: item.id, sort_order: i }));
|
||||
|
||||
const { error } = await fetchSortSections(sortPayload);
|
||||
if (!error) {
|
||||
window.$message?.success('排序已更新');
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true;
|
||||
@@ -149,8 +209,8 @@ function handleEdit(row: Api.Www.SectionBlock) {
|
||||
formData.theme = row.theme;
|
||||
formData.bg_type = row.bg_type;
|
||||
formData.bg_value = row.bg_value;
|
||||
formData.overlay_enabled = row.overlay_enabled;
|
||||
formData.overlay_opacity = row.overlay_opacity;
|
||||
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;
|
||||
@@ -168,7 +228,7 @@ function handleEdit(row: Api.Www.SectionBlock) {
|
||||
formData.media_url_zh = row.media_url_zh;
|
||||
formData.media_url_en = row.media_url_en;
|
||||
formData.video_url = row.video_url;
|
||||
formData.is_visible = row.is_visible;
|
||||
formData.is_visible = Boolean(row.is_visible);
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
@@ -239,7 +299,7 @@ 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" />
|
||||
<NSelect v-model:value="formData.layout" :options="layoutOptions" :render-label="renderLayoutLabel" :render-option="renderLayoutOption" />
|
||||
</NFormItem>
|
||||
<NFormItem label="主题">
|
||||
<NRadioGroup v-model:value="formData.theme">
|
||||
@@ -251,6 +311,7 @@ onMounted(loadData);
|
||||
<NRadioButton value="color">纯色</NRadioButton>
|
||||
<NRadioButton value="image">图片</NRadioButton>
|
||||
<NRadioButton value="gradient">渐变</NRadioButton>
|
||||
<NRadioButton value="video">视频</NRadioButton>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem label="背景值">
|
||||
@@ -336,3 +397,12 @@ onMounted(loadData);
|
||||
</NModal>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* 布局类型下拉:收起态(输入框内)隐藏 desc,下拉展开列表里始终显示 desc */
|
||||
.n-base-selection-label .layout-desc,
|
||||
.n-base-selection-tag__content .layout-desc,
|
||||
.n-base-selection-tags .layout-desc {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -74,7 +74,7 @@ const columns = computed<DataTableColumns<Api.Www.Product>>(() => [
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.is_visible,
|
||||
value: Boolean(row.is_visible),
|
||||
onUpdateValue: (val: boolean) => handleToggleVisible(row, val)
|
||||
});
|
||||
}
|
||||
@@ -155,7 +155,7 @@ function handleEdit(row: Api.Www.Product) {
|
||||
formData.intro_zh = row.intro_zh;
|
||||
formData.intro_en = row.intro_en;
|
||||
formData.cover_url = row.cover_url;
|
||||
formData.is_visible = row.is_visible;
|
||||
formData.is_visible = Boolean(row.is_visible);
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const columns: DataTableColumns<Api.Www.ProductSeries> = [
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.is_visible,
|
||||
value: Boolean(row.is_visible),
|
||||
onUpdateValue: (val: boolean) => handleToggleVisible(row, val)
|
||||
});
|
||||
}
|
||||
@@ -95,7 +95,7 @@ function handleEdit(row: Api.Www.ProductSeries) {
|
||||
formData.subtitle_zh = row.subtitle_zh;
|
||||
formData.subtitle_en = row.subtitle_en;
|
||||
formData.cover_url = row.cover_url;
|
||||
formData.is_visible = row.is_visible;
|
||||
formData.is_visible = Boolean(row.is_visible);
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user