|
|
@@ -0,0 +1,496 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <el-scrollbar>
|
|
|
+ <el-collapse v-model="activeNames">
|
|
|
+ <template v-for="(object,index) in objects">
|
|
|
+ <el-collapse-item v-if="object.permissionsConfig && (object.permissionsConfig.length > 0)"
|
|
|
+ :title="object.itemName" :name="object.id">
|
|
|
+ <el-form
|
|
|
+ :key="object.id"
|
|
|
+ label-width="auto"
|
|
|
+ label-position="right"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ <template v-for="(perm) in object.permissionsConfig">
|
|
|
+ <template v-if="object.id == 'WorkSpaceDrawType'">
|
|
|
+ <!-- 背景-->
|
|
|
+ <el-form-item v-if="perm == '0'" label="背景颜色">
|
|
|
+ <el-popover trigger="click" placement="right" :width="265">
|
|
|
+ <template #reference>
|
|
|
+ <ColorButton style="width: 100px" :color="background.color || '#fff'"/>
|
|
|
+ </template>
|
|
|
+ <ColorPicker
|
|
|
+ :modelValue="background.color"
|
|
|
+ @update:modelValue="(color: string) => updateBackground({color: color, fill: color})"
|
|
|
+ />
|
|
|
+ </el-popover>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-else-if="perm == '1'" label="背景尺寸">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="11">
|
|
|
+ <el-input
|
|
|
+ v-model="canvasWidth"
|
|
|
+ :value="pageSizeWidth"
|
|
|
+ @change="changeTemplateWidth"
|
|
|
+ oninput="value=value.replace(/[^\d.]/g,'')"
|
|
|
+ >
|
|
|
+ <template #prepend>宽</template>
|
|
|
+ </el-input>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1"/>
|
|
|
+ <el-col :span="11">
|
|
|
+ <el-input
|
|
|
+ v-model="canvasHeight"
|
|
|
+ :value="pageSizeHeight"
|
|
|
+ @change="changeTemplateHeight"
|
|
|
+ oninput="value=value.replace(/[^\d.]/g,'')"
|
|
|
+ >
|
|
|
+ <template #prepend>高</template>
|
|
|
+ </el-input>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-else-if="perm == '2'" label="上传背景">
|
|
|
+ <!-- 暂不实现-->
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="object.name == 'textbox'">
|
|
|
+ <!-- 文字-->
|
|
|
+ <el-form-item v-if="perm == '0'" label="内容">
|
|
|
+ <!-- <el-tag type="primary">请点击右侧元素更改文字内容</el-tag>-->
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="22">
|
|
|
+ <!-- <el-input @change="eleTextChange(object.id, $event)"/>-->
|
|
|
+ <el-select
|
|
|
+ v-model="object.text"
|
|
|
+ filterable
|
|
|
+ allow-create
|
|
|
+ default-first-option
|
|
|
+ @change="eleTextChange(object.id, $event)">
|
|
|
+ <el-option
|
|
|
+ label="aaaa"
|
|
|
+ value="aaaa"/>
|
|
|
+ </el-select>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '1'" label="字号">
|
|
|
+ <el-button-group class="full-group">
|
|
|
+ <el-tooltip placement="top" content="增大字号" :hide-after="0">
|
|
|
+ <el-button class="font-size" @click="handleElementFontsize(object.id,'+')">
|
|
|
+ <IconFontSize/>
|
|
|
+ +
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+
|
|
|
+ <el-tooltip placement="top" content="减小字号" :hide-after="0">
|
|
|
+ <el-button @click="handleElementFontsize(object.id,'-')">
|
|
|
+ <IconFontSize/>
|
|
|
+ -
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ </el-button-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '2'" label="字体">
|
|
|
+ <el-select v-model="object.fontFamily" placement="left" style="width: 180px"
|
|
|
+ @change="handleElementFontFamily(object.id, $event)">
|
|
|
+ <el-option-group v-for="group in fontOptionGroups" :key="group.label" :label="group.label">
|
|
|
+ <template v-if="group.type == 0">
|
|
|
+ <el-option v-for="item in group.options" :key="item" :value="item.value" :label="item.label"
|
|
|
+ :style="{fontFamily: item.value}"></el-option>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-option v-for="item in group.options" :key="item.id" :value="item.fontFamily"
|
|
|
+ :label="item.fontName">
|
|
|
+ <el-image style="height: 18px; width: 90px" :src="item.fontThumbUrl"></el-image>
|
|
|
+ </el-option>
|
|
|
+ </template>
|
|
|
+ </el-option-group>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '3'" label="颜色">
|
|
|
+ <el-tooltip placement="top" content="文字颜色" :hide-after="0">
|
|
|
+ <div @click.stop>
|
|
|
+ <el-popover trigger="click" placement="right" :width="265" @click.stop>
|
|
|
+ <template #reference>
|
|
|
+ <el-button class="font-color">
|
|
|
+ <TextColorButton :color="object.color">
|
|
|
+ <IconText/>
|
|
|
+ </TextColorButton>
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ <ColorPicker :modelValue="object.color"
|
|
|
+ @update:modelValue="(color: string) => updateFontColor(object.id,color)"/>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
+ </el-tooltip>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '4'" label="字间距">
|
|
|
+ <el-button-group class="full-group">
|
|
|
+ <el-tooltip placement="top" content="减小缩进" :hide-after="0">
|
|
|
+ <el-button @click="handleElementCharSpacing(object.id,'-')">
|
|
|
+ <IconIndentLeft/>
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip placement="top" content="增大缩进" :hide-after="0">
|
|
|
+ <el-button @click="handleElementCharSpacing(object.id,'+')">
|
|
|
+ <IconIndentRight/>
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ </el-button-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '5'" label="尺寸">
|
|
|
+ <el-tag type="primary">请拖拽右侧元素更改元素尺寸</el-tag>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '6'" label="位置">
|
|
|
+ <el-tag type="primary">请拖拽右侧元素更改元素位置</el-tag>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="object.name == 'image'">
|
|
|
+ <!-- 图片-->
|
|
|
+ <el-form-item v-if="perm == '0'" label="上传图片">
|
|
|
+ <FileInput class="full-width-btn" @change="(files: FileList) => replaceImage(object.id,files)">
|
|
|
+ <el-button class="full-btn">
|
|
|
+ <IconTransform class="btn-icon"/>
|
|
|
+ </el-button>
|
|
|
+ </FileInput>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '1'" label="尺寸">
|
|
|
+ <el-tag type="primary">请拖拽右侧元素更改元素尺寸</el-tag>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="perm == '2'" label="位置">
|
|
|
+ <el-tag type="primary">请拖拽右侧元素更改元素位置</el-tag>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ </template>
|
|
|
+ </el-form>
|
|
|
+ </el-collapse-item>
|
|
|
+ </template>
|
|
|
+ </el-collapse>
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import useCanvas from "@/views/Canvas/useCanvas";
|
|
|
+import {computed, ref} from "vue";
|
|
|
+import {MaxSize, MinSize, TransparentFill} from "@/configs/background";
|
|
|
+import {WorkSpaceElement} from "@/types/canvas";
|
|
|
+import {storeToRefs} from "pinia";
|
|
|
+import {useFabricStore, useMainStore, useTemplatesStore} from "@/store";
|
|
|
+import {propertiesToInclude, WorkSpaceDrawType} from "@/configs/canvas";
|
|
|
+import {Gradient, Pattern, Image, util, Textbox} from "fabric";
|
|
|
+import useHandleBackground from "@/hooks/useHandleBackground";
|
|
|
+import {mm2px, px2mm} from "@/utils/image";
|
|
|
+import {ElMessage} from "element-plus";
|
|
|
+import useI18n from "@/hooks/useI18n";
|
|
|
+import {ArcText} from "@/extension/object/ArcText";
|
|
|
+import {FontGroupOption} from "@/types/elements";
|
|
|
+
|
|
|
+const {t} = useI18n();
|
|
|
+const mainStore = useMainStore();
|
|
|
+const {sizeMode, unitMode} = storeToRefs(mainStore);
|
|
|
+const {canvasObject, systemFonts, onlineFonts} = storeToRefs(mainStore)
|
|
|
+const [canvas] = useCanvas();
|
|
|
+const templatesStore = useTemplatesStore();
|
|
|
+const {setBackgroudImage} = useHandleBackground();
|
|
|
+const {currentTemplate} = storeToRefs(templatesStore);
|
|
|
+const fabricStore = useFabricStore();
|
|
|
+const {clip, safe, zoom, opacity} = storeToRefs(fabricStore);
|
|
|
+const objects = canvas.getObjects().filter((object) => !["WorkSpaceMaskType", "WorkSpaceClipType", "WorkSpaceSafeType", "WorkSpaceClipType"].includes(object.id));
|
|
|
+
|
|
|
+
|
|
|
+const activeNames = ref([])
|
|
|
+
|
|
|
+const pageSizeWidth = computed(() => {
|
|
|
+ return Math.round(templateWidth.value * 100) / 100
|
|
|
+})
|
|
|
+const pageSizeHeight = computed(() => {
|
|
|
+ return Math.round(templateHeight.value * 100) / 100
|
|
|
+})
|
|
|
+const templateWidth = computed(() => {
|
|
|
+ const workWidth = currentTemplate.value.width / currentTemplate.value.zoom;
|
|
|
+ return unitMode.value === 0 ? px2mm(workWidth) : workWidth;
|
|
|
+});
|
|
|
+
|
|
|
+const templateHeight = computed(() => {
|
|
|
+ const workHeight = currentTemplate.value.height / currentTemplate.value.zoom;
|
|
|
+ return unitMode.value === 0 ? px2mm(workHeight) : workHeight;
|
|
|
+});
|
|
|
+
|
|
|
+const canvasWidth = ref<number>(templateWidth.value);
|
|
|
+const canvasHeight = ref<number>(templateHeight.value);
|
|
|
+
|
|
|
+const background = computed(() => {
|
|
|
+ if (!currentTemplate.value) {
|
|
|
+ return {
|
|
|
+ fillType: 0,
|
|
|
+ fill: TransparentFill,
|
|
|
+ backgroundColor: "#fff",
|
|
|
+ } as WorkSpaceElement;
|
|
|
+ }
|
|
|
+ if (!currentTemplate.value.workSpace) {
|
|
|
+ return {
|
|
|
+ fillType: 0,
|
|
|
+ fill: TransparentFill,
|
|
|
+ backgroundColor: "#fff",
|
|
|
+ } as WorkSpaceElement;
|
|
|
+ }
|
|
|
+ return currentTemplate.value.workSpace;
|
|
|
+});
|
|
|
+
|
|
|
+const fontOptionGroups = ref<FontGroupOption[]>([
|
|
|
+ {
|
|
|
+ type: 0,
|
|
|
+ label: '系统字体',
|
|
|
+ options: systemFonts.value
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 1,
|
|
|
+ label: '在线字体',
|
|
|
+ options: onlineFonts.value
|
|
|
+ }
|
|
|
+])
|
|
|
+
|
|
|
+
|
|
|
+// 固定宽高
|
|
|
+const isFixed = ref(false);
|
|
|
+// 设置背景
|
|
|
+const updateBackground = (props: Partial<WorkSpaceElement>) => {
|
|
|
+ const [canvas] = useCanvas();
|
|
|
+ const workSpaceDraw = canvas.getObjects().filter((item) => item.id === WorkSpaceDrawType)[0];
|
|
|
+ if (!workSpaceDraw) return;
|
|
|
+ workSpaceDraw.set({...props});
|
|
|
+ if (props.fill instanceof Pattern) {
|
|
|
+ props.fill = props.fill.toObject() as Pattern
|
|
|
+ }
|
|
|
+ templatesStore.updateWorkSpace({workSpace: {...background.value, ...props}});
|
|
|
+ const workProps = workSpaceDraw.toObject(propertiesToInclude as any[]);
|
|
|
+ templatesStore.updateElement({id: workSpaceDraw.id, props: {...workProps, ...props}});
|
|
|
+ canvas.renderAll();
|
|
|
+};
|
|
|
+
|
|
|
+// 修改上传背景
|
|
|
+const changeBackgroundImage = async (imageURL: string) => {
|
|
|
+ if (background.value.imageSize === "repeat") {
|
|
|
+ const backgroundImage = await util.loadImage(imageURL);
|
|
|
+ const workSpacePattern = new Pattern({
|
|
|
+ source: backgroundImage,
|
|
|
+ repeat: "repeat",
|
|
|
+ });
|
|
|
+ updateBackground({fill: workSpacePattern, imageURL});
|
|
|
+ } else {
|
|
|
+ setBackgroudImage(imageURL);
|
|
|
+ updateBackground({fill: TransparentFill, imageURL});
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取画布尺寸
|
|
|
+const getCanvasSize = () => {
|
|
|
+ let width =
|
|
|
+ unitMode.value === 0 ? mm2px(canvasWidth.value) : canvasWidth.value;
|
|
|
+ let height =
|
|
|
+ unitMode.value === 0 ? mm2px(canvasHeight.value) : canvasHeight.value;
|
|
|
+ width = width * zoom.value;
|
|
|
+ height = height * zoom.value;
|
|
|
+ return {width, height};
|
|
|
+};
|
|
|
+
|
|
|
+// 修改画布宽度
|
|
|
+const changeTemplateWidth = () => {
|
|
|
+ const [canvas] = useCanvas();
|
|
|
+ const workSpaceDraw = canvas
|
|
|
+ .getObjects()
|
|
|
+ .filter((item) => item.id === WorkSpaceDrawType)[0];
|
|
|
+ if (!workSpaceDraw) return;
|
|
|
+ const ratio = currentTemplate.value.height / currentTemplate.value.width;
|
|
|
+ let {width, height} = getCanvasSize();
|
|
|
+ if (width / zoom.value < mm2px(MinSize)) {
|
|
|
+ ElMessage({
|
|
|
+ message: t("style.minimumSizeLimit") + MinSize,
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ width = mm2px(MinSize) * zoom.value;
|
|
|
+ }
|
|
|
+ if (width / zoom.value > mm2px(MaxSize)) {
|
|
|
+ ElMessage({
|
|
|
+ message: t("style.maximumSizeLimit") + MaxSize,
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ width = mm2px(MaxSize) * zoom.value;
|
|
|
+ }
|
|
|
+ height = isFixed.value ? width * ratio : height;
|
|
|
+ workSpaceDraw.set({width: width / zoom.value, height: height / zoom.value});
|
|
|
+ templatesStore.setSize(width, height, zoom.value);
|
|
|
+ sizeMode.value = 2;
|
|
|
+ canvas.renderAll();
|
|
|
+ // resetCanvas()
|
|
|
+ // addHistorySnapshot();
|
|
|
+};
|
|
|
+
|
|
|
+// 修改画布高度
|
|
|
+const changeTemplateHeight = () => {
|
|
|
+ const [canvas] = useCanvas();
|
|
|
+ const workSpaceDraw = canvas
|
|
|
+ .getObjects()
|
|
|
+ .filter((item) => item.id === WorkSpaceDrawType)[0];
|
|
|
+ if (!workSpaceDraw) return;
|
|
|
+ const ratio = currentTemplate.value.height / currentTemplate.value.width;
|
|
|
+ let {width, height} = getCanvasSize();
|
|
|
+ if (height / zoom.value < mm2px(MinSize)) {
|
|
|
+ ElMessage({
|
|
|
+ message: t("style.minimumSizeLimit") + MinSize,
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ height = mm2px(MinSize) * zoom.value;
|
|
|
+ }
|
|
|
+ if (height / zoom.value > mm2px(MaxSize)) {
|
|
|
+ ElMessage({
|
|
|
+ message: t("style.maximumSizeLimit") + MaxSize,
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ height = mm2px(MaxSize) * zoom.value;
|
|
|
+ }
|
|
|
+ width = isFixed.value ? height / ratio : width;
|
|
|
+ workSpaceDraw.set({width: width / zoom.value, height: height / zoom.value});
|
|
|
+ templatesStore.setSize(width, height, zoom.value);
|
|
|
+ sizeMode.value = 2;
|
|
|
+ canvas.renderAll();
|
|
|
+ // resetCanvas()
|
|
|
+ // addHistorySnapshot();
|
|
|
+};
|
|
|
+
|
|
|
+//查找目标元素
|
|
|
+const findObject = (objId: string) => {
|
|
|
+ return objects.find((item) => item.id === objId);
|
|
|
+}
|
|
|
+
|
|
|
+//修改文字信息
|
|
|
+const eleTextChange = (objId: string, text) => {
|
|
|
+ const handleElement = findObject(objId) as Textbox | ArcText;
|
|
|
+
|
|
|
+ if (handleElement.isEditing) {
|
|
|
+ handleElement.setSelectionStyles({text})
|
|
|
+ } else {
|
|
|
+ templatesStore.modifedElement(handleElement, {text})
|
|
|
+ }
|
|
|
+ canvas.renderAll()
|
|
|
+}
|
|
|
+
|
|
|
+// 修改字体大小
|
|
|
+const handleElementFontsize = (objId: string, mode: string) => {
|
|
|
+ const handleElement = findObject(objId) as Textbox | ArcText;
|
|
|
+
|
|
|
+ if (handleElement.fontSize <= 6) return
|
|
|
+ const fontSize = mode === '+' ? handleElement.fontSize + 1 : handleElement.fontSize - 1
|
|
|
+ if (handleElement.isEditing) {
|
|
|
+ handleElement.setSelectionStyles({fontSize})
|
|
|
+ } else {
|
|
|
+ templatesStore.modifedElement(handleElement, {fontSize})
|
|
|
+ }
|
|
|
+ canvas.renderAll()
|
|
|
+}
|
|
|
+
|
|
|
+// 修改字体族
|
|
|
+const handleElementFontFamily = (objId: string, fontFamily: string) => {
|
|
|
+ const handleElement = findObject(objId) as Textbox | ArcText;
|
|
|
+
|
|
|
+ if (handleElement.isEditing) {
|
|
|
+ handleElement.setSelectionStyles({fontFamily})
|
|
|
+ } else {
|
|
|
+ templatesStore.modifedElement(handleElement, {fontFamily})
|
|
|
+ }
|
|
|
+ canvas.renderAll()
|
|
|
+}
|
|
|
+
|
|
|
+// 修改字体颜色
|
|
|
+const updateFontColor = (objId: string, fill: string) => {
|
|
|
+ const handleElement = findObject(objId) as Textbox | ArcText;
|
|
|
+
|
|
|
+ if (handleElement.isEditing) {
|
|
|
+ handleElement.setSelectionStyles({fill})
|
|
|
+ } else {
|
|
|
+ templatesStore.modifedElement(handleElement, {fill, color: fill})
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 修改缩进
|
|
|
+const handleElementCharSpacing = (objId: string, mode: '+' | '-') => {
|
|
|
+ const handleElement = findObject(objId) as Textbox | ArcText;
|
|
|
+
|
|
|
+ const handleCharSpacing = handleElement.charSpacing
|
|
|
+ const charSpacing = mode === '+' ? handleCharSpacing + 10 : handleCharSpacing - 10
|
|
|
+ templatesStore.modifedElement(handleElement, {charSpacing})
|
|
|
+}
|
|
|
+
|
|
|
+// 替换图片(保持当前的样式)
|
|
|
+const replaceImage = (objId: string, files: FileList) => {
|
|
|
+ const handleElement = findObject(objId) as Image;
|
|
|
+
|
|
|
+ const props = {src: files.fileLink};
|
|
|
+ handleElement.setSrc(files.fileLink); //红就红吧type硬赋值了
|
|
|
+ templatesStore.updateElement({id: handleElement.id, props});
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ console.log(objects)
|
|
|
+ activeNames.value = objects.map(x => x.id)
|
|
|
+})
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+:deep(.el-tabs__item) {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-search {
|
|
|
+ margin: 0 auto;
|
|
|
+ width: 80%;
|
|
|
+ padding: 20px 10px 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-upload {
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-tabs {
|
|
|
+ width: 90%;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-templates {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ padding: 2px;
|
|
|
+
|
|
|
+ .thumbnail {
|
|
|
+ display: flex;
|
|
|
+ width: 124px;
|
|
|
+ margin: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thumbnail img {
|
|
|
+ outline: 1px solid $borderColor;
|
|
|
+ margin: 0 5px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ outline-color: $themeColor;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.col-img {
|
|
|
+ height: 100px;
|
|
|
+
|
|
|
+ img {
|
|
|
+ max-height: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+</style>
|