Sfoglia il codice sorgente

新增背景和颜色的选择逻辑

Shinohara Haruna 5 mesi fa
parent
commit
f36da26d84

+ 119 - 0
smsb-plus-ui/src/components/BackgroundSelector.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="background-selector">
+    <template v-if="isCanvas">
+      <el-radio-group v-model="bgType" class="bg-type-radio" size="small">
+        <el-radio-button label="image">图片</el-radio-button>
+        <el-radio-button label="color">纯色</el-radio-button>
+      </el-radio-group>
+      <div class="bg-section" v-if="bgType === 'image'">
+        <div class="bg-label">选择背景图片</div>
+        <MediaFileSelector v-model="mediaId" accept="image/*" :single="true" :onlyImage="true" />
+      </div>
+      <div class="bg-section" v-else>
+        <div class="bg-label">选择纯色背景</div>
+        <el-color-picker v-model="color" />
+      </div>
+    </template>
+    <template v-else>
+      <div class="bg-section">
+        <div class="bg-label">选择颜色</div>
+        <el-color-picker v-model="color" />
+      </div>
+    </template>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, computed } from 'vue';
+import MediaFileSelector from './MediaFileSelector.vue';
+
+const props = defineProps<{
+  modelValue: string; // bg 字段,canvas 可能是图片url或颜色,其他组件为颜色
+  isCanvas?: boolean; // 是否画布
+}>();
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const bgType = ref<'image' | 'color'>('color');
+const mediaId = ref<string | null>(null);
+const color = ref<string>(props.modelValue || '#ffffff');
+
+// 初始化类型
+if (props.isCanvas) {
+  if (props.modelValue && /^https?:\/\//.test(props.modelValue)) {
+    bgType.value = 'image';
+    mediaId.value = props.modelValue;
+  } else {
+    bgType.value = 'color';
+    color.value = props.modelValue || '#ffffff';
+  }
+}
+
+// 切换类型时同步
+watch(bgType, (val) => {
+  if (val === 'image') {
+    if (mediaId.value) {
+      emit('update:modelValue', mediaId.value);
+      emit('change', mediaId.value);
+    }
+  } else {
+    emit('update:modelValue', color.value);
+    emit('change', color.value);
+  }
+});
+
+// 监听图片选择
+watch(mediaId, (val) => {
+  if (bgType.value === 'image' && val) {
+    emit('update:modelValue', val);
+    emit('change', val);
+  }
+});
+
+// 监听颜色选择
+watch(color, (val) => {
+  if (bgType.value === 'color') {
+    emit('update:modelValue', val);
+    emit('change', val);
+  }
+});
+
+// 外部变更时同步内部
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (props.isCanvas) {
+      if (val && /^https?:\/\//.test(val)) {
+        bgType.value = 'image';
+        mediaId.value = val;
+      } else {
+        bgType.value = 'color';
+        color.value = val || '#ffffff';
+      }
+    } else {
+      color.value = val || '#ffffff';
+    }
+  }
+);
+</script>
+
+<style scoped>
+.background-selector {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.bg-section {
+  margin-bottom: 6px;
+}
+
+.bg-label {
+  font-size: 13px;
+  color: #888;
+  margin-bottom: 2px;
+}
+
+.bg-type-radio {
+  margin-bottom: 8px;
+}
+</style>

+ 57 - 14
smsb-plus-ui/src/components/MediaFileSelector.vue

@@ -26,7 +26,7 @@
       </el-form>
       <el-table v-loading="dialogLoading" ref="fileTable" :data="fileList" reserve-selection row-key="id"
         @selection-change="handleSelectionFile" @select="handleSelect" @select-all="handleSelectAll">
-        <el-table-column type="selection" width="55" header-align="center" />
+        <el-table-column type="selection" width="55" header-align="center" :selectable="isSelectableRow" />
         <el-table-column label="类型" header-align="center" prop="type" width="80">
           <template #default="scope">
             <dict-tag :options="smsb_source_type" :value="scope.row.type" />
@@ -58,7 +58,7 @@ import { ref, watch, defineProps, defineEmits, nextTick } from 'vue';
 import { listMinioData } from '@/api/smsb/source/minioData';
 import type { MinioDataVO, MinioDataQuery } from '@/api/smsb/source/minioData_type';
 
-const props = defineProps<{ modelValue: string }>();
+const props = defineProps<{ modelValue: string; single?: boolean; onlyImage?: boolean }>();
 const emit = defineEmits(['update:modelValue']);
 
 const dialogVisible = ref(false);
@@ -66,6 +66,11 @@ const dialogLoading = ref(false);
 const fileList = ref<MinioDataVO[]>([]);
 const fileTotal = ref(0);
 const selectedFiles = ref<any[]>([]);
+// 控制哪些行可选
+function isSelectableRow(row: any) {
+  if (props.onlyImage) return row.type === 1;
+  return true;
+}
 const fileTable = ref();
 
 const queryParams = ref<MinioDataQuery>({
@@ -124,19 +129,51 @@ const getFileList = async () => {
 
 // 多选框选中文件数据
 function handleSelectionFile(selection: MinioDataVO[]) {
-  // 只做新增
-  selection.forEach((item) => {
-    if (!selectedFiles.value.some((f) => String(f.id) === String(item.id))) {
-      selectedFiles.value.push({
-        id: item.id,
-        name: item.originalName,
-        type: item.type,
-        duration: item.type === 1 ? 10 : item.duration,
-        order: 0
-      });
+  if (props.single && props.onlyImage) {
+    // 单选且仅图片
+    if (selection.length > 0) {
+      const img = selection.find((item) => item.type === 1);
+      if (img) {
+        selectedFiles.value = [
+          {
+            id: img.id,
+            name: img.originalName,
+            type: img.type,
+            duration: 10,
+            order: 1
+          }
+        ];
+      } else {
+        selectedFiles.value = [];
+      }
+    } else {
+      selectedFiles.value = [];
     }
-  });
-  selectedFiles.value = selectedFiles.value.map((f, idx) => ({ ...f, order: idx + 1 }));
+  } else if (props.single) {
+    // 单选任意类型
+    if (selection.length > 0) {
+      selectedFiles.value = [
+        {
+          id: selection[0].id,
+          name: selection[0].originalName,
+          type: selection[0].type,
+          duration: selection[0].duration,
+          order: 1
+        }
+      ];
+    } else {
+      selectedFiles.value = [];
+    }
+  } else {
+    // 多选多类型
+    selectedFiles.value = selection.map((item, idx) => ({
+      id: item.id,
+      name: item.originalName,
+      type: item.type,
+      duration: item.duration,
+      order: idx + 1
+    }));
+  }
 }
 // 取消单个选中
 function handleSelect(selection: MinioDataVO[], row: MinioDataVO) {
@@ -158,6 +195,12 @@ function removeFile(idx: number) {
   emitChange();
 }
 function confirmSelect() {
+  if (props.single && props.onlyImage) {
+    if (selectedFiles.value.length === 0 || selectedFiles.value[0].type !== 1) {
+      ElMessage.error('只能选择图片文件作为背景');
+      return;
+    }
+  }
   dialogVisible.value = false;
   emitChange();
 }

+ 9 - 0
smsb-plus-ui/src/views/smsb/itemProgram/EditProgram.vue

@@ -130,6 +130,13 @@
               <template v-else-if="key === 'mediaId'">
                 <MediaFileSelector v-model="selectedComponent[key]" />
               </template>
+              <template v-else-if="key === 'bg'">
+                <BackgroundSelector v-model="selectedComponent[key]" :isCanvas="selectedComponent.type === 'canvas'" />
+              </template>
+              <template
+                v-else-if="key === 'color' && (selectedComponent.type === 'text' || selectedComponent.type === 'scrollingText')">
+                <BackgroundSelector v-model="selectedComponent[key]" :isCanvas="false" />
+              </template>
               <template v-else>
                 <el-input v-model="selectedComponent[key]" />
               </template>
@@ -278,6 +285,8 @@ import CanvasBoard from './component/CanvasBoard.vue';
 import TextBoard from './component/TextBoard.vue';
 import ScrollingTextBoard from './component/ScrollingTextBoard.vue';
 import MediaAssetBoard from './component/MediaAssetBoard.vue';
+import MediaFileSelector from '@/components/MediaFileSelector.vue';
+import BackgroundSelector from '@/components/BackgroundSelector.vue';
 import LiveBoard from './component/LiveBoard.vue';
 import WebPageBoard from './component/WebPageBoard.vue';
 import {