美食家大橙子 发表于 3 天前

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

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

需求分析

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

https://i-blog.csdnimg.cn/direct/d543e0df729d4a92aee4ab59c21b025d.png
https://i-blog.csdnimg.cn/direct/ea1835f25c904013940c07b491e84f51.gif
https://i-blog.csdnimg.cn/direct/2724e102a44648ff852d0015b36101a2.gif
需求功能

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

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

// 主题色
const themeColors = ref([
"#FFFFFF",
"#000000",
"#E7E6E6",
"#44546A",
"#4874CB",
"#EE822F",
"#F2BA02",
"#75BD42",
"#30C0B4",
"#E54C5E",
]);
// 标准色 Standard color
const standardColors = ref([
"#C00000",
"#FF0000",
"#FFC000",
"#FFFF00",
"#92D050",
"#00B050",
"#00B0F0",
"#0070C0",
"#002060",
"#7030A0",
]);
// 主题渐变色
const gradientColors = ref([
["#F2F2F2", "#D9D9D9", "#BFBFBF", "#A6A6A6", "#808080"],
["#808080", "#595959", "#404040", "#262626", "#0D0D0D"],
["#D0CECE", "#AFABAB", "#767171", "#3B3838", "#181717"],
["#D6DCE5", "#ADB9CA", "#8497B0", "#333F50", "#222A35"],
["#DAE3F5", "#B6C7EA", "#91ACE0", "#2E54A1", "#1E386B"],
["#FCE6D5", "#F8CDAC", "#F5B482", "#C65F10", "#843F0B"],
["#FFF2CA", "#FEE695", "#FED961", "#B68C02", "#795D01"],
["#E3F2D9", "#C8E5B3", "#ACD78E", "#588E32", "#3B5F21"],
["#D4F4F2", "#A9E9E4", "#7DDFD7", "#249087", "#18605A"],
["#FADBDF", "#F5B7BF", "#EF949E", "#C81D31", "#851321"],
]);
进行子主组件的v-model实现

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 colorSelect = (val: any) => {
colorValue.value = val;
emit("update:modelValue", colorValue.value); // 更新父组件中的值
handleCancel();
};
子组件布局实现

使用el-popover套一层,然后遍历主题色标准色进行flex布局,假如弹窗组件使用了:visible="popconfirmVisible" 则需要配置trigger="focus"进行鼠标移出弹窗隐蔽弹窗。
<template>
<div class="color-picker-wrapper" ref="wrapperRef">
    <el-popover
      width="205px"
      ref="colorPopover"
      @confirm="handleConfirm"
      @cancel="handleCancel"
      v-model:visible="popconfirmVisible"
      trigger="focus"
    >
      <template #reference>
      <div @click="handleClick" class="color-box">
          <div
            class="color-pice"
            :style="{ backgroundColor: colorValue }"
          ></div>
      </div>
      </template>
      <div style="width: 180px; text-align: left">
      <div
          style="
            font-size: 12px;
            width: 100%;
            background-color: #f4f5f7;
            height: 20px;
            line-height: 20px;
            padding-left: 5px;
            margin-bottom: 5px;
            color: #444e63;
          "
      >
          主题颜色
      </div>
      <div
          style="height: 20px; display: flex; justify-content: space-between"
      >
          <div
            class="theme-color-item"
            v-for="(item, index) in themeColors"
            @click="colorSelect(item)"
            :key="index"
            :style="{ background: item }"
          ></div>
      </div>
      <div style="width: 100%; display: flex; justify-content: space-between">
          <div
            v-for="(item, index) in gradientColors"
            :key="index"
            style="
            height: 64px;
            width: 12px;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            "
          >
            <div
            v-for="(item1, index1) in item"
            :key="index1"
            @click="colorSelect(item1)"
            :style="{ background: item1 }"
            class="theme-color-block"
            ></div>
          </div>
      </div>
      </div>
      <div class="theme-color">标准色</div>
      <div style="height: 20px; display: flex; justify-content: space-between">
      <div
          class="theme-color-item"
          v-for="(item, index) in standardColors"
          :key="index"
          @click="colorSelect(item)"
          :style="{ background: item }"
      ></div>
      </div>
      <el-divider style="margin: 0; margin-top: 5px"></el-divider>

      <div class="colorPalette-box">
      <!-- <img :src="'../../assets/image/colorPalette.png':'../../assets/image/colorPalette.png'" style="width: 26px" /> -->
      <div class="colorPalette-text" @click="showColor" style="display: flex">
          <div style="" class="colorPalette-icon"></div>
          <div style="width: 90px">其他字体颜色...</div>
          <el-color-picker
            :teleported="false"
            v-model="colorValue"
            @change="colorChange"
            size="small"
          />
      </div>
      </div>
    </el-popover>
