Explorar o código

feat(components): 添加支持最近使用颜色的色彩选择器组件

- 创建 ColorPickerWithRecent 组件,支持最近使用颜色功能
- 实现 localStorage 存储最近使用的颜色
- 添加全局颜色更新通知机制,确保多实例同步
- 替换原有 el-color-picker 为新的 ColorPickerWithRecent 组件
- 在营销编辑页面、图片编辑器、设置页面等多个位置应用新组件
- 实现颜色选择确认机制,区分临时选择和最终确认的颜色
panqiuyao hai 6 días
pai
achega
e429b2a40f

+ 2 - 1
frontend/src/views/Setting/index.vue

@@ -97,7 +97,7 @@
               <div class="form-item">
                 <label>800图颜色配置:</label>
                 <div class="select-wrapper flex left">
-                  <el-color-picker v-model="formData.basic_configs.color_800image" />
+                  <ColorPickerWithRecent v-model="formData.basic_configs.color_800image" />
                 </div>
               </div>
 
@@ -179,6 +179,7 @@ import { onMounted, watch } from 'vue';
 import  { getAllUserConfigs,  setAllUserConfigs } from '@/apis/setting'
 import socket from "@/stores/modules/socket";
 import headerBar from '@/components/header-bar/index.vue';
+import ColorPickerWithRecent from '@/views/components/common/ColorPickerWithRecent.vue'
 import client from "@/stores/modules/client";
 import icpList from '@/utils/ipc';
 const clientStore = client();

+ 5 - 1
frontend/src/views/components/PictureEditor/mixin/edit/index.js

@@ -1,5 +1,6 @@
 
 import Teleport from '@/components/Teleport'
+import ColorPickerWithRecent from '@/views/components/common/ColorPickerWithRecent.vue'
 
 import * as TextboxConfig from './module/TextboxConfig'
 import fabric from "../../js/fabric-adapter";
