Vue3实现高仿word自定义颜色选择器组件(支持 v-model)

打印 上一主题 下一主题

主题 1883|帖子 1883|积分 5649

Vue3实现高仿word自定义颜色选择器组件(支持 v-model)

需求分析

近来接到一个需求,前端拿到AI组通过检测算法得到的瑕疵数据,为了标识每个瑕疵,通过设置瑕疵的颜色进行标志,产品要求仿照word的颜色设置来进行设置取色器。
大抵结果




需求功能

   

  • 颜色选择:用户可以通过点击预定义的颜色块或使用颜色选择器选择颜色。
  • 双向绑定:子组件支持 v-model,可以与父组件进行双向数据绑定。
  • 弹出式计划:颜色选择器以弹窗形式展示,提供更好的用户体验。
  • 可以选择默认主题色,标准色,及其自定义其他字体颜色。
  • 样式优化:通过 SCSS 样式美化组件。
  实现所需技能

   

  • vue3+elementPlus。
  • elementPlus的Popconfirm 气泡确认框。
  • elementPlus的ColorPicker 颜色选择器。
  从UI哪里拿到主题颜色标准色

  1. // 主题色
  2. const themeColors = ref([
  3.   "#FFFFFF",
  4.   "#000000",
  5.   "#E7E6E6",
  6.   "#44546A",
  7.   "#4874CB",
  8.   "#EE822F",
  9.   "#F2BA02",
  10.   "#75BD42",
  11.   "#30C0B4",
  12.   "#E54C5E",
  13. ]);
  14. // 标准色 Standard color
  15. const standardColors = ref([
  16.   "#C00000",
  17.   "#FF0000",
  18.   "#FFC000",
  19.   "#FFFF00",
  20.   "#92D050",
  21.   "#00B050",
  22.   "#00B0F0",
  23.   "#0070C0",
  24.   "#002060",
  25.   "#7030A0",
  26. ]);
  27. // 主题渐变色
  28. const gradientColors = ref([
  29.   ["#F2F2F2", "#D9D9D9", "#BFBFBF", "#A6A6A6", "#808080"],
  30.   ["#808080", "#595959", "#404040", "#262626", "#0D0D0D"],
  31.   ["#D0CECE", "#AFABAB", "#767171", "#3B3838", "#181717"],
  32.   ["#D6DCE5", "#ADB9CA", "#8497B0", "#333F50", "#222A35"],
  33.   ["#DAE3F5", "#B6C7EA", "#91ACE0", "#2E54A1", "#1E386B"],
  34.   ["#FCE6D5", "#F8CDAC", "#F5B482", "#C65F10", "#843F0B"],
  35.   ["#FFF2CA", "#FEE695", "#FED961", "#B68C02", "#795D01"],
  36.   ["#E3F2D9", "#C8E5B3", "#ACD78E", "#588E32", "#3B5F21"],
  37.   ["#D4F4F2", "#A9E9E4", "#7DDFD7", "#249087", "#18605A"],
  38.   ["#FADBDF", "#F5B7BF", "#EF949E", "#C81D31", "#851321"],
  39. ]);
复制代码
进行子主组件的v-model实现

  1. const emit = defineEmits(["update:modelValue"]);
  2. const props = defineProps({
  3.   modelValue: {
  4.     type: String,
  5.     default: "#FFFFFF",
  6.   },
  7. });
  8. watch(
  9.   () => props.modelValue,
  10.   (newVal) => {
  11.     // console.log("modelValue changed:", newVal);
  12.     colorValue.value = newVal; // 更新子组件的内部状态
  13.   }
  14. );
  15. const colorSelect = (val: any) => {
  16.   colorValue.value = val;
  17.   emit("update:modelValue", colorValue.value); // 更新父组件中的值
  18.   handleCancel();
  19. };
复制代码
子组件布局实现

