Sfoglia il codice sorgente

添加取色器组件

pipipi-pikachu 5 anni fa
parent
commit
b71b78f9f1

+ 11 - 0
package-lock.json

@@ -1935,6 +1935,12 @@
       "integrity": "sha1-qcpLcKGLJwzLK8Cqr+/R1Ia36nQ=",
       "dev": true
     },
+    "@types/tinycolor2": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npm.taobao.org/@types/tinycolor2/download/@types/tinycolor2-1.4.2.tgz",
+      "integrity": "sha1-chylxdGimItKiG41wv/Fc1tq+98=",
+      "dev": true
+    },
     "@types/uglify-js": {
       "version": "3.11.1",
       "resolved": "https://registry.npm.taobao.org/@types/uglify-js/download/@types/uglify-js-3.11.1.tgz?cache=0&sync_timestamp=1605057452755&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fuglify-js%2Fdownload%2F%40types%2Fuglify-js-3.11.1.tgz",
@@ -15318,6 +15324,11 @@
       "resolved": "https://registry.npm.taobao.org/tiny-emitter/download/tiny-emitter-2.1.0.tgz",
       "integrity": "sha1-HRpW7fxRxD6GPLtTgqcjMONVVCM="
     },
+    "tinycolor2": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npm.taobao.org/tinycolor2/download/tinycolor2-1.4.2.tgz",
+      "integrity": "sha1-P2pNEHGtB2dtf6Ry4frECnGdiAM="
+    },
     "tmp": {
       "version": "0.0.33",
       "resolved": "https://registry.npm.taobao.org/tmp/download/tmp-0.0.33.tgz",

+ 2 - 0
package.json

@@ -28,6 +28,7 @@
     "prosemirror-state": "^1.3.3",
     "prosemirror-view": "^1.16.4",
     "store2": "^2.12.0",
+    "tinycolor2": "^1.4.2",
     "vue": "^3.0.0",
     "vuedraggable": "^4.0.1",
     "vuex": "^4.0.0-0"
@@ -45,6 +46,7 @@
     "@types/prosemirror-schema-basic": "^1.0.1",
     "@types/prosemirror-schema-list": "^1.0.1",
     "@types/resize-observer-browser": "^0.1.4",
+    "@types/tinycolor2": "^1.4.2",
     "@typescript-eslint/eslint-plugin": "^2.33.0",
     "@typescript-eslint/parser": "^2.33.0",
     "@vue/cli-plugin-babel": "~4.5.0",

+ 131 - 0
src/components/ColorPicker/Alpha.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="alpha">
+    <div class="alpha-checkboard-wrap">
+      <Checkboard />
+    </div>
+    <div class="alpha-gradient" :style="{ background: gradientColor }"></div>
+    <div 
+      class="alpha-container" 
+      ref="alphaRef"
+      @mousedown="$event => handleMouseDown($event)"
+    >
+      <div class="alpha-pointer" :style="{ left: color.a * 100 + '%' }">
+        <div class="alpha-picker"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, onUnmounted, PropType, ref } from 'vue'
+
+import Checkboard from './Checkboard.vue'
+import { ColorFormats } from 'tinycolor2'
+
+export default defineComponent({
+  name: 'alpha',
+  components: {
+    Checkboard,
+  },
+  props: {
+    modelValue: {
+      type: Object as PropType<ColorFormats.RGBA>,
+      required: true,
+    },
+  },
+  setup(props, { emit }) {
+    const color = computed(() => props.modelValue)
+    const gradientColor = computed(() => {
+      const rgbaStr = [color.value.r, color.value.g, color.value.b].join(',')
+      return `linear-gradient(to right, rgba(${rgbaStr}, 0) 0%, rgba(${rgbaStr}, 1) 100%)`
+    })
+
+    const alphaRef = ref<HTMLElement | null>(null)
+    const handleChange = (e: MouseEvent) => {
+      e.preventDefault()
+      if(!alphaRef.value) return
+      const containerWidth = alphaRef.value.clientWidth
+      const xOffset = alphaRef.value.getBoundingClientRect().left + window.pageXOffset
+      const left = e.pageX - xOffset
+      let a
+
+      if(left < 0) a = 0
+      else if(left > containerWidth) a = 1
+      else a = Math.round(left * 100 / containerWidth) / 100
+
+      if(color.value.a !== a) {
+        emit('update:modelValue', {
+          r: color.value.r,
+          g: color.value.g,
+          b: color.value.b,
+          a: a,
+        })
+      }
+    }
+
+    const unbindEventListeners = () => {
+      window.removeEventListener('mousemove', handleChange)
+      window.removeEventListener('mouseup', unbindEventListeners)
+    }
+    const handleMouseDown = (e: MouseEvent) => {
+      handleChange(e)
+      window.addEventListener('mousemove', handleChange)
+      window.addEventListener('mouseup', unbindEventListeners)
+    }
+
+    onUnmounted(unbindEventListeners)
+
+    return {
+      alphaRef,
+      gradientColor,
+      handleMouseDown,
+      color,
+    }
+  },
+})
+</script>
+
+<style>
+.alpha {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+.alpha-checkboard-wrap {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  overflow: hidden;
+}
+.alpha-gradient {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+.alpha-container {
+  cursor: pointer;
+  position: relative;
+  z-index: 2;
+  height: 100%;
+  margin: 0 3px;
+}
+.alpha-pointer {
+  z-index: 2;
+  position: absolute;
+}
+.alpha-picker {
+  cursor: pointer;
+  width: 4px;
+  height: 8px;
+  box-shadow: 0 0 2px rgba(0, 0, 0, .6);
+  background: #fff;
+  margin-top: 1px;
+  transform: translateX(-2px);
+}
+</style>

+ 72 - 0
src/components/ColorPicker/Checkboard.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="checkerboard" :style="bgStyle"></div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue'
+
+const checkboardCache = {}
+const renderCheckboard = (white: string, grey: string, size: number) => {
+  const canvas = document.createElement('canvas')
+  canvas.width = canvas.height = size * 2
+  const ctx = canvas.getContext('2d')
+  
+  if(!ctx) return null
+
+  ctx.fillStyle = white
+  ctx.fillRect(0, 0, canvas.width, canvas.height)
+  ctx.fillStyle = grey
+  ctx.fillRect(0, 0, size, size)
+  ctx.translate(size, size)
+  ctx.fillRect(0, 0, size, size)
+  return canvas.toDataURL()
+}
+
+const getCheckboard = (white: string, grey: string, size: number) => {
+  const key = white + ',' + grey + ',' + size
+  if(checkboardCache[key]) return checkboardCache[key]
+  
+  const checkboard = renderCheckboard(white, grey, size)
+  checkboardCache[key] = checkboard
+  return checkboard
+}
+
+export default defineComponent({
+  name: 'checkboard',
+  props: {
+    size: {
+      type: Number,
+      default: 8,
+    },
+    white: {
+      type: String,
+      default: '#fff',
+    },
+    grey: {
+      type: String,
+      default: '#e6e6e6',
+    },
+  },
+  setup(props) {
+    const bgStyle = computed(() => {
+      const checkboard = getCheckboard(props.white, props.grey, props.size)
+      return { backgroundImage: `url(${checkboard})` }
+    })
+
+    return {
+      bgStyle,
+    }
+  },
+})
+</script>
+
+<style>
+.checkerboard {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background-size: contain;
+}
+</style>

+ 63 - 0
src/components/ColorPicker/EditableInput.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="editable-input">
+    <input
+      class="input-content"
+      :value="val"
+      @input="$event => handleInput($event)"
+    >
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, PropType } from 'vue'
+import tinycolor, { ColorFormats } from 'tinycolor2'
+
+export default defineComponent({
+  name: 'editable-input',
+  props: {
+    modelValue: {
+      type: Object as PropType<ColorFormats.RGBA>,
+      required: true,
+    },
+  },
+  setup(props, { emit }) {
+    const val = computed(() => {
+      let _hex = ''
+      if(props.modelValue.a < 1) _hex = tinycolor(props.modelValue).toHex8String().toUpperCase()
+      else _hex = tinycolor(props.modelValue).toHexString().toUpperCase()
+      return _hex.replace('#', '')
+    })
+
+    const handleInput = (e: InputEvent) => {
+      const value = (e.target as HTMLInputElement).value
+      if(value.length >= 6) emit('update:modelValue', tinycolor(value).toRgb())
+    }
+
+    return {
+      val,
+      handleInput,
+    }
+  },
+})
+</script>
+
+<style>
+.editable-input {
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+  text-align: center;
+  font-size: 14px;
+}
+.input-content {
+  width: 100%;
+  padding: 3px;
+  border: 0;
+  border-bottom: 1px solid #ddd;
+  outline: none;
+  text-align: center;
+}
+.input-label {
+  text-transform: capitalize;
+}
+</style>

+ 127 - 0
src/components/ColorPicker/Hue.vue

@@ -0,0 +1,127 @@
+<template>
+  <div class="hue">
+    <div 
+      class="hue-container"
+      ref="hueRef"
+      @mousedown="$event => handleMouseDown($event)"
+    >
+      <div 
+        class="hue-pointer"
+        :style="{ left: pointerLeft }"
+      >
+        <div class="hue-picker"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, onUnmounted, PropType, ref, watch } from 'vue'
+import tinycolor, { ColorFormats } from 'tinycolor2'
+
+export default defineComponent({
+  name: 'hue',
+  props: {
+    modelValue: {
+      type: Object as PropType<ColorFormats.RGBA>,
+      required: true,
+    },
+  },
+  setup(props, { emit }) {
+    const oldHue = ref(0)
+    const pullDirection = ref('')
+    
+    const color = computed(() => tinycolor(props.modelValue).toHsl())
+
+    const pointerLeft = computed(() => {
+      if(color.value.h === 0 && pullDirection.value === 'right') return '100%'
+      return color.value.h * 100 / 360 + '%'
+    })
+
+    watch(() => props.modelValue, () => {
+      const hsl = tinycolor(props.modelValue).toHsl()
+      const h = hsl.h
+      if(h !== 0 && h - oldHue.value > 0) pullDirection.value = 'right'
+      if(h !== 0 && h - oldHue.value < 0) pullDirection.value = 'left'
+      oldHue.value = h
+    })
+
+    const hueRef = ref<HTMLElement | null>(null)
+    const handleChange = (e: MouseEvent) => {
+      e.preventDefault()
+      if(!hueRef.value) return
+
+      const containerWidth = hueRef.value.clientWidth
+      const xOffset = hueRef.value.getBoundingClientRect().left + window.pageXOffset
+      const left = e.pageX - xOffset
+      let h, percent
+      
+      if(left < 0) h = 0
+      else if(left > containerWidth) h = 360
+      else {
+        percent = left * 100 / containerWidth
+        h = (360 * percent / 100)
+      }
+      if(color.value.h !== h) {
+        const rgba = tinycolor({
+          h,
+          l: color.value.l,
+          s: color.value.s,
+          a: color.value.a,
+        }).toRgb()
+
+        emit('update:modelValue', rgba)
+      }
+    }
+
+    const unbindEventListeners = () => {
+      window.removeEventListener('mousemove', handleChange)
+      window.removeEventListener('mouseup', unbindEventListeners)
+    }
+    const handleMouseDown = (e: MouseEvent) => {
+      handleChange(e)
+      window.addEventListener('mousemove', handleChange)
+      window.addEventListener('mouseup', unbindEventListeners)
+    }
+
+    onUnmounted(unbindEventListeners)
+
+    return {
+      hueRef,
+      handleMouseDown,
+      pointerLeft,
+    }
+  },
+})
+</script>
+
+<style>
+.hue {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
+}
+.hue-container {
+  cursor: pointer;
+  margin: 0 2px;
+  position: relative;
+  height: 100%;
+}
+.hue-pointer {
+  z-index: 2;
+  position: absolute;
+  top: 0;
+}
+.hue-picker {
+  cursor: pointer;
+  margin-top: 1px;
+  width: 4px;
+  height: 8px;
+  box-shadow: 0 0 2px rgba(0, 0, 0, .6);
+  background: #fff;
+  transform: translateX(-2px);
+}
+</style>