@@ -26,6 +27,7 @@ const DEFAULT_CLIP_SETTINGS = {
 export default  {
   components: {
     Teleport,
+    ColorPickerWithRecent,
   },
   data() {
     return {
@@ -91,7 +93,7 @@ export default  {
     shadow(){
       let  shadowText = this.shadowText
       return `${shadowText.x}px ${shadowText.y}px ${shadowText.vague}px  ${shadowText.color} `
-    }
+    },
   },
   watch: {
     editLayer(){
@@ -247,6 +249,8 @@ export default  {
     judge(){
         return this.editLayer?.id
     },
+
+
     //翻转
     Flip(type){
       if(!this.judge()) return;

+ 2 - 2
frontend/src/views/components/PictureEditor/mixin/edit/module/textbox.js

@@ -74,12 +74,12 @@ const textbox = () => {
             <div class=" ">
               <div class="flex left " style="align-items: center; margin-right: 15px;">
                 <span class="label-text">颜色:</span>
-                <el-color-picker
+                <ColorPickerWithRecent
                   :model-value="layerState.fill"
                   @change="(val)=>setLayerAttr('fill', val)"
                   size="small"
                   class="mar-left-5"
-                />
+               />
               </div>
 
                 <div class="mar-top-10 flex left">

+ 4 - 6
frontend/src/views/components/PictureEditor/mixin/edit/module/tools.js

@@ -92,12 +92,11 @@ cutout(){
       <!-- 背景色 -->
       <div class="flex left mar-top-10">
         <div class="label-text">背景:</div>
-        <el-color-picker
+        <ColorPickerWithRecent
           v-model="clipSettings.fillColor"
           @change="applyClipPath"
           size="small"
-          :predefine="['#FFFFFF', '#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF', 'rgba(255,255,255,0.5)', 'rgba(0,0,0,0.5)']"
-        />
+         />
         <span class="mar-left-10">{{ clipSettings.fillColor || '无' }}</span>
       </div>
 
@@ -119,12 +118,11 @@ cutout(){
       <!-- 描边 -->
       <div class="flex left mar-top-10">
         <div class="label-text">描边:</div>
-        <el-color-picker
+        <ColorPickerWithRecent
           v-model="clipSettings.strokeColor"
           @change="applyClipPath"
           size="small"
-          :predefine="['#FFFFFF', '#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF']"
-        />
+      />
         <span class="mar-left-10">{{ clipSettings.strokeColor || '无' }}</span>
       </div>
 

+ 160 - 0
frontend/src/views/components/common/ColorPickerWithRecent.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="color-picker-wrapper">
+    <el-color-picker
+      :model-value="confirmedColor"
+      @change="handleColorConfirm"
+      :size="size"
+      :predefine="predefineColors"
+      v-bind="$attrs"
+    />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ColorPickerWithRecent',
+  props: {
+    modelValue: {
+      type: String,
+      default: ''
+    },
+    size: {
+      type: String,
+      default: 'default'
+    },
+    // 默认预设颜色
+    defaultPredefine: {
+      type: Array,
+      default: () => ['#FFFFFF', '#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF']
+    }
+  },
+  data() {
+    return {
+      // 全局最近使用的颜色(所有实例共享)
+      recentColors: [],
+      // 已确认的颜色(只有点击OK后才更新)
+      confirmedColor: this.modelValue
+    }
+  },
+  computed: {
+    // 合并最近使用颜色和默认颜色
+    predefineColors() {
+      // 如果有最近使用的颜色,优先显示最近使用的 + 默认颜色
+      if (this.recentColors.length > 0) {
+        // 合并并去重
+        const combined = [...this.recentColors, ...this.defaultPredefine];
+        return [...new Set(combined)].slice(0, 10); // 限制总数量
+      }
+      // 如果没有最近使用的颜色,只显示默认颜色
+      return this.defaultPredefine;
+    }
+  },
+  methods: {
+    // 处理颜色确认(用户点击OK按钮)
+    handleColorConfirm(color) {
+      if (color) {
+        // 添加到最近使用的颜色列表
+        this.addToRecentColors(color);
+
+        // 更新已确认的颜色(CSS会自动更新显示)
+        this.confirmedColor = color;
+
+        // 触发父组件的change事件
+        this.$emit('update:modelValue', color);
+        this.$emit('change', color);
+      }
+    },
+
+    // 添加颜色到最近使用列表
+    addToRecentColors(color) {
+      // 使用全局存储,确保所有实例同步
+      const globalRecentColors = this.getGlobalRecentColors();
+
+      // 移除已存在的相同颜色
+      const filtered = globalRecentColors.filter(c => c !== color);
+
+      // 添加到列表开头
+      const newColors = [color, ...filtered].slice(0, 10); // 最多保留10个
+
+      // 更新全局存储
+      this.setGlobalRecentColors(newColors);
+
+      // 更新当前实例的数据
+      this.recentColors = newColors;
+
+      // 通知其他实例更新
+
+
+      this.notifyOtherInstances(newColors);
+    },
+
+    // 获取全局最近颜色
+    getGlobalRecentColors() {
+      try {
+        const stored = localStorage.getItem('canvasEditorRecentColors');
+        return stored ? JSON.parse(stored) : [];
+      } catch (error) {
+        console.warn('Failed to load recent colors from localStorage:', error);
+        return [];
+      }
+    },
+
+    // 设置全局最近颜色
+    setGlobalRecentColors(colors) {
+      try {
+        localStorage.setItem('canvasEditorRecentColors', JSON.stringify(colors));
+      } catch (error) {
+        console.warn('Failed to save recent colors to localStorage:', error);
+      }
+    },
+
+    // 通知其他实例更新
+    notifyOtherInstances(colors) {
+      // 通过自定义事件通知其他ColorPickerWithRecent实例
+      window.dispatchEvent(new CustomEvent('colorPickerRecentColorsUpdate', {
+        detail: { colors }
+      }));
+    },
+
+    // 监听其他实例的颜色更新
+    handleGlobalColorsUpdate(event) {
+      this.recentColors = event.detail.colors;
+    }
+  },
+  watch: {
+    // 监听父组件传入的modelValue变化
+    modelValue: {
+      handler(newValue) {
+        this.confirmedColor = newValue;
+      },
+      immediate: true
+    }
+  },
+  mounted() {
+    // 组件挂载时从localStorage加载最近使用的颜色
+    this.recentColors = this.getGlobalRecentColors();
+
+    // 监听其他实例的颜色更新事件
+    window.addEventListener('colorPickerRecentColorsUpdate', this.handleGlobalColorsUpdate);
+  },
+  beforeUnmount() {
+    // 组件销毁前移除事件监听
+    window.removeEventListener('colorPickerRecentColorsUpdate', this.handleGlobalColorsUpdate);
+  }
+}
+</script>
+
+<style scoped>
+.color-picker-wrapper {
+  --picker-color: v-bind(confirmedColor);
+}
+
+.color-picker-wrapper :deep(.el-color-picker__color-inner) {
+  background-color: var(--picker-color) !important;
+}
+
+/* 确保在所有情况下都应用这个样式 */
+.color-picker-wrapper :deep(.el-color-picker) {
+  --el-color-primary: var(--picker-color);
+}
+</style>

+ 4 - 0
frontend/src/views/components/marketingEdit/index.vue

@@ -24,6 +24,7 @@ import canvas1 from './canvas1.json'
 import { buildRenderPlans, normalizeGoods } from './generateImagesPlan'
 import { renderImagesByPlans, generateAllStyleImageBundles } from './generateImagesRender'
 
+import ColorPickerWithRecent from '@/views/components/common/ColorPickerWithRecent.vue'
 const FIXED_CANVAS_WIDTH = 395
 export default {
   name: 'marketingImageEditor',
@@ -35,6 +36,9 @@ export default {
       clientStore
     }
   },
+  components: {
+    ColorPickerWithRecent
+  },
 
   props: {
     data: {

+ 1 - 1
frontend/src/views/components/marketingEdit/tpl/header.js

@@ -117,7 +117,7 @@ export  default function tpl(){
                     <el-input v-model.number="canvasForm.height" placeholder="请输入高度"></el-input>
                   </el-form-item>
                   <el-form-item label="背景颜色">
-                      <el-color-picker v-model="canvasForm.bg_color" />
+                      <ColorPickerWithRecent v-model="canvasForm.bg_color" />
                   </el-form-item>
                   <el-form-item label="多货号模式">
                     <el-radio-group v-model="canvasForm.multi_goods_mode">