|
|
@@ -0,0 +1,723 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <el-card class="mb-12px" body-class="pr-0px!">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <span>元素</span>
|
|
|
+ <span class="flex gap-4px">
|
|
|
+ <LuPlus class="cursor-pointer" @click="handleAdd" size="14px" />
|
|
|
+ <LuTrash2 class="cursor-pointer" @click="handleClear" size="14px" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-scrollbar height="140px">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in elements || []"
|
|
|
+ :key="index"
|
|
|
+ class="flex items-center pr-12px"
|
|
|
+ @click="handleEdit(item, index)"
|
|
|
+ >
|
|
|
+ <span class="flex-1 truncate text-#00ff00 cursor-pointer">
|
|
|
+ {{ getElementLabel(item, index) }}
|
|
|
+ </span>
|
|
|
+ <LuTrash2 class="cursor-pointer shrink-0" @click.stop="handleDelete(index)" size="14px" />
|
|
|
+ </div>
|
|
|
+ </el-scrollbar>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-dialog v-model="dialogVisible" title="编辑元素" width="520px" draggable>
|
|
|
+ <el-form v-if="formData" :model="formData" label-position="left" label-width="90px">
|
|
|
+ <el-form-item label="类型">
|
|
|
+ <el-select v-model="formData.type">
|
|
|
+ <el-option label="矩形" value="rect" />
|
|
|
+ <el-option label="文本" value="text" />
|
|
|
+ <el-option label="图片" value="image" />
|
|
|
+ <el-option label="圆弧" value="arc" />
|
|
|
+ <el-option label="直线" value="line" />
|
|
|
+ <el-option label="三角形" value="triangle" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- Rect -->
|
|
|
+ <template v-if="formData.type === 'rect'">
|
|
|
+ <el-form-item label="位置/大小">
|
|
|
+ <div class="w-full flex gap-4px">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.x"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.y"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ <div class="w-full flex gap-4px mt-4px">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.width"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>宽</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.height"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>高</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="背景颜色">
|
|
|
+ <ColorPicker
|
|
|
+ v-model:pureColor="formData.props.background_color"
|
|
|
+ format="hex8"
|
|
|
+ picker-type="chrome"
|
|
|
+ use-type="pure"
|
|
|
+ />
|
|
|
+ <span class="text-text-active">{{ formData.props.background_color }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="边框颜色">
|
|
|
+ <ColorPicker
|
|
|
+ v-model:pureColor="formData.props.border.color"
|
|
|
+ format="hex8"
|
|
|
+ picker-type="chrome"
|
|
|
+ use-type="pure"
|
|
|
+ />
|
|
|
+ <span class="text-text-active">{{ formData.props.border.color }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="边框宽度">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.border.width"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="圆角">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.border.radius"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Text -->
|
|
|
+ <template v-else-if="formData.type === 'text'">
|
|
|
+ <el-form-item label="位置">
|
|
|
+ <div class="w-full flex gap-4px">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.x"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.y"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="宽度">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.width"
|
|
|
+ :min="1"
|
|
|
+ :max="1000"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字体颜色">
|
|
|
+ <ColorPicker
|
|
|
+ v-model:pureColor="formData.props.font_color"
|
|
|
+ format="hex8"
|
|
|
+ picker-type="chrome"
|
|
|
+ use-type="pure"
|
|
|
+ />
|
|
|
+ <span class="text-text-active">{{ formData.props.font_color }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字体样式">
|
|
|
+ <el-select-v2
|
|
|
+ v-model="formData.props.font_family"
|
|
|
+ placeholder="请选择"
|
|
|
+ :options="fontOptions"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="字体大小">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.font_size"
|
|
|
+ :min="1"
|
|
|
+ :max="1000"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="">
|
|
|
+ <el-radio-group v-model="formData.props.text_decor">
|
|
|
+ <el-radio-button :value="LineEnum.LV_TEXT_DECOR_NONE">
|
|
|
+ <el-tooltip content="none">
|
|
|
+ <MdNotInterested size="14px" />
|
|
|
+ </el-tooltip>
|
|
|
+ </el-radio-button>
|
|
|
+ <el-radio-button :value="LineEnum.LV_TEXT_DECOR_UNDERLINE">
|
|
|
+ <el-tooltip content="underline">
|
|
|
+ <RxUnderline size="14px" />
|
|
|
+ </el-tooltip>
|
|
|
+ </el-radio-button>
|
|
|
+ <el-radio-button :value="LineEnum.LV_TEXT_DECOR_STRIKETHROUGH">
|
|
|
+ <el-tooltip content="strikethrough">
|
|
|
+ <RxStrikethrough size="14px" />
|
|
|
+ </el-tooltip>
|
|
|
+ </el-radio-button>
|
|
|
+ <el-radio-button
|
|
|
+ :value="LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']"
|
|
|
+ >
|
|
|
+ <el-tooltip content="underline & strikethrough">
|
|
|
+ <GoStrikethrough size="14px" />
|
|
|
+ </el-tooltip>
|
|
|
+ </el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="文本">
|
|
|
+ <el-input
|
|
|
+ spellcheck="false"
|
|
|
+ type="textarea"
|
|
|
+ v-model="formData.props.text"
|
|
|
+ placeholder="请输入文本"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Image -->
|
|
|
+ <template v-else-if="formData.type === 'image'">
|
|
|
+ <el-form-item label="位置/大小">
|
|
|
+ <div class="w-full flex gap-4px">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.x"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.y"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ <div class="w-full flex gap-4px mt-4px">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.width"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>宽</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.height"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>高</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="图片">
|
|
|
+ <ImageSelect v-model="formData.props.image" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="透明度">
|
|
|
+ <div class="w-full flex items-center gap-16px">
|
|
|
+ <el-slider v-model="formData.props.alpha" :min="0" :max="255" style="flex: 1" />
|
|
|
+ <span class="text-text-active w-40px text-right">
|
|
|
+ {{ formData.props.alpha }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="遮罩颜色">
|
|
|
+ <ColorPicker
|
|
|
+ v-model:pureColor="formData.props.recolor"
|
|
|
+ format="hex8"
|
|
|
+ picker-type="chrome"
|
|
|
+ use-type="pure"
|
|
|
+ />
|
|
|
+ <span class="text-text-active">{{ formData.props.recolor }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Arc -->
|
|
|
+ <template v-else-if="formData.type === 'arc'">
|
|
|
+ <el-form-item label="位置">
|
|
|
+ <div class="w-full flex gap-4px">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.x"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.y"
|
|
|
+ :min="-10000"
|
|
|
+ :max="10000"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="角度">
|
|
|
+ <div class="w-full flex gap-4px">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.start_angle"
|
|
|
+ :min="0"
|
|
|
+ :max="360"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>开始</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.end_angle"
|
|
|
+ :min="0"
|
|
|
+ :max="360"
|
|
|
+ controls-position="right"
|
|
|
+ >
|
|
|
+ <template #prefix>结束</template>
|
|
|
+ </input-number>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="颜色">
|
|
|
+ <ColorPicker
|
|
|
+ v-model:pureColor="formData.props.color"
|
|
|
+ format="hex8"
|
|
|
+ picker-type="chrome"
|
|
|
+ use-type="pure"
|
|
|
+ />
|
|
|
+ <span class="text-text-active">{{ formData.props.color }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="宽度">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.width"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="半径">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.radius"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Line -->
|
|
|
+ <template v-else-if="formData.type === 'line'">
|
|
|
+ <el-form-item label="颜色">
|
|
|
+ <ColorPicker
|
|
|
+ v-model:pureColor="formData.props.color"
|
|
|
+ format="hex8"
|
|
|
+ picker-type="chrome"
|
|
|
+ use-type="pure"
|
|
|
+ />
|
|
|
+ <span class="text-text-active">{{ formData.props.color }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="宽度">
|
|
|
+ <input-number
|
|
|
+ v-model="formData.props.width"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="圆角">
|
|
|
+ <el-switch v-model="formData.props.round" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-card class="mt-8px" body-class="p-8px!">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <span>点</span>
|
|
|
+ <LuPlus class="cursor-pointer" size="16px" @click="handleAddPoint('line')" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-scrollbar max-height="120px">
|
|
|
+ <div
|
|
|
+ class="flex items-center gap-4px box-border pr-12px mb-4px"
|
|
|
+ v-for="(p, idx) in formData.props.points || []"
|
|
|
+ :key="idx"
|
|
|
+ >
|
|
|
+ <div class="w-full flex items-center gap-4px relative">
|
|
|
+ <input-number
|
|
|
+ controls-position="right"
|
|
|
+ :model-value="p.x"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ @change="(val) => setPoint('line', idx, 'x', val)"
|
|
|
+ >
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ controls-position="right"
|
|
|
+ :model-value="p.y"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ @change="(val) => setPoint('line', idx, 'y', val)"
|
|
|
+ >
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ <div class="cursor-pointer" @click="handleDeletePoint('line', idx)">
|
|
|
+ <LuTrash2 size="14px" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-scrollbar>
|
|
|
+ </el-card>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Triangle -->
|
|
|
+ <template v-else-if="formData.type === 'triangle'">
|
|
|
+ <el-form-item label="背景颜色">
|
|
|
+ <ColorPicker
|
|
|
+ v-model:pureColor="formData.props.background_color"
|
|
|
+ format="hex8"
|
|
|
+ picker-type="chrome"
|
|
|
+ use-type="pure"
|
|
|
+ />
|
|
|
+ <span class="text-text-active">{{ formData.props.background_color }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-card class="mt-8px" body-class="p-8px!">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <span>点</span>
|
|
|
+ <LuPlus class="cursor-pointer" size="16px" @click="handleAddPoint('triangle')" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-scrollbar max-height="120px">
|
|
|
+ <div
|
|
|
+ class="flex items-center gap-4px box-border pr-12px mb-4px"
|
|
|
+ v-for="(p, idx) in formData.props.points || []"
|
|
|
+ :key="idx"
|
|
|
+ >
|
|
|
+ <div class="w-full flex items-center gap-4px relative">
|
|
|
+ <input-number
|
|
|
+ controls-position="right"
|
|
|
+ :model-value="p.x"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ @change="(val) => setPoint('triangle', idx, 'x', val)"
|
|
|
+ >
|
|
|
+ <template #prefix>X</template>
|
|
|
+ </input-number>
|
|
|
+ <input-number
|
|
|
+ controls-position="right"
|
|
|
+ :model-value="p.y"
|
|
|
+ :min="0"
|
|
|
+ :max="10000"
|
|
|
+ @change="(val) => setPoint('triangle', idx, 'y', val)"
|
|
|
+ >
|
|
|
+ <template #prefix>Y</template>
|
|
|
+ </input-number>
|
|
|
+ <div class="cursor-pointer" @click="handleDeletePoint('triangle', idx)">
|
|
|
+ <LuTrash2 size="14px" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-scrollbar>
|
|
|
+ </el-card>
|
|
|
+ </template>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <el-button type="primary" @click="dialogVisible = false">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import type { Ref } from 'vue'
|
|
|
+import { computed, ref, watch } from 'vue'
|
|
|
+import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
|
|
|
+import { ColorPicker } from '@/components'
|
|
|
+import { LineEnum } from '../span-group/data'
|
|
|
+import { MdNotInterested } from 'vue-icons-plus/md'
|
|
|
+import { RxUnderline, RxStrikethrough } from 'vue-icons-plus/rx'
|
|
|
+import { GoStrikethrough } from 'vue-icons-plus/go'
|
|
|
+import ImageSelect from '@/views/designer/config/property/components/ImageSelect.vue'
|
|
|
+import { useProjectStore } from '@/store/modules/project'
|
|
|
+
|
|
|
+type CanvasElementType = 'rect' | 'text' | 'image' | 'arc' | 'line' | 'triangle'
|
|
|
+
|
|
|
+type Point = { x: number; y: number }
|
|
|
+
|
|
|
+type RectProps = {
|
|
|
+ x: number
|
|
|
+ y: number
|
|
|
+ width: number
|
|
|
+ height: number
|
|
|
+ background_color: string
|
|
|
+ border: {
|
|
|
+ color: string
|
|
|
+ width: number
|
|
|
+ radius: number
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type TextProps = {
|
|
|
+ x: number
|
|
|
+ y: number
|
|
|
+ width: number
|
|
|
+ font_color: string
|
|
|
+ font_size: number
|
|
|
+ font_family: string
|
|
|
+ text_decor: LineEnum
|
|
|
+ text: string
|
|
|
+}
|
|
|
+
|
|
|
+type ImageProps = {
|
|
|
+ x: number
|
|
|
+ y: number
|
|
|
+ width: number
|
|
|
+ height: number
|
|
|
+ image: string
|
|
|
+ alpha: number
|
|
|
+ recolor: string
|
|
|
+}
|
|
|
+
|
|
|
+type ArcProps = {
|
|
|
+ x: number
|
|
|
+ y: number
|
|
|
+ start_angle: number
|
|
|
+ end_angle: number
|
|
|
+ color: string
|
|
|
+ width: number
|
|
|
+ radius: number
|
|
|
+}
|
|
|
+
|
|
|
+type LineProps = {
|
|
|
+ color: string
|
|
|
+ width: number
|
|
|
+ round: boolean
|
|
|
+ points: Point[]
|
|
|
+}
|
|
|
+
|
|
|
+type TriangleProps = {
|
|
|
+ background_color: string
|
|
|
+ points: Point[]
|
|
|
+}
|
|
|
+
|
|
|
+type CanvasElement =
|
|
|
+ | { type: 'rect'; props: RectProps }
|
|
|
+ | { type: 'text'; props: TextProps }
|
|
|
+ | { type: 'image'; props: ImageProps }
|
|
|
+ | { type: 'arc'; props: ArcProps }
|
|
|
+ | { type: 'line'; props: LineProps }
|
|
|
+ | { type: 'triangle'; props: TriangleProps }
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ values: Ref<CanvasElement[]>
|
|
|
+}>()
|
|
|
+
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const formData = ref<CanvasElement>()
|
|
|
+const editingIndex = ref<number | null>(null)
|
|
|
+
|
|
|
+const elements = computed({
|
|
|
+ get() {
|
|
|
+ return (props.values?.value || []) as CanvasElement[]
|
|
|
+ },
|
|
|
+ set(list: CanvasElement[]) {
|
|
|
+ props.values.value = list
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const projectStore = useProjectStore()
|
|
|
+const fontOptions = computed(() => {
|
|
|
+ const list = (projectStore.project?.resources.fonts || []).map((font) => {
|
|
|
+ return {
|
|
|
+ label: font.fileName,
|
|
|
+ value: font.id
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return [{ label: '默认字体', value: 'xx' }, ...list]
|
|
|
+})
|
|
|
+
|
|
|
+const getElementLabel = (el: CanvasElement, index: number) => {
|
|
|
+ const typeLabelMap: Record<CanvasElementType, string> = {
|
|
|
+ rect: 'Rect',
|
|
|
+ text: 'Text',
|
|
|
+ image: 'Image',
|
|
|
+ arc: 'Arc',
|
|
|
+ line: 'Line',
|
|
|
+ triangle: 'Triangle'
|
|
|
+ }
|
|
|
+ return `${typeLabelMap[el.type] || 'Element'}_${index}`
|
|
|
+}
|
|
|
+
|
|
|
+const createDefaultElement = (type: CanvasElementType): CanvasElement => {
|
|
|
+ if (type === 'rect') {
|
|
|
+ return {
|
|
|
+ type: 'rect',
|
|
|
+ props: {
|
|
|
+ x: 100,
|
|
|
+ y: 75,
|
|
|
+ width: 100,
|
|
|
+ height: 50,
|
|
|
+ background_color: '#2196f3ff',
|
|
|
+ border: {
|
|
|
+ color: '#000000ff',
|
|
|
+ width: 0,
|
|
|
+ radius: 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'text') {
|
|
|
+ return {
|
|
|
+ type: 'text',
|
|
|
+ props: {
|
|
|
+ x: 100,
|
|
|
+ y: 75,
|
|
|
+ width: 100,
|
|
|
+ font_color: '#000000ff',
|
|
|
+ font_size: 16,
|
|
|
+ font_family: 'xx',
|
|
|
+ text_decor: LineEnum.LV_TEXT_DECOR_NONE,
|
|
|
+ text: ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'image') {
|
|
|
+ return {
|
|
|
+ type: 'image',
|
|
|
+ props: {
|
|
|
+ x: 100,
|
|
|
+ y: 75,
|
|
|
+ width: 100,
|
|
|
+ height: 75,
|
|
|
+ image: '',
|
|
|
+ alpha: 255,
|
|
|
+ recolor: '#ffffff00'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'arc') {
|
|
|
+ return {
|
|
|
+ type: 'arc',
|
|
|
+ props: {
|
|
|
+ x: 100,
|
|
|
+ y: 80,
|
|
|
+ start_angle: 0,
|
|
|
+ end_angle: 270,
|
|
|
+ color: '#000000ff',
|
|
|
+ width: 6,
|
|
|
+ radius: 30
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'line') {
|
|
|
+ return {
|
|
|
+ type: 'line',
|
|
|
+ props: {
|
|
|
+ color: '#000000ff',
|
|
|
+ width: 2,
|
|
|
+ round: true,
|
|
|
+ points: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ type: 'triangle',
|
|
|
+ props: {
|
|
|
+ background_color: '#2196f3ff',
|
|
|
+ points: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleAdd = () => {
|
|
|
+ elements.value.push(createDefaultElement('text'))
|
|
|
+}
|
|
|
+
|
|
|
+const handleDelete = (index: number) => {
|
|
|
+ elements.value.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const handleClear = () => {
|
|
|
+ elements.value = []
|
|
|
+}
|
|
|
+
|
|
|
+const handleEdit = (record: CanvasElement, index: number) => {
|
|
|
+ editingIndex.value = index
|
|
|
+ formData.value = record
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 切换元素类型时,按类型重置该元素的数据
|
|
|
+watch(
|
|
|
+ () => formData.value?.type,
|
|
|
+ (newType, oldType) => {
|
|
|
+ if (!newType || !oldType || newType === oldType) return
|
|
|
+ const idx = editingIndex.value
|
|
|
+ if (idx === null) return
|
|
|
+ const next = createDefaultElement(newType as CanvasElementType)
|
|
|
+ elements.value[idx] = next
|
|
|
+ formData.value = elements.value[idx]
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+const handleAddPoint = (shape: 'line' | 'triangle') => {
|
|
|
+ if (!formData.value) return
|
|
|
+ const points = (formData.value.props as any).points as Point[]
|
|
|
+ if (shape === 'line' && points.length >= 2) return
|
|
|
+ if (shape === 'triangle' && points.length >= 3) return
|
|
|
+ points.push({ x: 0, y: 0 })
|
|
|
+}
|
|
|
+
|
|
|
+const handleDeletePoint = (_shape: 'line' | 'triangle', index: number) => {
|
|
|
+ if (!formData.value) return
|
|
|
+ const points = (formData.value.props as any).points as Point[]
|
|
|
+ points.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const setPoint = (_shape: 'line' | 'triangle', index: number, key: 'x' | 'y', val?: number) => {
|
|
|
+ if (typeof val === 'undefined' || !formData.value) return
|
|
|
+ const points = (formData.value.props as any).points as Point[]
|
|
|
+ const p = points[index]
|
|
|
+ if (!p) return
|
|
|
+ p[key] = val
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped></style>
|