<template> <div :class="classes" v-clickoutside="handleClose"> <div ref="reference" @click="toggleVisible" :class="wrapClasses"> <i class="ivu-icon ivu-icon-arrow-down-b ivu-input-icon ivu-input-icon-normal"></i> <div :class="inputClasses"> <div :class="[prefixCls + '-color']"> <div :class="[prefixCls + '-color-empty']" v-show="value === '' && !visible"> <i class="ivu-icon ivu-icon-ios-close-empty"></i> </div> <div v-show="value || visible" :style="{backgroundColor: displayedColor}"></div> </div> </div> </div> <transition :name="transition"> <Drop v-show="visible" @click.native="handleTransferClick" :class="{ [prefixCls + '-transfer']: transfer }" class-name="ivu-transfer-no-max-height" :placement="placement" ref="drop" :data-transfer="transfer" v-transfer-dom> <div :class="[prefixCls + '-picker']"> <div :class="[prefixCls + '-picker-wrapper']"> <div :class="[prefixCls + '-picker-panel']"> <Saturation v-model="saturationColors" @change="childChange"></Saturation> </div> <div :class="[prefixCls + '-picker-hue-slider']"> <Hue v-model="saturationColors" @change="childChange"></Hue> </div> <div v-if="alpha" :class="[prefixCls + '-picker-alpha-slider']"> <Alpha v-model="saturationColors" @change="childChange"></Alpha> </div> <recommend-colors v-if="colors.length" :list="colors" :class="[prefixCls + '-picker-colors']" @picker-color="handleSelectColor"></recommend-colors> <recommend-colors v-if="!colors.length && recommend" :list="recommendedColor" :class="[prefixCls + '-picker-colors']" @picker-color="handleSelectColor"></recommend-colors> </div> <div :class="[prefixCls + '-confirm']"> <span :class="[prefixCls + '-confirm-color']">{{ formatColor }}</span> <Confirm @on-pick-success="handleSuccess" @on-pick-clear="handleClear"></Confirm> </div> </div> </Drop> </transition> </div> </template> <script> import tinycolor from 'tinycolor2'; import clickoutside from '../../directives/clickoutside'; import TransferDom from '../../directives/transfer-dom'; import Drop from '../../components/select/dropdown.vue'; import RecommendColors from './recommend-colors.vue'; import Confirm from '../date-picker/base/confirm.vue'; import Saturation from './saturation.vue'; import Hue from './hue.vue'; import Alpha from './alpha.vue'; import { oneOf } from '../../utils/assist'; import Emitter from '../../mixins/emitter'; const prefixCls = 'ivu-color-picker'; const inputPrefixCls = 'ivu-input'; function _colorChange (data, oldHue) { data = data === '' ? '#2d8cf0' : data; const alpha = data && data.a; let color; // hsl is better than hex between conversions if (data && data.hsl) { color = tinycolor(data.hsl); } else if (data && data.hex && data.hex.length > 0) { color = tinycolor(data.hex); } else { color = tinycolor(data); } if (color && (color._a === undefined || color._a === null)) { color.setAlpha(alpha || 1); } const hsl = color.toHsl(); const hsv = color.toHsv(); if (hsl.s === 0) { hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0; } // when the hsv.v is less than 0.0164 (base on test) // because of possible loss of precision // the result of hue and saturation would be miscalculated if (hsv.v < 0.0164) { hsv.h = data.h || (data.hsv && data.hsv.h) || 0; hsv.s = data.s || (data.hsv && data.hsv.s) || 0; } if (hsl.l < 0.01) { hsl.h = data.h || (data.hsl && data.hsl.h) || 0; hsl.s = data.s || (data.hsl && data.hsl.s) || 0; } return { hsl: hsl, hex: color.toHexString().toUpperCase(), rgba: color.toRgb(), hsv: hsv, oldHue: data.h || oldHue || hsl.h, source: data.source, a: data.a || color.getAlpha() }; } export default { name: 'ColorPicker', mixins: [ Emitter ], components: { Drop, Confirm, RecommendColors, Saturation, Hue, Alpha }, directives: { clickoutside, TransferDom }, props: { value: { type: String }, alpha: { type: Boolean, default: false }, recommend: { type: Boolean, default: false }, format: { validator (value) { return oneOf(value, ['hsl', 'hsv', 'hex', 'rgb']); } }, colors: { type: Array, default () { return []; } }, disabled: { type: Boolean, default: false }, size: { validator (value) { return oneOf(value, ['small', 'large', 'default']); }, default: 'default' }, placement: { validator (value) { return oneOf(value, ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end']); }, default: 'bottom' }, transfer: { type: Boolean, default: false } }, data () { return { val: _colorChange(this.value), prefixCls: prefixCls, visible: false, disableCloseUnderTransfer: false, // transfer 模式下,点击Drop也会触发关闭 recommendedColor: [ '#2d8cf0', '#19be6b', '#ff9900', '#ed3f14', '#00b5ff', '#19c919', '#f9e31c', '#ea1a1a', '#9b1dea', '#00c2b1', '#ac7a33', '#1d35ea', '#8bc34a', '#f16b62', '#ea4ca3', '#0d94aa', '#febd79', '#5d4037', '#00bcd4', '#f06292', '#cddc39', '#607d8b', '#000000', '#ffffff' ] }; }, computed: { transition () { if (this.placement === 'bottom-start' || this.placement === 'bottom' || this.placement === 'bottom-end') { return 'slide-up'; } else { return 'fade'; } }, saturationColors: { get () { return this.val; }, set (newVal) { this.val = newVal; this.$emit('on-active-change', this.formatColor); } }, classes () { return [ `${prefixCls}`, { [`${prefixCls}-transfer`]: this.transfer } ]; }, wrapClasses () { return [ `${prefixCls}-rel`, `${prefixCls}-${this.size}`, `${inputPrefixCls}-wrapper`, `${inputPrefixCls}-wrapper-${this.size}` ]; }, inputClasses () { return [ `${prefixCls}-input`, `${inputPrefixCls}`, `${inputPrefixCls}-${this.size}`, { [`${inputPrefixCls}-disabled`]: this.disabled } ]; }, displayedColor () { let color; if (this.visible) { const rgba = this.saturationColors.rgba; color = { r: rgba.r, g: rgba.g, b: rgba.b, a: rgba.a }; } else { color = tinycolor(this.value).toRgb(); } return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`; }, formatColor () { const value = this.saturationColors; const format = this.format; let color; const rgba = `rgba(${value.rgba.r}, ${value.rgba.g}, ${value.rgba.b}, ${value.rgba.a})`; if (format) { if (format === 'hsl') { color = tinycolor(value.hsl).toHslString(); } else if (format === 'hsv') { color = tinycolor(value.hsv).toHsvString(); } else if (format === 'hex') { color = value.hex; } else if (format === 'rgb') { color = rgba; } } else if (this.alpha) { color = rgba; } else { color = value.hex; } return color; } }, watch: { value (newVal) { this.val = _colorChange(newVal); }, visible (val) { this.val = _colorChange(this.value); if (val) { this.$refs.drop.update(); } else { this.$refs.drop.destroy(); } } }, methods: { // 开启 transfer 时,点击 Drop 即会关闭,这里不让其关闭 handleTransferClick () { if (this.transfer) this.disableCloseUnderTransfer = true; }, handleClose () { if (this.disableCloseUnderTransfer) { this.disableCloseUnderTransfer = false; return false; } this.visible = false; }, toggleVisible () { this.visible = !this.visible; }, childChange (data) { this.colorChange(data); }, colorChange (data, oldHue) { this.oldHue = this.saturationColors.hsl.h; this.saturationColors = _colorChange(data, oldHue || this.oldHue); }, isValidHex (hex) { return tinycolor(hex).isValid(); }, simpleCheckForValidColor (data) { const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v']; let checked = 0; let passed = 0; for (let i = 0; i < keysToCheck.length; i++) { const letter = keysToCheck[i]; if (data[letter]) { checked++; if (!isNaN(data[letter])) { passed++; } } } if (checked === passed) { return data; } }, handleSuccess () { const color = this.formatColor; this.$emit('input', color); this.$emit('on-change', color); this.dispatch('FormItem', 'on-form-change', color); this.handleClose(); }, handleClear () { this.$emit('input', ''); this.$emit('on-change', ''); this.dispatch('FormItem', 'on-form-change', ''); this.handleClose(); }, handleSelectColor (color) { this.val = _colorChange(color); } } }; </script>