|
|
<script setup lang="ts">
|
|
|
import useSelect from "@/hooks/select";
|
|
|
import { fetchArrayByAttrObject, setAttrObjectByArray } from "@/utils/utils";
|
|
|
// 属性选项
|
|
|
import { textTypeConf } from "@/config/attribute/baseType";
|
|
|
import video_type_1 from "@/assets/modelSetting/video_type_1.png";
|
|
|
import video_type_2 from "@/assets/modelSetting/video_type_2.png";
|
|
|
import video_type_3 from "@/assets/modelSetting/video_type_3.png";
|
|
|
|
|
|
const event = inject("event");
|
|
|
const update = getCurrentInstance();
|
|
|
const { mixinState, canvasEditor } = useSelect();
|
|
|
/**业务属性 */
|
|
|
const formData = ref({
|
|
|
iconType: 1
|
|
|
});
|
|
|
|
|
|
const rules = {
|
|
|
iconType: [{ required: true, message: "请选择图标类型", trigger: "change" }]
|
|
|
};
|
|
|
|
|
|
const iconOptions = ref([
|
|
|
{
|
|
|
value: 1,
|
|
|
label: "图标1",
|
|
|
type: "1",
|
|
|
url: video_type_1
|
|
|
},
|
|
|
{
|
|
|
value: 2,
|
|
|
label: "图标2",
|
|
|
type: "2",
|
|
|
url: video_type_2
|
|
|
},
|
|
|
{
|
|
|
value: 3,
|
|
|
label: "图标3",
|
|
|
type: "3",
|
|
|
url: video_type_3
|
|
|
}
|
|
|
]);
|
|
|
const formRef = ref(null);
|
|
|
const submitForm = () => {
|
|
|
formRef.value?.validate((valid: boolean) => {
|
|
|
if (valid) {
|
|
|
// 模拟提交接口
|
|
|
// axios
|
|
|
// .post("/api/submit-icon", form.value)
|
|
|
// .then(() => {
|
|
|
// ElMessage.success("提交成功");
|
|
|
// })
|
|
|
// .catch(() => {
|
|
|
// ElMessage.error("提交失败");
|
|
|
// });
|
|
|
} else {
|
|
|
// ElMessage.error("表单验证失败");
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
/**拓展属性 */
|
|
|
// 通用属性
|
|
|
const baseAttr = reactive({
|
|
|
id: "",
|
|
|
opacity: 0,
|
|
|
angle: 0,
|
|
|
fill: "#fff",
|
|
|
left: 0,
|
|
|
top: 0,
|
|
|
strokeWidth: 0,
|
|
|
strokeDashArray: [],
|
|
|
stroke: "#fff",
|
|
|
shadow: {
|
|
|
color: "#fff",
|
|
|
blur: 0,
|
|
|
offsetX: 0,
|
|
|
offsetY: 0
|
|
|
},
|
|
|
points: {},
|
|
|
selectable: false,
|
|
|
userProperty: [
|
|
|
{
|
|
|
key: "key",
|
|
|
value: "value"
|
|
|
}
|
|
|
]
|
|
|
});
|
|
|
|
|
|
// 动效属性
|
|
|
const animationAttr = reactive({
|
|
|
type: "None"
|
|
|
});
|
|
|
// 字体属性
|
|
|
const fontAttr = reactive({
|
|
|
fontSize: 0,
|
|
|
fontFamily: "",
|
|
|
lineHeight: 0,
|
|
|
charSpacing: 0,
|
|
|
fontWeight: "",
|
|
|
textBackgroundColor: "#fff",
|
|
|
textAlign: "",
|
|
|
fontStyle: "",
|
|
|
underline: false,
|
|
|
linethrough: false,
|
|
|
overline: false
|
|
|
});
|
|
|
|
|
|
// 获取画布边界
|
|
|
const getCanvasBoundaries = () => {
|
|
|
const canvasObj = canvasEditor.canvas.getObjects()[1];
|
|
|
return {
|
|
|
width: canvasObj.width,
|
|
|
height: canvasObj.height
|
|
|
};
|
|
|
};
|
|
|
|
|
|
const constrainObjectWithinCanvas = obj => {
|
|
|
const { width, height } = getCanvasBoundaries();
|
|
|
|
|
|
// 获取对象的当前尺寸和位置
|
|
|
const objLeft = obj.left;
|
|
|
const objTop = obj.top;
|
|
|
const objWidth = obj.width * obj.scaleX;
|
|
|
const objHeight = obj.height * obj.scaleY;
|
|
|
// 限制对象在画布内移动
|
|
|
if (objLeft < 0) {
|
|
|
obj.left = 0;
|
|
|
}
|
|
|
if (objTop < 0) {
|
|
|
obj.top = 0;
|
|
|
}
|
|
|
if (objLeft + objWidth > width) {
|
|
|
obj.left = width - objWidth;
|
|
|
}
|
|
|
if (objTop + objHeight > height) {
|
|
|
obj.top = height - objHeight;
|
|
|
}
|
|
|
|
|
|
// 更新对象的位置
|
|
|
obj.setCoords();
|
|
|
};
|
|
|
|
|
|
const getObjectAttr = e => {
|
|
|
const activeObject = canvasEditor.canvas.getActiveObject();
|
|
|
// 不是当前obj,跳过
|
|
|
if (e && e.target && e.target !== activeObject) return;
|
|
|
if (activeObject) {
|
|
|
// base
|
|
|
baseAttr.id = activeObject.get("id");
|
|
|
baseAttr.userProperty = fetchArrayByAttrObject(
|
|
|
activeObject.get("userProperty")
|
|
|
);
|
|
|
baseAttr.opacity = activeObject.get("opacity") * 100;
|
|
|
baseAttr.fill = activeObject.get("fill");
|
|
|
baseAttr.left = activeObject.get("left");
|
|
|
baseAttr.top = activeObject.get("top");
|
|
|
baseAttr.stroke = activeObject.get("stroke");
|
|
|
baseAttr.strokeWidth = activeObject.get("strokeWidth");
|
|
|
baseAttr.shadow = activeObject.get("shadow") || {};
|
|
|
baseAttr.angle = activeObject.get("angle") || 0;
|
|
|
baseAttr.points = activeObject.get("points") || {};
|
|
|
baseAttr.selectable = activeObject.get("selectable");
|
|
|
console.log("activeObject", activeObject);
|
|
|
|
|
|
const textTypes = ["i-text", "text", "textbox"];
|
|
|
if (textTypes.includes(activeObject.type)) {
|
|
|
fontAttr.fontSize = activeObject.get("fontSize");
|
|
|
fontAttr.fontFamily = activeObject.get("fontFamily");
|
|
|
fontAttr.lineHeight = activeObject.get("lineHeight");
|
|
|
fontAttr.textAlign = activeObject.get("textAlign");
|
|
|
fontAttr.underline = activeObject.get("underline");
|
|
|
fontAttr.linethrough = activeObject.get("linethrough");
|
|
|
fontAttr.charSpacing = activeObject.get("charSpacing");
|
|
|
fontAttr.overline = activeObject.get("overline");
|
|
|
fontAttr.fontStyle = activeObject.get("fontStyle");
|
|
|
fontAttr.textBackgroundColor = activeObject.get("textBackgroundColor");
|
|
|
fontAttr.fontWeight = activeObject.get("fontWeight");
|
|
|
}
|
|
|
// update 动画属性
|
|
|
if (activeObject?.animation && activeObject?.animation?.type != "None") {
|
|
|
const animateObject = activeObject.get("animation");
|
|
|
animationAttr.type = animateObject.type;
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const selectCancel = () => {
|
|
|
baseAttr.fill = "";
|
|
|
update?.proxy?.$forceUpdate();
|
|
|
};
|
|
|
|
|
|
const init = () => {
|
|
|
// 获取字体数据
|
|
|
event.on("selectCancel", selectCancel);
|
|
|
event.on("selectOne", getObjectAttr);
|
|
|
canvasEditor.canvas.on("object:modified", getObjectAttr);
|
|
|
// 监听 object:moving 事件以限制移动范围
|
|
|
canvasEditor.canvas.on("object:moving", e => {
|
|
|
const activeObject = e.target;
|
|
|
if (activeObject) {
|
|
|
constrainObjectWithinCanvas(activeObject);
|
|
|
canvasEditor.canvas.renderAll();
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 通用属性改变
|
|
|
const changeCommon = (key, value) => {
|
|
|
const activeObject = canvasEditor.canvas.getActiveObjects()[0];
|
|
|
// 透明度特殊转换
|
|
|
if (key === "opacity") {
|
|
|
activeObject && activeObject.set(key, value / 100);
|
|
|
canvasEditor.canvas.renderAll();
|
|
|
return;
|
|
|
}
|
|
|
// 旋转角度适配
|
|
|
if (key === "angle") {
|
|
|
activeObject.rotate(value);
|
|
|
canvasEditor.canvas.renderAll();
|
|
|
return;
|
|
|
}
|
|
|
// 用户属性
|
|
|
if (key === "userProperty_key") {
|
|
|
activeObject &&
|
|
|
activeObject.set(
|
|
|
"userProperty",
|
|
|
setAttrObjectByArray(baseAttr.userProperty)
|
|
|
);
|
|
|
canvasEditor.canvas.renderAll();
|
|
|
return;
|
|
|
}
|
|
|
if (key === "userProperty_value") {
|
|
|
activeObject &&
|
|
|
activeObject.set(
|
|
|
"userProperty",
|
|
|
setAttrObjectByArray(baseAttr.userProperty)
|
|
|
);
|
|
|
canvasEditor.canvas.renderAll();
|
|
|
return;
|
|
|
}
|
|
|
if (key === "iconType") {
|
|
|
// 在 group 对象中查找唯一的 fabric.Image 对象
|
|
|
const imageObject = activeObject._objects[1];
|
|
|
|
|
|
if (imageObject) {
|
|
|
const targetIcon = toRaw(iconOptions.value).find(
|
|
|
item => item.value === value
|
|
|
);
|
|
|
console.log(targetIcon, "activeObject_iconType", activeObject);
|
|
|
|
|
|
if (targetIcon) {
|
|
|
// 修改图片路径
|
|
|
imageObject.setSrc(
|
|
|
targetIcon.url,
|
|
|
() => {
|
|
|
// 刷新画布
|
|
|
console.log("Image loaded successfully activeObject_iconType");
|
|
|
activeObject.addWithUpdate(); // 更新对象的坐标和边界框
|
|
|
canvasEditor.canvas.renderAll();
|
|
|
},
|
|
|
{ crossOrigin: "anonymous" }
|
|
|
);
|
|
|
|
|
|
// 加载新的图片并替换旧的图片对象
|
|
|
// fabric.Image.fromURL(
|
|
|
// targetIcon.url,
|
|
|
// img => {
|
|
|
// // 使用 oldImage.get() 获取所有属性,并设置到新图片上
|
|
|
// // img.set(imageObject.get());
|
|
|
// // 在 group 中替换旧的图片对象
|
|
|
// console.log(img, "old_img", imageObject);
|
|
|
// const index = activeObject._objects.indexOf(imageObject);
|
|
|
// if (index !== -1) {
|
|
|
// activeObject._objects[index] = {
|
|
|
// ...imageObject.get(),
|
|
|
// src: img.src
|
|
|
// }; // 替换为新的图片对象
|
|
|
// activeObject.addWithUpdate(); // 更新 group 的边界和变换
|
|
|
// canvasEditor.canvas.renderAll(); // 刷新画布
|
|
|
// } else {
|
|
|
// console.error("Image object not found in the group.");
|
|
|
// }
|
|
|
// },
|
|
|
// {
|
|
|
// crossOrigin: "anonymous" // 如果图片跨域需要设置
|
|
|
// }
|
|
|
// );
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
activeObject && activeObject.set(key, value);
|
|
|
canvasEditor.canvas.renderAll();
|
|
|
|
|
|
// 更新属性
|
|
|
getObjectAttr();
|
|
|
};
|
|
|
|
|
|
onMounted(init);
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
event.off("selectCancel", selectCancel);
|
|
|
event.off("selectOne", getObjectAttr);
|
|
|
canvasEditor.canvas.off("object:modified", getObjectAttr);
|
|
|
canvasEditor.canvas.off("object:moving");
|
|
|
});
|
|
|
</script>
|
|
|
<template>
|
|
|
<div class="box" v-if="mixinState.mSelectMode === 'one'">
|
|
|
<!-- 字体属性 -->
|
|
|
<div v-show="textTypeConf.includes(mixinState.mSelectOneType)">
|
|
|
<!-- 字体属性 -->
|
|
|
</div>
|
|
|
<!-- ID属性 -->
|
|
|
<!-- <div>
|
|
|
<div class="flex-view">
|
|
|
<div class="flex-item">
|
|
|
<span class="label">{{ $t("attributes.id") }}</span>
|
|
|
<div class="content slider-box">
|
|
|
<input
|
|
|
v-model="baseAttr.id"
|
|
|
@change="changeCommon('id', baseAttr.id)"
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div> -->
|
|
|
<el-form
|
|
|
ref="formRef"
|
|
|
:rules="rules"
|
|
|
:inline="true"
|
|
|
:model="formData"
|
|
|
class="demo-form-inline"
|
|
|
label-position="top"
|
|
|
>
|
|
|
<el-form-item label="选择图标" class="w-full" prop="iconType">
|
|
|
<el-radio-group
|
|
|
v-model="formData.iconType"
|
|
|
@change="changeCommon('iconType', formData.iconType)"
|
|
|
>
|
|
|
<el-radio
|
|
|
v-for="option in iconOptions"
|
|
|
:key="option.value"
|
|
|
:label="option.value"
|
|
|
>
|
|
|
<i>
|
|
|
<!-- 图标 -->
|
|
|
</i>
|
|
|
{{ option.label }}
|
|
|
</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<!-- 动态属性 -->
|
|
|
<!-- <div>
|
|
|
<ul>
|
|
|
<li class="flex-view" v-for="(v, k) in baseAttr.userProperty" :key="k">
|
|
|
<div class="flex-item">
|
|
|
<span class="content slider-box">
|
|
|
<input
|
|
|
v-model="baseAttr.userProperty[k].key"
|
|
|
@change="
|
|
|
changeCommon('userProperty_key', baseAttr.userProperty)
|
|
|
"
|
|
|
/>
|
|
|
</span>
|
|
|
<div class="content slider-box">
|
|
|
<input
|
|
|
v-model="baseAttr.userProperty[k].value"
|
|
|
@change="
|
|
|
changeCommon('userProperty_value', baseAttr.userProperty)
|
|
|
"
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|
|
|
</li>
|
|
|
<li
|
|
|
class="flex-view"
|
|
|
style="justify-content: center; color: dodgerblue; text-align: center"
|
|
|
@click="
|
|
|
() => {
|
|
|
baseAttr.userProperty.push({
|
|
|
key: 'key' + baseAttr.userProperty.length,
|
|
|
value: 'value'
|
|
|
});
|
|
|
}
|
|
|
"
|
|
|
>
|
|
|
<span>新增一项</span>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
</template>
|