+ 122 - 0
src/components/ColorPicker/Saturation.vue

@@ -0,0 +1,122 @@
+<template>
+  <div 
+    class="saturation"
+    ref="saturationRef"
+    :style="{ background: bgColor }"
+    @mousedown="$event => handleMouseDown($event)"
+  >
+    <div class="saturation-white"></div>
+    <div class="saturation-black"></div>
+    <div class="saturation-pointer" 
+      :style="{
+        top: pointerTop,
+        left: pointerLeft,
+      }"
+    >
+      <div class="saturation-circle"></div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, onUnmounted, PropType, ref } from 'vue'
+import tinycolor, { ColorFormats } from 'tinycolor2'
+import throttle from 'lodash/throttle'
+import clamp from 'lodash/clamp'
+
+export default defineComponent({
+  name: 'saturation',
+  props: {
+    modelValue: {
+      type: Object as PropType<ColorFormats.RGBA>,
+      required: true,
+    },
+  },
+  setup(props, { emit }) {
+    const color = computed(() => tinycolor(props.modelValue).toHsv())
+
+    const bgColor = computed(() => `hsl(${color.value.h}, 100%, 50%)`)
+    const pointerTop = computed(() => (-(color.value.v * 100) + 1) + 100 + '%')
+    const pointerLeft = computed(() => color.value.s * 100 + '%')
+
+    const emitChangeEvent = throttle(function(param) {
+      emit('update:modelValue', param)
+    }, 20, { leading: true, trailing: false })
+
+    const saturationRef = ref<HTMLElement | null>(null)
+    const handleChange = (e: MouseEvent) => {
+      e.preventDefault()
+      if(!saturationRef.value) return
+      
+      const containerWidth = saturationRef.value.clientWidth
+      const containerHeight = saturationRef.value.clientHeight
+      const xOffset = saturationRef.value.getBoundingClientRect().left + window.pageXOffset
+      const yOffset = saturationRef.value.getBoundingClientRect().top + window.pageYOffset
+      const left = clamp(e.pageX - xOffset, 0, containerWidth)
+      const top = clamp(e.pageY - yOffset, 0, containerHeight)
+      const saturation = left / containerWidth
+      const bright = clamp(-(top / containerHeight) + 1, 0, 1)
+
+      const rgba = tinycolor({
+        h: color.value.h,
+        s: saturation,
+        v: bright,
+        a: color.value.a,
+      }).toRgb()
+
+      emitChangeEvent(rgba)
+    }
+
+    
+    const unbindEventListeners = () => {
+      window.removeEventListener('mousemove', handleChange)
+      window.removeEventListener('mouseup', unbindEventListeners)
+    }
+    const handleMouseDown = (e: MouseEvent) => {
+      handleChange(e)
+      window.addEventListener('mousemove', handleChange)
+      window.addEventListener('mouseup', unbindEventListeners)
+    }
+
+    onUnmounted(unbindEventListeners)
+
+    return {
+      saturationRef,
+      bgColor,
+      handleMouseDown,
+      pointerTop,
+      pointerLeft,
+    }
+  },
+})
+</script>
+
+<style>
+.saturation,
+.saturation-white,
+.saturation-black {
+  cursor: pointer;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+.saturation-white {
+  background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
+}
+.saturation-black {
+  background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
+}
+.saturation-pointer {
+  cursor: pointer;
+  position: absolute;
+}
+.saturation-circle {
+  width: 4px;
+  height: 4px;
+  box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0, 0, 0, .3), 0 0 1px 2px rgba(0, 0, 0, .4);
+  border-radius: 50%;
+  transform: translate(-2px, -2px);
+}
+</style>