使用el-popover套一层,然后遍历主题色标准色进行flex布局,假如弹窗组件使用了:visible="popconfirmVisible" 则需要配置trigger="focus"进行鼠标移出弹窗隐蔽弹窗。
  1. <template>
  2.   <div class="color-picker-wrapper" ref="wrapperRef">
  3.     <el-popover
  4.       width="205px"
  5.       ref="colorPopover"
  6.       @confirm="handleConfirm"
  7.       @cancel="handleCancel"
  8.       v-model:visible="popconfirmVisible"
  9.       trigger="focus"
  10.     >
  11.       <template #reference>
  12.         <div @click="handleClick" class="color-box">
  13.           <div
  14.             class="color-pice"
  15.             :style="{ backgroundColor: colorValue }"
  16.           ></div>
  17.         </div>
  18.       </template>
  19.       <div style="width: 180px; text-align: left">
  20.         <div
  21.           style="
  22.             font-size: 12px;
  23.             width: 100%;
  24.             background-color: #f4f5f7;
  25.             height: 20px;
  26.             line-height: 20px;
  27.             padding-left: 5px;
  28.             margin-bottom: 5px;
  29.             color: #444e63;
  30.           "
  31.         >
  32.           主题颜色
  33.         </div>
  34.         <div
  35.           style="height: 20px; display: flex; justify-content: space-between"
  36.         >
  37.           <div
  38.             class="theme-color-item"
  39.             v-for="(item, index) in themeColors"
  40.             @click="colorSelect(item)"
  41.             :key="index"
  42.             :style="{ background: item }"
  43.           ></div>
  44.         </div>
  45.         <div style="width: 100%; display: flex; justify-content: space-between">
  46.           <div
  47.             v-for="(item, index) in gradientColors"
  48.             :key="index"
  49.             style="
  50.               height: 64px;
  51.               width: 12px;
  52.               display: flex;
  53.               flex-direction: column;
  54.               justify-content: space-between;
  55.             "
  56.           >
  57.             <div
  58.               v-for="(item1, index1) in item"
  59.               :key="index1"
  60.               @click="colorSelect(item1)"
  61.               :style="{ background: item1 }"
  62.               class="theme-color-block"
  63.             ></div>
  64.           </div>
  65.         </div>
  66.       </div>
  67.       <div class="theme-color">标准色</div>
  68.       <div style="height: 20px; display: flex; justify-content: space-between">
  69.         <div
  70.           class="theme-color-item"
  71.           v-for="(item, index) in standardColors"
  72.           :key="index"
  73.           @click="colorSelect(item)"
  74.           :style="{ background: item }"
  75.         ></div>
  76.       </div>
  77.       <el-divider style="margin: 0; margin-top: 5px"></el-divider>
  78.       <div class="colorPalette-box">
  79.         <!-- <img :src="'../../assets/image/colorPalette.png':'../../assets/image/colorPalette.png'" style="width: 26px" /> -->
  80.         <div class="colorPalette-text" @click="showColor" style="display: flex">
  81.           <div style="" class="colorPalette-icon"></div>
  82.           <div style="width: 90px">其他字体颜色...</div>
  83.           <el-color-picker
  84.             :teleported="false"
  85.             v-model="colorValue"
  86.             @change="colorChange"
  87.             size="small"
  88.           />
  89.         </div>
  90.       </div>
  91.     </el-popover>
  92.   </div>
  93. </template>
复制代码
子组件样式实现

