diff --git a/dashboard/frontend/src/views/www/components/RichTextEditor.vue b/dashboard/frontend/src/views/www/components/RichTextEditor.vue index 0caa310..90cb05b 100644 --- a/dashboard/frontend/src/views/www/components/RichTextEditor.vue +++ b/dashboard/frontend/src/views/www/components/RichTextEditor.vue @@ -90,19 +90,27 @@ const editorConfig = { padding: 0 12px; } -/* 深色主题适配(wangEditor 默认浅色) */ -:global(html.dark) .rich-editor, -:global(html.dark) .rich-editor :deep(.w-e-text-container), -:global(html.dark) .rich-editor :deep(.w-e-toolbar) { - background-color: transparent; - color: rgba(255, 255, 255, 0.82); -} - -:global(html.dark) .rich-editor :deep(.w-e-bar-item button) { - color: rgba(255, 255, 255, 0.72); -} - -:global(html.dark) .rich-editor :deep(.w-e-text-container [data-slate-editor]) { - color: rgba(255, 255, 255, 0.82); +/* + * 深色主题适配:wangEditor 通过 CSS 变量(--w-e-*)控制全部内部元素颜色, + * 覆盖变量即可一次性适配工具栏、编辑区、下拉菜单、模态框等所有子组件, + * 无需逐元素覆写 background-color / color(旧方案漏掉了下拉菜单等内部元素)。 + */ +:global(html.dark) .rich-editor { + --w-e-textarea-bg-color: #18181c; + --w-e-textarea-color: rgba(255, 255, 255, 0.82); + --w-e-textarea-border-color: rgba(255, 255, 255, 0.12); + --w-e-textarea-slight-border-color: rgba(255, 255, 255, 0.08); + --w-e-textarea-slight-color: rgba(255, 255, 255, 0.4); + --w-e-textarea-slight-bg-color: rgba(255, 255, 255, 0.06); + --w-e-textarea-selected-border-color: #1668dc; + --w-e-textarea-handler-bg-color: #1668dc; + --w-e-toolbar-color: rgba(255, 255, 255, 0.72); + --w-e-toolbar-bg-color: #18181c; + --w-e-toolbar-active-color: rgba(255, 255, 255, 0.95); + --w-e-toolbar-active-bg-color: rgba(255, 255, 255, 0.1); + --w-e-toolbar-disabled-color: rgba(255, 255, 255, 0.3); + --w-e-toolbar-border-color: rgba(255, 255, 255, 0.1); + --w-e-modal-button-bg-color: rgba(255, 255, 255, 0.08); + --w-e-modal-button-border-color: rgba(255, 255, 255, 0.15); } diff --git a/dashboard/frontend/src/views/www/components/SectionManager.vue b/dashboard/frontend/src/views/www/components/SectionManager.vue index 0ff7ed3..b0a288e 100644 --- a/dashboard/frontend/src/views/www/components/SectionManager.vue +++ b/dashboard/frontend/src/views/www/components/SectionManager.vue @@ -47,11 +47,9 @@ const layoutOptions: LayoutOption[] = [ { label: '图文分栏', value: 'split_content', desc: '图片与文案左右并排介绍' }, { label: '图片画廊', value: 'gallery', desc: '多张图片网格展示' }, { label: '数据统计', value: 'stats', desc: '大数字展示关键指标数据' }, - { label: '视频展示', value: 'video_showcase', desc: '以视频为主的视觉区块' }, { label: '标题+媒体', value: 'title_media', desc: '顶部标题文案 + 下方图片/视频,对齐方向可配置' }, { label: '马赛克拼贴', value: 'mosaic', desc: '一大带多小的不规则网格,每个槽位支持图片/视频、中英双素材' }, - { label: '滚动叙事', value: 'scroll_narrative', desc: '随滚动逐步展开的叙事内容' }, - { label: '引言评价', value: 'quote', desc: '引用语 / 用户评价展示' }, + { label: '跑马灯', value: 'marquee', desc: '图片/视频轮播,可自定义切换间隔,最多 5 个素材' }, { label: '参数规格表', value: 'spec_table', desc: '产品技术参数规格表格' } ]; @@ -111,7 +109,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, 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 + 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), marquee_items: emptyMarqueeItems(1), marquee_interval: 3, min_height: null } as Record }); // 背景图/视频显示模式选项(bg_type 为图片/视频时生效) @@ -200,6 +198,43 @@ function handleMosaicPatternChange(value: string) { const mosaicSlotItems = computed(() => (formData.config.mosaic_items as MosaicItemForm[]) || []); const mosaicSlotLabelList = computed(() => mosaicSlotLabels(String(formData.config.mosaic_pattern ?? 'big_3'))); +// ===== 跑马灯(layout=marquee):图片/视频轮播,存 config.marquee_items / config.marquee_interval ===== +interface MarqueeItemForm { + type: 'image' | 'video'; + media_zh: string; + media_en: string; + /** 可选跳转链接 */ + link: string; +} +const MARQUEE_MAX = 5; + +function emptyMarqueeItem(): MarqueeItemForm { + return { type: 'image', media_zh: '', media_en: '', link: '' }; +} +function emptyMarqueeItems(count: number): MarqueeItemForm[] { + return Array.from({ length: count }, () => emptyMarqueeItem()); +} +// 归一化回填:类型/字段容错,数量限制 1~5(空则给 1 个空槽位) +function normalizeMarqueeItems(raw: unknown): MarqueeItemForm[] { + const arr = Array.isArray(raw) ? raw : []; + const list = arr.slice(0, MARQUEE_MAX).map((it): MarqueeItemForm => { + const o = (it ?? {}) as Partial; + return { type: o.type === 'video' ? 'video' : 'image', media_zh: o.media_zh || '', media_en: o.media_en || '', link: o.link || '' }; + }); + return list.length ? list : emptyMarqueeItems(1); +} +function handleAddMarqueeItem() { + const items = (formData.config.marquee_items as MarqueeItemForm[]) || []; + if (items.length >= MARQUEE_MAX) return; + formData.config.marquee_items = [...items, emptyMarqueeItem()]; +} +function handleRemoveMarqueeItem(idx: number) { + const items = (formData.config.marquee_items as MarqueeItemForm[]) || []; + const next = items.filter((_, i) => i !== idx); + formData.config.marquee_items = next.length ? next : emptyMarqueeItems(1); +} +const marqueeItems = computed(() => (formData.config.marquee_items as MarqueeItemForm[]) || []); + // 九宫格文案位置(hero_split 专用) const textPositionOptions = [ { label: '左上', value: 'top_left' }, @@ -334,7 +369,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, 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 }; + 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), marquee_items: emptyMarqueeItems(1), marquee_interval: 3, min_height: null }; dialogVisible.value = true; } @@ -381,9 +416,11 @@ function fillFormFromRow(row: Api.Www.SectionBlock) { 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' + mosaic_pattern: ['big_3', 'half_2', 'big_4', 'big_2'].includes(String(cfg.mosaic_pattern)) ? String(cfg.mosaic_pattern) : 'big_3', + marquee_interval: typeof cfg.marquee_interval === 'number' && cfg.marquee_interval >= 1 ? Math.min(cfg.marquee_interval, 60) : 3 }; formData.config.mosaic_items = normalizeMosaicItems(cfg.mosaic_items, String(formData.config.mosaic_pattern)); + formData.config.marquee_items = normalizeMarqueeItems(cfg.marquee_items); } function handleEdit(row: Api.Www.SectionBlock) { @@ -581,6 +618,26 @@ onMounted(loadData); 桌面端标题字号;移动端自动按约 62.5% 缩小(如填 48 → 移动端 30px)。留空 = 兜底 36px;引言布局无标题字段不受影响 + +
+ + + + + + + +
+
文案颜色(留空=跟随主题默认色) @@ -607,26 +664,6 @@ onMounted(loadData); {{ opt.label }} - -
- - - - - - - -
-
@@ -739,6 +776,40 @@ onMounted(loadData);
+