+ 239 - 0
src/components/ColorPicker/index.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="color-picker" @contextmenu.prevent>
+    <div class="picker-saturation-wrap">
+      <Saturation v-model="color" />
+    </div>
+    <div class="picker-controls">
+      <div class="picker-color-wrap">
+        <div class="picker-current-color" :style="{ background: currentColor }"></div>
+        <Checkboard />
+      </div>
+      <div class="picker-sliders">
+        <div class="picker-hue-wrap"><Hue v-model="color" /></div>
+        <div class="picker-alpha-wrap"><Alpha v-model="color" /></div>
+      </div>
+    </div>    
+
+    <div class="picker-field"><EditableInput v-model="color" /></div>
+
+    <div class="picker-presets">
+      <div
+        class="picker-presets-color"
+        v-for="c in themeColors"
+        :key="c"
+        :style="{background: c}"
+        @click="selectPresetColor(c)"
+      ></div>
+    </div>
+
+    <div class="picker-gradient-presets">
+      <div
+        class="picker-gradient-col"
+        v-for="(col, index) in presetColors"
+        :key="index"
+      >
+        <div class="picker-gradient-color"
+          v-for="c in col"
+          :key="c"
+          :style="{background: c}"
+          @click="selectPresetColor(c)"
+        ></div>
+      </div>
+    </div>
+
+    <div class="picker-presets">
+      <div
+        v-for="c in standardColors"
+        :key="c"
+        class="picker-presets-color"
+        :style="{ background: c }"
+        @click="selectPresetColor(c)"
+      ></div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue'
+import tinycolor, { ColorFormats } from 'tinycolor2'
+
+import Alpha from './Alpha.vue'
+import Checkboard from './Checkboard.vue'
+import Hue from './Hue.vue'
+import Saturation from './Saturation.vue'
+import EditableInput from './EditableInput.vue'
+
+const presetColorConfig = [
+  ['#7f7f7f', '#f2f2f2'],
+  ['#0d0d0d', '#808080'],
+  ['#1c1a10', '#ddd8c3'],
+  ['#0e243d', '#c6d9f0'],
+  ['#233f5e', '#dae5f0'],
+  ['#632623', '#f2dbdb'],
+  ['#4d602c', '#eaf1de'],
+  ['#3f3150', '#e6e0ec'],
+  ['#1e5867', '#d9eef3'],
+  ['#99490f', '#fee9da'],
+]
+
+const gradient = (startColor: string, endColor: string, step: number) => {
+  const _startColor = tinycolor(startColor).toRgb()
+  const _endColor = tinycolor(endColor).toRgb()
+
+  const rStep = (_endColor.r - _startColor.r) / step
+  const gStep = (_endColor.g - _startColor.g) / step
+  const bStep = (_endColor.b - _startColor.b) / step
+  const gradientColorArr = []
+
+  for(let i = 0; i < step; i++) {
+    const gradientColor = tinycolor({
+      r: _startColor.r + rStep * i,
+      g: _startColor.g + gStep * i,
+      b: _startColor.b + bStep * i,
+    }).toRgbString()
+    gradientColorArr.push(gradientColor)
+  }
+  return gradientColorArr
+}
+
+const getPresetColors = () => {
+  const presetColors = []
+  for(const color of presetColorConfig) {
+    presetColors.push(gradient(color[1], color[0], 5))
+  }
+  return presetColors
+}
+
+export default defineComponent({
+  name: 'color-picker',
+  components: {
+    Alpha,
+    Checkboard,
+    Hue,
+    Saturation,
+    EditableInput,
+  },
+  props: {
+    modelValue: {
+      type: String,
+      default: '#e86b99',
+    },
+  },
+  setup(props, { emit }) {
+    const color = computed({
+      get() {
+        return tinycolor(props.modelValue).toRgb()
+      },
+      set(rgba: ColorFormats.RGBA) {
+        const rgbaString = `rgba(${[rgba.r, rgba.g, rgba.b, rgba.a].join(',')})`
+        emit('update:modelValue', rgbaString)
+      },
+    })
+
+    const themeColors = ['#000000', '#ffffff', '#eeece1', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c']
+    const standardColors = ['#c21401', '#ff1e02', '#ffc12a', '#ffff3a', '#90cf5b', '#00af57', '#00afee', '#0071be', '#00215f', '#72349d']
+    const presetColors = getPresetColors()
+
+    const currentColor = computed(() => {
+      return `rgba(${[color.value.r, color.value.g, color.value.b, color.value.a].join(',')})`
+    })
+
+    const selectPresetColor = (colorString: string) => {
+      emit('update:modelValue', colorString)
+    }
+
+    return {
+      themeColors,
+      standardColors,
+      presetColors,
+      color,
+      currentColor,
+      selectPresetColor,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.color-picker {
+  position: relative;
+  width: 240px;
+  background: #fff;
+  user-select: none;
+}
+.picker-saturation-wrap {
+  width: 100%;
+  padding-bottom: 50%;
+  position: relative;
+  overflow: hidden;
+}
+.picker-controls {
+  display: flex;
+}
+.picker-sliders {
+  padding: 4px 0;
+  flex: 1;
+}
+.picker-hue-wrap {
+  position: relative;
+  height: 10px;
+}
+.picker-alpha-wrap {
+  position: relative;
+  height: 10px;
+  margin-top: 4px;
+  overflow: hidden;
+}
+.picker-color-wrap {
+  width: 24px;
+  height: 24px;
+  position: relative;
+  margin-top: 4px;
+  margin-right: 4px;
+
+  .checkerboard {
+    background-size: auto;
+  }
+}
+.picker-current-color {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
+}
+
+.picker-field {
+  margin-bottom: 8px;
+}
+
+.picker-presets {
+  @include grid-layout-wrapper();
+}
+.picker-presets-color {
+  @include grid-layout-item(10, 7%);
+
+  height: 0;
+  padding-bottom: 7%;
+  flex-shrink: 0;
+  position: relative;
+  cursor: pointer;
+}
+.picker-gradient-presets {
+  @include grid-layout-wrapper();
+}
+.picker-gradient-col {
+  @include grid-layout-item(10, 7%);
+
+  display: flex;
+  flex-direction: column;
+}
+.picker-gradient-color {
+  width: 100%;
+  height: 0;
+  padding-bottom: 100%;
+  position: relative;
+  cursor: pointer;
+}
+</style>

+ 0 - 33
src/components/Popover.vue

@@ -1,33 +0,0 @@
-<template>
-  <APopover trigger="click" overlayClassName="popover">
-    <template v-slot:content>
-      <slot name="content"></slot>
-    </template>
-    <slot></slot>
-  </APopover>
-</template>
-
-<script>
-import { Popover } from 'ant-design-vue'
-
-export default {
-  name: 'popover',
-  components: {
-    APopover: Popover,
-  },
-}
-</script>
-
-<style lang="scss">
-.popover {
-  padding-top: 6px;
-
-  .ant-popover-arrow {
-    display: none;
-  }
-  .ant-popover-inner {
-    border: 1px solid #eee;
-    box-shadow: 3px 3px 3px rgba(#000, 0.15);
-  }
-}
-</style>

+ 13 - 2
src/views/Editor/Toolbar/SlideStylePanel.vue

@@ -1,13 +1,24 @@
 <template>
   <div class="slide-style-panel">
-    slide-style-panel
+    <ColorPicker v-model="color" />
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue'
+import { defineComponent, ref } from 'vue'
+import ColorPicker from '@/components/ColorPicker/index.vue'
 
 export default defineComponent({
   name: 'slide-style-panel',
+  components: {
+    ColorPicker,
+  },
+  setup() {
+    const color = ref('#888')
+
+    return {
+      color,
+    }
+  },
 })
 </script>