news 2026/4/18 3:39:52

Vben Form使用 vue3+ant-design-vue+ts封装的自定义 Vue3 颜色选择器组件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vben Form使用 vue3+ant-design-vue+ts封装的自定义 Vue3 颜色选择器组件



项目结构:
废话不多说:上代码
index.vue

<script setup lang="ts">import{computed,useAttrs}from'vue';importcolorPickerfrom'./src/main.vue';import{camelCase}from'lodash-es';interfaceProps{value:string;}constprops=withDefaults(defineProps<Props>(),{value:'',});constemit=defineEmits(['change']);constselectColorPicker=(color:string)=>{emit('change',color);};constcolorsDefault=['#ff7e79','#fefe7f','#00ff81','#007ffe','#ff80c0','#ff0104','#00fcff','#847cc2','#fe00fe','#7e0101','#fc7f01','#027e04','#65b2f3','#f9b714','#068081','#8305a1','#b0cf29','#0bfa49','#409EFF','#304156',];constattrs=useAttrs();/** * 获取透传的事件 通过v-on绑定 * 可绑定的事件 https://www.tiny.cloud/docs/tinymce/latest/vue-ref/#event-binding */constevents=computed(()=>{constonEvents:Record<string,any>={};for(constkeyinattrs){if(key.startsWith('on')){consteventKey=camelCase(key.split('on')[1]!);onEvents[eventKey]=attrs[key];}}returnonEvents;});</script><template><colorPickerclass="ant3-color-picker":modelValue="props.value"v-on="events":predefine="colorsDefault"placement="auto-bottom"@change="selectColorPicker"@active-change="selectColorPicker"/></template>

index.ts

export{defaultasColorPicker}from'./index.vue';

main.vue

