mini画布印花操作同步
This commit is contained in:
@@ -5,6 +5,31 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { fabric } from "fabric-with-all";
|
import { fabric } from "fabric-with-all";
|
||||||
import { ref, watch, onMounted } from "vue";
|
import { ref, watch, onMounted } from "vue";
|
||||||
|
const KEYS = {
|
||||||
|
FILL_X: "location[0]",
|
||||||
|
FILL_Y: "location[1]",
|
||||||
|
FILL_ANGLE: "angle",
|
||||||
|
FILL_SCALEX: "scale[0]",
|
||||||
|
FILL_SCALEY: "scale[1]",
|
||||||
|
FILL_GAPX: "object.gapX",
|
||||||
|
FILL_GAPY: "object.gapY",
|
||||||
|
O_TOP: "object.top",
|
||||||
|
O_LEFT: "object.left",
|
||||||
|
O_SCALE_X: "object.scaleX",
|
||||||
|
O_SCALE_Y: "object.scaleY",
|
||||||
|
O_OPACITY: "object.opacity",
|
||||||
|
O_ANGLE: "object.angle",
|
||||||
|
O_FLIPX: "object.flipX",
|
||||||
|
O_FLIPY: "object.flipY",
|
||||||
|
O_BLENDMODE: "object.blendMode",
|
||||||
|
};
|
||||||
|
const ACTIONS = {
|
||||||
|
SELECT: "select",
|
||||||
|
UPDATE: "update",
|
||||||
|
DELETE: "delete",
|
||||||
|
SORT: "sort",
|
||||||
|
};
|
||||||
|
const emit = defineEmits(["change-canvas"]);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
list: { type: Array, default: () => [] },
|
list: { type: Array, default: () => [] },
|
||||||
});
|
});
|
||||||
@@ -40,19 +65,88 @@
|
|||||||
});
|
});
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
observer.value.disconnect();
|
observer.value.disconnect();
|
||||||
|
unbindEvent();
|
||||||
});
|
});
|
||||||
const initCanvas = () => {
|
const initCanvas = () => {
|
||||||
canvas = new fabric.Canvas(canvasRef.value, {
|
canvas = new fabric.Canvas(canvasRef.value, {
|
||||||
selection: false,
|
selection: false,
|
||||||
|
preserveObjectStacking: true,
|
||||||
});
|
});
|
||||||
canvas.setWidth(el.value.offsetWidth);
|
canvas.setWidth(el.value.offsetWidth);
|
||||||
canvas.setHeight(el.value.offsetHeight);
|
canvas.setHeight(el.value.offsetHeight);
|
||||||
|
bindEvent();
|
||||||
};
|
};
|
||||||
const updateCanvasSize = async () => {
|
const updateCanvasSize = async () => {
|
||||||
canvas.setWidth(el.value.offsetWidth);
|
canvas.setWidth(el.value.offsetWidth);
|
||||||
canvas.setHeight(el.value.offsetHeight);
|
canvas.setHeight(el.value.offsetHeight);
|
||||||
await setCanvasData();
|
await setCanvasData();
|
||||||
};
|
};
|
||||||
|
// 绑定事件
|
||||||
|
const bindEvent = () => {
|
||||||
|
canvas.on("object:modified", onObjectModified);
|
||||||
|
canvas.on("selection:created", onObjectSelected);
|
||||||
|
canvas.on("selection:updated", onObjectSelected);
|
||||||
|
};
|
||||||
|
// 解绑事件
|
||||||
|
const unbindEvent = () => {
|
||||||
|
canvas.off("object:modified", onObjectModified);
|
||||||
|
canvas.off("selection:created", onObjectSelected);
|
||||||
|
canvas.off("selection:updated", onObjectSelected);
|
||||||
|
};
|
||||||
|
// 处理对象修改事件
|
||||||
|
const onObjectModified = (e) => {
|
||||||
|
console.log(e);
|
||||||
|
const object = e.target;
|
||||||
|
const action = e.action;
|
||||||
|
const list = [];
|
||||||
|
const id = object.id;
|
||||||
|
if (action === "drag") {
|
||||||
|
list.push({
|
||||||
|
id: id,
|
||||||
|
action: ACTIONS.UPDATE,
|
||||||
|
key: KEYS.O_TOP,
|
||||||
|
value: object.top,
|
||||||
|
});
|
||||||
|
list.push({
|
||||||
|
id: id,
|
||||||
|
action: ACTIONS.UPDATE,
|
||||||
|
key: KEYS.O_LEFT,
|
||||||
|
value: object.left,
|
||||||
|
});
|
||||||
|
} else if (action === "rotate") {
|
||||||
|
list.push({
|
||||||
|
id: id,
|
||||||
|
action: ACTIONS.UPDATE,
|
||||||
|
key: KEYS.O_ANGLE,
|
||||||
|
value: object.angle,
|
||||||
|
});
|
||||||
|
} else if (action === "scale") {
|
||||||
|
list.push({
|
||||||
|
id: id,
|
||||||
|
action: ACTIONS.UPDATE,
|
||||||
|
key: KEYS.O_SCALE_X,
|
||||||
|
value: object.scaleX,
|
||||||
|
});
|
||||||
|
list.push({
|
||||||
|
id: id,
|
||||||
|
action: ACTIONS.UPDATE,
|
||||||
|
key: KEYS.O_SCALE_Y,
|
||||||
|
value: object.scaleY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
emit("change-canvas", list);
|
||||||
|
};
|
||||||
|
// 对象选中
|
||||||
|
const onObjectSelected = (e) => {
|
||||||
|
const id = e.selected[0].id;
|
||||||
|
const list = [
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
action: ACTIONS.SELECT,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
emit("change-canvas", list);
|
||||||
|
};
|
||||||
const urlToCanvas = (url) => {
|
const urlToCanvas = (url) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fabric.Image.fromURL(
|
fabric.Image.fromURL(
|
||||||
@@ -79,6 +173,23 @@
|
|||||||
|
|
||||||
for (let i = 0; i < props.list.length; i++) {
|
for (let i = 0; i < props.list.length; i++) {
|
||||||
let item = props.list[i];
|
let item = props.list[i];
|
||||||
|
let pattern = await setFill(item);
|
||||||
|
let rect = new fabric.Rect({
|
||||||
|
id: item.id,
|
||||||
|
width: cwidth,
|
||||||
|
height: cheight,
|
||||||
|
fill: pattern,
|
||||||
|
|
||||||
|
...item.object,
|
||||||
|
});
|
||||||
|
canvas.add(rect);
|
||||||
|
}
|
||||||
|
canvas.renderAll();
|
||||||
|
};
|
||||||
|
const setFill = async (item) => {
|
||||||
|
if (!item) return null;
|
||||||
|
const cwidth = canvas.width;
|
||||||
|
const cheight = canvas.height;
|
||||||
let image = await urlToCanvas(item.path);
|
let image = await urlToCanvas(item.path);
|
||||||
let offsetX = item.location[0];
|
let offsetX = item.location[0];
|
||||||
let offsetY = item.location[1];
|
let offsetY = item.location[1];
|
||||||
@@ -107,21 +218,71 @@
|
|||||||
offsetX, // 水平偏移
|
offsetX, // 水平偏移
|
||||||
offsetY, // 垂直偏移
|
offsetY, // 垂直偏移
|
||||||
});
|
});
|
||||||
let rect = new fabric.Rect({
|
return pattern;
|
||||||
id: item.id,
|
};
|
||||||
width: cwidth,
|
const updataList = async (list) => {
|
||||||
height: cheight,
|
const objects = canvas.getObjects();
|
||||||
fill: pattern,
|
// list.forEach((item) => {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
...item.object,
|
let item = list[i];
|
||||||
});
|
let object = objects.find((o) => o.id === item.id);
|
||||||
canvas.add(rect);
|
if (item.action === ACTIONS.UPDATE) {
|
||||||
|
let value = item.value;
|
||||||
|
switch (item.key) {
|
||||||
|
case KEYS.O_TOP:
|
||||||
|
object.set("top", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_LEFT:
|
||||||
|
object.set("left", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_OPACITY:
|
||||||
|
object.set("opacity", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_SCALE_X:
|
||||||
|
object.set("scaleX", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_SCALE_Y:
|
||||||
|
object.set("scaleY", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_ANGLE:
|
||||||
|
object.set("angle", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_FLIPX:
|
||||||
|
object.set("flipX", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_FLIPY:
|
||||||
|
object.set("flipY", value);
|
||||||
|
break;
|
||||||
|
case KEYS.O_BLENDMODE:
|
||||||
|
object.set("blendMode", value);
|
||||||
|
break;
|
||||||
|
case KEYS.FILL_X:
|
||||||
|
case KEYS.FILL_Y:
|
||||||
|
case KEYS.FILL_ANGLE:
|
||||||
|
case KEYS.FILL_SCALEX:
|
||||||
|
case KEYS.FILL_SCALEY:
|
||||||
|
case KEYS.FILL_GAPX:
|
||||||
|
case KEYS.FILL_GAPY:
|
||||||
|
let pattern = await setFill(
|
||||||
|
props.list.find((v) => v.id === item.id)
|
||||||
|
);
|
||||||
|
object.set("fill", pattern);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (item.action === ACTIONS.SELECT) {
|
||||||
|
canvas.setActiveObject(object);
|
||||||
|
} else if (item.action === ACTIONS.SORT) {
|
||||||
|
let ids = item.ids;
|
||||||
|
canvas.clear();
|
||||||
|
for (let j = 0; j < ids.length; j++) {
|
||||||
|
let id = ids[j];
|
||||||
|
let object = objects.find((o) => o.id === id);
|
||||||
|
canvas.add(object);
|
||||||
|
}
|
||||||
|
canvas.renderAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
};
|
|
||||||
const updataList = (list) => {
|
|
||||||
|
|
||||||
console.log(list);
|
|
||||||
};
|
};
|
||||||
defineExpose({
|
defineExpose({
|
||||||
updataList,
|
updataList,
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="test">
|
<div class="test">
|
||||||
<div class="control" v-for="(item, index) in list" :key="index">
|
<div
|
||||||
|
class="control"
|
||||||
|
:class="{ active: item.id === activeId }"
|
||||||
|
v-for="(item, index) in list"
|
||||||
|
:key="item.id"
|
||||||
|
@click="onSelect(item.id)"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<b>{{ item.name }}</b>
|
||||||
|
<button
|
||||||
|
v-if="index !== 0"
|
||||||
|
@click="onMove(item.id, list[index - 1].id)"
|
||||||
|
>
|
||||||
|
←
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="index !== list.length - 1"
|
||||||
|
@click="onMove(item.id, list[index + 1].id)"
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>偏移X</span>
|
<span>偏移X</span>
|
||||||
<input type="number" v-model="item.location[0]" />
|
<input type="number" v-model="item.location[0]" />
|
||||||
@@ -15,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>缩放</span>
|
<span>缩放</span>
|
||||||
<input type="number" v-model="item.scale" />
|
<input type="number" v-model="item.scale[0]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>水平间距</span>
|
<span>水平间距</span>
|
||||||
@@ -26,6 +47,7 @@
|
|||||||
<input type="number" v-model="item.object.gapY" />
|
<input type="number" v-model="item.object.gapY" />
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
<hr />
|
||||||
<div>
|
<div>
|
||||||
<span>缩放X</span>
|
<span>缩放X</span>
|
||||||
<input type="number" v-model="item.object.scaleX" step="0.1" />
|
<input type="number" v-model="item.object.scaleX" step="0.1" />
|
||||||
@@ -42,6 +64,11 @@
|
|||||||
<span>Y</span>
|
<span>Y</span>
|
||||||
<input type="number" v-model="item.object.top" />
|
<input type="number" v-model="item.object.top" />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>角度</span>
|
||||||
|
<input type="number" v-model="item.object.angle" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span>透明度</span>
|
<span>透明度</span>
|
||||||
<input
|
<input
|
||||||
@@ -54,7 +81,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<pingpu :list="list" ref="pingpuRef" />
|
<pingpu
|
||||||
|
:list="list"
|
||||||
|
ref="pingpuRef"
|
||||||
|
@change-canvas="updateCanvas"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -62,9 +93,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, watch } from "vue";
|
import { ref, onMounted, watch } from "vue";
|
||||||
import pingpu from "./pingpu.vue";
|
import pingpu from "./pingpu.vue";
|
||||||
|
const ACTIONS = {
|
||||||
|
SELECT: "select",
|
||||||
|
UPDATE: "update",
|
||||||
|
DELETE: "delete",
|
||||||
|
SORT: "sort",
|
||||||
|
};
|
||||||
|
|
||||||
const convertDotNotationToBracket = (str) =>
|
const convertDotNotationToBracket = (str) =>
|
||||||
str.replace(/(?:^|\.)(\d+)(?=\.|$)/g, "[#$1]").replace(/\[#/g, "[");
|
str.replace(/(?:^|\.)(\d+)(?=\.|$)/g, "[#$1]").replace(/\[#/g, "[");
|
||||||
|
const activeId = ref("1");
|
||||||
const list = ref([
|
const list = ref([
|
||||||
{
|
{
|
||||||
id: "1",
|
id: "1",
|
||||||
@@ -75,7 +113,7 @@
|
|||||||
location: [0, 0],
|
location: [0, 0],
|
||||||
scale: [1, 1],
|
scale: [1, 1],
|
||||||
angle: 0,
|
angle: 0,
|
||||||
name: "Print2",
|
name: "Print1",
|
||||||
priority: 1,
|
priority: 1,
|
||||||
object: {
|
object: {
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -121,11 +159,64 @@
|
|||||||
const deepCopy = (obj) => JSON.parse(JSON.stringify(obj));
|
const deepCopy = (obj) => JSON.parse(JSON.stringify(obj));
|
||||||
const oldList = ref(deepCopy(list.value));
|
const oldList = ref(deepCopy(list.value));
|
||||||
const pingpuRef = ref(null);
|
const pingpuRef = ref(null);
|
||||||
|
const updateCanvas = (arr) => {
|
||||||
|
oldList.value = deepCopy(list.value);
|
||||||
|
arr.forEach((item) => {
|
||||||
|
list.value.forEach((v) => {
|
||||||
|
if (item.action === ACTIONS.UPDATE) {
|
||||||
|
if (v.id === item.id) {
|
||||||
|
if (item.action === ACTIONS.UPDATE) {
|
||||||
|
try {
|
||||||
|
const key = item.key;
|
||||||
|
const str = /^\[/.test(item.key)
|
||||||
|
? "v" + key
|
||||||
|
: "v." + key;
|
||||||
|
eval(`${str} = item.value`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (item.action === ACTIONS.SELECT) {
|
||||||
|
activeId.value = item.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelect = (id) => {
|
||||||
|
activeId.value = id;
|
||||||
|
pingpuRef.value.updataList([
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
action: ACTIONS.SELECT,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
const onMove = (id, id2) => {
|
||||||
|
const obj1 = list.value.find((v) => v.id === id);
|
||||||
|
const obj2 = list.value.find((v) => v.id === id2);
|
||||||
|
const index1 = list.value.indexOf(obj1);
|
||||||
|
const index2 = list.value.indexOf(obj2);
|
||||||
|
if (index1 < index2) {
|
||||||
|
list.value.splice(index2, 0, list.value.splice(index1, 1)[0]);
|
||||||
|
} else {
|
||||||
|
list.value.splice(index1, 0, list.value.splice(index2, 1)[0]);
|
||||||
|
}
|
||||||
|
const ids = list.value.map((v) => v.id);
|
||||||
|
pingpuRef.value.updataList([
|
||||||
|
{
|
||||||
|
action: ACTIONS.SORT,
|
||||||
|
ids,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
watch(
|
watch(
|
||||||
() => list.value,
|
() => list.value,
|
||||||
() => updateList(),
|
() => updateList(),
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
);
|
);
|
||||||
|
// 监听列表变化属性变更
|
||||||
const updateList = () => {
|
const updateList = () => {
|
||||||
const changeList = [];
|
const changeList = [];
|
||||||
oldList.value.forEach((oldItem) => {
|
oldList.value.forEach((oldItem) => {
|
||||||
@@ -136,19 +227,18 @@
|
|||||||
changeList.push({
|
changeList.push({
|
||||||
...item,
|
...item,
|
||||||
id: oldItem.id,
|
id: oldItem.id,
|
||||||
action: "update-item",
|
action: ACTIONS.UPDATE,
|
||||||
key: convertDotNotationToBracket(item.key),
|
key: convertDotNotationToBracket(item.key),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
changeList.push({
|
changeList.push({
|
||||||
id: oldItem.id,
|
id: oldItem.id,
|
||||||
action: "delete-item",
|
action: ACTIONS.DELETE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
oldList.value = deepCopy(list.value);
|
oldList.value = deepCopy(list.value);
|
||||||
console.log(changeList);
|
|
||||||
if (changeList.length) {
|
if (changeList.length) {
|
||||||
pingpuRef.value.updataList(changeList);
|
pingpuRef.value.updataList(changeList);
|
||||||
}
|
}
|
||||||
@@ -161,8 +251,7 @@
|
|||||||
} else if (obj1[key] !== obj2[key]) {
|
} else if (obj1[key] !== obj2[key]) {
|
||||||
diffProps.push({
|
diffProps.push({
|
||||||
key: `${path}${key}`,
|
key: `${path}${key}`,
|
||||||
oldValue: obj1[key],
|
value: obj2[key],
|
||||||
newValue: obj2[key],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,14 +267,24 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
&.active {
|
||||||
|
border-color: rgb(17, 68, 223);
|
||||||
|
box-shadow: 0 0 5px rgb(17, 68, 223);
|
||||||
|
}
|
||||||
> div {
|
> div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
> * {
|
||||||
|
margin-right: 10px;
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
> span {
|
> span {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
> input {
|
> input:not([type="range"]) {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user