Explorar el Código

拖拽图片对组件、文字、开关组件、样式控件组件化

qianduan hace 9 meses
padre
commit
80c055b949

BIN
src/assets/componentIcon/Button.png


BIN
src/assets/componentIcon/Icon.png


BIN
src/assets/componentIcon/Picture.png


BIN
src/assets/componentIcon/Switch.png


BIN
src/assets/componentIcon/Text.png


+ 732 - 0
src/components/colorPicker.vue

@@ -0,0 +1,732 @@
+<template>
+    <!-- 取色器 -->
+    <div class="color-select">
+        <div class="saturation-value" ref="saturation_value" @mousedown="mousedownColorPalette">
+            <div :style="`background-color: hsl(${hue}, 100%, 50%);`">
+                <div class="point" :style="pointStyle"></div>
+            </div>
+            <div class="saturation-value-2"></div>
+            <div class="saturation-value-3"></div>
+        </div>
+        <div class="color-select-middle">
+            <div class="color-slider">
+                <div class="hue-slider slider-item" ref="hue_slider" @mousedown="mousedownHue">
+                    <div class="slider" :style="hueSliderStyle"></div>
+                </div>
+                <div class="alpha-slider slider-item" ref="alpha_slider" @mousedown="mousedownAlpha" v-if="props.alpha">
+                    <div class="slider" :style="alphaSliderStyle"></div>
+                    <div
+                        :style="`background: linear-gradient(to right, rgba(0,0,0,0), ${colorEnums.rgb});width: 100%;height: 100%`">
+                    </div>
+                </div>
+            </div>
+            <div class="color-diamond">
+                <div
+                    :style="`background-color: ${colorEnums.rgba};width: 100%;height: 100%;box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15), inset 0 0 4px rgba(0, 0, 0, .25);`">
+                </div>
+            </div>
+        </div>
+        <div class="color-value">
+            <div class="hex">
+                <label>
+                    <input :value="colorEnums.hex8" @input="hexChange" spellcheck="false" />
+                </label>
+                <p>Hex</p>
+            </div>
+            <div class="rgba-r">
+                <label>
+                    <input :value="red" @input="redChange" />
+                </label>
+                <p>R</p>
+            </div>
+            <div class="rgba-g">
+                <label>
+                    <input :value="green" @input="greenChange" />
+                </label>
+                <p>G</p>
+            </div>
+            <div class="rgba-b">
+                <label>
+                    <input :value="blue" @input="blueChange" />
+                </label>
+                <p>B</p>
+            </div>
+            <div class="rgba-a" v-if="props.alpha">
+                <label>
+                    <input :value="alpha" @input="alphaChange" />
+                </label>
+                <p>A</p>
+            </div>
+        </div>
+        <ul class="predefine">
+            <li class="predefine-item" v-for="(item, index) in predefine" :key="index" :style="`background-color: ${item}`"
+                @click="predefineChange(item)"></li>
+        </ul>
+        <div class="color-actions">
+            <span class="cancel" @click="emits('close')">取消</span>
+            <span class="confirm" @click="handleConfirm">确定</span>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch, onMounted } from 'vue'
+
+const props = defineProps({
+    color: {
+        type: Object || String,
+        default() {
+            return {
+                r: 217,
+                g: 128,
+                b: 95,
+                a: 1
+            }
+        }
+    },
+    predefine: {
+        type: Array,
+        default() {
+            return []
+        }
+    },
+    alpha: {
+        type: Boolean,
+        default: true
+    },
+    mode: {
+        type: String,
+        default: 'hex6' // hex6/hex8/rgb/rgba
+    }
+})
+
+const emits = defineEmits(['update:color', 'close', 'handleConfirm'])
+
+const saturation_value: any = ref(null)
+const hue_slider: any = ref(null)
+const alpha_slider: any = ref(null)
+
+let pointStyle: any = ref('top: 25%;left: 80%;')
+let hueSliderStyle: any = ref('left: 0;')
+let alphaSliderStyle: any = ref('left: calc(100% - 6px);')
+
+let hue: any = ref(0)
+let saturation: any = ref(1)
+let value: any = ref(1)
+
+let red: any = ref(255)
+let green: any = ref(0)
+let blue: any = ref(0)
+
+let alpha: any = ref(1)
+
+onMounted(() => {
+    console.log('parseColor(props.color)', parseColor(props.color))
+
+    let { r, g, b, a }: any = parseColor(props.color)
+    red.value = r
+    green.value = g
+    blue.value = b
+    alpha.value = a
+})
+
+watch(() => props.color, (newValue) => {
+    if (newValue) {
+        let { r, g, b, a }: any = parseColor(newValue)
+        red.value = r
+        green.value = g
+        blue.value = b
+        alpha.value = a
+    }
+}, { immediate: true, deep: true })
+
+watch([red, green, blue], () => {
+    let { h, s, v } = rgb2hsv(red.value, green.value, blue.value)
+
+    hue.value = h
+    saturation.value = s
+    value.value = v
+
+    // 移动背景板圆圈
+    pointStyle.value = `top: ${100 - v * 100}%;left: ${s * 100}%;`
+    // 移动色调滑块
+    hueSliderStyle.value = `left: ${(hue.value / 360) * 100}%;`
+})
+
+watch(alpha, () => {
+    // 移动透明度滑块
+    alphaSliderStyle.value = `left: ${alpha.value >= 1 ? 'calc(100% - 6px)' : alpha.value * 100 + '%'};`
+})
+
+let colorEnums = computed(() => {
+    let r = red.value
+    let g = green.value
+    let b = blue.value
+    let a = alpha.value
+    let h = hue.value
+    let s = saturation.value
+    let v = value.value
+    return {
+        rgb: `rgba(${r},${g},${b})`,
+        rgba: `rgba(${r}, ${g}, ${b}, ${a})`,
+        hex6: rgba2hex(r, g, b),
+        hex8: rgba2hex(r, g, b, a),
+        hsv: `hsv(${h},${s},${v})`
+    }
+})
+
+// 确认选中的颜色值
+const handleConfirm = () => {
+    console.log('props.mode', props.mode)
+    console.log('handleConfirm', (colorEnums.value as any)[props.mode])
+    emits('update:color', (colorEnums.value as any)[props.mode])
+    emits('handleConfirm', (colorEnums.value as any)[props.mode])
+}
+
+// 输入框值变化,限制输入的值
+function hexChange(e) {
+    let v = e.target.value
+    if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) {
+        let { r, g, b, a }: any = hex2rgba(v)
+        red.value = r
+        green.value = g
+        blue.value = b
+        alpha.value = a
+    }
+}
+
+function redChange(e) {
+    let v = e.target.value
+    if (v !== '') {
+        v > 255 && (red.value = 255)
+        v < 0 && (red.value = 0)
+        v >= 0 && v <= 255 && (red.value = parseInt(v))
+    }
+}
+
+function greenChange(e) {
+    let v = e.target.value
+    if (v !== '') {
+        v > 255 && (green.value = 255)
+        v < 0 && (green.value = 0)
+        v >= 0 && v <= 255 && (green.value = parseInt(v))
+    }
+}
+
+function blueChange(e) {
+    let v = e.target.value
+    if (v !== '') {
+        v > 255 && (blue.value = 255)
+        v < 0 && (blue.value = 0)
+        v >= 0 && v <= 255 && (blue.value = parseInt(v))
+    }
+}
+
+function alphaChange(e) {
+    let v = e.target.value
+    if (v !== '') {
+        v = parseFloat(v)
+        alpha.value = v
+        v > 1 && (alpha.value = 1)
+        v < 0 && (alpha.value = 0)
+        v >= 0 && v <= 1 && (alpha.value = v)
+    }
+}
+
+// 点击预设方块事件
+function predefineChange(item) {
+    if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(item)) {
+        let { r, g, b, a }: any = hex2rgba(item)
+        red.value = r
+        green.value = g
+        blue.value = b
+        alpha.value = a
+    }
+}
+
+// 计算选中点的颜色值
+function handleChangeColorPalette(e) {
+    let w = saturation_value.value.clientWidth
+    let h = saturation_value.value.clientHeight
+    let x = e.pageX - saturation_value.value.getBoundingClientRect().left
+    let y = e.pageY - saturation_value.value.getBoundingClientRect().top
+    x = x < w && x > 0 ? x : x > w ? w : 0
+    y = y < h && y > 0 ? y : y > h ? h : 0
+    // 计算饱和度和亮度
+    saturation.value = Math.floor((x / w) * 100 + 0.5) / 100
+    value.value = Math.floor((1 - y / h) * 100 + 0.5) / 100
+    // hsv转化为rgb
+    let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value)
+    red.value = r
+    green.value = g
+    blue.value = b
+    // 移动背景板圆圈
+    pointStyle.value = `top: ${y}px;left: ${x}px;`
+}
+
+function mousedownColorPalette(e) {
+    // 鼠标按下计算饱和度和亮度并添加事件
+    handleChangeColorPalette(e)
+    // 添加整个页面的鼠标事件
+    window.addEventListener('mousemove', handleChangeColorPalette)
+    window.addEventListener('mouseup', mouseupColorPalette)
+}
+
+function mouseupColorPalette() {
+    // 鼠标松开后移除事件
+    window.removeEventListener('mousemove', handleChangeColorPalette)
+    window.removeEventListener('mouseup', mouseupColorPalette)
+}
+
+// 色调
+function handleChangeHue(e) {
+    let w = hue_slider.value.clientWidth
+    let x = e.pageX - saturation_value.value.getBoundingClientRect().left
+    x = x < w && x > 0 ? x : x > w ? w : 0
+    // 计算色调
+    hue.value = Math.floor((x / w) * 360 + 0.5)
+    // hsv转化为rgb
+    let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value)
+    red.value = r
+    green.value = g
+    blue.value = b
+    // 移动滑块
+    hueSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`
+}
+
+function mousedownHue(e) {
+    handleChangeHue(e)
+    window.addEventListener('mousemove', handleChangeHue)
+    window.addEventListener('mouseup', mouseupHue)
+}
+
+function mouseupHue() {
+    window.removeEventListener('mousemove', handleChangeHue)
+    window.removeEventListener('mouseup', mouseupHue)
+}
+
+// 透明度
+function handleChangeAlpha(e) {
+    let w = alpha_slider.value.clientWidth
+    let x = e.pageX - saturation_value.value.getBoundingClientRect().left
+    x = x < w && x > 0 ? x : x > w ? w : 0
+    // 计算透明度
+    alpha.value = Math.floor((x / w) * 100 + 0.5) / 100
+    // 移动滑块
+    alphaSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`
+}
+
+function mousedownAlpha(e) {
+    handleChangeAlpha(e)
+    window.addEventListener('mousemove', handleChangeAlpha)
+    window.addEventListener('mouseup', mouseupAlpha)
+}
+
+function mouseupAlpha() {
+    window.removeEventListener('mousemove', handleChangeAlpha)
+    window.removeEventListener('mouseup', mouseupAlpha)
+}
+
+/**
+ * 解析输入的数据,只能解析hex颜色和rgb对象形式的数据
+ * @param color
+ */
+function parseColor(color) {
+    if (color) {
+        let r, g, b, a
+        if (typeof color === 'string') {
+            if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3}|[0-9a-fA-F]{4})$/.test(color)) {
+                return hex2rgba(color)
+            } else if (color.includes('linear-gradient')) {
+                console.log('111parseColor111', color)
+                let matchColors = color.match(/#[0-9a-fA-F]{6}/g)
+                console.log('matchColors', matchColors)
+                let avgColor = getAvgColor(matchColors)
+                console.log('avgColor', avgColor)
+                return hex2rgba(avgColor)
+            }
+        } else {
+            r = color.r > 255 ? 255 : color.r < 0 ? 0 : color.r
+            g = color.g > 255 ? 255 : color.g < 0 ? 0 : color.g
+            b = color.b > 255 ? 255 : color.b < 0 ? 0 : color.b
+            a = color.a > 1 ? 1 : color.a < 0 ? 0 : color.a
+            return { r, g, b, a }
+        }
+    } else {
+        return null
+    }
+}
+
+function hsv2rgb(h, s, v) {
+    h === 360 && (h = 0)
+    let i = Math.floor(h / 60) % 6
+    let f = h / 60 - i
+    let p = v * (1 - s)
+    let q = v * (1 - s * f)
+    let t = v * (1 - s * (1 - f))
+    let r, g, b
+    if (i === 0) {
+        r = v
+        g = t
+        b = p
+    } else if (i === 1) {
+        r = q
+        g = v
+        b = p
+    } else if (i === 2) {
+        r = p
+        g = v
+        b = t
+    } else if (i === 3) {
+        r = p
+        g = q
+        b = v
+    } else if (i === 4) {
+        r = t
+        g = p
+        b = v
+    } else if (i === 5) {
+        r = v
+        g = p
+        b = q
+    }
+    r = Math.floor(r * 255 + 0.5)
+    g = Math.floor(g * 255 + 0.5)
+    b = Math.floor(b * 255 + 0.5)
+    return { r, g, b }
+}
+
+function rgb2hsv(r, g, b) {
+    let r1 = r / 255
+    let g1 = g / 255
+    let b1 = b / 255
+    let cmax = Math.max(r1, g1, b1)
+    let cmin = Math.min(r1, g1, b1)
+    let d = cmax - cmin
+    let h, s, v
+    if (d === 0) {
+        h = 0
+    } else if (cmax === r1) {
+        h = ((60 * (g1 - b1)) / d + 360) % 360
+    } else if (cmax === g1) {
+        h = 60 * ((b1 - r1) / d + 2)
+    } else if (cmax === b1) {
+        h = 60 * ((r1 - g1) / d + 4)
+    }
+    if (cmax === 0) {
+        s = 0
+    } else {
+        s = d / cmax
+    }
+    v = cmax
+    h = Math.floor(h + 0.5)
+    s = Math.floor(s * 100 + 0.5) / 100
+    v = Math.floor(v * 100 + 0.5) / 100
+    return { h, s, v }
+}
+
+function rgba2hex(r, g, b, a: any = 1) {
+    r = parseInt(r)
+    let r1 = r.toString(16).length !== 2 ? '0' + r.toString(16) : r.toString(16)
+    g = parseInt(g)
+    let g1 = g.toString(16).length !== 2 ? '0' + g.toString(16) : g.toString(16)
+    b = parseInt(b)
+    let b1 = b.toString(16).length !== 2 ? '0' + b.toString(16) : b.toString(16)
+    a = parseFloat(a)
+    let a1 = ''
+    if (a !== 1) {
+        let temp = Math.floor(256 * a)
+        a1 = temp.toString(16).length !== 2 ? '0' + temp.toString(16) : temp.toString(16)
+    }
+    return `#${r1}${g1}${b1}${a1}`.toUpperCase()
+}
+
+function hex2rgba(s) {
+    console.log('111111', s)
+    if (/^#?[0-9a-fA-F]{3}$/.test(s)) {
+        let b = s.substring(s.length - 1, s.length)
+        let g = s.substring(s.length - 2, s.length - 1)
+        let r = s.substring(s.length - 3, s.length - 2)
+        return hex2rgba(`${r + r}${g + g}${b + b}`)
+    }
+    if (/^#?[0-9a-fA-F]{4}$/.test(s)) {
+        let a = s.substring(s.length - 1, s.length)
+        let b = s.substring(s.length - 2, s.length - 1)
+        let g = s.substring(s.length - 3, s.length - 2)
+        let r = s.substring(s.length - 4, s.length - 3)
+        return hex2rgba(`${r + r}${g + g}${b + b}${a + a}`)
+    }
+    if (/^#?[0-9a-fA-F]{6}$/.test(s)) {
+        let b = parseInt('0x' + s.substring(s.length - 2, s.length))
+        let g = parseInt('0x' + s.substring(s.length - 4, s.length - 2))
+        let r = parseInt('0x' + s.substring(s.length - 6, s.length - 4))
+        return { r, g, b, a: 1 }
+    }
+    if (/^#?[0-9a-fA-F]{8}$/.test(s)) {
+        let a = parseInt('0x' + s.substring(s.length - 2, s.length))
+        a = a / 255
+        let b = parseInt('0x' + s.substring(s.length - 4, s.length - 2))
+        let g = parseInt('0x' + s.substring(s.length - 6, s.length - 4))
+        let r = parseInt('0x' + s.substring(s.length - 8, s.length - 6))
+        return { r, g, b, a }
+    }
+}
+
+function getAvgColor(arr) {
+    try {
+        let parseColor = function (hexStr) {
+            return hexStr.length === 4
+                ? hexStr
+                    .substr(1)
+                    .split('')
+                    .map(function (s) {
+                        return 0x11 * parseInt(s, 16)
+                    })
+                : [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(function (s) {
+                    return parseInt(s, 16)
+                })
+        }
+
+        let pad = function (s) {
+            return s.length === 1 ? '0' + s : s
+        }
+
+        let gradientColors: any = function (start, end, steps, gamma) {
+            let i
+            let j
+            let ms
+            let me
+            let output: any = []
+            let so: any = []
+            gamma = gamma || 1
+            let normalize = function (channel) {
+                return Math.pow(channel / 255, gamma)
+            }
+            start = parseColor(start).map(normalize)
+            end = parseColor(end).map(normalize)
+            for (i = 0; i < steps; i++) {
+                ms = i / (steps - 1)
+                me = 1 - ms
+                for (j = 0; j < 3; j++) {
+                    so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16))
+                }
+                output.push('#' + so.join(''))
+            }
+            return output
+        }
+        return gradientColors(arr[0], arr[1], 3)[1]
+    } catch (err) {
+        return arr[0]
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.color-select {
+    position: relative;
+    user-select: none;
+    width: 300px;
+    background: #fff;
+    padding: 10px;
+    /*border: 1px solid #ccc;*/
+    /*border-radius: 10px;*/
+}
+
+/* 饱和度和亮度 */
+.saturation-value {
+    cursor: pointer;
+    width: 100%;
+    height: 200px;
+    position: relative;
+    margin-bottom: 10px;
+    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
+}
+
+.saturation-value>div {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+}
+
+/* 圆圈 */
+.point {
+    box-sizing: border-box;
+    width: 6px;
+    height: 6px;
+    background-color: transparent;
+    border: 2px solid #ccc;
+    border-radius: 50%;
+    transform: translate(-50%, -50%);
+    position: absolute;
+    z-index: 9;
+}
+
+.saturation-value-2 {
+    background: linear-gradient(to right, white, #ffffff00);
+}
+
+.saturation-value-3 {
+    background: linear-gradient(to top, black, #ffffff00);
+}
+
+/* 色调 透明度 */
+.color-select-middle {
+    width: 100%;
+    display: flex;
+    margin-bottom: 10px;
+}
+
+.slider-item+.slider-item {
+    margin-top: 6px;
+}
+
+/* 色调滑块条 */
+.hue-slider {
+    position: relative;
+    height: 10px;
+    background: linear-gradient(90deg, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red);
+    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
+    width: 100%;
+}
+
+/* 透明度滑块条 */
+.alpha-slider {
+    position: relative;
+    height: 10px;
+    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
+    background: #fff url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==');
+    background-size: 10px 10px;
+    width: 100%;
+}
+
+/* 滑块 */
+.slider {
+    position: absolute;
+    box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
+    box-sizing: border-box;
+    width: 6px;
+    height: 100%;
+    background-color: #fff;
+}
+
+.color-slider {
+    flex: auto;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+}
+
+/* 颜色方块 */
+.color-diamond {
+    position: relative;
+    margin-left: 5px;
+    width: 26px;
+    height: 26px;
+    border-radius: 3px;
+    overflow: hidden;
+    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==');
+    background-size: 10px 10px;
+}
+
+/* 颜色的值 hex rgba */
+.color-value {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+}
+
+.color-value div {
+    padding: 0 3px;
+    text-align: center;
+}
+
+.color-value input {
+    font-size: 12px;
+    box-sizing: border-box;
+    width: 34px;
+    height: 24px;
+    padding: 0;
+    margin: 0;
+    outline: none;
+    text-align: center;
+    border-radius: 3px;
+    border: 1px solid #ccc;
+}
+
+.color-value p {
+    font-size: 12px;
+    margin: 3px 0 0;
+}
+
+.color-value .rgba-a {
+    padding-right: 0;
+}
+
+.color-value .hex {
+    flex: 1;
+    padding-left: 0;
+}
+
+.color-value .hex input {
+    width: 100%;
+    height: 24px;
+}
+
+/* 预设颜色  */
+.predefine {
+    width: 100%;
+    padding: 0;
+    margin: 10px 0 0;
+    list-style: none;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+}
+
+.predefine-item {
+    width: 20px;
+    height: 20px;
+    margin-bottom: 6px;
+    border: 1px solid #ccc;
+    border-radius: 6px;
+}
+
+.predefine-item+.predefine-item {
+    margin-left: 6px;
+}
+
+.predefine-item:nth-child(12n) {
+    margin-left: 0;
+}
+
+.color-actions {
+    font-size: 12px;
+    text-align: right;
+}
+
+.color-actions span {
+    cursor: pointer;
+    padding: 5px 12px;
+    line-height: 12px;
+    display: inline-block;
+    box-sizing: border-box;
+    border: 1px solid transparent;
+}
+
+.color-actions .cancel:hover {
+    background-color: #f5f7fa;
+}
+
+.color-actions .confirm {
+    border-color: #dcdfe6;
+    border-radius: 4px;
+    margin-left: 10px;
+}
+
+.color-actions .confirm:hover {
+    color: #1677ff;
+    border-color: #1677ff;
+}
+</style>

+ 32 - 26
src/components/drag/resizable.vue

@@ -1,6 +1,7 @@
 <template>
-    <Vue3DraggableResizable :snap="true" classNameActive="active" :key="dargData.soleId" :initW="initW" :initH="initH" v-model:x="left"
-        v-model:y="top" v-model:w="w" v-model:h="h" :active="active" :handles="sticks" :draggable="true" :resizable="true"
+    <Vue3DraggableResizable :classNameDraggable="dargData.config.lineNum ? 'forceAutoHeight' : ''" :snap="true"
+        classNameActive="active" :key="dargData.soleId" :initW="initW" :initH="initH" v-model:x="left" v-model:y="top"
+        v-model:w="w" v-model:h="h" v-model:active="active" :handles="sticks" :draggable="draggable" :resizable="resizable"
         @drag-start="print('drag-start')" @resize-start="print('resize-start')" @resizing="print('resizing')"
         @dragging="print('dragging')" @drag-end="print('drag-end')" @resize-end="print('resize-end')" @activated="activated"
         @deactivated="deactivated">
@@ -16,10 +17,12 @@ import { ref, watch } from "vue";
 const initW = ref(0)
 const initH = ref(0)
 const w = ref(0)
-const h = ref(0)
+const h: any = ref(null)
 const top = ref(0)
 const left = ref(0)
-const isResizable = ref(false)
+const isResizable = ref(false) //禁止缩放
+const draggable = ref(false) // 组件是否可拖动
+const resizable = ref(false) // 组件是否可调整大小
 const sticks = ref([])
 const active = ref(true)
 const currentItem: any = ref({})
@@ -31,28 +34,25 @@ const props = defineProps({
     index: Number,
 })
 // 监听 props.dargData 的变化
-watch(() => currentItem.value, (newValue) => {
-    initW.value = newValue.css.width
-    initH.value = newValue.css.height
-    w.value = newValue.css.width
-    h.value = newValue.css.height
-    top.value = newValue.css.top
-    left.value = newValue.css.left
-}, { immediate: false, deep: true })
-
-currentItem.value = JSON.parse(JSON.stringify(props.dargData))
-initW.value = currentItem.value.css.width
-initH.value = currentItem.value.css.height
-w.value = currentItem.value.css.width
-h.value = currentItem.value.css.height
-top.value = currentItem.value.css.top
-left.value = currentItem.value.css.left
-isResizable.value = currentItem.value.config.isResizable
-sticks.value = currentItem.value.config.sticks
-active.value = currentItem.value.active
+watch(() => props.dargData, (newValue) => {
+    if (newValue.css) {
+        currentItem.value = newValue
+        initW.value = newValue.css.width
+        initH.value = newValue.css.height
+        w.value = newValue.css.width
+        h.value = newValue.css.height
+        top.value = newValue.css.top
+        left.value = newValue.css.left
+        draggable.value = newValue.config.draggable
+        resizable.value = newValue.config.resizable
+        isResizable.value = newValue.config.isResizable
+        sticks.value = newValue.config.sticks
+        active.value = newValue.active
+    }
+}, { immediate: true, deep: true })
 
 // 子传父
-const emit = defineEmits(['passId', 'modifiedItem']);
+const emit = defineEmits(['passId', 'modifiedItem', 'inactivation']);
 // 修改项
 function print(val) {
     currentItem.value.css.width = w.value
@@ -72,12 +72,14 @@ const getWidget = (name: string) => {
 function activated() {
     const arr = {
         index: props.index,
-        soleId: props.dargData.soleId
+        soleId: props.dargData.soleId,
+        option: currentItem.value
     }
     emit('passId', arr);
 }
+// 失去活跃状态
 function deactivated() {
-    console.log(active.value, '失活');
+    emit('inactivation', currentItem.value);
 }
 </script>
 
@@ -85,4 +87,8 @@ function deactivated() {
 .vdr-container {
     box-sizing: unset;
 }
+
+.forceAutoHeight {
+    height: auto !important;
+}
 </style>

+ 53 - 0
src/components/iconLibrary.vue

@@ -0,0 +1,53 @@
+<script setup lang="ts">
+
+import { defineProps } from "vue";
+defineProps(["list"])
+const emits = defineEmits(['selectIcon'])
+
+function getAssetsImages(name) {
+    var arr = '../assets/iconData/' + name + '.png'
+    return new URL(`${arr}`, import.meta.url).href;
+}
+// 选择图标
+function selectIcon(params: any) {
+    emits('selectIcon', params)
+}
+</script>
+
+<template>
+    <div class="card_icon_library">
+        <div class="card_images_icon center_in" v-for="(item, index) in list" :key="index" @click="selectIcon(item)">
+            <el-image class="images_icon" :src="getAssetsImages(item.icon)" fit="contain" />
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.card_icon_library {
+    display: flex;
+}
+
+.card_images_icon {
+    position: relative;
+    display: flex;
+    -webkit-box-pack: center;
+    justify-content: center;
+    -webkit-box-align: center;
+    align-items: center;
+    width: 45px;
+    height: 45px;
+    margin-right: 3px;
+    cursor: pointer;
+}
+
+.card_images_icon:hover {
+    color: rgb(255, 255, 255);
+    background: rgb(15, 115, 230);
+    box-shadow: 0 2px 12px 0 rgba(15, 115, 230, 0.5)
+}
+
+.images_icon {
+    width: 24px;
+    height: 24px;
+}
+</style>

+ 73 - 0
src/components/maskedbox/boxSize.vue

@@ -0,0 +1,73 @@
+<script setup lang="ts">
+import { ref, watch } from "vue";
+const props = defineProps({
+    width: {
+        type: Number,
+        required: true
+    },
+    height: {
+        type: Number,
+        required: true
+    },
+})
+const widthNum: any = ref()
+const heightNum: any = ref()
+const lockFlag = ref(true)
+
+watch(() => [props.width, props.height], (newValue) => {
+    if (newValue) {
+        widthNum.value = newValue[0];
+        heightNum.value = newValue[1];
+    }
+}, { immediate: true, deep: true })
+
+const emit = defineEmits(['update:width', 'update:height']);
+function widthChange(params: any) {
+    const arr = params
+    emit('update:width', arr);
+}
+function heightChange(params: any) {
+    const arr = params
+    emit('update:width', arr);
+}
+function getLock(params: any) {
+    if (params) {
+        lockFlag.value = false
+    } else {
+        lockFlag.value = true
+    }
+}
+</script>
+
+<template>
+    <div class="same_row_in upAndDown_padding">
+        <span class="title_12 w_60 default_size">尺寸</span>
+        <div class="grey_input_bgc">
+            <el-input-number class="card_number" v-model="widthNum" :min="1" size="small" controls-position="right"
+                @change="widthChange" />
+            <span>W</span>
+        </div>
+        <div class="card_lock center_in" @click="getLock(lockFlag)">
+            <el-icon v-if="lockFlag">
+                <Unlock />
+            </el-icon>
+            <el-icon v-else>
+                <Lock />
+            </el-icon>
+        </div>
+        <div class="grey_input_bgc">
+            <el-input-number class="card_number" :disabled="true" v-model="heightNum" :min="1" size="small"
+                controls-position="right" @change="heightChange" />
+            <span>H</span>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.card_lock {
+    flex: 0 0 auto;
+    cursor: pointer;
+    width: 30px;
+}
+
+</style>

+ 70 - 5
src/components/maskedbox/colorSelection.vue

@@ -1,7 +1,15 @@
 <template>
     <div class="same_row_in upAndDown_padding">
         <span class="title_12 w_60 default_size" style="flex: none;">{{ title }}</span>
-        <el-color-picker v-model="iconColoril" show-alpha @change="handleColorChange" />
+        <!-- <el-color-picker v-model="iconColoril" show-alpha @change="handleColorChange" /> -->
+        <el-popover v-model:visible="colorPickerVisible" popper-class="down-popover" placement="right" :width="300"
+            trigger="click" @mousedown.stop>
+            <template #reference>
+                <div :style="{ backgroundColor: iconColoril }" class="card_color_picker"></div>
+            </template>
+            <colorPicker :color="colorPickerColor" mode="rgba" @mousedown.stop @close="closePicker"
+                @handleConfirm="handleConfirm"></colorPicker>
+        </el-popover>
         <div class="leftmargin_8">
             <el-input v-model="hexColor" size="small">
                 <template #prefix>
@@ -9,7 +17,7 @@
                 </template>
             </el-input>
         </div>
-        <div class="grey_input_bgc leftmargin_8">
+        <div class="grey_input_bgc number_bgc_grey leftmargin_8">
             <el-input-number class="card_number" v-model="alpha" :min="0" :max="1" :step="0.1" size="small"
                 controls-position="right" @change="colorNum" />
             <span>A</span>
@@ -19,6 +27,8 @@
 
 <script setup lang="ts">
 import { ref, watch } from "vue";
+import colorPicker from "../../components/colorPicker.vue";
+
 import color from 'color'
 const props = defineProps({
     iconColor: {
@@ -29,9 +39,13 @@ const props = defineProps({
         type: String,
     }
 })
+const colorPickerVisible = ref(false)
+// 取色器按钮背景色
 const iconColoril: any = ref()
-const alpha = ref(100);
+const alpha = ref(1);
+// hex输入框颜色
 const hexColor = ref('');
+const colorPickerColor: any = ref('')
 // rgba hex颜色格式化
 const handleColorChange = (rgbaStr) => {
     const hex = color(rgbaStr).hex();
@@ -51,6 +65,8 @@ const colorNum = (value: any) => {
     const rgbaStr = props.iconColor;
     const newAlpha = value;
     const arr = adjustAlpha(rgbaStr, newAlpha);
+
+    colorPickerColor.value = formattedColor(arr)
     // 更新父组件值
     emit('update:iconColor', arr);
     function adjustAlpha(rgbaStr: string, alpha: number): string {
@@ -61,15 +77,64 @@ const colorNum = (value: any) => {
         const [r, g, b] = [match[1], match[2], match[3]].map(Number);
         return `rgba(${r}, ${g}, ${b}, ${alpha})`;
     }
+};
+// 取色器选择颜色
+function handleConfirm(params: any) {
+    // 初始化hex颜色
+    colorPickerVisible.value = false
+    emit('update:iconColor', params);
+}
+// 关闭颜色取色器
+function closePicker() {
+    colorPickerVisible.value = false
+}
+// 对象rgba
+function formattedColor(rgbaStr: string): string {
+    const match = rgbaStr.match(/^rgba\((\d+), (\d+), (\d+), ([^\)]+)\)$/);
+    if (!match) {
+        throw new Error('Invalid RGBA string');
+    }
+    const [r, g, b, a] = [match[1], match[2], match[3], match[4]].map(Number);
+    let arr: any = {
+        r: r,
+        g: g,
+        b: b,
+        a: a,
+    }
+    return arr;
 }
-
 watch(() => props.iconColor, (newValue) => {
     if (newValue) {
+        console.log(newValue, 25);
         iconColoril.value = newValue;
         // 初始化hex颜色
         hexColor.value = handleColorChange(newValue)
+        // 初始化取色器颜色
+        colorPickerColor.value = formattedColor(newValue)
     }
 }, { immediate: true, deep: true })
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss">
+.down-popover {
+    padding: 0 !important;
+}
+</style>
+<style lang="scss" scoped>
+.card_color_picker {
+    flex: none;
+    box-sizing: border-box;
+    border: 1px solid rgba(0, 0, 0, 0.2);
+    width: 40px;
+    height: 24px;
+    background-size: contain;
+    background-repeat: no-repeat;
+    background-position: center center;
+    cursor: pointer;
+    border-radius: 3px;
+}
+
+.number_bgc_grey {
+    width: 75px !important;
+}
+</style>

+ 59 - 0
src/components/maskedbox/fontSize.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { ref, watch } from "vue";
+const props = defineProps({
+    fontSize: {
+        type: Number,
+        required: true
+    },
+    fontWeight: {
+        type: Number,
+        required: true
+    },
+})
+const lineNum: any = ref()
+const fontWeightNum: any = ref()
+const fontWeightData = [{
+    label: '细体',
+    value: 300,
+}, {
+    label: '常规',
+    value: 400,
+}, {
+    label: '粗体',
+    value: 500,
+}]
+
+watch(() => [props.fontSize, props.fontWeight], (newValue) => {
+    if (newValue) {
+        lineNum.value = newValue[0];
+        fontWeightNum.value = newValue[1];
+    }
+}, { immediate: true, deep: true })
+
+const emit = defineEmits(['update:fontSize', 'update:fontWeight']);
+function numberChange(params: any) {
+    const arr = params
+    emit('update:fontSize', arr);
+}
+function weightChange(params: any) {
+    const arr = params
+    emit('update:fontWeight', arr);
+}
+</script>
+
+<template>
+    <div class="same_row_in upAndDown_padding">
+        <span class="title_12 w_60 default_size" style="flex: none;">字体</span>
+        <div class="grey_input_bgc w_60_index">
+            <el-input-number class="card_number" v-model="lineNum" :min="1" size="small" controls-position="right"
+                @change="numberChange" />
+        </div>
+        <div class="card_select_bg" style="width: 100%;">
+            <el-select v-model="fontWeightNum" placeholder="字体粗细" size="small" @change="weightChange">
+                <el-option @mousedown.stop v-for="item in fontWeightData" :key="item.value" :label="item.label" :value="item.value" />
+            </el-select>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped></style>

+ 9 - 3
src/components/maskedbox/iconSize.vue

@@ -5,6 +5,10 @@ const props = defineProps({
         type: Number,
         required: true
     },
+    iconName: {
+        type: Number,
+        required: true
+    },
 })
 const lineNum: any = ref()
 
@@ -19,10 +23,13 @@ function numberChange(params: any) {
     const arr = params
     emit('update:num', arr);
 }
-
 function selectIcon() {
     emit('childEvent');
 }
+function getAssetsImages(name) {
+    var arr = '../../assets/iconData/' + name + '.png'
+    return new URL(`${arr}`, import.meta.url).href;
+}
 </script>
 
 <template>
@@ -30,8 +37,7 @@ function selectIcon() {
         <div class="same_row_in upAndDown_padding">
             <span class="title_12 w_60 default_size" style="flex: none;">大小</span>
             <div class="images_card center_in" @click="selectIcon">
-                <el-image style="width: 16px; height: 16px"
-                    src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" fit="fill" />
+                <el-image style="width: 16px; height: 16px" :src="getAssetsImages(iconName)" fit="fill" />
             </div>
             <div class="grey_input_bgc w_60_index">
                 <el-input-number class="card_number" v-model="lineNum" :min="1" size="small" controls-position="right"

+ 1 - 1
src/components/maskedbox/lineNumber.vue

@@ -2,7 +2,7 @@
     <div class="same_row_in upAndDown_padding">
         <span class="title_12 w_60 default_size" style="flex: none;">行数</span>
         <div class="grey_input_bgc w_60_index">
-            <el-input-number class="card_number" v-model="lineNum" size="small" controls-position="right"
+            <el-input-number class="card_number" v-model="lineNum" min="0" size="small" controls-position="right"
                 @change="bindingChange" />
         </div>
         <div class="card_alignment">

+ 37 - 0
src/components/maskedbox/literalName.vue

@@ -0,0 +1,37 @@
+<script setup lang="ts">
+import { ref, watch } from "vue";
+const props = defineProps({
+    name: {
+        type: Number,
+        required: true
+    },
+})
+const nameText: any = ref()
+
+watch(() => props.name, (newValue) => {
+    if (newValue) {
+        nameText.value = newValue;
+    }
+}, { immediate: true, deep: true })
+
+const emit = defineEmits(['update:name']);
+function textChange(params: any) {
+    const arr = params
+    emit('update:name', arr);
+}
+</script>
+
+<template>
+    <div class="same_row_in upAndDown_padding">
+        <span class="title_12 w_60 default_size">名称</span>
+        <div class="text_input_name">
+            <el-input v-model="nameText" size="small" :placeholder="name" @input="textChange" />
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.text_input_name {
+    width: calc(100% - 60px);
+}
+</style>

+ 3 - 0
src/components/maskedbox/textAlignment.vue

@@ -31,12 +31,15 @@ const arrList: any = ref([{
     icon: 'icon-textRight',
     type: 'right',
 }])
+const emit = defineEmits(['update:align']);
+
 onMounted(() => {
     selectAlignment(props.align)
 })
 function selectAlignment(type: any) {
     arrList.value.forEach(item => {
         if (item.type == type) {
+            emit('update:align', type);
             item.flag = true
         } else {
             item.flag = false

+ 11 - 0
src/components/widgets/Button.vue

@@ -0,0 +1,11 @@
+<template>
+  <div style="width: 50px;font-size: 30px;">button1{{ input }}</div>
+</template>
+
+<script setup lang="ts">
+
+import { ref } from "vue";
+const input = ref('')
+</script>
+
+<style lang="scss" scoped></style>

+ 11 - 0
src/components/widgets/Icon.vue

@@ -0,0 +1,11 @@
+<template>
+  <div style="width: 50px;font-size: 30px;">icon{{ input }}</div>
+</template>
+
+<script setup lang="ts">
+
+import { ref } from "vue";
+const input = ref('')
+</script>
+
+<style lang="scss" scoped></style>

+ 11 - 0
src/components/widgets/Picture.vue

@@ -0,0 +1,11 @@
+<template>
+  <div style="width: 50px;font-size: 30px;">pictre{{ input }}</div>
+</template>
+
+<script setup lang="ts">
+
+import { ref } from "vue";
+const input = ref('')
+</script>
+
+<style lang="scss" scoped></style>

+ 1 - 5
src/components/widgets/SceneButton.vue

@@ -2,9 +2,6 @@
     <div class="center_in">
         <div class="card_btn center_in" :style="{ width: btndata.css.width + 'px', height: btndata.css.height + 'px' }">
             <div class="bg_item_btn center_in">
-                <!-- <el-icon :size="config.config.size" :color="config.css.iconColor">
-                    <BellFilled />
-                </el-icon> -->
                 <div class="icon_card_btn"
                     :style="{ width: btndata.config.size + 'px', height: btndata.config.size + 'px', }">
                     <el-image class="images_icon"
@@ -19,7 +16,7 @@
   
 <script lang="ts" setup>
 import { defineProps, watch, ref } from "vue";
-const props = defineProps(["config", "sendSon"])
+const props = defineProps(["config"])
 
 const btndata: any = ref({})
 function getAssetsImages(name) {
@@ -28,7 +25,6 @@ function getAssetsImages(name) {
 }
 
 watch(() => props.config, (newValue) => {
-    console.log(newValue, 889);
     if (newValue) {
         btndata.value = newValue
     }

+ 15 - 0
src/components/widgets/Switch.vue

@@ -0,0 +1,15 @@
+<template>
+  <el-switch style="width: 40px;height: 20px;pointer-events: none;" v-model="value" />
+</template>
+
+<script setup lang="ts">
+
+import { ref } from "vue";
+const value = ref(false)
+</script>
+
+<style lang="scss">
+.el-switch {
+  vertical-align: unset;
+}
+</style>

+ 26 - 0
src/components/widgets/Text.vue

@@ -0,0 +1,26 @@
+<template>
+  <div :class="configData.config.lineNum != null ? 'text_word' : ''"
+    :style="{ width: configData.css.width + 'px', height: configData.css.height + 'px', fontSize: configData.css.fontSize + 'px', fontWeight: configData.css.fontWeight, textAlign: configData.css.textAlign, }">
+    {{ configData.name }}</div>
+</template>
+
+<script setup lang="ts">
+import { defineProps, watch, ref } from "vue";
+const props = defineProps(["config"])
+
+const configData: any = ref({})
+watch(() => props.config, (newValue) => {
+  if (newValue) {
+    configData.value = newValue
+  }
+}, { immediate: true, deep: true })
+</script>
+
+<style lang="scss" scoped>
+.text_word {
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>

+ 1 - 1
src/components/widgets/getWidget.ts

@@ -6,6 +6,6 @@ for (let each in modules) {
     gets[name] = (modules[each] as any).default
 }
 
-// console.log(gets);
+console.log(gets);
 
 export default gets

+ 1 - 2
src/module/config/button.ts

@@ -1,7 +1,6 @@
 const btnConfig = {
     isActive: false,
-    type: 1,
-    name: '文本',
+    name: '场景',
     size: 30,
     img: 'set',
     lineNum: null,

+ 0 - 1
src/module/config/input.ts

@@ -1,7 +1,6 @@
 const configList1 = {
     isActive: false,
     type: 1,
-    name: '文本',
     size: 30,
     color: "#158cfb",
     isResizable: true, //禁止缩放

+ 18 - 0
src/module/config/switch.ts

@@ -0,0 +1,18 @@
+const switchConfig = {
+    isActive: false,
+    isResizable: true, //禁止缩放
+    aspectRatio: false, //禁止等比例缩放
+    draggable: true, //组件是否可拖动
+    resizable: false, //禁止调整大小
+}
+
+const switchCss = {
+    top: 0,
+    left: 0,
+    width: 40,
+    height: 20,
+    colorOpen: "rgba(61, 61, 61, 1)",
+    colorClose: "rgba(61, 61, 61, 1)",
+}
+
+export { switchConfig, switchCss }

+ 23 - 0
src/module/config/text.ts

@@ -0,0 +1,23 @@
+const TextConfig = {
+    name: '文字',
+    isActive: false,
+    type: 1,
+    lineNum: null,
+    isResizable: true, //禁止缩放
+    sticks: ['mr', 'ml'],
+    resizable: true, //禁止调整大小
+    aspectRatio: false, //禁止等比例缩放
+}
+
+const TextCss = {
+    top: 200,
+    left: 30,
+    width: 100,
+    height: 'auto',
+    fontSize: 14,
+    fontWeight: 500,
+    textAlign: 'center',
+    color: "rgba(61, 61, 61, 1)",
+}
+
+export { TextConfig, TextCss }

+ 34 - 5
src/module/index.ts

@@ -1,25 +1,54 @@
 //不同模块组件
+import { TextConfig, TextCss } from "./config/text";
+import { switchConfig, switchCss } from "./config/switch";
 import { btnConfig, btnCss } from "./config/button";
 import { configList1, cssList1 } from "./config/input";
 
+const baseComponent = [{
+    id: 5, type: 'Text', name: "文字", active: false,
+    displayPicture: true,
+    config: TextConfig,
+    css: TextCss,
+}, {
+    id: 6, type: 'Switch', name: "开关", active: false,
+    displayPicture: true,
+    config: switchConfig,
+    css: switchCss,
+}, {
+    id: 7, type: 'Button', name: "按钮", active: false,
+    displayPicture: true,
+    config: btnConfig,
+    css: btnCss,
+}, {
+    id: 8, type: 'Icon', name: "图标", active: false,
+    displayPicture: true,
+    config: btnConfig,
+    css: btnCss,
+}, {
+    id: 9, type: 'Picture', name: "图片", active: false,
+    displayPicture: true,
+    config: btnConfig,
+    css: btnCss,
+}]
+
 const attributeValue = [{
     id: 1, type: 'Input', name: "输入框3", active: false,
     config: configList1,
     css: cssList1,
 }, {
-    id: 2, type: 'SceneButton', name: "场景1", active: false,
+    id: 2, type: 'SceneButton', name: "场景", active: false,
     config: btnConfig,
     css: btnCss,
 }]
 
 const layout = [{
-    id: 3, type: 'Input', name: "输入框3", active: false,
-    config: configList1,
-    css: cssList1,
+    id: 3, type: 'SceneButton', name: "场景", active: false,
+    config: btnConfig,
+    css: btnCss,
 }, {
     id: 4, type: 'SceneButton', name: "场景2", active: false,
     config: btnConfig,
     css: btnCss,
 }]
 
-export { attributeValue, layout };
+export { baseComponent, attributeValue, layout };

+ 1 - 1
src/style.css

@@ -93,4 +93,4 @@ body,
 .w_60_index {
     width: 60px !important;
     margin-right: 5px;
-}
+}

+ 143 - 31
src/views/HomeView.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="home-main-part">
-    <div class="nav center_in">
+    <div class="nav center_in" @mousedown.stop>
       <div class="nav_left">
         <el-image style="width: 20px; height: 20px" :src="getAssetsImages('logo.png')" />
         <div class="head_left_title">宝智达NEW_PANEL</div>
@@ -63,11 +63,15 @@
         </div>
         <div class="left_paneRight">
           <el-scrollbar style="width: 100%; height: 100%" class="component-list scrollbar-wrapper">
-            <draggable :list="componentLibrary" itemKey="id" animation="300" @start="onStart" @end="onEnd"
-              :clone="cloneComponent" :group="{ name: 'itxst', pull: 'clone' }" :touch-start-threshold="50"
-              :fallback-tolerance="50" :sort="false">
+            <draggable class="component_layout" :list="componentLibrary" itemKey="id" animation="300" @start="onStart"
+              @end="onEnd" :clone="cloneComponent" :group="{ name: 'itxst', pull: 'clone', put: false }"
+              :touch-start-threshold="50" :fallback-tolerance="50" :sort="false">
               <template #item="{ element }">
-                <div class="item_module_left move">
+                <div class="card_Base_component" v-if="element.displayPicture">
+                  <el-image class="images_Base_component" :src="getComponentPicture(element.type)" fit="contain" />
+                  <span>{{ element.name }}</span>
+                </div>
+                <div class="item_module_left move" v-else>
                   <component :is="getWidget(element.type)" :config="element"></component>
                 </div>
               </template>
@@ -85,7 +89,8 @@
                 ghostClass="ghost">
                 <template #item="{ element, index }">
                   <div class="item_module_center move">
-                    <VueDrag :index="index" :dargData="element" @passId="getId" @modifiedItem="modifiedItem"></VueDrag>
+                    <VueDrag :index="index" :dargData="element" @passId="getId" @modifiedItem="modifiedItem"
+                      @inactivation="inactivation"></VueDrag>
                   </div>
                 </template>
               </draggable>
@@ -94,15 +99,15 @@
         </div>
       </div>
       <!-- 右侧 -->
-      <div class="panel_right">
+      <div class="panel_right" @mousedown.stop>
         <!-- 样式编辑控件 -->
-        <div class="card_maskedbox">
+        <div class="card_maskedbox" v-if="currentMove.soleId">
           <maskedbox :config="currentMove" @childEvent="childEvent"></maskedbox>
+          {{ currentMove }}
         </div>
-        {{ currentMove }}
-        <!-- <div class="tabs_radio center_in" v-if="tabPosition == 'style'">
+        <div class="tabs_radio center_in" v-else>
           <el-empty description="暂无样式可调" :image-size="130" />
-        </div> -->
+        </div>
         <!-- 右侧对齐方式 -->
         <div class="card_alignment">
           <div class="bg_ment">
@@ -124,8 +129,15 @@
     </div>
   </div>
   <!-- 图标抽屉 -->
-  <el-drawer v-model="drawer" title="选择图标" direction="rtl">
-    <span @click="getIconSelection('equal')">Hi, there!</span>
+  <el-drawer v-model="drawer" title="选择图标" direction="rtl" @mousedown.stop>
+    <el-tabs v-model="iconName" class="demo-tabs" @tab-click="toggleIcon">
+      <el-tab-pane label="空心图标" name="hollow">空心图标</el-tab-pane>
+      <el-tab-pane label="实心图标" name="solid">实心图标</el-tab-pane>
+      <el-tab-pane label="我的图标" name="mine">
+        <iconLibrary :list="iconList" @selectIcon="getIconSelection"></iconLibrary>
+      </el-tab-pane>
+    </el-tabs>
+    <div class="card_bottom_hint center_in" v-if="iconName != 'mine'">找不到合适图标? 您可以自定义<span>上传图标</span></div>
   </el-drawer>
 </template>
 
@@ -135,8 +147,9 @@ import Draggable from "vuedraggable";
 import moduleLibrary from "../components/moduleLibrary.vue";
 import getName from '../components/widgets/getWidget';
 import VueDrag from '../components/drag/resizable.vue'
+import iconLibrary from '../components/iconLibrary.vue'
 import maskedbox from './maskedbox.vue'
-import { attributeValue, layout } from '../module/index';
+import { baseComponent, attributeValue, layout } from '../module/index';
 import { ref, computed } from "vue";
 import { useRouter } from 'vue-router'
 import { ElMessageBox } from 'element-plus'
@@ -149,8 +162,9 @@ const activeName = ref<string>('bank');
 const current = ref<string>('1');
 const imageUrl = new URL('@/assets/images/head.png', import.meta.url).href;
 // 组件库
-const extraImgs = ref(attributeValue);
-const componentLibrary = JSON.parse(JSON.stringify(extraImgs.value))
+const extraImgs: any = ref(baseComponent);
+const componentLibrary: any = ref([])
+componentLibrary.value = JSON.parse(JSON.stringify(extraImgs.value))
 // 视图库
 const extraImgs1: any = ref([]);
 // 撤销操作的栈
@@ -161,6 +175,8 @@ const redoStack: any = ref([]);
 const activate: any = ref(true);
 // 操作组件
 const currentMove: any = ref({})
+// 克隆组件
+const cloneItem: any = ref({})
 // 撤销
 const canUndo = computed(() => {
   return undoStack.value.length > 0;
@@ -173,6 +189,13 @@ const canRedo = computed(() => {
 const clearFlag = computed(() => {
   return extraImgs1.value.length > 0
 })
+// 切换图标tabs项
+const iconName: any = ref('mine')
+const iconList: any = [{
+  icon: 'set',
+}, {
+  icon: 'equal',
+}]
 const btnAlignment = [
   { type: 'left', label: "左对齐", value: 'icon-zuoduiqi' },
   { type: 'centerHorizontally', label: "水平居中", value: 'icon-hengxiangjuzhong' },
@@ -181,9 +204,14 @@ const btnAlignment = [
   { type: 'verticalCenter', label: "垂直居中", value: 'icon-zongxiangjuzhong' },
   { type: 'bottom', label: "底对齐", value: 'icon-xiaduiqi' },
 ];
+// logo图片
 function getAssetsImages(name) {
   return new URL(`/src/assets/${name}`, import.meta.url).href;
 }
+// 组件图片
+function getComponentPicture(name) {
+  return new URL(`/src/assets/componentIcon/${name}.png`, import.meta.url).href;
+}
 
 //拖拽开始的事件
 const onStart = () => {
@@ -192,23 +220,27 @@ const onStart = () => {
 
 //拖拽结束的事件
 const onEnd = (value) => {
-  if (value.originalEvent.cancelable) {
-    const num = (375 / 2) - (currentMove.value.css.width / 2)
-    currentMove.value.css.left = num
-    currentMove.value.css.top = value.originalEvent.offsetY
-    const arr = JSON.parse(JSON.stringify(currentMove.value))
+  if (value.originalEvent.cancelable && value.to.className == 'Bzuil') {
+    const num = (375 / 2) - (cloneItem.value.css.width / 2)
+    cloneItem.value.css.left = num
+    cloneItem.value.active = true
+    cloneItem.value.css.top = value.originalEvent.offsetY
+    const arr = JSON.parse(JSON.stringify(cloneItem.value))
+
     extraImgs1.value.push(arr)
+    currentMove.value = arr
 
     undoStack.value.push({ type: 'add', item: arr })
     redoStack.value = [];
   } else {
-    currentMove.value = {}
+    cloneItem.value = {}
     extraImgs1.value.splice(value.newIndex, 1)
   }
 };
+
 // 克隆组件
 const cloneComponent = (value) => {
-  currentMove.value = {
+  cloneItem.value = {
     soleId: "box_" + new Date().getTime(),
     ...value
   }
@@ -220,11 +252,15 @@ const getWidget = (name: string) => {
 }
 // 组件库选择
 const handleReceivedData = (num: any) => {
+  extraImgs.value = []
   if (num == 1) {
-    extraImgs.value = attributeValue
+    extraImgs.value = baseComponent
   } else if (num == 2) {
+    extraImgs.value = attributeValue
+  } else if (num == 3) {
     extraImgs.value = layout
   }
+  componentLibrary.value = JSON.parse(JSON.stringify(extraImgs.value))
 }
 // 切换tabs
 function handleClick(): void {
@@ -301,13 +337,21 @@ function preview() {
 const currentIndex: any = ref(null)
 const currentSoleId: any = ref(null)
 function getId(params: any) {
-  // console.log(params, 8);
+  // 激活项索引处理对齐方式
   currentIndex.value = params.index
   currentSoleId.value = params.soleId
+  // 切换组件修改样式
+  currentMove.value = params.option
+}
+// 失去活跃状态
+function inactivation(params: any) {
+  if (currentMove.value.soleId == params.soleId) {
+    currentMove.value = {}
+  }
 }
-
 const startX = ref('');
 const startY = ref('');
+// 拖拽修改选中项
 function modifiedItem(params: any) {
   if (params.type == 'drag-start') {
     startX.value = params.item.css.top
@@ -346,17 +390,32 @@ function alignment(params: any) {
   }
   console.log(extraImgs1.value, 5);
 }
-// 选择图标抽屉
+// 切换图片选择图标抽屉
 function childEvent() {
   drawer.value = true
 }
+// 切换图标库
+function toggleIcon(params: any) {
+  console.log(params, 27)
+}
 // 选择图标
-function getIconSelection(params:any) {
-  console.log(params,currentMove,222);
-  
+function getIconSelection(params: any) {
+  console.log(params, currentMove, 222);
+  drawer.value = false
+  currentMove.value.config.img = params.icon
 }
 </script>
 
+<style lang="scss">
+.el-drawer__header {
+  margin-bottom: 0px;
+}
+
+.el-drawer__body {
+  padding: 10px 20px;
+}
+</style>
+
 <style lang="scss" scoped>
 .home-main-part {
   min-width: 960px;
@@ -525,7 +584,7 @@ function getIconSelection(params:any) {
   flex-direction: column;
   position: absolute;
   top: 0px;
-  width: calc(200px - 20px);
+  width: calc(220px - 20px);
   height: 100%;
   background-color: rgb(240, 241, 245);
   transition: left 0.2s cubic-bezier(0.7, 0.3, 0.1, 1) 0s;
@@ -538,6 +597,40 @@ function getIconSelection(params:any) {
   text-align: center;
 }
 
+.component_layout {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+
+.card_Base_component {
+  cursor: grab;
+  margin-top: 10px;
+  width: calc(50% - 5px);
+  display: flex;
+  flex-direction: column;
+}
+
+.images_Base_component {
+  width: 100%;
+  height: 100%;
+}
+
+.card_Base_component span {
+  width: 100%;
+  display: block;
+  padding: 0px;
+  font-size: 12px;
+  color: rgb(74, 74, 74);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  text-align: center;
+  pointer-events: none;
+  line-height: 25px;
+}
+
 .panel_center {
   overflow-y: overlay;
   position: relative;
@@ -567,6 +660,7 @@ function getIconSelection(params:any) {
 
 .item_module_left {
   cursor: grab;
+  width: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
@@ -710,4 +804,22 @@ img {
 .Bzuil {
   height: 100%;
 }
+
+// 图标篇
+.card_bottom_hint {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: rgb(255, 255, 255);
+  font-size: 12px;
+  font-weight: 400;
+  height: 50px;
+  border-top: 1px solid var(--el-border-color);
+
+  span {
+    color: #409EFF;
+    cursor: pointer;
+  }
+}
 </style>

+ 10 - 2
src/views/TestDrag.vue

@@ -7,10 +7,10 @@
                 </div>
             </DraggableContainer> -->
             <DraggableContainer :referenceLineColor="'#0f0'">
-                <Vue3DraggableResizable>
+                <Vue3DraggableResizable  @activated="activated" @deactivated="deactivated">
                     <div style="width: 200px;height: 200px;background-color: #fff;">2151</div>
                 </Vue3DraggableResizable>
-                <Vue3DraggableResizable>
+                <Vue3DraggableResizable @activated="activated" @deactivated="deactivated">
                     <div style="width: 200px;height: 200px;background-color: #fff;">Another test</div>
                 </Vue3DraggableResizable>
             </DraggableContainer>
@@ -36,6 +36,14 @@ import { attributeValue } from '../module/index';
 const extraImgs = ref(attributeValue);
 const extraImgs1 = ref([]);
 
+// 激活项索引
+function activated() {
+    console.log('活跃')
+}
+// 失去活跃状态
+function deactivated() {
+    console.log('失去活跃状态')
+}
 </script>
 
 <style lang="scss" scoped>

+ 20 - 94
src/views/maskedbox.vue

@@ -9,27 +9,6 @@
             </el-icon>
         </div>
         <div v-if="basicFlag" class="card_second_level">
-            <div class="same_row_in upAndDown_padding">
-                <span class="title_12 w_60 default_size">尺寸</span>
-                <div class="grey_input_bgc">
-                    <el-input-number class="card_number" v-model="config.css.width" :min="1" size="small"
-                        controls-position="right" />
-                    <span>W</span>
-                </div>
-                <div class="card_lock center_in" @click="getLock(lockFlag)">
-                    <el-icon v-if="lockFlag">
-                        <Unlock />
-                    </el-icon>
-                    <el-icon v-else>
-                        <Lock />
-                    </el-icon>
-                </div>
-                <div class="grey_input_bgc">
-                    <el-input-number class="card_number" :disabled="true" v-model="config.css.height" :min="1" size="small"
-                        controls-position="right" />
-                    <span>H</span>
-                </div>
-            </div>
             <div class="same_row_in upAndDown_padding" v-if="config.config.size">
                 <span class="title_12 w_60 default_size" style="flex: none;">层级</span>
                 <div class="grey_input_bgc w_60_index">
@@ -40,38 +19,17 @@
                     <el-slider v-model="config.config.size" size="small" />
                 </div>
             </div>
-            <div class="same_row_in upAndDown_padding">
-                <span class="title_12 w_60 default_size" style="flex: none;">颜色</span>
-                <el-color-picker v-model="config.css.iconColor" show-alpha @change="handleColorChange" />
-                <div class="leftmargin_8">
-                    <el-input v-model="hexColor" size="small">
-                        <template #prefix>
-                            <i style="margin-right: 0px;">#</i>
-                        </template>
-                    </el-input>
-                </div>
-                <div class="grey_input_bgc leftmargin_8">
-                    <el-input-number class="card_number" v-model="alpha" :min="0" :max="1" :step="0.1" size="small"
-                        controls-position="right" @change="colorNum" />
-                    <span>A</span>
-                </div>
-            </div>
-            <div class="same_row_in upAndDown_padding">
-                <span class="title_12 w_60 default_size" style="flex: none;">字体</span>
-                <div class="grey_input_bgc w_60_index">
-                    <el-input-number class="card_number" v-model="config.config.size" :min="1" size="small"
-                        controls-position="right" />
-                </div>
-                <div class="card_select_bg" style="width: 100%;">
-                    <el-select v-model="config.css.fontWeight" placeholder="Select" size="small">
-                        <el-option v-for="item in fontWeightData" :key="item.value" :label="item.label"
-                            :value="item.value" />
-                    </el-select>
-                </div>
-            </div>
+            <literalName v-if="'name' in config.config" v-model:name="config.config.name"></literalName>
+            <boxSize v-if="config.css.width && config.css.height != 'auto' && config.css.height && config.config.resizable"
+                v-model:width="config.css.width" v-model:height="config.css.height"></boxSize>
+            <colorSelection v-if="config.css.color" v-model:iconColor="config.css.color" title="颜色"></colorSelection>
+            <fontSize v-if="config.css.fontSize && config.css.fontWeight" v-model:fontSize="config.css.fontSize"
+                v-model:fontWeight="config.css.fontWeight"></fontSize>
+            <textAlignment v-if="config.css.textAlign" v-model:align="config.css.textAlign"></textAlignment>
+            <lineNumber v-if="'lineNum' in config.config" v-model:num="config.config.lineNum"></lineNumber>
         </div>
     </div>
-    <div class="box_padding" v-if="config.css">
+    <div class="box_padding" v-if="config.config.img">
         <div class="same_row_in">
             <span class="default_size title_5">图标</span>
             <el-icon style="cursor: pointer;" :class="imgFlag ? '' : 'putAway'" @click="getbasicSetting('img')">
@@ -79,7 +37,9 @@
             </el-icon>
         </div>
         <div v-if="imgFlag" class="card_second_level">
-            <iconSize v-model:num="config.config.size" @childEvent="childEvent"></iconSize>
+            <fontSize v-model:fontSize="config.css.fontSize" v-model:fontWeight="config.css.fontWeight"></fontSize>
+            <iconSize v-model:num="config.config.size" v-model:iconName="config.config.img" @mousedown.stop
+                @childEvent="childEvent"></iconSize>
             <colorSelection v-model:iconColor="config.css.iconColor" title="选择颜色"></colorSelection>
             <textAlignment v-model:align="config.css.textAlign"></textAlignment>
             <lineNumber v-model:num="config.config.lineNum"></lineNumber>
@@ -91,6 +51,9 @@
 import { ref, watch } from "vue";
 import color from 'color';
 import layout from '../components/maskedbox/layout.vue';
+import literalName from '../components/maskedbox/literalName.vue';
+import boxSize from '../components/maskedbox/boxSize.vue';
+import fontSize from '../components/maskedbox/fontSize.vue';
 import iconSize from '../components/maskedbox/iconSize.vue';
 import colorSelection from '../components/maskedbox/colorSelection.vue';
 import textAlignment from '../components/maskedbox/textAlignment.vue';
@@ -102,21 +65,11 @@ const props = defineProps({
         required: true
     },
 })
-const fontWeightData = [{
-    label: '细体',
-    value: 300,
-}, {
-    label: '常规',
-    value: 400,
-}, {
-    label: '粗体',
-    value: 500,
-}]
+
 const basicFlag = ref(true)
 const imgFlag = ref(true)
-const lockFlag = ref(true)
+
 function getbasicSetting(params: any) {
-    console.log();
     if (params == 'basic') {
         if (basicFlag.value) {
             basicFlag.value = false
@@ -131,13 +84,6 @@ function getbasicSetting(params: any) {
         }
     }
 }
-function getLock(params: any) {
-    if (params) {
-        lockFlag.value = false
-    } else {
-        lockFlag.value = true
-    }
-}
 
 const alpha = ref(1);
 const hexColor = ref('');
@@ -155,29 +101,17 @@ const handleColorChange = (rgbaStr) => {
     }
 };
 
-const colorNum = (value: any) => {
-    const rgbaStr = props.config.css.iconColor;
-    const newAlpha = value;
-    props.config.css.iconColor = adjustAlpha(rgbaStr, newAlpha);
-
-    function adjustAlpha(rgbaStr: string, alpha: number): string {
-        const match = rgbaStr.match(/^rgba\((\d+), (\d+), (\d+), ([^\)]+)\)$/);
-        if (!match) {
-            throw new Error('Invalid RGBA string');
-        }
-        const [r, g, b] = [match[1], match[2], match[3]].map(Number);
-        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
-    }
-}
-const emit = defineEmits(['childEvent'])
+const emit = defineEmits(['childEvent', 'updateValue'])
 // 选择图标抽屉
 function childEvent() {
     emit('childEvent');
 }
+
 watch(() => props.config, (newValue) => {
     if (newValue.css) {
         // 初始化hex颜色
         hexColor.value = handleColorChange(newValue.css.iconColor)
+        emit('updateValue', newValue);
     }
 }, { immediate: true, deep: true })
 </script>
@@ -205,13 +139,6 @@ watch(() => props.config, (newValue) => {
     width: 20px;
 }
 
-.card_lock {
-    flex: 0 0 auto;
-    cursor: pointer;
-    width: 30px;
-}
-
-
 .card_select_bg {
     background-color: #f1f1f7;
     padding: 2px;
@@ -234,5 +161,4 @@ watch(() => props.config, (newValue) => {
     width: 13px;
     height: 13px;
 }
-
 </style>