|
|
@@ -0,0 +1,603 @@
|
|
|
+<template>
|
|
|
+ <el-dialog v-model="show" title="背景颜色" width="440px" draggable>
|
|
|
+ <el-scrollbar height="400px">
|
|
|
+ <div class="w-full flex flex-col items-center">
|
|
|
+ <el-segmented v-if="useType === 'both'" v-model="type" :options="typeOptions" />
|
|
|
+
|
|
|
+ <div v-if="type !== 'pure'" class="w-full flex flex-col items-center">
|
|
|
+ <h4 class="m-0">预览</h4>
|
|
|
+ <div class="w-80% h-60px rounded-4px mb-10px" :style="previewGradient"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 纯色 -->
|
|
|
+ <div v-if="type === 'pure'" class="mt-20px">
|
|
|
+ <ColorPicker v-model:pureColor="pureColor" format="hex8" picker-type="chrome" use-type="pure" isWidget />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 基础渐变 -->
|
|
|
+ <div v-else-if="type === 'gradient'" class="w-full">
|
|
|
+ <el-form label-width="80px" label-position="left">
|
|
|
+ <el-form-item label="渐变方向">
|
|
|
+ <el-radio-group v-model="basicDirection">
|
|
|
+ <el-radio value="horizontal">水平渐变</el-radio>
|
|
|
+ <el-radio value="vertical">垂直渐变</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="开始颜色">
|
|
|
+ <div class="flex items-center gap-12px w-full">
|
|
|
+ <ColorPicker v-model:pureColor="basicStartColor" format="hex8" picker-type="chrome" use-type="pure" />
|
|
|
+ <div>
|
|
|
+ {{ basicStartColor }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="结束颜色">
|
|
|
+ <div class="flex items-center gap-12px w-full">
|
|
|
+ <ColorPicker v-model:pureColor="basicEndColor" format="hex8" picker-type="chrome" use-type="pure" />
|
|
|
+ <div>
|
|
|
+ {{ basicEndColor }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="开始点">
|
|
|
+ <div class="flex items-center gap-8px w-full">
|
|
|
+ <el-slider v-model="basicStartPos" :min="0" :max="255" style="flex: 1" />
|
|
|
+ <span class="w-40px text-right text-text-active">
|
|
|
+ {{ basicStartPos }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="结束点">
|
|
|
+ <div class="flex items-center gap-8px w-full">
|
|
|
+ <el-slider v-model="basicEndPos" :min="0" :max="255" style="flex: 1" />
|
|
|
+ <span class="w-40px text-right text-text-active">
|
|
|
+ {{ basicEndPos }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 高级渐变 -->
|
|
|
+ <div v-else class="w-full">
|
|
|
+ <el-form label-position="top" class="advanced-form">
|
|
|
+ <el-form-item label="渐变类型">
|
|
|
+ <el-radio-group v-model="advancedType" @change="onChangeAdvancedType">
|
|
|
+ <el-radio label="linear">线性渐变</el-radio>
|
|
|
+ <el-radio label="radial">径向渐变</el-radio>
|
|
|
+ <el-radio label="conical">锥形渐变</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 线性渐变:起始/结束坐标 -->
|
|
|
+ <template v-if="advancedType === 'linear'">
|
|
|
+ <el-form-item label="开始坐标">
|
|
|
+ <div class="w-full flex items-center gap-8px">
|
|
|
+ <input-number v-model="linearStartX" :min="0" :max="width" size="small" style="flex: 1;">
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number v-model="linearStartY" :min="0" :max="height" size="small" style="flex: 1;">
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="结束坐标">
|
|
|
+ <div class="w-full flex items-center gap-8px">
|
|
|
+ <input-number v-model="linearEndX" :min="0" :max="width" size="small" style="flex: 1;">
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number v-model="linearEndY" :min="0" :max="height" size="small" style="flex: 1;">
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 径向渐变:中心点 -->
|
|
|
+ <template v-else-if="advancedType === 'radial'">
|
|
|
+ <el-form-item label="中心点坐标">
|
|
|
+ <div class="w-full flex items-center gap-8px">
|
|
|
+ <input-number v-model="centerX" :min="0" :max="width" size="small" style="flex: 1;">
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number v-model="centerY" :min="0" :max="height" size="small" style="flex: 1;">
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 锥形渐变:中心点 + 角度 -->
|
|
|
+ <template v-else>
|
|
|
+ <el-form-item label="中心点坐标">
|
|
|
+ <div class="w-full flex items-center gap-8px">
|
|
|
+ <input-number v-model="centerX" :min="0" :max="width" size="small" style="flex: 1;" />
|
|
|
+ <input-number v-model="centerY" :min="0" :max="height" size="small" style="flex: 1;" />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="角度">
|
|
|
+ <div class="w-full flex items-center gap-8px">
|
|
|
+ <input-number v-model="coneStartAngle" :min="0" :max="3600" size="small" style="width: 100%;">
|
|
|
+ <template #prefix>S</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number v-model="coneEndAngle" :min="0" :max="3600" size="small" style="width: 100%;">
|
|
|
+ <template #prefix>E</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 渐变点列表 -->
|
|
|
+ <el-form-item label="渐变点">
|
|
|
+ <div class="w-full flex flex-col gap-12px">
|
|
|
+ <el-button type="primary" :disabled="pointsList.length >= 16" @click="addPoint">
|
|
|
+ 新增渐变点
|
|
|
+ </el-button>
|
|
|
+ <div v-for="(pt, index) in pointsList" :key="index" class="gradient-point-item">
|
|
|
+ <el-card shadow="hover" bodyClass="p-10px! relative">
|
|
|
+ <div class="flex gap-12px items-center pr-12px jusitfy-between">
|
|
|
+ <ColorPicker class="flex-1" v-model:pureColor="pt.color" format="hex8" picker-type="chrome"
|
|
|
+ use-type="pure" />
|
|
|
+ <div class="flex-1/3">
|
|
|
+ <div>颜色值</div>
|
|
|
+ <div>{{ pt.color }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="flex-1/3">
|
|
|
+ <div>位置:{{ pt.point }}</div>
|
|
|
+ <div><el-slider v-model="pt.point" :min="0" :max="255" /></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="absolute right-4px top-0">
|
|
|
+ <el-button v-if="pointsList.length > 2" link @click="removePoint(index)">
|
|
|
+ <LuTrash2 size="14px" />
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-scrollbar>
|
|
|
+
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <el-button type="primary" @click="submit">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ <div v-if="useType != 'pure'" class="trigger" @click="open" :style="triggerStyle">
|
|
|
+ <slot name="trigger"></slot>
|
|
|
+ </div>
|
|
|
+ <ColorPicker v-else v-model:pureColor="pureColor" format="hex8" picker-type="chrome" use-type="pure" />
|
|
|
+
|
|
|
+</template>
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, watch } from 'vue'
|
|
|
+import { ColorPicker } from '..'
|
|
|
+import { parseCssGradient, generateCssGradient } from '@/utils'
|
|
|
+import { klona } from 'klona'
|
|
|
+import { LuTrash2 } from 'vue-icons-plus/lu'
|
|
|
+
|
|
|
+import type { GradientColor } from '@/lvgl-widgets/type'
|
|
|
+
|
|
|
+const pureColor = defineModel<string>('pureColor')
|
|
|
+const gradientColor = defineModel<GradientColor | string>('gradientColor')
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<{
|
|
|
+ useType?: 'pure' | 'gradient' | 'both';
|
|
|
+ onlyColor?: boolean;
|
|
|
+ width?: number
|
|
|
+ height?: number
|
|
|
+}>(), {
|
|
|
+ useType: 'pure'
|
|
|
+})
|
|
|
+const show = ref(false)
|
|
|
+
|
|
|
+const type = ref<'pure' | 'gradient' | 'advanced'>('pure')
|
|
|
+const typeOptions = computed(() => {
|
|
|
+ const options = [
|
|
|
+ {
|
|
|
+ label: '纯色',
|
|
|
+ value: 'pure'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '基础渐变',
|
|
|
+ value: 'gradient'
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ // 如果宽高存在,则高级渐变可选
|
|
|
+ if (props.width && props.height) {
|
|
|
+ options.push({
|
|
|
+ label: '高级渐变',
|
|
|
+ value: 'advanced'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return options
|
|
|
+})
|
|
|
+
|
|
|
+// 内部渐变配置对象
|
|
|
+const internalGradient = ref<GradientColor | null>(null)
|
|
|
+
|
|
|
+const ensureGradient = () => {
|
|
|
+ if (!internalGradient.value) {
|
|
|
+ internalGradient.value = {
|
|
|
+ type: 'linear',
|
|
|
+ gradientType: 'gradient',
|
|
|
+ direction: 'horizontal',
|
|
|
+ points: [
|
|
|
+ {
|
|
|
+ point: 0,
|
|
|
+ color: pureColor.value || '#000000FF'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ point: 255,
|
|
|
+ color: '#FFFFFFFF'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ start: {
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ },
|
|
|
+ end: {
|
|
|
+ x: props.width || 100,
|
|
|
+ y: props.height || 100
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const emitGradientChange = () => {
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ gradientColor.value = klona(internalGradient.value)
|
|
|
+}
|
|
|
+
|
|
|
+// 外部 v-model 同步到内部对象
|
|
|
+watch(
|
|
|
+ () => gradientColor.value,
|
|
|
+ (val) => {
|
|
|
+ if (!val) {
|
|
|
+ internalGradient.value = null
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (typeof val === 'string') {
|
|
|
+ internalGradient.value = parseCssGradient(val) || null
|
|
|
+ } else {
|
|
|
+ internalGradient.value = klona(val)
|
|
|
+ type.value = val.gradientType === 'gradient' ? 'gradient' : 'advanced'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+watch(type, (val) => {
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ internalGradient.value.gradientType = val === 'gradient' ? 'gradient' : 'advanced'
|
|
|
+ // 保留前2个渐变点
|
|
|
+ if (val === 'gradient') {
|
|
|
+ internalGradient.value.type = 'linear'
|
|
|
+ const points = internalGradient.value.points
|
|
|
+ if (points && points.length > 2) {
|
|
|
+ internalGradient.value.points = points.slice(0, 2)
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 打开弹窗时,根据当前是否有渐变自动切换 tab
|
|
|
+const open = () => {
|
|
|
+ if (gradientColor.value) {
|
|
|
+ ensureGradient()
|
|
|
+ }
|
|
|
+ show.value = true
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ open
|
|
|
+})
|
|
|
+
|
|
|
+// 触发器样式:优先使用渐变,否则使用纯色
|
|
|
+const triggerStyle = computed(() => {
|
|
|
+ if (gradientColor.value) {
|
|
|
+ if (typeof gradientColor.value === 'string') {
|
|
|
+ return {
|
|
|
+ backgroundImage: gradientColor.value as string
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ backgroundImage: generateCssGradient(gradientColor.value as GradientColor, props.width, props.height)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ backgroundColor: pureColor.value || '#ffffff00'
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 预览渐变色
|
|
|
+const previewGradient = computed(() => {
|
|
|
+ return { background: internalGradient.value ? generateCssGradient(internalGradient.value, props.width, props.height) : '#ffffff00' }
|
|
|
+})
|
|
|
+
|
|
|
+// 基础渐变:方向
|
|
|
+const basicDirection = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.direction || 'horizontal'
|
|
|
+ },
|
|
|
+ set(val: 'horizontal' | 'vertical') {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ internalGradient.value.direction = val
|
|
|
+ internalGradient.value.type = 'linear'
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 基础渐变:起始/结束颜色
|
|
|
+const basicStartColor = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.points?.[0]?.color || '#000000FF'
|
|
|
+ },
|
|
|
+ set(val: string) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value?.points) return
|
|
|
+ internalGradient.value.points[0].color = val
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const basicEndColor = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ const pts = internalGradient.value?.points || []
|
|
|
+ return pts[pts.length - 1]?.color || '#FFFFFFFF'
|
|
|
+ },
|
|
|
+ set(val: string) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value?.points) return
|
|
|
+ const pts = internalGradient.value.points
|
|
|
+ pts[pts.length - 1].color = val
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 基础渐变:起始/结束点(0~255),若起始 > 结束,则交换
|
|
|
+const basicStartPos = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ const pos = internalGradient.value?.points?.[0]?.point
|
|
|
+ return pos ?? 0
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value?.points) return
|
|
|
+ const pts = internalGradient.value.points
|
|
|
+ let v = Math.min(255, Math.max(0, val))
|
|
|
+
|
|
|
+ pts[0].point = v
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const basicEndPos = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ const pts = internalGradient.value?.points
|
|
|
+ const pos = pts?.[pts.length - 1]?.point
|
|
|
+ return pos ?? 255
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value?.points) return
|
|
|
+ const pts = internalGradient.value.points
|
|
|
+ let v = Math.min(255, Math.max(0, val))
|
|
|
+
|
|
|
+ pts[pts.length - 1].point = v
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 高级渐变:类型
|
|
|
+const advancedType = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.type || 'linear'
|
|
|
+ },
|
|
|
+ set(val: GradientColor['type']) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ internalGradient.value.type = val
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 线性渐变:起始/结束坐标(0~100,对应控件宽高百分比)
|
|
|
+const linearStartX = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.start?.x ?? 0
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.start) internalGradient.value.start = { x: 0, y: 0 }
|
|
|
+ internalGradient.value.start.x = Math.max(0, val)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const linearStartY = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.start?.y ?? 0
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.start) internalGradient.value.start = { x: 0, y: 0 }
|
|
|
+ internalGradient.value.start.y = Math.max(0, val)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const linearEndX = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.end?.x ?? 100
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.end) internalGradient.value.end = { x: 100, y: 100 }
|
|
|
+ internalGradient.value.end.x = Math.max(0, val)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const linearEndY = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.end?.y ?? 100
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.end) internalGradient.value.end = { x: 100, y: 100 }
|
|
|
+ internalGradient.value.end.y = Math.max(0, val)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 径向/锥形渐变:中心点(0~100,默认 50)
|
|
|
+const centerX = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.center?.x ?? 50
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.center) internalGradient.value.center = { x: 50, y: 50 }
|
|
|
+ internalGradient.value.center.x = Math.max(0, val)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const centerY = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.center?.y ?? 50
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.center) internalGradient.value.center = { x: 50, y: 50 }
|
|
|
+ internalGradient.value.center.y = Math.max(0, val)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 锥形渐变:角度 0~3600
|
|
|
+const coneStartAngle = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.angle?.start ?? 0
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.angle) internalGradient.value.angle = { start: 0, end: 360 }
|
|
|
+ internalGradient.value.angle.start = Math.min(3600, Math.max(0, val))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const coneEndAngle = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ return internalGradient.value?.angle?.end ?? 360
|
|
|
+ },
|
|
|
+ set(val: number) {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (!internalGradient.value.angle) internalGradient.value.angle = { start: 0, end: 360 }
|
|
|
+ internalGradient.value.angle.end = Math.min(3600, Math.max(0, val))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 高级渐变:渐变点列表(最少 2,最多 16)
|
|
|
+const pointsList = computed({
|
|
|
+ get() {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value!.points || internalGradient.value!.points.length < 2) {
|
|
|
+ internalGradient.value!.points = [
|
|
|
+ { color: '#000000FF', point: 0 },
|
|
|
+ { color: '#FFFFFFFF', point: 255 }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ return internalGradient.value!.points!
|
|
|
+ },
|
|
|
+ set(val) {
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ internalGradient.value.points = val
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const sortPointsByPosition = () => {
|
|
|
+ if (!internalGradient.value?.points) return
|
|
|
+ internalGradient.value.points = internalGradient.value.points.sort((a, b) => {
|
|
|
+ const pa = a.point ?? a.point
|
|
|
+ const pb = b.point ?? b.point
|
|
|
+ return pa - pb
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const addPoint = () => {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value?.points) return
|
|
|
+ if (internalGradient.value.points.length >= 16) return
|
|
|
+
|
|
|
+ const last = internalGradient.value.points[internalGradient.value.points.length - 1]
|
|
|
+ const lastPos = last.point ?? 255
|
|
|
+ const newPos = Math.min(255, lastPos + 10)
|
|
|
+
|
|
|
+ internalGradient.value.points.push({
|
|
|
+ color: '#FFFFFFFF',
|
|
|
+ point: newPos
|
|
|
+ })
|
|
|
+ sortPointsByPosition()
|
|
|
+}
|
|
|
+
|
|
|
+const removePoint = (index: number) => {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value?.points) return
|
|
|
+ if (internalGradient.value.points.length <= 2) return
|
|
|
+ internalGradient.value.points.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const onChangeAdvancedType = (val) => {
|
|
|
+ ensureGradient()
|
|
|
+ if (!internalGradient.value) return
|
|
|
+ if (val === 'linear') {
|
|
|
+ internalGradient.value.end = {
|
|
|
+ x: props.width || 100,
|
|
|
+ y: props.height || 100
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ internalGradient.value.center = {
|
|
|
+ x: (props.width || 100) / 2,
|
|
|
+ y: (props.height || 100) / 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const submit = () => {
|
|
|
+ emitGradientChange()
|
|
|
+ show.value = false
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.trigger {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
|
|
|
+ background-repeat: repeat;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+</style>
|