|
@@ -0,0 +1,439 @@
|
|
|
|
+<template>
|
|
|
|
+ <TempTable :data="tData" ref="tableRef" :id="data.id" />
|
|
|
|
+ <PropertyUpdate
|
|
|
|
+ :describes="describes"
|
|
|
|
+ :data="data"
|
|
|
|
+ :target="shape"
|
|
|
|
+ @change="emit('updateShape', { ...data })"
|
|
|
|
+ />
|
|
|
|
+ <Operate
|
|
|
|
+ :target="shape"
|
|
|
|
+ :menus="operateMenus"
|
|
|
|
+ @show="menuShowHandler"
|
|
|
|
+ @hide="menuHideHandler"
|
|
|
|
+ />
|
|
|
|
+
|
|
|
|
+ <template v-for="(raw, rawNdx) in data.content">
|
|
|
|
+ <template v-for="(_, colNdx) in raw">
|
|
|
|
+ <TextDom
|
|
|
|
+ v-if="tableRef?.texts[rawNdx] && tableRef?.texts[rawNdx][colNdx]?.getNode()"
|
|
|
|
+ :shape="tableRef.texts[rawNdx][colNdx].getNode()!"
|
|
|
|
+ @submit="(val) => submitInputHandler(val, rawNdx, colNdx)"
|
|
|
|
+ @show="showInputHandler"
|
|
|
|
+ @hide="quitInputHandler"
|
|
|
|
+ />
|
|
|
|
+ </template>
|
|
|
|
+ </template>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
+import TempTable from "./temp-table.vue";
|
|
|
|
+import TextDom from "../share/text-area.vue";
|
|
|
|
+import { TableData, getMouseStyle, defaultStyle, TableCollData } from "./index.ts";
|
|
|
|
+import { PropertyUpdate, Operate } from "../../propertys";
|
|
|
|
+import { useComponentStatus } from "@/core/hook/use-component.ts";
|
|
|
|
+import { Transform } from "konva/lib/Util";
|
|
|
|
+import { MathUtils } from "three";
|
|
|
|
+import {
|
|
|
|
+ useCustomTransformer,
|
|
|
|
+ useGetTransformerOperType,
|
|
|
|
+} from "@/core/hook/use-transformer.ts";
|
|
|
|
+import { copy, getResizeCorsur } from "@/utils/shared.ts";
|
|
|
|
+import { computed, ref, watch, watchEffect } from "vue";
|
|
|
|
+import { Group } from "konva/lib/Group";
|
|
|
|
+import { DC } from "@/deconstruction.js";
|
|
|
|
+import { Minus, Plus } from "@element-plus/icons-vue";
|
|
|
|
+import { Rect } from "konva/lib/shapes/Rect";
|
|
|
|
+import { setShapeTransform } from "@/utils/shape.ts";
|
|
|
|
+import { Text } from "konva/lib/shapes/Text";
|
|
|
|
+import {
|
|
|
|
+ useMouseShapesStatus,
|
|
|
|
+ useMouseShapeStatus,
|
|
|
|
+} from "@/core/hook/use-mouse-status.ts";
|
|
|
|
+import { useCursor, usePointerPos } from "@/core/hook/use-global-vars.ts";
|
|
|
|
+import { numEq, Pos } from "@/utils/math.ts";
|
|
|
|
+
|
|
|
|
+const props = defineProps<{ data: TableData }>();
|
|
|
|
+const emit = defineEmits<{
|
|
|
|
+ (e: "updateShape", value: TableData): void;
|
|
|
|
+ (e: "addShape", value: TableData): void;
|
|
|
|
+ (e: "delShape"): void;
|
|
|
|
+}>();
|
|
|
|
+type TableRef = {
|
|
|
|
+ shape: DC<Group>;
|
|
|
|
+ texts: DC<Text>[][];
|
|
|
|
+ getMouseIntersect: (
|
|
|
|
+ pos?: Pos
|
|
|
|
+ ) => {
|
|
|
|
+ rawBorderNdx: number;
|
|
|
|
+ colBorderNdx: number;
|
|
|
|
+ rawNdx: number;
|
|
|
|
+ colNdx: number;
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+const tableRef = ref<TableRef>();
|
|
|
|
+const status = useMouseShapeStatus(computed(() => tableRef.value?.shape));
|
|
|
|
+let inter: Pick<
|
|
|
|
+ ReturnType<TableRef["getMouseIntersect"]>,
|
|
|
|
+ "rawBorderNdx" | "colBorderNdx"
|
|
|
|
+> | null = null;
|
|
|
|
+const pos = usePointerPos();
|
|
|
|
+const cursor = useCursor();
|
|
|
|
+const shapesStatus = useMouseShapesStatus();
|
|
|
|
+watch(
|
|
|
|
+ () => status.value.hover && !status.value.press,
|
|
|
|
+ (hover, _, onCleanup) => {
|
|
|
|
+ if (!hover) return;
|
|
|
|
+ onCleanup(
|
|
|
|
+ watch(
|
|
|
|
+ () => pos.value && tableRef.value?.getMouseIntersect(pos.value),
|
|
|
|
+ (inter, _, onCleanup) => {
|
|
|
|
+ const $shape = shape.value?.getNode();
|
|
|
|
+ if ($shape && inter && (~inter.colBorderNdx || ~inter.rawBorderNdx)) {
|
|
|
|
+ onCleanup(
|
|
|
|
+ cursor.push(getResizeCorsur(!~inter.rawBorderNdx, $shape.rotation()))
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ { immediate: true, flush: "post" }
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
+ { flush: "post" }
|
|
|
|
+);
|
|
|
|
+watch(
|
|
|
|
+ () => status.value.press,
|
|
|
|
+ (press, _, onCleanup) => {
|
|
|
|
+ if (!press) return;
|
|
|
|
+ inter = tableRef.value?.getMouseIntersect() || null;
|
|
|
|
+ const $shape = shape.value?.getNode();
|
|
|
|
+ if ($shape && inter && (~inter.colBorderNdx || ~inter.rawBorderNdx)) {
|
|
|
|
+ const pop = cursor.push(
|
|
|
|
+ getResizeCorsur(!~inter.rawBorderNdx, shape.value?.getNode().rotation())
|
|
|
|
+ );
|
|
|
|
+ const isActive = shapesStatus.actives.includes($shape);
|
|
|
|
+ if (isActive) {
|
|
|
|
+ shapesStatus.actives = shapesStatus.actives.filter((s) => s !== $shape);
|
|
|
|
+ }
|
|
|
|
+ onCleanup(() => {
|
|
|
|
+ inter = null;
|
|
|
|
+ pop();
|
|
|
|
+ // if (isActive) {
|
|
|
|
+ // shapesStatus.actives = [...shapesStatus.actives, $shape];
|
|
|
|
+ // }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ { flush: "post" }
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+const getColMinSize = (col: TableCollData) => {
|
|
|
|
+ const minw = (col.padding || 0) * 2 + (col.fontSize || 12) + 4;
|
|
|
|
+ const minh = (col.padding || 0) * 2 + (col.fontSize || 12) + 4;
|
|
|
|
+ return { w: minw, h: minh };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const getOperType = useGetTransformerOperType();
|
|
|
|
+const matToData = (data: TableData, mat: Transform, initData?: TableData) => {
|
|
|
|
+ if (!initData) {
|
|
|
|
+ initData = copy(data);
|
|
|
|
+ }
|
|
|
|
+ const dec = mat.decompose();
|
|
|
|
+ if (!inter || (!~inter.colBorderNdx && !~inter.rawBorderNdx)) {
|
|
|
|
+ const oldData = copy(data);
|
|
|
|
+ data.height = dec.scaleY * initData.height;
|
|
|
|
+ data.width = dec.scaleX * initData.width;
|
|
|
|
+
|
|
|
|
+ let w = 0;
|
|
|
|
+ let h = 0;
|
|
|
|
+ data.content.forEach((raw, rndx) => {
|
|
|
|
+ raw.forEach((col, cndx) => {
|
|
|
|
+ const initCol = initData.content[rndx][cndx];
|
|
|
|
+ const minSize = getColMinSize(initCol);
|
|
|
|
+ col.width = Math.max(minSize.w, data.width * (initCol.width / initData.width));
|
|
|
|
+ col.height = Math.max(
|
|
|
|
+ minSize.h,
|
|
|
|
+ data.height * (initCol.height / initData.height)
|
|
|
|
+ );
|
|
|
|
+ if (rndx === 0) {
|
|
|
|
+ w += col.width;
|
|
|
|
+ }
|
|
|
|
+ if (cndx === 0) {
|
|
|
|
+ h += col.height;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ const eqW = numEq(w, data.width);
|
|
|
|
+ const eqH = numEq(h, data.height);
|
|
|
|
+
|
|
|
|
+ if (!eqW || !eqH) {
|
|
|
|
+ const type = getOperType();
|
|
|
|
+ if (type) {
|
|
|
|
+ Object.assign(data, oldData);
|
|
|
|
+ } else {
|
|
|
|
+ data.width = w;
|
|
|
|
+ data.height = h;
|
|
|
|
+ const initDec = new Transform(initData.mat).decompose();
|
|
|
|
+ data.mat = new Transform()
|
|
|
|
+ .translate(eqW ? dec.x : initDec.x, eqH ? dec.y : initDec.y)
|
|
|
|
+ .rotate(MathUtils.degToRad(dec.rotation)).m;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ data.mat = new Transform()
|
|
|
|
+ .translate(dec.x, dec.y)
|
|
|
|
+ .rotate(MathUtils.degToRad(dec.rotation)).m;
|
|
|
|
+ }
|
|
|
|
+ return data;
|
|
|
|
+ }
|
|
|
|
+ const initDec = new Transform(initData.mat).decompose();
|
|
|
|
+ const move = new Transform().rotate(MathUtils.degToRad(-dec.rotation)).point({
|
|
|
|
+ x: dec.x - initDec.x,
|
|
|
|
+ y: dec.y - initDec.y,
|
|
|
|
+ });
|
|
|
|
+ if (~inter.rawBorderNdx) {
|
|
|
|
+ const ndxRaw = inter.rawBorderNdx - 1;
|
|
|
|
+ const ndx = ndxRaw === -1 ? 0 : ndxRaw;
|
|
|
|
+ let offset = ndxRaw === -1 ? -move.y : move.y;
|
|
|
|
+ const minSize = getColMinSize(data.content[ndx][0]);
|
|
|
|
+ const h = Math.max(minSize.h, initData.content[ndx][0].height + offset);
|
|
|
|
+ offset = h - initData.content[ndx][0].height;
|
|
|
|
+
|
|
|
|
+ data.content[ndx].forEach(
|
|
|
|
+ (col, colNdx) => (col.height = initData.content[ndx][colNdx].height + offset)
|
|
|
|
+ );
|
|
|
|
+ data.height = initData.height + offset;
|
|
|
|
+
|
|
|
|
+ if (ndxRaw === -1) {
|
|
|
|
+ const translate = new Transform()
|
|
|
|
+ .rotate(MathUtils.degToRad(dec.rotation))
|
|
|
|
+ .point({ x: 0, y: -offset });
|
|
|
|
+ data.mat = new Transform()
|
|
|
|
+ .translate(translate.x, translate.y)
|
|
|
|
+ .multiply(new Transform(initData.mat)).m;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ const ndxRaw = inter.colBorderNdx - 1;
|
|
|
|
+ const ndx = ndxRaw === -1 ? 0 : ndxRaw;
|
|
|
|
+ let offset = ndxRaw === -1 ? -move.x : move.x;
|
|
|
|
+ const minSize = getColMinSize(data.content[0][ndx]);
|
|
|
|
+ const w = Math.max(minSize.w, initData.content[0][ndx].width + offset);
|
|
|
|
+ offset = w - initData.content[0][ndx].width;
|
|
|
|
+ data.content.forEach((row, rowNdx) => {
|
|
|
|
+ row[ndx].width = initData.content[rowNdx][ndx].width + offset;
|
|
|
|
+ });
|
|
|
|
+ data.width = initData.width + offset;
|
|
|
|
+ if (ndxRaw === -1) {
|
|
|
|
+ const translate = new Transform()
|
|
|
|
+ .rotate(MathUtils.degToRad(dec.rotation))
|
|
|
|
+ .point({ x: -offset, y: 0 });
|
|
|
|
+ data.mat = new Transform()
|
|
|
|
+ .translate(translate.x, translate.y)
|
|
|
|
+ .multiply(new Transform(initData.mat)).m;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return data;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let repShape: Rect | null;
|
|
|
|
+const sync = (data: TableData) => {
|
|
|
|
+ if (repShape) {
|
|
|
|
+ repShape.width(data.width);
|
|
|
|
+ repShape.height(data.height);
|
|
|
|
+ const tf = new Transform(data.mat);
|
|
|
|
+ setShapeTransform(repShape, tf);
|
|
|
|
+ initData = copy(data);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let initData: TableData;
|
|
|
|
+const { shape, tData, data, operateMenus, describes } = useComponentStatus<
|
|
|
|
+ Group,
|
|
|
|
+ TableData
|
|
|
|
+>({
|
|
|
|
+ emit,
|
|
|
|
+ props,
|
|
|
|
+ getMouseStyle,
|
|
|
|
+ defaultStyle,
|
|
|
|
+ alignment: (data, mat) => {
|
|
|
|
+ const tf = shape.value!.getNode().getTransform();
|
|
|
|
+ mat.multiply(tf);
|
|
|
|
+ matToData(data, mat);
|
|
|
|
+ sync(data);
|
|
|
|
+ },
|
|
|
|
+ transformType: "custom",
|
|
|
|
+ customTransform(callback, shape, data) {
|
|
|
|
+ useCustomTransformer(shape, data, {
|
|
|
|
+ openSnap: true,
|
|
|
|
+ transformerConfig: { flipEnabled: false },
|
|
|
|
+ getRepShape() {
|
|
|
|
+ repShape = new Rect();
|
|
|
|
+ sync(data.value);
|
|
|
|
+ return {
|
|
|
|
+ shape: repShape as any,
|
|
|
|
+ };
|
|
|
|
+ },
|
|
|
|
+ beforeHandler(data, mat) {
|
|
|
|
+ return matToData(copy(data), mat, initData);
|
|
|
|
+ },
|
|
|
|
+ handler(data, mat) {
|
|
|
|
+ matToData(data, mat, initData);
|
|
|
|
+ // sync(data);
|
|
|
|
+ },
|
|
|
|
+ callback(data) {
|
|
|
|
+ callback();
|
|
|
|
+ sync(data);
|
|
|
|
+ },
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ copyHandler(mat, data) {
|
|
|
|
+ const tf = shape.value!.getNode().getTransform();
|
|
|
|
+ mat.multiply(tf);
|
|
|
|
+ return matToData({ ...data }, mat);
|
|
|
|
+ },
|
|
|
|
+ propertys: [
|
|
|
|
+ "fill",
|
|
|
|
+ "stroke",
|
|
|
|
+ "fontColor",
|
|
|
|
+ "strokeWidth",
|
|
|
|
+ "fontSize",
|
|
|
|
+ // "ref",
|
|
|
|
+ "opacity",
|
|
|
|
+ // "zIndex"
|
|
|
|
+ "align",
|
|
|
|
+ "fontStyle",
|
|
|
|
+ ],
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+watchEffect((onCleanup) => {
|
|
|
|
+ shape.value = tableRef.value?.shape;
|
|
|
|
+ onCleanup(() => (shape.value = undefined));
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+watch(
|
|
|
|
+ () => data.value.fontSize,
|
|
|
|
+ () => {
|
|
|
|
+ data.value.content.forEach((raw) => {
|
|
|
|
+ raw.forEach((col) => {
|
|
|
|
+ col.fontSize = data.value.fontSize;
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ const $shape = shape.value!.getNode();
|
|
|
|
+ data.value = matToData(data.value, $shape.getTransform());
|
|
|
|
+
|
|
|
|
+ sync(data.value);
|
|
|
|
+ $shape.fire("bound-change");
|
|
|
|
+ },
|
|
|
|
+ { flush: "sync" }
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+watchEffect(
|
|
|
|
+ () => {
|
|
|
|
+ data.value.content.forEach((raw) => {
|
|
|
|
+ raw.forEach((col) => {
|
|
|
|
+ col.fontColor = data.value.fontColor;
|
|
|
|
+ col.fontStyle = data.value.fontStyle;
|
|
|
|
+ col.align = data.value.align;
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ { flush: "sync" }
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+let addMenu: any;
|
|
|
|
+const menuShowHandler = () => {
|
|
|
|
+ const config = tableRef.value!.getMouseIntersect();
|
|
|
|
+ addMenu = [
|
|
|
|
+ {
|
|
|
|
+ icon: Plus,
|
|
|
|
+ label: "插入行",
|
|
|
|
+ handler: () => {
|
|
|
|
+ const tempRaw = data.value.content[config.rawNdx];
|
|
|
|
+ data.value.content.splice(
|
|
|
|
+ config.rawNdx,
|
|
|
|
+ 0,
|
|
|
|
+ tempRaw.map((item) => ({ ...item, content: "" }))
|
|
|
|
+ );
|
|
|
|
+ data.value.height += tempRaw[0].height;
|
|
|
|
+ sync(data.value);
|
|
|
|
+ emit("updateShape", { ...data.value });
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ icon: Minus,
|
|
|
|
+ label: "删除行",
|
|
|
|
+ handler: () => {
|
|
|
|
+ const tempRaw = data.value.content[config.rawNdx];
|
|
|
|
+ data.value.content.splice(config.rawNdx, 1);
|
|
|
|
+ data.value.height -= tempRaw[0].height;
|
|
|
|
+ if (data.value.content.length === 0) {
|
|
|
|
+ emit("delShape");
|
|
|
|
+ } else {
|
|
|
|
+ sync(data.value);
|
|
|
|
+ emit("updateShape", data.value);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ icon: Plus,
|
|
|
|
+ label: "插入列",
|
|
|
|
+ handler: () => {
|
|
|
|
+ const tempCol = data.value.content[0][config.colNdx];
|
|
|
|
+ for (let i = 0; i < data.value.content.length; i++) {
|
|
|
|
+ const raw = data.value.content[i];
|
|
|
|
+ raw.splice(config.colNdx, 0, { ...tempCol, content: "" });
|
|
|
|
+ }
|
|
|
|
+ data.value.width += tempCol.width;
|
|
|
|
+ sync(data.value);
|
|
|
|
+ emit("updateShape", data.value);
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ icon: Minus,
|
|
|
|
+ label: "删除列",
|
|
|
|
+ handler: () => {
|
|
|
|
+ const tempCol = data.value.content[0][config.colNdx];
|
|
|
|
+ for (let i = 0; i < data.value.content.length; i++) {
|
|
|
|
+ const raw = data.value.content[i];
|
|
|
|
+ raw.splice(config.colNdx, 1);
|
|
|
|
+ }
|
|
|
|
+ data.value.width -= tempCol.width;
|
|
|
|
+ if (data.value.content[0].length === 0) {
|
|
|
|
+ emit("delShape");
|
|
|
|
+ } else {
|
|
|
|
+ sync(data.value);
|
|
|
|
+ emit("updateShape", data.value);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+ operateMenus.unshift(...addMenu);
|
|
|
|
+};
|
|
|
|
+const menuHideHandler = () => {
|
|
|
|
+ for (let i = 0; i < addMenu.length; i++) {
|
|
|
|
+ const ndx = operateMenus.indexOf(addMenu[i]);
|
|
|
|
+ if (ndx !== -1) {
|
|
|
|
+ operateMenus.splice(ndx, 1);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ addMenu = [];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const showText = ref(false);
|
|
|
|
+const showInputHandler = () => {
|
|
|
|
+ showText.value = true;
|
|
|
|
+ const ndx = shapesStatus.actives.indexOf(shape.value!.getNode());
|
|
|
|
+ if (~ndx) {
|
|
|
|
+ shapesStatus.actives = shapesStatus.actives.filter(
|
|
|
|
+ (v) => v !== shape.value!.getNode()
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+const quitInputHandler = () => {
|
|
|
|
+ showText.value = false;
|
|
|
|
+};
|
|
|
|
+const submitInputHandler = (val: string, rawNdx: number, colNdx: number) => {
|
|
|
|
+ quitInputHandler();
|
|
|
|
+ data.value.content[rawNdx][colNdx].content = val;
|
|
|
|
+ emit("updateShape", data.value);
|
|
|
|
+};
|
|
|
|
+</script>
|