table.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. <template>
  2. <TempTable
  3. :data="tData"
  4. ref="tableRef"
  5. :id="data.id"
  6. @update-content="submitInputHandler"
  7. editer
  8. />
  9. <PropertyUpdate
  10. :describes="describes"
  11. :data="data"
  12. :target="shape"
  13. @change="emit('updateShape', { ...data })"
  14. @delete="emit('delShape')"
  15. />
  16. <Operate
  17. :target="shape"
  18. :menus="operateMenus"
  19. @show="menuShowHandler"
  20. @hide="menuHideHandler"
  21. />
  22. </template>
  23. <script lang="ts" setup>
  24. import TempTable from "./temp-table.vue";
  25. import {
  26. TableData,
  27. getMouseStyle,
  28. defaultStyle,
  29. matResponse,
  30. getColMinSize,
  31. } from "./index.ts";
  32. import { PropertyUpdate, Operate } from "../../html-mount/propertys/index.ts";
  33. import { useComponentStatus } from "@/core/hook/use-component.ts";
  34. import { Transform } from "konva/lib/Util";
  35. import { MathUtils } from "three";
  36. import {
  37. useCustomTransformer,
  38. useGetTransformerOperType,
  39. useTransformer,
  40. } from "@/core/hook/use-transformer.ts";
  41. import { copy, getResizeCorsur } from "@/utils/shared.ts";
  42. import { computed, nextTick, ref, watch, watchEffect } from "vue";
  43. import { Group } from "konva/lib/Group";
  44. import { DC } from "@/deconstruction.js";
  45. import { Rect } from "konva/lib/shapes/Rect";
  46. import { setShapeTransform } from "@/utils/shape.ts";
  47. import { Text } from "konva/lib/shapes/Text";
  48. import {
  49. useMouseShapesStatus,
  50. useMouseShapeStatus,
  51. } from "@/core/hook/use-mouse-status.ts";
  52. import { useCursor, usePointerPos } from "@/core/hook/use-global-vars.ts";
  53. import { Pos } from "@/utils/math.ts";
  54. const props = defineProps<{ data: TableData }>();
  55. const emit = defineEmits<{
  56. (e: "updateShape", value: TableData): void;
  57. (e: "addShape", value: TableData): void;
  58. (e: "delShape"): void;
  59. }>();
  60. type TableRef = {
  61. shape: DC<Group>;
  62. texts: DC<Text>[][];
  63. getMouseIntersect: (
  64. pos?: Pos
  65. ) => {
  66. rowBorderNdx: number;
  67. colBorderNdx: number;
  68. rowNdx: number;
  69. colNdx: number;
  70. };
  71. };
  72. const tableRef = ref<TableRef>();
  73. const status = useMouseShapeStatus(computed(() => tableRef.value?.shape));
  74. let inter: Pick<
  75. ReturnType<TableRef["getMouseIntersect"]>,
  76. "rowBorderNdx" | "colBorderNdx"
  77. > | null = null;
  78. const pos = usePointerPos();
  79. const cursor = useCursor();
  80. const shapesStatus = useMouseShapesStatus();
  81. watch(
  82. () => status.value.hover && !status.value.press,
  83. (hover, _, onCleanup) => {
  84. if (!hover) return;
  85. onCleanup(
  86. watch(
  87. () => pos.value && tableRef.value?.getMouseIntersect(pos.value),
  88. (inter, _, onCleanup) => {
  89. const $shape = shape.value?.getNode();
  90. if ($shape && inter && (~inter.colBorderNdx || ~inter.rowBorderNdx)) {
  91. onCleanup(
  92. cursor.push(getResizeCorsur(!~inter.rowBorderNdx, $shape.rotation()))
  93. );
  94. }
  95. },
  96. { immediate: true, flush: "post" }
  97. )
  98. );
  99. },
  100. { flush: "post" }
  101. );
  102. watch(
  103. () => status.value.press,
  104. (press, _, onCleanup) => {
  105. if (!press) return;
  106. inter = tableRef.value?.getMouseIntersect() || null;
  107. const $shape = shape.value?.getNode();
  108. if ($shape && inter && (~inter.colBorderNdx || ~inter.rowBorderNdx)) {
  109. const pop = cursor.push(
  110. getResizeCorsur(!~inter.rowBorderNdx, shape.value?.getNode().rotation())
  111. );
  112. const isActive = shapesStatus.actives.includes($shape);
  113. if (isActive) {
  114. shapesStatus.actives = shapesStatus.actives.filter((s) => s !== $shape);
  115. }
  116. onCleanup(() => {
  117. inter = null;
  118. pop();
  119. });
  120. }
  121. },
  122. { flush: "post" }
  123. );
  124. const getOperType = useGetTransformerOperType();
  125. const matToData = (data: TableData, mat: Transform, initData?: TableData) => {
  126. if (!initData) {
  127. initData = copy(data);
  128. }
  129. const dec = mat.decompose();
  130. if (!inter || (!~inter.colBorderNdx && !~inter.rowBorderNdx)) {
  131. return matResponse({ data, mat, operType: getOperType()! }, initData);
  132. }
  133. const initDec = new Transform(initData.mat).decompose();
  134. const move = new Transform().rotate(MathUtils.degToRad(-dec.rotation)).point({
  135. x: dec.x - initDec.x,
  136. y: dec.y - initDec.y,
  137. });
  138. if (~inter.rowBorderNdx) {
  139. const ndxrow = inter.rowBorderNdx - 1;
  140. const ndx = ndxrow === -1 ? 0 : ndxrow;
  141. let offset = ndxrow === -1 ? -move.y : move.y;
  142. const minSize = getColMinSize(data.content[ndx][0]);
  143. const h = Math.max(minSize.h, initData.content[ndx][0].height + offset);
  144. offset = h - initData.content[ndx][0].height;
  145. data.content[ndx].forEach(
  146. (col, colNdx) => (col.height = initData.content[ndx][colNdx].height + offset)
  147. );
  148. data.height = initData.height + offset;
  149. if (ndxrow === -1) {
  150. const translate = new Transform()
  151. .rotate(MathUtils.degToRad(dec.rotation))
  152. .point({ x: 0, y: -offset });
  153. data.mat = new Transform()
  154. .translate(translate.x, translate.y)
  155. .multiply(new Transform(initData.mat)).m;
  156. }
  157. } else {
  158. const ndxrow = inter.colBorderNdx - 1;
  159. const ndx = ndxrow === -1 ? 0 : ndxrow;
  160. let offset = ndxrow === -1 ? -move.x : move.x;
  161. const minSize = getColMinSize(data.content[0][ndx]);
  162. const w = Math.max(minSize.w, initData.content[0][ndx].width + offset);
  163. offset = w - initData.content[0][ndx].width;
  164. data.content.forEach((row, rowNdx) => {
  165. row[ndx].width = initData.content[rowNdx][ndx].width + offset;
  166. });
  167. data.width = initData.width + offset;
  168. if (ndxrow === -1) {
  169. const translate = new Transform()
  170. .rotate(MathUtils.degToRad(dec.rotation))
  171. .point({ x: -offset, y: 0 });
  172. data.mat = new Transform()
  173. .translate(translate.x, translate.y)
  174. .multiply(new Transform(initData.mat)).m;
  175. }
  176. }
  177. return data;
  178. };
  179. let repShape: Rect | null;
  180. const transformer = useTransformer();
  181. const sync = (data: TableData) => {
  182. if (repShape) {
  183. repShape.width(data.width);
  184. repShape.height(data.height);
  185. const tf = new Transform(data.mat);
  186. setShapeTransform(repShape, tf);
  187. initData = copy(data);
  188. transformer.forceUpdate();
  189. }
  190. };
  191. let initData: TableData;
  192. const { shape, tData, data, operateMenus, describes } = useComponentStatus<
  193. Group,
  194. TableData
  195. >({
  196. emit,
  197. props,
  198. getMouseStyle,
  199. defaultStyle,
  200. // alignment: (data, mat) => {
  201. // matResponse({ data, mat, increment: true });
  202. // sync(data);
  203. // return data;
  204. // },
  205. transformType: "custom",
  206. customTransform(callback, shape, data) {
  207. useCustomTransformer(shape, data, {
  208. openSnap: true,
  209. transformerConfig: { flipEnabled: false },
  210. getRepShape() {
  211. repShape = new Rect();
  212. sync(data.value);
  213. return {
  214. shape: repShape as any,
  215. update(data) {
  216. sync(data);
  217. },
  218. };
  219. },
  220. beforeHandler(data, mat) {
  221. return matToData(copy(data), mat, initData);
  222. },
  223. handler(data, mat) {
  224. matToData(data, mat, initData);
  225. },
  226. callback(data) {
  227. callback();
  228. nextTick(() => shape.value?.getNode().fire("bound-change"));
  229. },
  230. });
  231. },
  232. copyHandler(mat, data) {
  233. return matResponse({ data, mat, increment: true });
  234. },
  235. propertys: [
  236. "fill",
  237. "stroke",
  238. "fontColor",
  239. "strokeWidth",
  240. "fontSize",
  241. // "ref",
  242. "opacity",
  243. // "zIndex"
  244. "align",
  245. "fontStyle",
  246. ],
  247. });
  248. watchEffect((onCleanup) => {
  249. shape.value = tableRef.value?.shape;
  250. onCleanup(() => (shape.value = undefined));
  251. });
  252. watch(
  253. () => data.value.fontSize,
  254. () => {
  255. data.value.content.forEach((row) => {
  256. row.forEach((col) => {
  257. col.fontSize = data.value.fontSize;
  258. });
  259. });
  260. const $shape = shape.value!.getNode();
  261. data.value = matToData(data.value, $shape.getTransform());
  262. sync(data.value);
  263. $shape.fire("bound-change");
  264. },
  265. { flush: "sync" }
  266. );
  267. watchEffect(
  268. () => {
  269. data.value.content.forEach((row) => {
  270. row.forEach((col) => {
  271. col.fontColor = data.value.fontColor;
  272. col.fontStyle = data.value.fontStyle;
  273. col.align = data.value.align;
  274. });
  275. });
  276. },
  277. { flush: "sync" }
  278. );
  279. let addMenu: any;
  280. const menuShowHandler = () => {
  281. const config = tableRef.value!.getMouseIntersect();
  282. addMenu = [];
  283. if (!data.value.notaddRow) {
  284. addMenu.push({
  285. label: "插入行",
  286. handler: () => {
  287. const temprow = data.value.content[config.rowNdx];
  288. data.value.content.splice(
  289. config.rowNdx,
  290. 0,
  291. temprow.map((item) => ({ ...item, content: "" }))
  292. );
  293. data.value.height += temprow[0].height;
  294. sync(data.value);
  295. nextTick(() => shape.value?.getNode().fire("bound-change"));
  296. emit("updateShape", { ...data.value });
  297. },
  298. });
  299. }
  300. const canDelRaw = data.value.content[config.rowNdx].every((item) => !item.notdel);
  301. if (canDelRaw) {
  302. addMenu.push({
  303. label: "删除行",
  304. handler: () => {
  305. const temprow = data.value.content[config.rowNdx];
  306. data.value.content.splice(config.rowNdx, 1);
  307. data.value.height -= temprow[0].height;
  308. nextTick(() => shape.value?.getNode().fire("bound-change"));
  309. if (data.value.content.length === 0) {
  310. emit("delShape");
  311. } else {
  312. sync(data.value);
  313. emit("updateShape", data.value);
  314. }
  315. },
  316. });
  317. }
  318. if (!data.value.notaddCol) {
  319. addMenu.push({
  320. label: "插入列",
  321. handler: () => {
  322. const tempCol = data.value.content[0][config.colNdx];
  323. for (let i = 0; i < data.value.content.length; i++) {
  324. const row = data.value.content[i];
  325. row.splice(config.colNdx, 0, { ...tempCol, content: "" });
  326. }
  327. data.value.width += tempCol.width;
  328. nextTick(() => shape.value?.getNode().fire("bound-change"));
  329. sync(data.value);
  330. emit("updateShape", data.value);
  331. },
  332. });
  333. }
  334. const canDelCol = data.value.content.every((items) => !items[config.colNdx].notdel);
  335. if (canDelCol) {
  336. addMenu.push({
  337. label: "删除列",
  338. handler: () => {
  339. const tempCol = data.value.content[0][config.colNdx];
  340. for (let i = 0; i < data.value.content.length; i++) {
  341. const row = data.value.content[i];
  342. row.splice(config.colNdx, 1);
  343. }
  344. data.value.width -= tempCol.width;
  345. nextTick(() => shape.value?.getNode().fire("bound-change"));
  346. if (data.value.content[0].length === 0) {
  347. emit("delShape");
  348. } else {
  349. sync(data.value);
  350. emit("updateShape", data.value);
  351. }
  352. },
  353. });
  354. }
  355. operateMenus.unshift(...addMenu);
  356. };
  357. const menuHideHandler = () => {
  358. for (let i = 0; i < addMenu.length; i++) {
  359. const ndx = operateMenus.indexOf(addMenu[i]);
  360. if (ndx !== -1) {
  361. operateMenus.splice(ndx, 1);
  362. }
  363. }
  364. addMenu = [];
  365. };
  366. const submitInputHandler = (playData: {
  367. val: string;
  368. rowNdx: number;
  369. colNdx: number;
  370. }) => {
  371. data.value.content[playData.rowNdx][playData.colNdx].content = playData.val;
  372. emit("updateShape", data.value);
  373. };
  374. </script>