样式中,需要修改elementPlus的组件,其中最需要修改el-color-picker使其拉长得以点击其他字体颜色...的div块唤醒弹窗。
  1. <style lang="scss" scoped>
  2. .color-box:hover {
  3.   border-color: #409eff;
  4.   transition: 0.5s;
  5. }
  6. .color-box {
  7.   width: 22px;
  8.   height: 22px;
  9.   background-color: #fff;
  10.   border: 1px solid #dcdfe6;
  11.   border-radius: 2px;
  12.   display: flex;
  13.   justify-content: center;
  14.   align-items: center;
  15.   cursor: pointer;
  16.   .color-pice {
  17.     width: 14px;
  18.     height: 14px;
  19.   }
  20. }
  21. ::v-deep .el-popconfirm__action {
  22.   margin-top: 0px !important;
  23.   text-align: left;
  24. }
  25. .theme-color-block {
  26.   width: 12px;
  27.   height: 12px;
  28.   cursor: pointer;
  29.   border: 1px solid #dcdfe6;
  30. }
  31. .theme-color-block:hover {
  32.   border: 1px solid #ffa800 !important;
  33.   transition: 0.5s !important;
  34. }
  35. .theme-color-item {
  36.   width: 12px;
  37.   height: 12px;
  38.   cursor: pointer;
  39.   border: 1px solid #dcdfe6;
  40. }
  41. .theme-color-item:hover {
  42.   border: 1px solid #ffa800;
  43.   transition: 0.5s;
  44. }
  45. .colorPalette-box {
  46.   display: flex;
  47.   font-size: 12px;
  48.   width: 100%;
  49.   cursor: pointer;
  50.   height: 26px;
  51.   line-height: 26px;
  52.   padding-left: 2px;
  53.   margin-bottom: 5px;
  54.   margin-top: 6px;
  55.   text-align: left;
  56.   color: #444e63;
  57. }
  58. .colorPalette-box:hover {
  59.   color: #165dff;
  60.   .colorPalette-icon {
  61.     background-image: url("../../assets/image/colorPaletteActive.png");
  62.   }
  63. }
  64. .colorPalette-box:hover {
  65.   background: #f2f2f2;
  66.   transition: 0.3s;
  67. }
  68. .colorPalette-icon {
  69.   width: 26px;
  70.   height: 26px;
  71.   position: relative;
  72.   left: -5px;
  73.   background-image: url("../../assets/image/colorPalette.png");
  74.   background-repeat: repeat; /* 在两个方向上平铺背景图片 */
  75.   background-size: cover; /* 覆盖整个元素,可能会被裁剪以适应尺寸 */
  76.   background-position: center; /* 背景图片居中 */
  77. }
  78. .colorPalette-text {
  79.   position: relative;
  80.   margin-left: 4px;
  81. }
  82. .theme-color {
  83.   font-size: 12px;
  84.   width: 100%;
  85.   background-color: #f4f5f7;
  86.   height: 20px;
  87.   text-align: left;
  88.   line-height: 20px;
  89.   padding-left: 5px;
  90.   margin-bottom: 5px;
  91.   margin-top: 6px;
  92.   color: #444e63;
  93. }
  94. ::v-deep .el-color-picker__panel {
  95.   position: absolute;
  96.   top: -0px !important;
  97.   left: 180px !important;
  98. }
  99. ::v-deep .el-color-picker__trigger {
  100.   width: 179px;
  101.   border: none;
  102.   position: relative;
  103.   top: 0px;
  104.   left: -142px;
  105.   z-index: 10000;
  106. }
  107. ::v-deep .el-color-picker__color {
  108.   display: none !important;
  109. }
  110. </style>