<script lang="ts">importtype{PropType}from'vue';importtype{ComponentSize}from'./type/types';importtype{IUseOptions}from'./useOptions';import{computed,defineComponent,nextTick,onMounted,provide,reactive,ref,watch}from'vue';import{CloseOutlined,DownOutlined}from'@ant-design/icons-vue';import{Popover,Input}from'ant-design-vue';importdebouncefrom'lodash/debounce';importAlphaSliderfrom'./components/alphaSlider.vue';importHueSliderfrom'./components/hueSlider.vue';importpreDefinefrom'./components/preDefine.vue';importSvPanelfrom'./components/svPanel.vue';import{isValidComponentSize}from'./lib//validators';importColorfrom'./lib/color';import{OPTIONS_KEY}from'./useOptions';constUPDATE_MODEL_EVENT='update:modelValue';exportdefaultdefineComponent({name:'ColorPicker',components:{[Popover.name]:Popover,HueSlider,SvPanel,PreDefine:preDefine,AlphaSlider,DownOutlined,CloseOutlined,Input,},props:{modelValue:{type:String,},showAlpha:{type:Boolean,},colorFormat:{type:String,default:'',},popperClass:{type:String,},predefine:{type:Array,default:()=>[],},disabled:{type:Boolean,default:false,},size:{type:StringasPropType<ComponentSize>,validator:isValidComponentSize,},},emits:['active-change','change',UPDATE_MODEL_EVENT],setup(props,{emit}){// 区分popover的关闭是通过点击body关闭的还是通过按钮、输入框关闭的consthasClickBtn=ref<boolean>(false);consthue=ref<any>(null);constsvPanel=ref<any>(null);constalpha=ref<any>(null);// dataconstcolor=reactive(newColor({enableAlpha:props.showAlpha,format:props.colorFormat,}));constshowPicker=ref(false);constshowPanelColor=ref(false);constcustomInput=ref('');// computedconstdisplayedColor=computed(()=>{if(!props.modelValue&&!showPanelColor.value){return'transparent';}returndisplayedRgb(color,props.showAlpha);});constcolorSize=computed(()=>{returnprops.size;});constcolorDisabled=computed(()=>{returnprops.disabled;});constcurrentColor=computed(()=>{return!props.modelValue&&!showPanelColor.value?'':color.value;});// watchwatch(()=>props.modelValue,(newVal)=>{if(!newVal){showPanelColor.value=false;}elseif(newVal&&newVal!==color.value){color.fromString(newVal);}});watch(()=>currentColor.value,(val)=>{customInput.value=val;emit('active-change',val);});watch(()=>color.value,()=>{if(!props.modelValue&&!showPanelColor.value){showPanelColor.value=true;}});watch(()=>showPicker.value,(newVal)=>{if(newVal){hasClickBtn.value=false;}else{if(!hasClickBtn.value){hide();}}nextTick(()=>{setTimeout(()=>{hue.value?.update();svPanel.value?.update();alpha.value?.update();},10);});});// methodsfunctiondisplayedRgb(color,showAlpha){if(!(colorinstanceofColor)){thrownewTypeError('color should be instance of _color Class');}const{r,g,b}=color.toRgb();returnshowAlpha?`rgba(${r},${g},${b},${color.get('alpha')/100})`:`rgb(${r},${g},${b})`;}functionsetShowPicker(value){showPicker.value=value;}constdebounceSetShowPicker=debounce(setShowPicker,50);functionhide(){debounceSetShowPicker(false);resetColor();}functionresetColor(){nextTick(()=>{if(props.modelValue){color.fromString(props.modelValue);}else{showPanelColor.value=false;}});}functionhandleTrigger(){if(colorDisabled.value)return;debounceSetShowPicker(!showPicker.value);}functionhandleConfirm(value:string){color.fromString(value.target.value);currentColor.value=value.target.value;emit(UPDATE_MODEL_EVENT,color.value);emit('change',color.value);}functionconfirmValue(){hasClickBtn.value=true;constvalue=color.value;emit(UPDATE_MODEL_EVENT,value);emit('change',value);debounceSetShowPicker(false);nextTick(()=>{constnewColor=newColor({enableAlpha:props.showAlpha,format:props.colorFormat,});newColor.fromString(props.modelValue);if(!color.compare(newColor)){resetColor();}});showPicker.value=false;}functionclear(){hasClickBtn.value=true;debounceSetShowPicker(false);emit(UPDATE_MODEL_EVENT,props.modelValue||null);emit('change',props.modelValue||null);resetColor();showPicker.value=false;}functiongetPopupContainer(triggerNode:HTMLElement){returntriggerNode.parentElement;}onMounted(()=>{if(props.modelValue){color.fromString(props.modelValue);customInput.value=currentColor.value;}});provide<IUseOptions>(OPTIONS_KEY,{currentColor,});return{showPicker,color:colorasColor,customInput,displayedColor,colorSize,colorDisabled,svPanel,hue,alpha,showPanelColor,getPopupContainer,handleTrigger,handleConfirm,clear,confirmValue,};},});</script><template><a-popover v-model:open="showPicker":get-popup-container="getPopupContainer"trigger="click"class="ant3-color-picker":class="[colorSize ? `ant3-color-picker--${colorSize}` : '']"v-bind="$attrs"><template #contentclass="ant-color-dropdown"><divclass="ant-color-dropdown__main-wrapper"><SvPanel ref="svPanel":color="color"/><HueSlider ref="hue"class="hue-slider":color="color"vertical/></div><AlphaSlider v-if="showAlpha"ref="alpha":color="color"/><PreDefine v-if="predefine.length > 0"ref="predefine":color="color":colors="predefine"/><divclass="ant-color-dropdown__btns"><divclass="ant-color-dropdown__value"><Input v-model:value="customInput":default-value="modelValue"placeholder="请输入颜色HEX值"allow-clear size="small"@change="handleConfirm"@blur="handleConfirm"@pressEnter="handleConfirm"@keyup.enter.native="handleConfirm"/></div><divclass="ant-dropdown__btns"><a-button size="small"class="ant-cancel-button"@click="clear">取消</a-button><a-buttontype="primary"size="small"@click="confirmValue">确定</a-button></div></div></template><divclass="ant-color-picker__trigger"@click="handleTrigger"><spanclass="el-color-picker__color":class="{ 'is-alpha': showAlpha }"><spanclass="el-color-picker__color-inner":style="{backgroundColor:displayedColor,}"><DownOutlined v-show="modelValue || showPanelColor"style="color: #909399"/><CloseOutlined v-if="!modelValue && !showPanelColor"style="color: #909399"/></span></span></div></a-popover></template><style>.ant3-color-picker+div.ant-popover{position:fixed!important;}</style><style lang="scss"scoped>.ant3-color-picker{position:relative;clear:both;box-sizing:border-box;display:inline-block;width:36px;height:36px;padding:4px;font-size:0;cursor:pointer;border:1px solid #e6e6e6;border-radius:4px;&.is-disabled{color:#c0c4cc;cursor:not-allowed;background-color:#fff;}.el-color-picker__color{position:relative;box-sizing:border-box;display:block;width:100%;height:100%;text-align:center;border:1px solid #909399;border-radius:2px;&.is-alpha{background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==');}}.el-color-picker__color-inner{position:absolute;inset:0;font-size:12px;line-height:28px;}}.ant-color-dropdown__main-wrapper{display:flex;height:180px;margin-bottom:8px;}.ant-color-dropdown__btns{display:flex;justify-content:space-between;margin-top:6px;text-align:right;}.ant-color-dropdown__value{font-size:12px;color:#000;}.ant-cancel-button{margin-right:6px;}// large 大小.ant3-color-picker--large{width:40px;height:40px;}//small 大小.ant3-color-picker--small{width:32px;height:32px;}</style>

useOptions.ts

import{inject}from'vue';importtype{ComputedRef}from'vue';exportinterfaceIUseOptions{currentColor:ComputedRef<String>;}exportconstOPTIONS_KEY=Symbol();exportconstuseOptions=()=>{returninject<IUseOptions>(OPTIONS_KEY);};

types.ts

exportinterfaceOptions{enableAlpha:boolean;format:string;value?:string;}exporttypeNullable<T>=T|null;exporttypeComponentSize='large'|'default'|'small';

style.scss

$namespace:'ant';$ant-separator:'__';$state-prefix:'is-';

lib下面color.ts

import{hasOwn}from'@vue/shared';importtype{Options}from'../type/types';consthsv2hsl=function(hue:number,sat:number,val:number){return[hue,(sat*val)/((hue=(2-sat)*val)<1?hue:2-hue)||0,hue/2];};constisOnePointZero=function(n:unknown){returntypeofn==='string'&&n.indexOf('.')!==-1&&parseFloat(n)===1;};constisPercentage=function(n:unknown){returntypeofn==='string'&&n.indexOf('%')!==-1;};constbound01=function(value:number|string,max:number|string){if(isOnePointZero(value))value='100%';constprocessPercent=isPercentage(value);value=Math.min(maxasnumber,Math.max(0,parseFloat(`${value}`)));if(processPercent){value=parseInt(`${value*(maxasnumber)}`,10)/100;}if(Math.abs(value-(maxasnumber))<0.000001){return1;}return(value%(maxasnumber))/parseFloat(maxasstring);};constINT_HEX_MAP={10:'A',11:'B',12:'C',13:'D',14:'E',15:'F'};consthexOne=function(value:number){value=Math.min(Math.round(value),255);consthigh=Math.floor(value/16);constlow=value%16;return`${INT_HEX_MAP[high]||high}${INT_HEX_MAP[low]||low}`;};consttoHex=function({r,g,b}){if(isNaN(r)||isNaN(g)||isNaN(b))return'';return`#${hexOne(r)}${hexOne(g)}${hexOne(b)}`;};constHEX_INT_MAP={A:10,B:11,C:12,D:13,E:14,F:15};constparseHexChannel=function(hex:string){if(hex.length===2){return((HEX_INT_MAP[hex[0].toUpperCase()]||+hex[0])*16+(HEX_INT_MAP[hex[1].toUpperCase()]||+hex[1]));}returnHEX_INT_MAP[hex[1].toUpperCase()]||+hex[1];};consthsl2hsv=function(hue:number,sat:number,light:number){sat=sat/100;light=light/100;letsmin=sat;constlmin=Math.max(light,0.01);// let sv// let vlight*=2;sat*=light<=1?light:2-light;smin*=lmin<=1?lmin:2-lmin;constv=(light+sat)/2;constsv=light===0?(2*smin)/(lmin+smin):(2*sat)/(light+sat);return{h:hue,s:sv*100,v:v*100,};};constrgb2hsv=function(r,g,b){r=bound01(r,255);g=bound01(g,255);b=bound01(b,255);constmax=Math.max(r,g,b);constmin=Math.min(r,g,b);leth;constv=max;constd=max-min;consts=max===0?0:d/max;if(max===min){h=0;// achromatic}else{switch(max){caser:{h=(g-b)/d+(g<b?6:0);break;}caseg:{h=(b-r)/d+2;break;}caseb:{h=(r-g)/d+4;break;}}h/=6;}return{h:h*360,s:s*100,v:v*100};};consthsv2rgb=function(h,s,v){h=bound01(h,360)*6;s=bound01(s,100);v=bound01(v,100);consti=Math.floor(h);constf=h-i;constp=v*(1-s);constq=v*(1-f*s);constt=v*(1-(1-f)*s);constmod=i%6;constr=[v,q,p,p,t,v][mod];constg=[t,v,v,q,p,p][mod];constb=[p,p,t,v,v,q][mod];return{r:Math.round(r*255),g:Math.round(g*255),b:Math.round(b*255),};};exportdefaultclassColor{private_hue=0;private_saturation=100;private_value=100;private_alpha=100;publicenableAlpha=false;publicformat='hex';publicvalue='';publicselected?:boolean;constructor(options?:Options){options=options||({}asOptions);for(constoptioninoptions){if(hasOwn(options,option)){this[option]=options[option];}}this.doOnChange();}set(prop:{[key:string]:any}|any,value?:number){if(arguments.length===1&&typeofprop==='object'){for(constpinprop){if(hasOwn(prop,p)){this.set(p,prop[p]);}}return;}this[`_${prop}`]=value;this.doOnChange();}get(prop:string){if(prop==='alpha'){returnMath.floor(this[`_${prop}`]);}returnthis[`_${prop}`];}toRgb(){returnhsv2rgb(this._hue,this._saturation,this._value);}fromString(value){if(!value){this._hue=0;this._saturation=100;this._value=100;this.doOnChange();return;}constfromHSV=(h,s,v)=>{this._hue=Math.max(0,Math.min(360,h));this._saturation=Math.max(0,Math.min(100,s));this._value=Math.max(0,Math.min(100,v));this.doOnChange();};if(value.indexOf('hsl')!==-1){constparts=value.replace(/hsla|hsl|\(|\)/gm,'').split(/\s|,/g).filter((val)=>val!=='').map((val,index)=>(index>2?parseFloat(val):parseInt(val,10)));if(parts.length===4){this._alpha=parseFloat(parts[3])*100;}elseif(parts.length===3){this._alpha=100;}if(parts.length>=3){const{h,s,v}=hsl2hsv(parts[0],parts[1],parts[2]);fromHSV(h,s,v);}}elseif(value.indexOf('hsv')!==-1){constparts=value.replace(/hsva|hsv|\(|\)/gm,'').split(/\s|,/g).filter((val)=>val!=='').map((val,index)=>(index>2?parseFloat(val):parseInt(val,10)));if(parts.length===4){this._alpha=parseFloat(parts[3])*100;}elseif(parts.length===3){this._alpha=100;}if(parts.length>=3){fromHSV(parts[0],parts[1],parts[2]);}}elseif(value.indexOf('rgb')!==-1){constparts=value.replace(/rgba|rgb|\(|\)/gm,'').split(/\s|,/g).filter((val)=>val!=='').map((val,index)=>(index>2?parseFloat(val):parseInt(val,10)));if(parts.length===4){this._alpha=parseFloat(parts[3])*100;}elseif(parts.length===3){this._alpha=100;}if(parts.length>=3){const{h,s,v}=rgb2hsv(parts[0],parts[1],parts[2]);fromHSV(h,s,v);}}elseif(value.indexOf('#')!==-1){consthex=value.replace('#','').trim();if(!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$|^[0-9a-fA-F]{8}$/.test(hex))return;letr,g,b;if(hex.length===3){r=parseHexChannel(hex[0]+hex[0]);g=parseHexChannel(hex[1]+hex[1]);b=parseHexChannel(hex[2]+hex[2]);}elseif(hex.length===6||hex.length===8){r=parseHexChannel(hex.substring(0,2));g=parseHexChannel(hex.substring(2,4));b=parseHexChannel(hex.substring(4,6));}if(hex.length===8){this._alpha=(parseHexChannel(hex.substring(6))/255)*100;}elseif(hex.length===3||hex.length===6){this._alpha=100;}const{h,s,v}=rgb2hsv(r,g,b);fromHSV(h,s,v);}}compare(color){return(Math.abs(color._hue-this._hue)<2&&Math.abs(color._saturation-this._saturation)<1&&Math.abs(color._value-this._value)<1&&Math.abs(color._alpha-this._alpha)<1);}doOnChange(){const{_hue,_saturation,_value,_alpha,format}=this;if(this.enableAlpha){switch(format){case'hsl':{consthsl=hsv2hsl(_hue,_saturation/100,_value/100);this.value=`hsla(${_hue},${Math.round(hsl[1]*100)}%,${Math.round(hsl[2]*100)}%,${this.get('alpha')/100})`;break;}case'hsv':{this.value=`hsva(${_hue},${Math.round(_saturation)}%,${Math.round(_value)}%,${this.get('alpha')/100})`;break;}case'hex':{this.value=`${toHex(hsv2rgb(_hue,_saturation,_value))}${hexOne((_alpha*255)/100)}`;break;}default:{const{r,g,b}=hsv2rgb(_hue,_saturation,_value);this.value=`rgba(${r},${g},${b},${this.get('alpha')/100})`;}}}else{switch(format){case'hsl':{consthsl=hsv2hsl(_hue,_saturation/100,_value/100);this.value=`hsl(${_hue},${Math.round(hsl[1]*100)}%,${Math.round(hsl[2]*100)}%)`;break;}case'hsv':{this.value=`hsv(${_hue},${Math.round(_saturation)}%,${Math.round(_value)}%)`;break;}case'rgb':{const{r,g,b}=hsv2rgb(_hue,_saturation,_value);this.value=`rgb(${r},${g},${b})`;break;}default:{this.value=toHex(hsv2rgb(_hue,_saturation,_value));}}}}}

draggable.ts

letisDragging=false;exportdeclareinterfaceIOptions{drag?:(event:Event)=>void;start?:(event:Event)=>void;end?:(event:Event)=>void;}exportdefaultfunction(ele:HTMLElement,options:IOptions){constmoveFun=function(event:Event){options?.drag?.(event);};constupFun=function(event:Event){document.removeEventListener('mousemove',moveFun);document.removeEventListener('mouseup',upFun);document.onselectstart=null;document.ondragstart=null;isDragging=false;options?.end?.(event);};ele.addEventListener('mousedown',function(event:Event){if(isDragging)return;document.onselectstart=()=>false;document.ondragstart=()=>false;document.addEventListener('mousemove',moveFun);document.addEventListener('mouseup',upFun);isDragging=true;options?.start?.(event);});}

validators.ts

exportconstisValidComponentSize=(val:string):boolean=>{return['','large','default','small'].includes(val);};

alphaSlider.vue

<template><divclass="ant-color-alpha-slider":class="{ 'is-vertical': vertical }"><div ref="bar"class="ant-color-alpha-slider__bar":style="{background,}" @click="handleClick"></div><div ref="thumb"class="ant-color-alpha__thumb":style="{ left: thumbLeft + 'px', top: thumbTop + 'px' }"></div></div></template><script lang="ts">import{defineComponent,ref,getCurrentInstance,shallowRef,watch,onMounted}from'vue';importdraggablefrom'../lib/draggable';importtype{PropType}from'vue';importtypeColorfrom'../lib/color';importtype{Nullable}from'../type/types';exportdefaultdefineComponent({name:'AlphaSlider',props:{color:{type:ObjectasPropType<Color>,},vertical:{type:Boolean,default:false,},},setup(props,{emit}){constinstance=getCurrentInstance();// refconstbar=shallowRef<Nullable<HTMLElement>>(null);constthumb=shallowRef<Nullable<HTMLElement>>(null);// dataconstthumbLeft=ref<number>(0);constthumbTop=ref<number>(0);constbackground=ref<Nullable<string>>(null);// watchwatch(()=>props.color?.get('alpha'),()=>{update();});watch(()=>props.color?.value,()=>{update();});// methodsfunctionhandleClick(event:Event){consttarget=event.target;if(target!==thumb.value){handleDrag(event);}}functionhandleDrag(event){constele=instance?.vnode.elasHTMLElement;constrect=ele.getBoundingClientRect();if(!props.vertical){letleft=event.clientX-rect.left;left=Math.max((thumb.valueasHTMLElement).offsetWidth/2,left);left=Math.min(left,rect.width-(thumb.valueasHTMLElement).offsetWidth/2);props.color?.set('alpha',Math.round(((left-(thumb.valueasHTMLElement).offsetWidth/2)/(rect.width-(thumb.valueasHTMLElement).offsetWidth))*100));}else{lettop=event.clientY-rect.top;top=Math.max((thumb.valueasHTMLElement).offsetHeight/2,top);top=Math.min(top,rect.height-(thumb.valueasHTMLElement).offsetHeight/2);props.color?.set('alpha',Math.round(((top-(thumb.valueasHTMLElement).offsetHeight/2)/(rect.height-(thumb.valueasHTMLElement).offsetHeight))*100));}}functionupdate(){thumbLeft.value=getThumbLeft();thumbTop.value=getThumbTop();background.value=getBackground();}functiongetThumbLeft():number{if(props.vertical)return0;constele=instance?.vnode.el;constalpha=props.color?.get('alpha');if(!ele)return0;returnMath.round((alpha*(ele.offsetWidth-(thumb.valueasHTMLElement).offsetWidth/2))/100);}functiongetThumbTop():number{constele=instance?.vnode.el;if(!props.vertical)return0;constalpha=props.color?.get('alpha');if(!ele)return0;returnMath.round((alpha*(ele.offsetHeight-(thumb.valueasHTMLElement).offsetHeight/2))/100);}functiongetBackground(){if(props.color&&props.color.value){const{r,g,b}=props.color.toRgb();return`linear-gradient(to right, rgba(${r},${g},${b}, 0) 0%, rgba(${r},${g},${b}, 1) 100%)`;}returnnull;}onMounted(()=>{constdragConfig={drag:(event)=>{handleDrag(event);},end:(event)=>{handleDrag(event);},};draggable(bar.valueasHTMLElement,dragConfig);draggable(thumb.valueasHTMLElement,dragConfig);update();});return{bar,thumb,background,handleClick,thumbLeft,thumbTop,update,};},});</script><style lang="scss"scoped>.ant-color-alpha-slider{position:relative;box-sizing:border-box;width:280px;height:12px;background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==');.ant-color-alpha-slider__bar{position:relative;height:100%;background:linear-gradient(to right,rgb(255255255/0%)0%,rgb(255255255/100%)100%);}.ant-color-alpha__thumb{position:absolute;top:0;left:0;z-index:1;box-sizing:border-box;width:4px;height:100%;cursor:pointer;background:#fff;border:1px solid #f0f0f0;border-radius:1px;box-shadow:002pxrgb(000/60%);}&.is-vertical{width:20px;height:180px;.ant-color-alpha-slider__bar{background:linear-gradient(to bottom,rgb(255255255/0%)0%,rgb(255255255/100%)100%);}.ant-color-alpha__thumb{top:0;left:0;width:100%;height:4px;}}}</style>

hueSlider.vue

<template><divclass="ant-color-hue-slider":class="{ 'is-vertical': vertical }"><div ref="bar"class="ant-color-hue-slider__bar"@click="handleClick"></div><div ref="thumb"class="ant-color-hue-slider__thumb":style="{left:thumbLeft+'px',top:thumbTop+'px',}"></div></div></template><script lang="ts">import{defineComponent,ref,computed,onMounted,getCurrentInstance,watch}from'vue';importdraggablefrom'../lib/draggable';importtype{PropType}from'vue';importtypeColorfrom'../lib/color';importtype{Nullable}from'../type/types';exportdefaultdefineComponent({name:'HueSlider',props:{color:{type:ObjectasPropType<Color>,required:true,},vertical:{type:Boolean,default:false,},},setup(props){constinstance=getCurrentInstance();// refconstthumb=ref<Nullable<HTMLElement>>(null);constbar=ref<Nullable<HTMLElement>>(null);// dataconstthumbLeft=ref(0);constthumbTop=ref(0);// computedconsthueValue=computed(()=>props.color.get('hue'));// watchwatch(()=>hueValue.value,()=>{update();});// methodsfunctionhandleClick(event:Event){consttarget=event.target;if(target!==thumb.value){handleDrag(event);}}functionhandleDrag(event){constele=instance?.vnode.elasHTMLElement;constrect=ele.getBoundingClientRect();lethue;if(!props.vertical){letleft=event.clientX-rect.left;left=Math.min(left,rect.width-(thumb.valueasHTMLElement).offsetWidth/2);left=Math.max((thumb.valueasHTMLElement).offsetWidth/2,left);hue=Math.round(((left-(thumb.valueasHTMLElement).offsetWidth/2)/(rect.width-(thumb.valueasHTMLElement).offsetWidth))*360);}else{lettop=event.clientY-rect.top;top=Math.min(top,rect.height-(thumb.valueasHTMLElement).offsetHeight/2);top=Math.max((thumb.valueasHTMLElement).offsetHeight/2,top);hue=Math.round(((top-(thumb.valueasHTMLElement).offsetHeight/2)/(rect.height-(thumb.valueasHTMLElement).offsetHeight))*360);}props.color.set('hue',hue);}functiongetThumbLeft():number{constele=instance?.vnode.elasHTMLElement;if(props.vertical)return0;consthue=props.color.get('hue');if(!ele)return0;returnMath.round((hue*(ele.offsetWidth-(thumb.valueasHTMLElement).offsetWidth/2))/360);}functiongetThumbTop():number{constele=instance?.vnode.elasHTMLElement;if(!props.vertical)return0;consthue=props.color.get('hue');if(!ele)return0;returnMath.round((hue*(ele.offsetHeight-(thumb.valueasHTMLElement).offsetHeight/2))/360);}functionupdate(){thumbLeft.value=getThumbLeft();thumbTop.value=getThumbTop();}onMounted(()=>{constdragConfig={drag:(event)=>{handleDrag(event);},end:(event)=>{handleDrag(event);},};draggable(bar.valueasHTMLElement,dragConfig);draggable(thumb.valueasHTMLElement,dragConfig);update();});return{thumb,bar,thumbLeft,thumbTop,hueValue,handleClick,update,};},});</script><style lang="scss"scoped>.ant-color-hue-slider{position:relative;box-sizing:border-box;width:280px;height:12px;background-color:red;//float: right;&.is-vertical{width:12px;height:180px;padding:2px0;.ant-color-hue-slider__bar{background:linear-gradient(to bottom,#f000%,#ff017%,#0f033%,#0ff50%,#00f67%,#f0f83%,#f00100%);}.ant-color-hue-slider__thumb{top:0;left:0;width:100%;height:4px;}}.ant-color-hue-slider__bar{position:relative;height:100%;background:linear-gradient(to right,#f000%,#ff017%,#0f033%,#0ff50%,#00f67%,#f0f83%,#f00100%);}.ant-color-hue-slider__thumb{position:absolute;top:0;left:0;z-index:1;box-sizing:border-box;width:4px;height:100%;cursor:pointer;background-color:#fff;border:1px solid #f0f0f0;border-radius:1px;box-shadow:002px #0009;}}</style>

preDefine.vue

<template><divclass="ant-color-predefine"><divclass="ant-color-predefine__colors"><div v-for="(item, index) in rgbaColors":key="colors[index]":class="{ selected: item.selected, 'is-alpha': item._alpha < 100 }"class="ant-color-predefine__color-selector"@click="handleSelect(index)"><div:style="{ backgroundColor: item.value }"></div></div></div></div></template><script lang="ts">import{defineComponent,ref,watch,watchEffect}from'vue';importColorfrom'../lib/color';import{useOptions}from'../useOptions';importtype{PropType}from'vue';exportdefaultdefineComponent({name:'PreDefine',props:{colors:{type:Array,required:true,},color:{type:ObjectasPropType<Color>,required:true,},},setup(props){constcurrentColor:any=useOptions()?.currentColor;// dataconstrgbaColors=ref(parseColors(props.colors,props.color));// watchwatch(()=>currentColor.value,(val)=>{constcolor=newColor();color.fromString(val);rgbaColors.value.forEach((item)=>{item.selected=color.compare(item);});});watchEffect(()=>{rgbaColors.value=parseColors(props.colors,props.color);});// methodsfunctionhandleSelect(index){props.color.fromString(props.colors[index]);}functionparseColors(colors,color){returncolors.map((value)=>{constc=newColor();c.enableAlpha=true;c.format='rgba';c.fromString(value);c.selected=c.value===color.value;returnc;});}return{rgbaColors,handleSelect,};},});</script><style lang="scss"scoped>.ant-color-predefine{display:flex;width:280px;margin-top:8px;font-size:12px;.ant-color-predefine__colors{display:flex;flex:1;flex-wrap:wrap;}.ant-color-predefine__color-selector{width:20px;height:20px;margin:008px8px;cursor:pointer;border-radius:4px;&:nth-child(10n+1){margin-left:0;}>div{display:flex;height:100%;border-radius:3px;}&.selected{box-shadow:003px2px #40a9ff;}}}</style>

svPanel.vue

<template><divclass="ant-color-svpanel":style="{backgroundColor:background,}"><divclass="ant-color-svpanel__white"></div><divclass="ant-color-svpanel__black"></div><divclass="ant-color-svpanel__cursor":style="{top:cursorTop+'px',left:cursorLeft+'px',}"><div></div></div></div></template><script lang="ts">import{defineComponent,ref,computed,watch,getCurrentInstance,onMounted,nextTick}from'vue';importdraggablefrom'../lib/draggable';importtype{PropType}from'vue';importtypeColorfrom'../lib/color';exportdefaultdefineComponent({name:'SvPanel',props:{color:{type:ObjectasPropType<Color>,},},setup(props){constinstance=getCurrentInstance();// dataconstcursorTop=ref(0);constcursorLeft=ref(0);constbackground=ref('hsl(0, 100%, 50%)');// computedconstcolorValue=computed(()=>{consthue=props.color?.get('hue');constvalue=props.color?.get('value');return{hue,value};});// watchwatch(()=>colorValue.value,()=>{update();});// methodsfunctionupdate(){constsaturation=props.color?.get('saturation');constvalue=props.color?.get('value');constele=instance?.vnode.elasHTMLElement;cursorLeft.value=(saturation*ele.clientWidth)/100;cursorTop.value=((100-value)*ele.clientHeight)/100;background.value=`hsl(${props.color?.get('hue')}, 100%, 50%)`;}functionhandleDrag(event){constele=instance?.vnode.elasHTMLElement;constrect=ele.getBoundingClientRect();letleft=event.clientX-rect.left;lettop=event.clientY-rect.top;left=Math.max(0,left);left=Math.min(left,rect.width);top=Math.max(0,top);top=Math.min(top,rect.height);cursorLeft.value=left;cursorTop.value=top;props.color?.set({saturation:(left/rect.width)*100,value:100-(top/rect.height)*100,});}onMounted(()=>{constele=instance?.vnode.elasHTMLElement;draggable(ele,{drag:(event)=>{handleDrag(event);},end:(event)=>{handleDrag(event);},});});return{cursorTop,cursorLeft,background,colorValue,handleDrag,update,};},});</script><style lang="scss"scoped>.ant-color-svpanel{position:relative;width:280px;height:180px;margin-right:10px;.ant-color-svpanel__white,.ant-color-svpanel__black{position:absolute;inset:0;}.ant-color-svpanel__white{background:linear-gradient(to right,#fff,rgb(255255255/0%));}.ant-color-svpanel__black{background:linear-gradient(to top,#000,rgb(000/0%));}.ant-color-svpanel__cursor{position:absolute;>div{width:4px;height:4px;border-radius:50%;box-shadow:0001.5px #fff,inset001px1pxrgb(000/30%),001px2pxrgb(000/40%);transform:translate(-2px,-2px);}}}</style>

Vben Form在adapter定义 集成到schema中的一种

constColorPicker=defineAsyncComponent(()=>import('#/components/ColorPicker').then((res)=>res.ColorPicker));

schema引用

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 11:42:38

Release 屏障与 Acquire 屏障

最小概念了解&#xff1a;一对“发布&#xff08;publish&#xff09;/订阅&#xff08;consume&#xff09;”规则Release&#xff08;释放 / 发布&#xff09;是什么发生在 写端。语义&#xff1a;Release 之前的所有普通读写&#xff0c;在“对外可见的顺序”上&#xff0c;…

作者头像 李华
网站建设 2026/4/16 16:15:44

Diskinfo下载官网数据监测TensorRT运行时磁盘IO

Diskinfo下载官网数据监测TensorRT运行时磁盘IO 在现代AI系统部署中&#xff0c;一个常被忽视的事实是&#xff1a;模型跑得快&#xff0c;不一定服务响应就快。我们见过太多案例——GPU利用率不到30%&#xff0c;推理延迟却高达数秒。问题出在哪&#xff1f;答案往往藏在“看不…

作者头像 李华
网站建设 2026/4/13 17:30:10

ZigBee:低功耗物联的“网状神经”——成都泽耀

一、什么是ZigBee&#xff1f; ZigBee&#xff0c;也称紫蜂&#xff0c;是一种低速、低功耗、低成本的无线网络协议&#xff0c;其底层基于IEEE 802.15.4标准&#xff0c;专为低数据速率、长时间运行的无线传感与控制网络而设计。它支持大规模节点组网与多种网络拓扑&#xff0…

作者头像 李华
网站建设 2026/4/17 14:05:06

Excalidraw入驻DooTask,开启手绘协作新时代

Excalidraw入驻DooTask&#xff0c;开启手绘协作新时代 当团队会议中的“我意思是……”变成反复澄清的循环&#xff0c;当产品需求在层层转述中逐渐失真——我们不得不承认&#xff1a;纯文本和线性流程&#xff0c;正在扼杀协作的原始生命力。就在这个节点&#xff0c;DooTas…

作者头像 李华
网站建设 2026/4/17 0:16:16

【工具】制作电脑托盘音乐频谱显示工具

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 效果演示&#xff1a; Github: https://github.com/1061700625/SpectraTray 下载链接(github)&#xff1a;https://github.com/1061700625/SpectraTra…

作者头像 李华
网站建设 2026/4/16 18:01:21

TensorRT-LLM性能调优:提升LLM推理效率

TensorRT-LLM性能调优&#xff1a;提升LLM推理效率 在当前大语言模型&#xff08;LLM&#xff09;广泛应用的背景下&#xff0c;一个70B参数级别的模型若以原生PyTorch部署&#xff0c;单次生成可能消耗数GB显存、延迟高达秒级&#xff0c;吞吐量却仅有几百tokens/秒。这种资源…

作者头像 李华