</div>
</template>
子组件样式实现

样式中,需要修改elementPlus的组件,其中最需要修改el-color-picker使其拉长得以点击其他字体颜色...的div块唤醒弹窗。

<style lang="scss" scoped>
.color-box:hover {
border-color: #409eff;
transition: 0.5s;
}
.color-box {
width: 22px;
height: 22px;
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.color-pice {
    width: 14px;
    height: 14px;
}
}
::v-deep .el-popconfirm__action {
margin-top: 0px !important;
text-align: left;
}
.theme-color-block {
width: 12px;
height: 12px;
cursor: pointer;
border: 1px solid #dcdfe6;
}
.theme-color-block:hover {
border: 1px solid #ffa800 !important;
transition: 0.5s !important;
}
.theme-color-item {
width: 12px;
height: 12px;
cursor: pointer;
border: 1px solid #dcdfe6;
}
.theme-color-item:hover {
border: 1px solid #ffa800;
transition: 0.5s;
}
.colorPalette-box {
display: flex;
font-size: 12px;
width: 100%;
cursor: pointer;
height: 26px;
line-height: 26px;
padding-left: 2px;
margin-bottom: 5px;
margin-top: 6px;
text-align: left;
color: #444e63;
}
.colorPalette-box:hover {
color: #165dff;
.colorPalette-icon {
    background-image: url("../../assets/image/colorPaletteActive.png");
}
}
.colorPalette-box:hover {
background: #f2f2f2;
transition: 0.3s;
}
.colorPalette-icon {
width: 26px;
height: 26px;
position: relative;
left: -5px;
background-image: url("../../assets/image/colorPalette.png");
background-repeat: repeat; /* 在两个方向上平铺背景图片 */
background-size: cover; /* 覆盖整个元素,可能会被裁剪以适应尺寸 */
background-position: center; /* 背景图片居中 */
}
.colorPalette-text {
position: relative;
margin-left: 4px;
}
.theme-color {
font-size: 12px;
width: 100%;
background-color: #f4f5f7;
height: 20px;
text-align: left;
line-height: 20px;
padding-left: 5px;
margin-bottom: 5px;
margin-top: 6px;
color: #444e63;
}
::v-deep .el-color-picker__panel {
position: absolute;
top: -0px !important;
left: 180px !important;
}
::v-deep .el-color-picker__trigger {
width: 179px;
border: none;
position: relative;
top: 0px;
left: -142px;
z-index: 10000;
}
::v-deep .el-color-picker__color {
display: none !important;
}
</style>
子组件全部代码:

<template>
<div class="color-picker-wrapper" ref="wrapperRef">
    <el-popover
      width="205px"
      ref="colorPopover"
      @confirm="handleConfirm"
      @cancel="handleCancel"
      v-model:visible="popconfirmVisible"
      trigger="focus"
    >
      <template #reference>
      <div @click="handleClick" class="color-box">
          <div
            class="color-pice"
            :style="{ backgroundColor: colorValue }"
          ></div>
      </div>
      </template>
      <div style="width: 180px; text-align: left">
      <div
          style="
            font-size: 12px;
            width: 100%;
            background-color: #f4f5f7;
            height: 20px;
            line-height: 20px;
            padding-left: 5px;
            margin-bottom: 5px;
            color: #444e63;
          "
      >
          主题颜色
      </div>
      <div
          style="height: 20px; display: flex; justify-content: space-between"
      >
          <div
            class="theme-color-item"
            v-for="(item, index) in themeColors"
            @click="colorSelect(item)"
            :key="index"
            :style="{ background: item }"
          ></div>
      </div>
      <div style="width: 100%; display: flex; justify-content: space-between">
          <div
            v-for="(item, index) in gradientColors"
            :key="index"
            style="
            height: 64px;
            width: 12px;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            "
          >
            <div
            v-for="(item1, index1) in item"
            :key="index1"
            @click="colorSelect(item1)"
            :style="{ background: item1 }"
            class="theme-color-block"
            ></div>
          </div>
      </div>
      </div>
      <div class="theme-color">标准色</div>
      <div style="height: 20px; display: flex; justify-content: space-between">
      <div
          class="theme-color-item"
          v-for="(item, index) in standardColors"
          :key="index"
          @click="colorSelect(item)"
          :style="{ background: item }"
      ></div>
      </div>
      <el-divider style="margin: 0; margin-top: 5px"></el-divider>

      <div class="colorPalette-box">
      <!-- <img :src="'../../assets/image/colorPalette.png':'../../assets/image/colorPalette.png'" style="width: 26px" /> -->
      <div class="colorPalette-text" @click="showColor" style="display: flex">
          <div style="" class="colorPalette-icon"></div>
          <div style="width: 90px">其他字体颜色...</div>
          <el-color-picker
            :teleported="false"
            v-model="colorValue"
            @change="colorChange"
            size="small"
          />
      </div>
      </div>
    </el-popover>