复制代码
子组件全部代码:

  1. <template>
  2.   <div class="color-picker-wrapper" ref="wrapperRef">
  3.     <el-popover
  4.       width="205px"
  5.       ref="colorPopover"
  6.       @confirm="handleConfirm"
  7.       @cancel="handleCancel"
  8.       v-model:visible="popconfirmVisible"
  9.       trigger="focus"
  10.     >
  11.       <template #reference>
  12.         <div @click="handleClick" class="color-box">
  13.           <div
  14.             class="color-pice"
  15.             :style="{ backgroundColor: colorValue }"
  16.           ></div>
  17.         </div>
  18.       </template>
  19.       <div style="width: 180px; text-align: left">
  20.         <div
  21.           style="
  22.             font-size: 12px;
  23.             width: 100%;
  24.             background-color: #f4f5f7;
  25.             height: 20px;
  26.             line-height: 20px;
  27.             padding-left: 5px;
  28.             margin-bottom: 5px;
  29.             color: #444e63;
  30.           "
  31.         >
  32.           主题颜色
  33.         </div>
  34.         <div
  35.           style="height: 20px; display: flex; justify-content: space-between"
  36.         >
  37.           <div
  38.             class="theme-color-item"
  39.             v-for="(item, index) in themeColors"
  40.             @click="colorSelect(item)"
  41.             :key="index"
  42.             :style="{ background: item }"
  43.           ></div>
  44.         </div>
  45.         <div style="width: 100%; display: flex; justify-content: space-between">
  46.           <div
  47.             v-for="(item, index) in gradientColors"
  48.             :key="index"
  49.             style="
  50.               height: 64px;
  51.               width: 12px;
  52.               display: flex;
  53.               flex-direction: column;
  54.               justify-content: space-between;
  55.             "
  56.           >
  57.             <div
  58.               v-for="(item1, index1) in item"
  59.               :key="index1"
  60.               @click="colorSelect(item1)"
  61.               :style="{ background: item1 }"
  62.               class="theme-color-block"
  63.             ></div>
  64.           </div>
  65.         </div>
  66.       </div>
  67.       <div class="theme-color">标准色</div>
  68.       <div style="height: 20px; display: flex; justify-content: space-between">
  69.         <div
  70.           class="theme-color-item"
  71.           v-for="(item, index) in standardColors"
  72.           :key="index"
  73.           @click="colorSelect(item)"
  74.           :style="{ background: item }"
  75.         ></div>
  76.       </div>
  77.       <el-divider style="margin: 0; margin-top: 5px"></el-divider>
  78.       <div class="colorPalette-box">
  79.         <!-- <img :src="'../../assets/image/colorPalette.png':'../../assets/image/colorPalette.png'" style="width: 26px" /> -->
  80.         <div class="colorPalette-text" @click="showColor" style="display: flex">
  81.           <div style="" class="colorPalette-icon"></div>
  82.           <div style="width: 90px">其他字体颜色...</div>
  83.           <el-color-picker
  84.             :teleported="false"
  85.             v-model="colorValue"
  86.             @change="colorChange"
  87.             size="small"
  88.           />
  89.         </div>
  90.       </div>
  91.     </el-popover>
  92.   </div>
  93. </template>
  94. <script setup lang="ts">import { onUnmounted, ref, watch } from "vue";const emit = defineEmits(["update:modelValue"]);const props = defineProps({  modelValue: {    type: String,    default: "#FFFFFF",  },});watch(  () => props.modelValue,  (newVal) => {    // console.log("modelValue changed:", newVal);    colorValue.value = newVal; // 更新子组件的内部状态  });const colorValue = ref(props.modelValue);const colorPopover: any = ref(null);// 主题色
  95. const themeColors = ref([
  96.   "#FFFFFF",
  97.   "#000000",
  98.   "#E7E6E6",
  99.   "#44546A",
  100.   "#4874CB",
  101.   "#EE822F",
  102.   "#F2BA02",
  103.   "#75BD42",
  104.   "#30C0B4",
  105.   "#E54C5E",
  106. ]);
  107. // 标准色 Standard color
  108. const standardColors = ref([
  109.   "#C00000",
  110.   "#FF0000",
  111.   "#FFC000",
  112.   "#FFFF00",
  113.   "#92D050",
  114.   "#00B050",
  115.   "#00B0F0",
  116.   "#0070C0",
  117.   "#002060",
  118.   "#7030A0",
  119. ]);
  120. // 主题渐变色
  121. const gradientColors = ref([
  122.   ["#F2F2F2", "#D9D9D9", "#BFBFBF", "#A6A6A6", "#808080"],
  123.   ["#808080", "#595959", "#404040", "#262626", "#0D0D0D"],
  124.   ["#D0CECE", "#AFABAB", "#767171", "#3B3838", "#181717"],
  125.   ["#D6DCE5", "#ADB9CA", "#8497B0", "#333F50", "#222A35"],
  126.   ["#DAE3F5", "#B6C7EA", "#91ACE0", "#2E54A1", "#1E386B"],
  127.   ["#FCE6D5", "#F8CDAC", "#F5B482", "#C65F10", "#843F0B"],
  128.   ["#FFF2CA", "#FEE695", "#FED961", "#B68C02", "#795D01"],
  129.   ["#E3F2D9", "#C8E5B3", "#ACD78E", "#588E32", "#3B5F21"],
  130.   ["#D4F4F2", "#A9E9E4", "#7DDFD7", "#249087", "#18605A"],
  131.   ["#FADBDF", "#F5B7BF", "#EF949E", "#C81D31", "#851321"],
  132. ]);
  133. const popconfirmVisible = ref(false);const showColor = () => {};const colorChange = (val: any) => {  emit("update:modelValue", colorValue.value); // 更新父组件中的值  handleCancel();};const handleClick = () => {  popconfirmVisible.value = true; // 显示 Popconfirm};const handleConfirm = () => {  popconfirmVisible.value = false; // 隐蔽 Popconfirm};const colorSelect = (val: any) => {  colorValue.value = val;  emit("update:modelValue", colorValue.value); // 更新父组件中的值  handleCancel();};const handleCancel = () => {  popconfirmVisible.value = false; // 隐蔽 Popconfirm};const clicked = ref(false);</script>
  134. <style lang="scss" scoped>
  135. .color-box:hover {
  136.   border-color: #409eff;
  137.   transition: 0.5s;
  138. }
  139. .color-box {
  140.   width: 22px;
  141.   height: 22px;
  142.   background-color: #fff;
  143.   border: 1px solid #dcdfe6;
  144.   border-radius: 2px;
  145.   display: flex;
  146.   justify-content: center;
  147.   align-items: center;
  148.   cursor: pointer;
  149.   .color-pice {
  150.     width: 14px;
  151.     height: 14px;
  152.   }
  153. }
  154. ::v-deep .el-popconfirm__action {
  155.   margin-top: 0px !important;
  156.   text-align: left;
  157. }
  158. .theme-color-block {
  159.   width: 12px;
  160.   height: 12px;
  161.   cursor: pointer;
  162.   border: 1px solid #dcdfe6;
  163. }
  164. .theme-color-block:hover {
  165.   border: 1px solid #ffa800 !important;
  166.   transition: 0.5s !important;
  167. }
  168. .theme-color-item {
  169.   width: 12px;
  170.   height: 12px;
  171.   cursor: pointer;
  172.   border: 1px solid #dcdfe6;
  173. }
  174. .theme-color-item:hover {
  175.   border: 1px solid #ffa800;
  176.   transition: 0.5s;
  177. }
  178. .colorPalette-box {
  179.   display: flex;
  180.   font-size: 12px;
  181.   width: 100%;
  182.   cursor: pointer;
  183.   height: 26px;
  184.   line-height: 26px;
  185.   padding-left: 2px;
  186.   margin-bottom: 5px;
  187.   margin-top: 6px;
  188.   text-align: left;
  189.   color: #444e63;
  190. }
  191. .colorPalette-box:hover {
  192.   color: #165dff;
  193.   .colorPalette-icon {
  194.     background-image: url("../../assets/image/colorPaletteActive.png");
  195.   }
  196. }
  197. .colorPalette-box:hover {
  198.   background: #f2f2f2;
  199.   transition: 0.3s;
  200. }
  201. .colorPalette-icon {
  202.   width: 26px;
  203.   height: 26px;
  204.   position: relative;
  205.   left: -5px;
  206.   background-image: url("../../assets/image/colorPalette.png");
  207.   background-repeat: repeat; /* 在两个方向上平铺背景图片 */
  208.   background-size: cover; /* 覆盖整个元素,可能会被裁剪以适应尺寸 */
  209.   background-position: center; /* 背景图片居中 */
  210. }
  211. .colorPalette-text {
  212.   position: relative;
  213.   margin-left: 4px;
  214. }
  215. .theme-color {
  216.   font-size: 12px;
  217.   width: 100%;
  218.   background-color: #f4f5f7;
  219.   height: 20px;
  220.   text-align: left;
  221.   line-height: 20px;
  222.   padding-left: 5px;
  223.   margin-bottom: 5px;
  224.   margin-top: 6px;
  225.   color: #444e63;
  226. }
  227. ::v-deep .el-color-picker__panel {
  228.   position: absolute;
  229.   top: -0px !important;
  230.   left: 180px !important;
  231. }
  232. ::v-deep .el-color-picker__trigger {
  233.   width: 179px;
  234.   border: none;
  235.   position: relative;
  236.   top: 0px;
  237.   left: -142px;
  238.   z-index: 10000;
  239. }
  240. ::v-deep .el-color-picker__color {
  241.   display: none !important;
  242. }
  243. </style>
复制代码
父组件调用方式

  1. <template>
  2.         ColourSle v-model="selectedColor"></ColourSle>
  3. </template><
  4. <script lang="ts" setup>
  5.   const ColourSle = defineAsyncComponent(
  6.     () => import("../../components/colourSle/index.vue")
  7.   );
  8.   const selectedColor = ref("#00FF00");
  9. </script>
复制代码
使用结果:

后续代码待优化,优化后会更换当前代码。
完结


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

美食家大橙子

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表