</div>
</template>
<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);// 主题色
const themeColors = ref([
"#FFFFFF",
"#000000",
"#E7E6E6",
"#44546A",
"#4874CB",
"#EE822F",
"#F2BA02",
"#75BD42",
"#30C0B4",
"#E54C5E",
]);
// 标准色 Standard color
const standardColors = ref([
"#C00000",
"#FF0000",
"#FFC000",
"#FFFF00",
"#92D050",
"#00B050",
"#00B0F0",
"#0070C0",
"#002060",
"#7030A0",
]);
// 主题渐变色
const gradientColors = ref([
["#F2F2F2", "#D9D9D9", "#BFBFBF", "#A6A6A6", "#808080"],
["#808080", "#595959", "#404040", "#262626", "#0D0D0D"],
["#D0CECE", "#AFABAB", "#767171", "#3B3838", "#181717"],
["#D6DCE5", "#ADB9CA", "#8497B0", "#333F50", "#222A35"],
["#DAE3F5", "#B6C7EA", "#91ACE0", "#2E54A1", "#1E386B"],
["#FCE6D5", "#F8CDAC", "#F5B482", "#C65F10", "#843F0B"],
["#FFF2CA", "#FEE695", "#FED961", "#B68C02", "#795D01"],
["#E3F2D9", "#C8E5B3", "#ACD78E", "#588E32", "#3B5F21"],
["#D4F4F2", "#A9E9E4", "#7DDFD7", "#249087", "#18605A"],
["#FADBDF", "#F5B7BF", "#EF949E", "#C81D31", "#851321"],
]);
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>
<style lang="scss" scoped>
.color-box:hover {
border-color: #409eff;
transition: 0.5s;
}
.color-box {
width: 22px;
height: 22px;
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.color-pice {
    width: 14px;
    height: 14px;
}
}
::v-deep .el-popconfirm__action {
margin-top: 0px !important;
text-align: left;
}
.theme-color-block {
width: 12px;
height: 12px;
cursor: pointer;
border: 1px solid #dcdfe6;
}
.theme-color-block:hover {
border: 1px solid #ffa800 !important;
transition: 0.5s !important;
}
.theme-color-item {
width: 12px;
height: 12px;
cursor: pointer;
border: 1px solid #dcdfe6;
}
.theme-color-item:hover {
border: 1px solid #ffa800;
transition: 0.5s;
}
.colorPalette-box {
display: flex;
font-size: 12px;
width: 100%;
cursor: pointer;
height: 26px;
line-height: 26px;
padding-left: 2px;
margin-bottom: 5px;
margin-top: 6px;
text-align: left;
color: #444e63;
}
.colorPalette-box:hover {
color: #165dff;
.colorPalette-icon {
    background-image: url("../../assets/image/colorPaletteActive.png");
}
}
.colorPalette-box:hover {
background: #f2f2f2;
transition: 0.3s;
}
.colorPalette-icon {
width: 26px;
height: 26px;
position: relative;
left: -5px;
background-image: url("../../assets/image/colorPalette.png");
background-repeat: repeat; /* 在两个方向上平铺背景图片 */
background-size: cover; /* 覆盖整个元素,可能会被裁剪以适应尺寸 */
background-position: center; /* 背景图片居中 */
}
.colorPalette-text {
position: relative;
margin-left: 4px;
}
.theme-color {
font-size: 12px;
width: 100%;
background-color: #f4f5f7;
height: 20px;
text-align: left;
line-height: 20px;
padding-left: 5px;
margin-bottom: 5px;
margin-top: 6px;
color: #444e63;
}
::v-deep .el-color-picker__panel {
position: absolute;
top: -0px !important;
left: 180px !important;
}
::v-deep .el-color-picker__trigger {
width: 179px;
border: none;
position: relative;
top: 0px;
left: -142px;
z-index: 10000;
}
::v-deep .el-color-picker__color {
display: none !important;
}
</style>
父组件调用方式

<template>
        ColourSle v-model="selectedColor"></ColourSle>
</template><
<script lang="ts" setup>
const ColourSle = defineAsyncComponent(
    () => import("../../components/colourSle/index.vue")
);
const selectedColor = ref("#00FF00");
</script>
使用结果:
https://i-blog.csdnimg.cn/direct/55e44f5c8d4545518e2d884d4e17533d.gif
后续代码待优化,优化后会更换当前代码。
完结


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Vue3实现高仿word自定义颜色选择器组件(支持 v-model)