Browse Source

feat: 优化项目细节

bill 3 months ago
parent
commit
491d5d822c
60 changed files with 846 additions and 771 deletions
  1. 1 0
      public/icons/m_rotate.svg
  2. 0 3
      src/constant/help-style.ts
  3. 2 1
      src/constant/index.ts
  4. 4 4
      src/core/components/arrow/arrow.vue
  5. 1 1
      src/core/components/circle/circle.vue
  6. 1 1
      src/core/components/circle/index.ts
  7. 1 1
      src/core/components/group/group.vue
  8. 1 1
      src/core/components/image/image.vue
  9. 2 1
      src/core/components/image/index.ts
  10. 1 0
      src/core/components/index.ts
  11. 31 9
      src/core/components/line/single-line.vue
  12. 4 9
      src/core/components/line/temp-line.vue
  13. 3 0
      src/core/components/line/use-draw.ts
  14. 21 20
      src/core/components/polygon/index.ts
  15. 3 1
      src/core/components/rectangle/index.ts
  16. 1 1
      src/core/components/rectangle/rectangle.vue
  17. 37 11
      src/core/components/serial/index.ts
  18. 3 19
      src/core/components/serial/serial-group.vue
  19. 2 2
      src/core/components/share/edit-line.vue
  20. 5 4
      src/core/components/share/edit-point.vue
  21. 1 1
      src/core/components/share/size-line.vue
  22. 1 1
      src/core/components/share/text-area.vue
  23. 3 3
      src/core/components/table/index.ts
  24. 10 2
      src/core/components/text/index.ts
  25. 1 0
      src/core/components/text/temp-text.vue
  26. 8 7
      src/core/components/triangle/index.ts
  27. 1 1
      src/core/components/triangle/triangle.vue
  28. 2 0
      src/core/components/util.ts
  29. 1 1
      src/core/helper/active-boxs.vue
  30. 0 1
      src/core/helper/layers.vue
  31. 1 1
      src/core/helper/snap-lines.vue
  32. 4 4
      src/core/hook/use-alignment.ts
  33. 32 2
      src/core/hook/use-dxf.ts
  34. 9 6
      src/core/hook/use-expose.ts
  35. 42 38
      src/core/hook/use-interactive.ts
  36. 1 1
      src/core/hook/use-selection.ts
  37. 59 11
      src/core/hook/use-transformer.ts
  38. 1 1
      src/core/html-mount/propertys/components/color.vue
  39. 5 3
      src/core/html-mount/propertys/describes.json
  40. 3 2
      src/core/html-mount/propertys/mount.vue
  41. 5 5
      src/example/components/header/actions.ts
  42. 8 6
      src/example/components/slide/actions.ts
  43. 2 0
      src/example/components/slide/menu.ts
  44. 8 4
      src/example/components/slide/slide-item.vue
  45. 6 2
      src/example/components/slide/slide.vue
  46. 4 3
      src/example/fuse/store.ts
  47. 11 20
      src/example/fuse/views/overview/header.vue
  48. 1 1
      src/example/fuse/views/overview/index.vue
  49. 1 1
      src/example/fuse/views/overview/query.vue
  50. 10 6
      src/example/fuse/views/overview/slide-icons.vue
  51. 1 1
      src/example/fuse/views/overview/slide.vue
  52. 249 0
      src/example/fuse/views/tabulation/gen-tab.ts
  53. 0 277
      src/example/fuse/views/tabulation/gen.ts
  54. 7 6
      src/example/fuse/views/tabulation/index.vue
  55. 0 223
      src/example/fuse/views/tabulation/slide-icons.vue
  56. 34 17
      src/example/fuse/views/tabulation/slide.vue
  57. 41 16
      src/example/platform/platform-draw.ts
  58. 8 7
      src/utils/colors.ts
  59. 122 0
      src/utils/polygon.ts
  60. 19 1
      src/utils/shared.ts

File diff suppressed because it is too large
+ 1 - 0
public/icons/m_rotate.svg


+ 0 - 3
src/constant/help-style.ts

@@ -1,5 +1,2 @@
-import { getMouseColors } from "../utils/colors.ts"
-
 export const themeColor = import.meta.env.VITE_PRIMARY
-export const themeMouseColors = getMouseColors(themeColor)
 

+ 2 - 1
src/constant/index.ts

@@ -3,4 +3,5 @@ export const DomOutMountId = 'dom-out-mount'
 export const DataGroupId = 'data-group'
 export const rendererMap = new WeakMap<any, { unmounteds: (() => void)[] }>()
 export const rendererName = 'draw-renderer'
-export const defaultLayer = 'default'
+export const defaultLayer = 'default'
+export const themeColor = import.meta.env.VITE_PRIMARY

+ 4 - 4
src/core/components/arrow/arrow.vue

@@ -28,7 +28,7 @@ import { Line } from "konva/lib/shapes/Line";
 import { Pos } from "@/utils/math.ts";
 import { Group } from "konva/lib/Group";
 import { flatPositions } from "@/utils/shared.ts";
-import { themeColor } from "@/constant/help-style.ts";
+import { themeColor } from "@/constant";
 
 const props = defineProps<{ data: ArrowData }>();
 const emit = defineEmits<{
@@ -77,12 +77,12 @@ const { shape, tData, operateMenus, describes, data } = useComponentStatus<
   },
   propertys: [
     "fill",
-    "pointerPosition",
+    // "pointerPosition",
     "strokeWidth",
-    "pointerLength",
+    // "pointerLength",
     // "dash",
     // "ref",
-    "opacity",
+    // "opacity",
     // "zIndex",
   ],
 });

+ 1 - 1
src/core/components/circle/circle.vue

@@ -82,7 +82,7 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
     "strokeWidth",
     "fontSize",
     // "ref",
-    "opacity",
+    // "opacity",
     // "dash",
     //  "zIndex"
     "align",

+ 1 - 1
src/core/components/circle/index.ts

@@ -124,7 +124,7 @@ if (!initRadius) {
 }
 
 export const getPredefine = (key: keyof CircleData) => {
-  if (key === 'fill') {
+  if (['fill', 'stroke'].includes(key)) {
     return { canun: true }
   } else if (key === 'strokeWidth') {
     return { proportion: true }

+ 1 - 1
src/core/components/group/group.vue

@@ -26,7 +26,7 @@ import { useOperMode } from "@/core/hook/use-status.ts";
 import { EntityShape } from "@/deconstruction.js";
 import { getBaseItem } from "../util.ts";
 import { useForciblyShowItemIds } from "@/core/hook/use-global-vars.ts";
-import { themeColor } from "@/constant/help-style.ts";
+import { themeColor } from "@/constant";
 
 const props = defineProps<{ data: GroupData }>();
 const emit = defineEmits<{

+ 1 - 1
src/core/components/image/image.vue

@@ -79,7 +79,7 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
   },
   propertys: [
     // "stroke", "strokeWidth",
-    "opacity",
+    // "opacity",
     //  "ref", "zIndex"
   ],
   debug: true,

+ 2 - 1
src/core/components/image/index.ts

@@ -9,6 +9,7 @@ import { getMouseColors } from "@/utils/colors.ts";
 import { imageInfo } from "@/utils/resource.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Size } from "@/utils/math.ts";
+import { themeColor } from "@/constant";
 
 export { default as Component } from "./image.vue";
 export { default as TempComponent } from "./temp-image.vue";
@@ -16,7 +17,7 @@ export { default as TempComponent } from "./temp-image.vue";
 export const shapeName = "图片";
 export const defaultStyle = {
   strokeWidth: 0,
-  stroke: "#000000",
+  stroke: themeColor,
 };
 
 export const addMode = "dot";

+ 1 - 0
src/core/components/index.ts

@@ -52,6 +52,7 @@ export const components = _components as Components
 
 export type Components = {
   [key in keyof typeof _components]: (typeof _components)[key] & {
+    delItem?: (store: DrawStore, data: DrawItem<key>) => void
     useDraw?: () => void
     getSnapInfos?: (items: DrawItem<key>) => ComponentSnapInfo[];
     GroupComponent: (props: { data: DrawItem[] }) => any;

+ 31 - 9
src/core/components/line/single-line.vue

@@ -8,20 +8,20 @@
     :id="data.id"
     :disablePoint="!canEdit || mode.include(Mode.readonly)"
     :ndx="0"
-    @dragstart="emit('updateBefore')"
+    @dragstart="dragstartHandler(points.map((item) => item.id))"
     @update:line="
       (p) => {
         emit('updatePoint', { ...points[0], ...p[0] });
         emit('updatePoint', { ...points[1], ...p[1] });
       }
     "
-    @dragend="emit('update')"
+    @dragend="dragendHandler"
     @add-point="addPoint"
   />
 
   <v-group>
     <SizeLine
-      v-if="config.showComponentSize"
+      v-if="config.showComponentSize || isDrawIng || dragIng"
       :points="points"
       :strokeWidth="style.strokeWidth"
       :stroke="style.stroke"
@@ -40,7 +40,7 @@
       :id="line.id"
       :disable="addMode"
       :color="style.stroke"
-      @dragstart="dragstartHandler"
+      @dragstart="dragstartHandler([point.id])"
       @update:position="(p) => emit('updatePoint', { ...point, ...p })"
       @dragend="dragendHandler"
       @delete="delPoint(point)"
@@ -51,6 +51,7 @@
     :describes="describes"
     :data="line"
     :target="shape"
+    :name="shapeName"
     @change="
       () => {
         emit('updateBefore');
@@ -65,7 +66,7 @@
 
 <script lang="ts" setup>
 import { computed, ref } from "vue";
-import { getMouseStyle, getSnapInfos, LineData } from "./index.ts";
+import { getMouseStyle, getSnapInfos, LineData, shapeName } from "./index.ts";
 import { onlyId } from "@/utils/shared.ts";
 import EditLine from "../share/edit-line.vue";
 import { Pos } from "@/utils/math.ts";
@@ -81,6 +82,7 @@ import { useCustomSnapInfos } from "@/core/hook/use-snap.ts";
 import { mergeDescribes } from "@/core/html-mount/propertys/index.ts";
 import { PropertyUpdate, Operate } from "../../html-mount/propertys/index.ts";
 import { useAnimationMouseStyle } from "@/core/hook/use-mouse-status.ts";
+import { themeColor } from "@/constant";
 
 const mode = useMode();
 
@@ -120,11 +122,24 @@ const menus = [
   },
 ];
 
-const [style] = useAnimationMouseStyle({
+const [mstyle] = useAnimationMouseStyle({
   shape,
   getMouseStyle,
   data: lineData as any,
 });
+const isDrawIng = computed(
+  () =>
+    props.addMode && props.data.lines.indexOf(props.line) === props.data.lines.length - 1
+);
+
+const style = computed(() =>
+  isDrawIng.value
+    ? {
+        ...mstyle.value,
+        stroke: themeColor,
+      }
+    : mstyle.value
+);
 
 const addPoint = (pos: Pos) => {
   emit("updateBefore");
@@ -141,18 +156,25 @@ const delPoint = (point: LineData["points"][number]) => {
 
 const infos = useCustomSnapInfos();
 let snapInfos: ComponentSnapInfo[];
-const dragstartHandler = () => {
+let dragIng = ref(false);
+const dragstartHandler = (eIds: string[]) => {
+  dragIng.value = true;
   emit("updateBefore");
+
   snapInfos = getSnapInfos({
     ...props.data,
-    lines: props.data.lines.filter((item) => item.id !== props.line.id),
+    lines: props.data.lines.filter(
+      (item) => !(eIds.includes(item.a) || eIds.includes(item.b))
+    ),
+    points: props.data.points.filter((item) => !eIds.includes(item.id)),
   });
   snapInfos.forEach((item) => {
     infos.add(item);
   });
 };
 const dragendHandler = () => {
+  dragIng.value = false;
   emit("update");
-  snapInfos.forEach((item) => infos.remove(item));
+  // snapInfos.forEach((item) => infos.remove(item));
 };
 </script>

+ 4 - 9
src/core/components/line/temp-line.vue

@@ -12,7 +12,6 @@
       @update-point="updatePointHandler"
       @update-before="updateBeforeHandler"
       @update="updateHandler"
-      @update-line="updateLineHandler"
       @del-line="delLineHandler(item)"
     />
   </v-group>
@@ -90,8 +89,10 @@ const addPointHandler = (p: LineData["points"][0], l: LineData["lines"][0]) => {
 
 const updatePointHandler = (p: LineData["points"][0]) => {
   const ndx = props.data.points.findIndex((pn) => pn.id === p.id);
-  ctx.update.points[p.id] = p;
-  props.data.points[ndx] = p;
+  // props.data.points[ndx] = p;
+  props.data.points[ndx].x = p.x;
+  props.data.points[ndx].y = p.y;
+  ctx.update.points[p.id] = props.data.points[ndx];
 };
 const delLineHandler = (l: LineData["lines"][0]) => {
   ctx.del.lines[l.id] = l;
@@ -103,12 +104,6 @@ const addLineHandler = (l: LineData["lines"][0]) => {
   props.data.lines.push(l);
 };
 
-const updateLineHandler = (l: LineData["lines"][0]) => {
-  console.log(l);
-  // const ndx = props.data.lines.findIndex((ln) => ln.id === l.id);
-  // props.data.lines[ndx] = l;
-};
-
 const updateHandler = () => {
   normalLineData(props.data, ctx);
   emit("updateShape");

+ 3 - 0
src/core/components/line/use-draw.ts

@@ -279,6 +279,9 @@ export const useDraw = () => {
   return drawItems;
 };
 
+
+
+
 export type NLineDataCtx = {
   del: {
     points: Record<string, LineData["points"][0]>;

+ 21 - 20
src/core/components/polygon/index.ts

@@ -10,29 +10,29 @@ export { default as TempComponent } from "./temp-polygon.vue";
 
 export const shapeName = "多边形";
 export const defaultStyle = {
-  stroke: '#000000',
-  fill: '#ffffff',
+  stroke: "#000000",
+  fill: "#ffffff",
   strokeWidth: 5,
   dash: [30, 0],
 };
 
 export const getMouseStyle = (data: PolygonData) => {
   const fillStatus = data.fill && getMouseColors(data.fill);
-  const strokeStatus = getMouseColors(data.stroke || defaultStyle.stroke);
+  const strokeStatus = data.stroke && getMouseColors(defaultStyle.stroke);
   const strokeWidth = data.strokeWidth || defaultStyle.strokeWidth;
 
   return {
     default: {
-      fill: data.fill || defaultStyle.fill,
-      stroke: data.stroke || defaultStyle.stroke,
+      fill: data.fill ,
+      stroke: data.stroke,
       strokeWidth,
     },
-    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus.press },
+    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus && strokeStatus.press },
     select: {
       fill: fillStatus && fillStatus.select,
-      stroke: strokeStatus.select,
+      stroke: strokeStatus && strokeStatus.select,
     },
   };
 };
@@ -78,14 +78,14 @@ export const interactiveFixData: InteractiveFix<"polygon"> = ({
   info,
 }) => {
   // data.points = [...info.consumed, info.cur!];
-    const nv = [...info.consumed, info.cur!];
-    data.points.length = nv.length
-    for (let i = 0; i < nv.length; i++) {
-      if (inRevise(data.points[i], nv[i])) {
-        data.points[i] = nv[i]
-      }
+  const nv = [...info.consumed, info.cur!];
+  data.points.length = nv.length;
+  for (let i = 0; i < nv.length; i++) {
+    if (inRevise(data.points[i], nv[i])) {
+      data.points[i] = nv[i];
     }
-  
+  }
+
   return data;
 };
 
@@ -108,9 +108,10 @@ export const matResponse = ({
   return data;
 };
 
-
 export const getPredefine = (key: keyof PolygonData) => {
-  if (key === 'strokeWidth') {
-    return { proportion: true }
+  if (["fill", "stroke"].includes(key)) {
+    return { canun: true };
+  } else if (key === "strokeWidth") {
+    return { proportion: true };
   }
-}
+};

+ 3 - 1
src/core/components/rectangle/index.ts

@@ -119,9 +119,11 @@ export const matResponse = ({
   return data;
 };
 export const getPredefine = (key: keyof RectangleData) => {
-  if (key === "fill") {
+
+  if (["fill", "stroke"].includes(key)) {
     return { canun: true };
   } else if (key === "strokeWidth") {
     return { proportion: true };
   }
+
 };

+ 1 - 1
src/core/components/rectangle/rectangle.vue

@@ -52,7 +52,7 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus({
     "strokeWidth",
     "fontSize",
     // "ref",
-    "opacity",
+    // "opacity",
     // "dash",
     //  "zIndex"
     "align",

+ 37 - 11
src/core/components/serial/index.ts

@@ -1,12 +1,11 @@
 import { getBaseItem } from "../util.ts";
 import { Transform } from "konva/lib/Util";
-import { CircleData, defaultStyle } from "../circle";
+import { CircleData, defaultStyle as circleDefaultStyle } from "../circle";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { DrawStore } from "@/core/store/index.ts";
 import { Pos } from "@/utils/math.ts";
 
 export {
-  defaultStyle,
   getMouseStyle,
   getSnapInfos,
   getSnapPoints,
@@ -19,6 +18,10 @@ export { default as TempComponent } from "./temp-serial.vue";
 
 export const shapeName = "序号";
 export const addMode = "dot";
+export const defaultStyle = {
+  ...circleDefaultStyle,
+  strokeWidth: 2,
+};
 
 export type SerialData = CircleData;
 
@@ -50,6 +53,7 @@ export const interactiveToData: InteractiveTo<"serial"> = ({
 }) => {
   if (info.cur) {
     let item = {
+      ...defaultStyle,
       fontSize: defaultStyle.fontSize,
       ...getBaseItem(),
       ...preset,
@@ -71,9 +75,33 @@ export const interactiveFixData: InteractiveFix<"serial"> = ({
   return data;
 };
 
+export const joinKey = "serial-table";
+export const delItem = (store: DrawStore, item: SerialData) => {
+  const table = store
+    .getTypeItems("table")
+    .find((item) => item.key === joinKey)!;
+  const data = store.getTypeItems("serial");
+  const ndx = data.indexOf(item);
 
+  for (let i = ndx + 1; i < data.length; i++) {
+    const c = (Number(data[i].content) - 1).toString();
+    table.content[i + 1][0].content = data[i].content = c.toString();
+  }
+  table.height -= table.content[ndx + 1][0].height;
+  table.content.splice(ndx + 1, 1);
 
-export const matResponse = ({data, mat, increment}: MatResponseProps<'serial'>, initRadius?: Pos) => {
+  if (data.length === 1) {
+    store.delItem("table", table.id);
+  } else {
+    store.setItem("table", { id: table.id, value: table });
+  }
+  store.delItem("serial", item.id);
+};
+
+export const matResponse = (
+  { data, mat, increment }: MatResponseProps<"serial">,
+  initRadius?: Pos
+) => {
   if (!initRadius) {
     initRadius = {
       x: data.radiusX,
@@ -81,20 +109,18 @@ export const matResponse = ({data, mat, increment}: MatResponseProps<'serial'>,
     };
   }
   if (increment) {
-    mat = mat.copy().multiply(new Transform(data.mat))
+    mat = mat.copy().multiply(new Transform(data.mat));
   }
   const dec = mat.decompose();
   data.radiusX = data.radiusY = dec.scaleY * initRadius.y;
 
   const w = (data.radiusX * 2) / Math.sqrt(2);
   data.fontSize = getSerialFontSizeByFontW(data, w);
-  data.mat = new Transform()
-    .translate(dec.x, dec.y)
-    .rotate(0).m;
+  data.mat = new Transform().translate(dec.x, dec.y).rotate(0).m;
   return data;
-}
+};
 export const getPredefine = (key: keyof CircleData) => {
-  if (key === 'fill') {
-    return { canun: true }
+  if (key === "fill") {
+    return { canun: true };
   }
-}
+};

+ 3 - 19
src/core/components/serial/serial-group.vue

@@ -4,7 +4,7 @@
       :data="item"
       @updateShape="(value: any) => store.setItem('serial', { id: item.id, value })"
       @addShape="addHandler"
-      @delShape="delHandler(ndx)"
+      @delShape="delItem(store, item)"
       v-if="!itemHasRegistor(item.id)"
     />
     <template v-else>
@@ -20,7 +20,7 @@ import { computed, watch } from "vue";
 import { useHistory } from "@/core/hook/use-history";
 import { ShapeType } from "..";
 import { TableData, interactiveToData as tableInteractiveToData } from "../table";
-import { getCurrentNdx, SerialData } from ".";
+import { delItem, getCurrentNdx, SerialData } from ".";
 import {
   useGetViewBoxPositionPixel,
   useViewerInvertTransform,
@@ -39,23 +39,7 @@ const addHandler = (value: SerialData) => {
 };
 
 const delHandler = (ndx: number) => {
-  const value = data.value[ndx];
-  const table = jTable.value!;
-  for (let i = ndx + 1; i < data.value.length; i++) {
-    const c = (Number(data.value[i].content) - 1).toString();
-    table.content[i + 1][0].content = data.value[i].content = c.toString();
-  }
-  table.height -= table.content[ndx + 1][0].height;
-  table.content.splice(ndx + 1, 1);
-
-  history.onceTrack(() => {
-    if (data.value.length === 1) {
-      store.delItem("table", table.id);
-    } else {
-      store.setItem("table", { id: table.id, value: table });
-    }
-    store.delItem("serial", value.id);
-  });
+  delItem(store, data.value[ndx]);
 };
 
 const joinKey = "serial-table";

+ 2 - 2
src/core/components/share/edit-line.vue

@@ -6,7 +6,7 @@
       opacity: opacity || 0,
       stroke: data.stroke,
       points: flatPositions(points),
-      hitStrokeWidth: data.strokeWidth + 20,
+      hitStrokeWidth: data.strokeWidth,
     }"
   />
 
@@ -30,7 +30,7 @@ import { useCustomSnapInfos, useGlobalSnapInfos, useSnap } from "@/core/hook/use
 import { ComponentSnapInfo } from "..";
 import { generateSnapInfos } from "../util";
 import { getMouseColors } from "@/utils/colors";
-import { themeColor } from "@/constant/help-style";
+import { themeColor } from "@/constant";
 import { Circle } from "konva/lib/shapes/Circle";
 import { SLineData } from "../sequent-line";
 

+ 5 - 4
src/core/components/share/edit-point.vue

@@ -1,6 +1,6 @@
 <template>
   <v-circle
-    :config="{ ...style, ...position, hitStrokeWidth: style.strokeWidth + 10 }"
+    :config="{ ...style, ...position, hitStrokeWidth: style.strokeWidth }"
     ref="circle"
   />
   <Operate :target="circle" :menus="[{ label: '删除', handler: () => emit('delete') }]" />
@@ -8,7 +8,7 @@
 
 <script lang="ts" setup>
 import { Pos } from "@/utils/math.ts";
-import { themeColor } from "@/constant/help-style.ts";
+import { themeColor } from "@/constant";
 import { computed, ref, watch } from "vue";
 import { DC } from "@/deconstruction";
 import { Circle } from "konva/lib/shapes/Circle";
@@ -48,7 +48,7 @@ const style = computed(() => {
   const size = props.size || 5;
   return {
     radius: size / 2,
-    fill: dragIng.value ? "#fff" : color.pub,
+    fill: isHover.value || dragIng.value ? "#fff" : color.pub,
     stroke: color.pub,
     strokeWidth: size / 4,
     opacity: props.opacity !== undefined ? props.opacity : props.disable ? 0.5 : 1,
@@ -94,9 +94,9 @@ const snap = useSnap(refSnapInfos);
 const circle = ref<DC<Circle>>();
 const offset = useShapeDrag(circle);
 const hResult = useShapeIsHover(circle);
+const isHover = hResult[0];
 const cursor = useCursor();
 if (!props.notDelete) {
-  const isHover = hResult[0];
   watch(
     isHover,
     (hover, _, onCleanup) => {
@@ -126,6 +126,7 @@ watch(offset, (offset, oldOffsert) => {
     const refSnapInfos = props.getSelfSnapInfos
       ? props.getSelfSnapInfos(point)
       : generateSnapInfos([point], true, true);
+    // console.log(refSnapInfos);
     const transform = snap.move(refSnapInfos);
     emit("update:position", transform ? transform.point(point) : point);
   } else {

+ 1 - 1
src/core/components/share/size-line.vue

@@ -48,7 +48,7 @@ const props = withDefaults(
   { stroke: "#999", strokeWidth: 1 }
 );
 
-const style = computed(() => ({ stroke: "#999", strokeWidth: 1 }));
+const style = computed(() => ({ stroke: "#999", strokeWidth: 10 }));
 const fontSize = computed(() => 10 + style.value.strokeWidth * 2);
 const len = computed(() =>
   props.closed ? props.points.length : props.points.length - 1

+ 1 - 1
src/core/components/share/text-area.vue

@@ -90,7 +90,7 @@ const getStyles = () => {
   return {
     fontSize: shape.fontSize() + "px",
     lineHeight: shape.lineHeight(),
-    fontFamily: shape.fontFamily(),
+    fontFamily: `"${shape.fontFamily()}"`,
     letterSpacing: shape.letterSpacing() + "px",
     textAlign: shape.align() as CanvasTextAlign,
     color: shape.fill().toString(),

+ 3 - 3
src/core/components/table/index.ts

@@ -1,5 +1,5 @@
 import { Transform } from "konva/lib/Util";
-import { themeMouseColors } from "@/constant/help-style.ts";
+import { themeColor } from "@/constant";
 import {
   BaseItem,
   generateSnapInfos,
@@ -29,7 +29,7 @@ export const defaultCollData = {
   fontSize: 16,
   align: "center",
   fontStyle: "normal",
-  fontColor: themeMouseColors.theme,
+  fontColor: themeColor
 };
 
 export const addMode = "area";
@@ -139,7 +139,7 @@ export const interactiveFixData: InteractiveFix<"table"> = ({
 
 export const getColMinSize = (col: TableCollData) => {
   const minw = (col.padding || 0) * 2 + (col.fontSize || 12) + 4;
-  const minh = (col.padding || 0) * 2 + (col.fontSize || 12) + 4;
+  const minh = (col.padding || 0) * 2 + (col.fontSize || 12) ;
   return { w: minw, h: minh };
 };
 

+ 10 - 2
src/core/components/text/index.ts

@@ -11,16 +11,24 @@ import { shallowReactive } from "vue";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { zeroEq } from "@/utils/math.ts";
 import { MathUtils } from "three";
+import { getSupportedFont } from "@/utils/shared.ts";
 
 export { default as Component } from "./text.vue";
 export { default as TempComponent } from "./temp-text.vue";
 
+// 使用检测后的字体
+const supportedFont = getSupportedFont([
+  'FangSong',
+  'SimSun',
+  'Microsoft YaHei',
+  'sans-serif'
+]);
+
 export const shapeName = "文字";
 export const defaultStyle = {
-  // stroke: themeMouseColors.theme,
   fill: '#000000',
   // strokeWidth: 0,
-  fontFamily: "Calibri",
+  fontFamily: supportedFont,
   fontSize: 16,
   align: "center",
   fontStyle: "normal",

+ 1 - 0
src/core/components/text/temp-text.vue

@@ -29,6 +29,7 @@ const emit = defineEmits<{
   (e: "updateContent", data: string): void;
   (e: "update:isEdit", val: boolean): void;
 }>();
+
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
 const shape = ref<DC<Text>>();
 defineExpose({

+ 8 - 7
src/core/components/triangle/index.ts

@@ -21,19 +21,19 @@ export const addMode = "area";
 
 export const getMouseStyle = (data: TriangleData) => {
   const fillStatus = data.fill && getMouseColors(data.fill);
-  const strokeStatus = getMouseColors(data.stroke || defaultStyle.stroke);
+  const strokeStatus = data.stroke && getMouseColors(data.stroke);
   const strokeWidth = data.strokeWidth;
 
   return {
     default: {
       fill: data.fill,
-      stroke: data.stroke || defaultStyle.stroke,
+      stroke: data.stroke ,
       strokeWidth,
     },
-    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus.hover },
-    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus.press },
-    select: { select: fillStatus && fillStatus.select, stroke: strokeStatus.select },
+    hover: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    focus: { fill: fillStatus && fillStatus.hover, stroke: strokeStatus && strokeStatus.hover },
+    press: { fill: fillStatus && fillStatus.press, stroke: strokeStatus && strokeStatus.press },
+    select: { select: fillStatus && fillStatus.select, stroke: strokeStatus && strokeStatus.select },
   };
 };
 
@@ -98,7 +98,8 @@ export const matResponse = ({data, mat, increment}: MatResponseProps<'triangle'>
   return data;
 }
 export const getPredefine = (key: keyof TriangleData) => {
-  if (key === "fill") {
+
+  if (["fill", "stroke"].includes(key)) {
     return { canun: true };
   } else if (key === "strokeWidth") {
     return { proportion: true };

+ 1 - 1
src/core/components/triangle/triangle.vue

@@ -52,7 +52,7 @@ const { shape, tData, operateMenus, describes, data } = useComponentStatus({
     "strokeWidth",
     "fontSize",
     // "ref",
-    "opacity",
+    // "opacity",
     // "dash",
     //  "zIndex"
     "align",

+ 2 - 0
src/core/components/util.ts

@@ -101,3 +101,5 @@ export const generateSnapInfos = (
     };
   });
 };
+
+

+ 1 - 1
src/core/helper/active-boxs.vue

@@ -18,7 +18,7 @@ import { reactive, ref, watchEffect } from "vue";
 import { useMouseShapesStatus } from "../hook/use-mouse-status";
 import { IRect } from "konva/lib/types";
 import { DC, EntityShape } from "@/deconstruction";
-import { themeColor } from "@/constant/help-style";
+import { themeColor } from "@/constant";
 import { Rect } from "konva/lib/shapes/Rect";
 import { useDashAnimation } from "../hook/use-animation";
 import { useOnComponentBoundChange } from "../hook/use-component";

+ 0 - 1
src/core/helper/layers.vue

@@ -37,7 +37,6 @@ const data = ref({
   width: 20,
   height: 20,
   rotation: 0,
-  // stroke: themeMouseColors.pub,
   fill: "#000000",
   url: "/icons/grouping.svg",
 });

+ 1 - 1
src/core/helper/snap-lines.vue

@@ -21,7 +21,7 @@ import { computed, ComputedRef, ref, watchEffect } from "vue";
 import { SnapInfo, useGlobalSnapInfos, useSnapResultInfo } from "../hook/use-snap";
 import { useViewerTransform } from "../hook/use-viewer";
 import { RectConfig } from "konva/lib/shapes/Rect";
-import { themeColor } from "@/constant/help-style";
+import { themeColor } from "@/constant";
 import { DC } from "@/deconstruction";
 import { Line } from "konva/lib/shapes/Line";
 import { useDashAnimation } from "../hook/use-animation";

+ 4 - 4
src/core/hook/use-alignment.ts

@@ -12,7 +12,7 @@ import { useMode, useOperMode } from './use-status'
 import { mergeFuns } from "@/utils/shared";
 import { getLineRelationMat, Pos } from "@/utils/math";
 import { Circle } from "konva/lib/shapes/Circle";
-import { themeMouseColors } from "@/constant/help-style";
+import { themeColor } from "@/constant";
 import { Line } from "konva/lib/shapes/Line";
 import { Shape } from "konva/lib/Shape";
 import { shapeTreeContain } from "@/utils/shape";
@@ -59,7 +59,7 @@ export const useJoinShapes = (
     for (let i = 1; i < points.value.length; i++) {
       const line = new Line({
         dash: [5, 10],
-        stroke: themeMouseColors.pub,
+        stroke: themeColor,
         strokeWidth: size / 4,
         visible: false,
       });
@@ -82,8 +82,8 @@ export const useJoinShapes = (
 
     const circleConfig = {
       radius: size / 2,
-      fill: themeMouseColors.disable,
-      stroke: themeMouseColors.press,
+      fill: themeColor,
+      stroke: '#ffffff',
       strokeWidth: size / 4,
       visible: false,
     };

+ 32 - 2
src/core/hook/use-dxf.ts

@@ -27,6 +27,7 @@ import { ArrowData, PointerPosition } from "../components/arrow";
 import { IconData } from "../components/icon";
 import { useSetViewport, useViewer, useViewerInvertTransform } from "./use-viewer";
 import { nextTick } from "vue";
+import { SLineData } from "../components/sequent-line";
 
 export const useGetDXF = () => {
   const store = useStore();
@@ -187,8 +188,8 @@ export const useGetDXF = () => {
       let item;
       let mat;
       switch (type) {
-        case "line":
-          item = _item as LineData;
+        case 'sequentLine':
+          item = _item as SLineData;
           writer.addLWPolyline(
             item.points.map((p) => ({ point: point2d(p.x, -p.y) })),
             {
@@ -198,6 +199,35 @@ export const useGetDXF = () => {
             }
           );
           break;
+        case 'line':
+          const litem = _item as LineData;
+          litem.lines.forEach(line => {
+            const a = litem.points.find(p => p.id === line.a)!
+            const b = litem.points.find(p => p.id === line.b)!
+            // writer.addLine(
+            //   point3d(a.x, a.y, 0),
+            //   point3d(b.x, b.y, 0),
+            //   {
+            //     trueColor: TrueColor.fromHex(line.stroke || "#FFFFFF").toString(),
+            //   }
+            // )
+
+            writer.addLWPolyline(
+              [
+                { point: point3d(a.x, -a.y, 0) },
+                { point: point3d(b.x, -b.y, 0) },
+              ],
+              {
+                flags: LWPolylineFlags.None,
+                constantWidth: line.strokeWidth,
+                trueColor: TrueColor.fromHex(
+                  line.stroke || "#FFFFFF"
+                ).toString(),
+              }
+            );
+          })
+
+          break;
 
         case "triangle":
         case "rectangle":

+ 9 - 6
src/core/hook/use-expose.ts

@@ -21,7 +21,7 @@ import { useMouseShapesStatus } from "./use-mouse-status.ts";
 import { Mode } from "@/constant/mode.ts";
 import { ElMessageBox } from "element-plus";
 import { mergeFuns } from "@/utils/shared.ts";
-import { themeColor } from "@/constant/help-style.ts";
+import { themeColor } from "@/constant";
 import { getImage, isSvgString } from "@/utils/resource.ts";
 import { useResourceHandler } from "./use-fetch.ts";
 import { useConfig } from "./use-config.ts";
@@ -86,7 +86,6 @@ export const useShortcutKey = () => {
       // 钢笔工具需要右键两次才退出,右键一次相当于完成
       const isDots = ['dots', 'single-dots'].includes(components[iProps.type].addMode);
       if (isDots && addCount > 0) {
-        console.log('quit')
         setTimeout(() => {
           enterDrawShape(iProps.type, iProps.preset, iProps.operate?.single);
         }, 10);
@@ -122,13 +121,17 @@ export const useShortcutKey = () => {
           const item = store.getItemById(id)
           const type = store.getType(id);
           if (!item?.disableDelete && type) {
-            return [type, id]
+            return [type, item] as const
           }
         }).filter(item => !!item);
-        console.log('del', delItems)
         history.onceTrack(() => {
-          delItems.forEach(([type, id]) => {
-            store.delItem(type as ShapeType, id);
+
+          delItems.forEach(([type, item]) => {
+            if (components[type as ShapeType].delItem) {
+              components[type as ShapeType].delItem!(store, item as any)
+            } else {
+              store.delItem(type as ShapeType, item!.id);
+            }
           })
         });
         if (delItems.length) {

+ 42 - 38
src/core/hook/use-interactive.ts

@@ -1,17 +1,14 @@
-import {
-  installGlobalVar,
-  useStage,
-} from "./use-global-vars.ts";
-import { useCan, useMode } from './use-status'
+import { installGlobalVar, useStage } from "./use-global-vars.ts";
+import { useCan, useMode } from "./use-status";
 import { DrawItem, ShapeType } from "../components";
-import { reactive, Ref, ref, watch, watchEffect } from "vue";
-import { Pos } from "../../utils/math.ts";
+import { nextTick, reactive, Ref, ref, watch, watchEffect } from "vue";
+import { eqPoint, Pos } from "../../utils/math.ts";
 import { clickListener, getOffset, listener } from "../../utils/event.ts";
 import { mergeFuns } from "../../utils/shared.ts";
 import { Mode } from "@/constant/mode.ts";
 
 export type InteractivePreset<T extends ShapeType = ShapeType> = {
-  key?: string
+  key?: string;
   type: T;
   callback?: () => void;
   preset?: Partial<DrawItem<T>> & { getMessages?: () => Pos[] | Area[] };
@@ -21,16 +18,15 @@ export type InteractivePreset<T extends ShapeType = ShapeType> = {
     data?: any;
   };
 };
-export const useInteractiveProps = installGlobalVar(
-  () => {
-    const props = ref<InteractivePreset | undefined>()
-    return props
-  },
-  Symbol("interactiveProps")
-);
+export const useInteractiveProps = installGlobalVar(() => {
+  const props = ref<InteractivePreset | undefined>();
+  return props;
+}, Symbol("interactiveProps"));
 
 export type Area = [Pos, Pos];
-export type InteractiveHook = typeof useInteractiveAreas | typeof useInteractiveDots
+export type InteractiveHook =
+  | typeof useInteractiveAreas
+  | typeof useInteractiveDots;
 export type InteractiveAreas = ReturnType<typeof useInteractiveAreas>;
 export type InteractiveDots = ReturnType<typeof useInteractiveDots>;
 export type Interactive = InteractiveAreas | InteractiveDots;
@@ -118,7 +114,7 @@ type UseInteractiveProps = {
   isRuning: Ref<boolean>;
   quit: () => void;
   beforeHandler?: (p: Pos) => Pos;
-  enter?: () => void
+  enter?: () => void;
   shapeType?: ShapeType;
   autoConsumed?: boolean;
 };
@@ -128,7 +124,7 @@ export const useInteractiveAreas = ({
   autoConsumed,
   beforeHandler,
   quit,
-  enter
+  enter,
 }: UseInteractiveProps) => {
   const mode = useMode();
   const can = useCan();
@@ -141,7 +137,7 @@ export const useInteractiveAreas = ({
     let downed = false;
     let tempArea: Area;
     let dragging = false;
-    enter && enter()
+    enter && enter();
 
     return mergeFuns(
       listener(dom, "pointerdown", (ev) => {
@@ -179,8 +175,8 @@ export const useInteractiveAreas = ({
       listener(document.documentElement, "pointerup", (ev) => {
         if (downed) {
           mode.del(Mode.draging);
-        } 
-        downed = false
+        }
+        downed = false;
         if (!dragging) return;
 
         if (can.dragMode) {
@@ -229,34 +225,42 @@ export const useInteractiveDots = ({
     let pushed = false;
     const empty = { x: -9999, y: -9999 };
     const pointer = ref(empty);
-    enter && enter()
+    enter && enter();
     mode.add(Mode.draging);
 
+    const move = (ev: MouseEvent) => {
+      if (!can.dragMode) return;
+      if (!pushed) {
+        messages.value.push(pointer.value);
+        singleDone.value = false;
+        pushed = true;
+      }
+
+      moveIng = true;
+      const position = getOffset(ev);
+      const current = beforeHandler ? beforeHandler(position) : position;
+      pointer.value.x = current.x;
+      pointer.value.y = current.y;
+    };
+
+    let prevPoint: Pos = { ...empty };
     return mergeFuns(
       () => {
         mode.del(Mode.draging);
       },
-      clickListener(dom, (_) => {
-        if (!moveIng || !can.dragMode) return;
+      clickListener(dom, (_, ev) => {
+        if (!moveIng || !can.dragMode || eqPoint(prevPoint, pointer.value))
+          return;
+        prevPoint = pointer.value;
+
         pointer.value = { ...empty };
         singleDone.value = true;
         moveIng = false;
         pushed = false;
-      }),
-      listener(dom, "pointermove", (ev) => {
-        if (!can.dragMode) return;
-        if (!pushed) {
-          messages.value.push(pointer.value);
-          singleDone.value = false;
-          pushed = true;
-        }
 
-        moveIng = true;
-        const position = getOffset(ev);
-        const current = beforeHandler ? beforeHandler(position) : position;
-        pointer.value.x = current.x;
-        pointer.value.y = current.y;
-      })
+        nextTick(() => move(ev));
+      }),
+      listener(dom, "pointermove", move)
     );
   };
   return useInteractiveExpose(

+ 1 - 1
src/core/hook/use-selection.ts

@@ -11,7 +11,7 @@ import {
   useFormalLayer,
   useHelperLayer,
 } from "./use-layer";
-import { themeColor } from "@/constant/help-style";
+import { themeColor } from "@/constant";
 import { dragListener } from "@/utils/event";
 import { Layer } from "konva/lib/Layer";
 import { useOperMode } from "./use-status";

+ 59 - 11
src/core/hook/use-transformer.ts

@@ -23,15 +23,22 @@ import { Line } from "konva/lib/shapes/Line";
 import { setShapeTransform } from "@/utils/shape.ts";
 import { Transformer } from "../transformer.ts";
 import { TransformerConfig } from "konva/lib/shapes/Transformer";
-import { themeColor, themeMouseColors } from "@/constant/help-style.ts";
+import { themeColor } from "@/constant";
 import { useComponentSnap } from "./use-snap.ts";
-import { useViewerInvertTransform, useViewerTransform } from "./use-viewer.ts";
+import {
+  useViewer,
+  useViewerInvertTransform,
+  useViewerTransform,
+} from "./use-viewer.ts";
 import { Rect } from "konva/lib/shapes/Rect";
 import { Text } from "konva/lib/shapes/Text";
 import { Group } from "konva/lib/Group";
 import { BaseItem } from "../components/util.ts";
 import { useGetComponentData } from "./use-component.ts";
 import { usePause } from "./use-pause.ts";
+import { getSvgContent, parseSvgContent } from "@/utils/resource.ts";
+import { Path } from "konva/lib/shapes/Path";
+import { Circle } from "konva/lib/shapes/Circle";
 
 export type TransformerExtends = Transformer & {
   queueShapes: Ref<EntityShape[]>;
@@ -40,16 +47,17 @@ export const useTransformer = installGlobalVar(() => {
   const anchorCornerRadius = 5;
   const transformer = new Transformer({
     borderStrokeWidth: 2,
-    borderStroke: themeMouseColors.pub,
+    borderStroke: themeColor,
     anchorCornerRadius,
     anchorSize: anchorCornerRadius * 2,
+    borderEnabled: true,
     rotationSnaps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360],
     rotationSnapTolerance: 3,
     anchorStrokeWidth: 2,
-    anchorStroke: themeMouseColors.pub,
+    anchorStroke: themeColor,
     anchorFill: "#fff",
     flipEnabled: false,
-    padding: 10,
+    padding: 0,
     useSingleNodeRotation: true,
   }) as TransformerExtends;
   transformer.queueShapes = ref([]);
@@ -62,7 +70,7 @@ export const useTransformer = installGlobalVar(() => {
     const $text = new Text({
       listening: false,
       fill: themeColor,
-      fontSize: 12,
+      fontSize: 6,
       width: 100,
       align: "center",
       offset: { x: 50, y: 0 },
@@ -84,7 +92,47 @@ export const useTransformer = installGlobalVar(() => {
       $g.destroy();
     });
   });
-  return transformer;
+
+  const cleanups: (() => void)[] = [];
+  const viewer = useViewer();
+  getSvgContent("/icons/m_rotate.svg").then((svgContent) => {
+    const svg = parseSvgContent(svgContent);
+    const group = new Group({listening: false});
+    const rotateRect = transformer.findOne<Rect>(".rotater")!;
+    const size = 8;
+    rotateRect.scale({ x: 2, y: 2 });
+
+    const bg = new Circle({ radius: 6, fill: themeColor, x: 0.5, y: 0.5 })
+    group.add(bg)
+    svg.paths.forEach((path) => {
+      const $path = new Path({ ...path, fill: '#ffffff' });
+      const scale = size / svg.width;
+      $path.scale({ x: scale, y: scale });
+      $path.offset({ x: svg.width / 2, y: svg.height / 2 });
+      group.add($path);
+    });
+
+    rotateRect.opacity(0);
+    rotateRect.parent!.add(group);
+    const update = () => {
+      setShapeTransform(group, rotateRect.getTransform());
+      group.x(group.x() + 8);
+      group.y(group.y() + 8);
+      group.visible(rotateRect.visible());
+    };
+    transformer.on("transform.updateRotate", () => setTimeout(update));
+    viewer.viewer.bus.on("transformChange", update);
+    cleanups.push(
+      () => transformer.off("transform.updateRotate"),
+      watch(() => transformer.queueShapes.value, update, { flush: "sync" }),
+      () => viewer.viewer.bus.off("transformChange", update)
+    );
+  });
+
+  return {
+    var: transformer,
+    onDestroy: () => mergeFuns(cleanups)(),
+  };
 }, Symbol("transformer"));
 
 export const usePointerIsTransformerInner = () => {
@@ -417,11 +465,11 @@ export const useShapeTransformer = <T extends EntityShape>(
         onCleanup(() => {
           try {
             for (const key in oldConfig) {
-              ;(transformer as any)[key](oldConfig[key]);
+              (transformer as any)[key](oldConfig[key]);
             }
-          } catch(e) {
-            console.error('页面销毁?')
-            console.error(e)
+          } catch (e) {
+            console.error("页面销毁?");
+            console.error(e);
           }
           stopTransformerForceUpdate();
           stopPointupListener();

+ 1 - 1
src/core/html-mount/propertys/components/color.vue

@@ -20,7 +20,7 @@
 
 <script lang="ts" setup>
 import { ref } from "vue";
-import { themeColor } from "@/constant/help-style";
+import { themeColor } from "@/constant";
 
 const props = defineProps<{ value?: string; canun?: boolean }>();
 

+ 5 - 3
src/core/html-mount/propertys/describes.json

@@ -21,7 +21,7 @@
   },
   "fontColor": {
     "type": "color",
-    "label": "字颜色",
+    "label": "字颜色",
     "layout-type": "column"
   },
   "coverFill": {
@@ -46,7 +46,8 @@
     "label": "边框粗细",
     "default": 1,
     "props": {
-      "min": 0.1,
+      "min": 1,
+      "step": 1,
       "max": 500
     },
     "layout-type": "column"
@@ -136,9 +137,10 @@
   },
   "fontSize": {
     "type": "num",
-    "label": "字大小",
+    "label": "字大小",
     "props": {
       "min": 12,
+      "step": 1,
       "max": 100
     },
     "layout-type": "column"

+ 3 - 2
src/core/html-mount/propertys/mount.vue

@@ -2,8 +2,8 @@
   <Teleport :to="`#${DomOutMountId}`" v-if="stage">
     <transition name="mount-fade">
       <div class="mount-layout" v-if="show">
-        <div v-if="type && components[type].shapeName" class="title">
-          <h4>设置{{ components[type].shapeName }}</h4>
+        <div v-if="name || (type && components[type].shapeName)" class="title">
+          <h4>设置{{ name || (type && components[type].shapeName) }}</h4>
           <icon
             name="close"
             size="20px"
@@ -64,6 +64,7 @@ import { ElButton } from "element-plus";
 const props = defineProps<{
   show?: boolean;
   target: DC<EntityShape> | undefined;
+  name?: string;
   data?: Record<string, any>;
   describes: PropertyDescribes;
 }>();

+ 5 - 5
src/example/components/header/actions.ts

@@ -46,24 +46,24 @@ export const getHeaderActions = (draw: Draw) => {
     }),
     redo: reactive({
       handler: () => draw.history.redo(),
-      text: "重做",
+      text: "恢复",
       icon: "redo",
       disabled: computed(() => !draw.history.hasRedo.value),
     }),
     clear: reactive({
       handler: () => draw.store.clear(),
       disabled: computed(() => draw.drawing),
-      text: "清",
+      text: "清",
       icon: "clear",
     }),
     rotateView: reactive({
       handler: () => rotateView(draw),
-      text: "旋转",
+      text: "旋转画布",
       icon: "rotate",
     }),
     initViewport: reactive({
       handler: () => draw.initViewport(),
-      text: "初始视图",
+      text: "适应视图",
       icon: "a_adapt",
     }),
     toggleShow: reactive({
@@ -78,7 +78,7 @@ export const getHeaderActions = (draw: Draw) => {
           }
         })
       },
-      text: "显示",
+      text: "显示隐藏项",
       icon: "a-visible",
     }),
     expose: reactive({

+ 8 - 6
src/example/components/slide/actions.ts

@@ -32,12 +32,12 @@ export const draw: MenuItem = {
   name: "绘制",
   value: uuid(),
   children: [
-    { icon: "line", ...genDrawItem('sequentLine') },
+    // { icon: "line", ...genDrawItem('sequentLine') },
     { icon: "line", ...genDrawItem('line') },
-    { icon: "arrows", ...genDrawItem("arrow") },
-    { icon: "rectangle", ...genDrawItem("rectangle") },
-    { icon: "circle", ...genDrawItem("circle") },
-    { icon: "triangulartriangular", ...genDrawItem("triangle") },
+    { icon: "arrows", ...genDrawItem("arrow"), single: true },
+    { icon: "rectangle", ...genDrawItem("rectangle"), single: true },
+    { icon: "circle", ...genDrawItem("circle"), single: true },
+    { icon: "triangulartriangular", ...genDrawItem("triangle"), single: true },
     { icon: "line", ...genDrawItem("polygon") },
   ],
 };
@@ -45,6 +45,7 @@ export const draw: MenuItem = {
 export const text: MenuItem = {
   icon: "text",
   ...genDrawItem("text", { content: "文本" }),
+  single: true
 };
 
 export const table: MenuItem = {
@@ -98,9 +99,10 @@ export const getPaperConfig = (p: number[], scale: number) => {
   return {size, margin}
 }
 export const paperConfigs = {
-  'a4': { size: [297, 210], scale: 3.8 },
+  'a4': { size: [297, 210], scale: 2.5 },
   'a3': { size: [420, 297], scale: 2.5 }
 }
+export type PaperKey = keyof typeof paperConfigs
 
 const setPaper = (draw: Draw, p: number[], scale: number) => {
   const { size, margin } = getPaperConfig(p, scale)

+ 2 - 0
src/example/components/slide/menu.ts

@@ -6,9 +6,11 @@ export type MenuItem = {
   name: string;
   value: string;
   type?: string
+  active?: boolean
   children?: MenuItem[];
   mount?: any,
   payload?: any;
+  single?: boolean
   handler?: (draw: Draw) => void;
 };
 

+ 8 - 4
src/example/components/slide/slide-item.vue

@@ -1,14 +1,18 @@
 <template>
-  <el-sub-menu :index="data.value || data.name" v-if="data.children?.length">
+  <el-sub-menu
+    :index="data.value || data.name"
+    v-if="data.children?.length"
+    :class="{ 'is-active': active }"
+  >
     <template #title>
       <div class="menu-layout">
         <Icon :name="data.icon" size="24px" />
         <span>{{ data.name }}</span>
       </div>
     </template>
-    <SlideItem v-for="item in data.children" :data="item" />
+    <SlideItem v-for="item in data.children" :data="item" :active="item.active" />
   </el-sub-menu>
-  <el-menu-item v-else :index="data.value || data.name">
+  <el-menu-item v-else :index="data.value || data.name" :class="{ 'is-active': active }">
     <div class="menu-layout">
       <Icon :name="data.icon" size="22px" />
       <span>{{ data.name }}</span>
@@ -19,7 +23,7 @@
 <script lang="ts" setup>
 import { ElSubMenu, ElMenuItem } from "element-plus";
 import { MenuItem } from "./menu";
-defineProps<{ data: MenuItem }>();
+defineProps<{ data: MenuItem; active?: boolean }>();
 </script>
 
 <style lang="scss">

+ 6 - 2
src/example/components/slide/slide.vue

@@ -9,7 +9,7 @@
       :popper-class="childType || 'slide-menu-poper'"
       @open="openHandler"
     >
-      <SlideItem v-for="menu in menus" :data="menu" />
+      <SlideItem v-for="menu in menus" :data="menu" :active="menu.active" />
     </el-menu>
 
     <div class="ext">
@@ -47,7 +47,7 @@ watch(activeMenu, (menu, oldMenu) => {
     menu.handler(props.draw);
     nextTick(() => (active.value = undefined));
   } else {
-    props.draw.enterDrawShape(menu.payload.type, menu.payload.preset);
+    props.draw.enterDrawShape(menu.payload.type, menu.payload.preset, menu.single);
   }
 });
 
@@ -162,14 +162,18 @@ const openHandler = (value: string) => {
 }
 
 .sub-menu-horizontal .el-menu--popup {
+  --el-menu-base-level-padding: 0;
   min-width: auto;
   .menu-layout {
+    padding-left: 20px;
+    padding-right: 20px;
     font-size: 32px;
     display: flex;
     align-items: center;
     span {
       margin-left: 5px;
       font-size: 14px;
+      color: var(--el-menu-text-color);
     }
   }
 }

+ 4 - 3
src/example/fuse/store.ts

@@ -1,12 +1,13 @@
 import { Ref, ref } from "vue";
 import { StoreData } from "@/core/store/store";
+import { PaperKey } from "../components/slide/actions";
 
 export const tableCoverKey = '__tableCoverKey'
 export const tableCoverScaleKey = '__tableCoverScaleKey'
 export const tableTableKey = '__tableTableKey'
 export const tableTitleKey = '__tableTitleKey'
-export const tableCoverWidth = 514
-export const tableCoverHeight = 425
+export const tableCoverWidth = 370
+export const tableCoverHeight = 306
 
 export type TabCover = {
   url: string;
@@ -29,7 +30,7 @@ export const refreshOverviewData = () => {
 export const tabulationData = ref() as Ref<{
   store: StoreData;
   cover: TabCover | null;
-  paperKey: string;
+  paperKey: PaperKey;
   viewport: number[] | null;
 }>
 export const refreshTabulationData = () => {

+ 11 - 20
src/example/fuse/views/overview/header.vue

@@ -27,12 +27,11 @@ import {
   tableCoverHeight,
 } from "../../store.ts";
 import { nextTick } from "vue";
-import { getRepTabulationStore } from "../tabulation/gen.ts";
-import { getPaperConfig, paperConfigs } from "@/example/components/slide/actions.ts";
 import { DataGroupId } from "@/constant/index.ts";
 import { Group } from "konva/lib/Group";
 import { Mode } from "@/constant/mode.ts";
 import { lineLen } from "@/utils/math.ts";
+import { repTabulationStore } from "../tabulation/gen-tab.ts";
 
 const draw = useDraw();
 
@@ -55,7 +54,7 @@ const actions = [
         const scene = await selectScene();
         emit("selectVR", scene);
       },
-      text: "VR参考",
+      text: "VR辅助",
       icon: "VR",
     },
   ],
@@ -69,9 +68,10 @@ const setViewToTableCover = async () => {
   const oldBack = draw.config.back;
   const oldShowCompass = draw.config.showCompass;
   const oldLabelLineConfig = { ...draw.config.labelLineConfig };
+  const getRect = () => draw.stage!.findOne<Group>(`#${DataGroupId}`)!.getClientRect();
 
   const pop = draw.mode.push(Mode.readonly);
-  const rect = draw.stage!.findOne<Group>(`#${DataGroupId}`)!.getClientRect();
+  const rect = getRect();
   const rectScale = (rect.width || 1080) / (rect.height || 850);
   const tableCoverScale = tableCoverWidth / tableCoverHeight;
 
@@ -84,8 +84,6 @@ const setViewToTableCover = async () => {
     width = rectScale * height;
   }
 
-  // const height = width / tableCoverScale;
-  console.log(width, rectScale);
   draw.config.size = { width, height };
   draw.config.showGrid = false;
   draw.config.back = undefined;
@@ -97,9 +95,10 @@ const setViewToTableCover = async () => {
 
   await nextTick();
   draw.initViewport();
+  await nextTick();
 
   return [
-    rect,
+    getRect(),
     () => {
       pop();
       draw.config.size = oldSize;
@@ -121,7 +120,7 @@ const saveHandler = async () => {
     const scale =
       lineLen(mat.point({ x: 1, y: 0 }), mat.point({ x: 0, y: 0 })) *
       draw.store.config.proportion.scale;
-
+    console.log(scale);
     const blob = await getImage(draw, "image/png");
     recover();
     await nextTick();
@@ -145,22 +144,14 @@ const saveHandler = async () => {
   await window.platform.saveTabulationCover(cover);
   await refreshTabulationData();
 
-  const paperKey = tabulationData.value.paperKey as "a4";
-  const pConfig = getPaperConfig(
-    paperConfigs[paperKey].size,
-    paperConfigs[paperKey].scale
-  );
-  const tabStore = await getRepTabulationStore(
-    tabulationData.value.store,
-    pConfig.size,
-    pConfig.margin,
-    paperKey,
-    cover
+  const tabStore = await repTabulationStore(
+    tabulationData.value.paperKey,
+    cover,
+    tabulationData.value.store
   );
   tabStore.config.compass = storeData.config.compass;
   await window.platform.saveTabulationData({
     ...tabulationData.value,
-    paperKey,
     store: tabStore,
   });
 };

+ 1 - 1
src/example/fuse/views/overview/index.vue

@@ -38,7 +38,7 @@ const init = async (draw: Draw) => {
   await refreshOverviewData();
   draw.config.showCompass = false;
   draw.config.showLabelLine = true;
-  draw.config.showComponentSize = true;
+  draw.config.showComponentSize = false;
   draw.config.back = { color: "#f0f2f5", opacity: 1 };
   draw.store.setConfig({ proportion: { scale: 10, unit: "mm" } });
   draw.store.setStore(overviewData.value.store);

+ 1 - 1
src/example/fuse/views/overview/query.vue

@@ -21,7 +21,7 @@ const init = async (draw: Draw) => {
   await refreshOverviewData();
   draw.config.showCompass = false;
   draw.config.showLabelLine = true;
-  draw.config.showComponentSize = true;
+  draw.config.showComponentSize = false;
   draw.config.back = { color: "#f0f2f5", opacity: 1 };
   draw.mode.push(Mode.readonly);
   draw.store.setStore(overviewData.value.store);

+ 10 - 6
src/example/fuse/views/overview/slide-icons.vue

@@ -50,12 +50,16 @@ const drawIcon = async (url: string, name: string) => {
     size.width = (maxSize / svgContent.height) * svgContent.width;
   }
 
-  props.draw.enterDrawShape("icon", {
-    url,
-    ...size,
-    name,
-    fill: "#000000",
-  });
+  props.draw.enterDrawShape(
+    "icon",
+    {
+      url,
+      ...size,
+      name,
+      fill: "#000000",
+    },
+    true
+  );
   emit("exit");
 };
 

+ 1 - 1
src/example/fuse/views/overview/slide.vue

@@ -23,7 +23,7 @@ const legend = {
 };
 
 const draw = useDraw();
-const menus = reactive([imp, drawAction, text, legend]);
+const menus = reactive([imp, drawAction, legend, text]);
 
 if (import.meta.env.DEV) {
   menus.push(test);

+ 249 - 0
src/example/fuse/views/tabulation/gen-tab.ts

@@ -0,0 +1,249 @@
+import { getBaseItem } from "@/core/components/util";
+import {
+  getPaperConfig,
+  paperConfigs,
+  PaperKey,
+} from "@/example/components/slide/actions";
+import {
+  TabCover,
+  tableCoverHeight,
+  tableCoverKey,
+  tableCoverScaleKey,
+  tableCoverWidth,
+  tableTableKey,
+  tableTitleKey,
+} from "../../store";
+import { matResponse } from "@/core/components/table";
+import { Transform } from "konva/lib/Util";
+import { getFixPosition } from "@/utils/bound";
+import { getImage } from "@/utils/resource";
+import { ImageData } from "@/core/components/image";
+import { DrawData } from "@/core/components";
+import { getEmptyStoreData, StoreData } from "@/core/store/store";
+import { defaultLayer } from "@/constant";
+
+export const getRealPixel = (real: number, paperKey: PaperKey) => {
+  const realPixelScale = paperConfigs[paperKey].scale;
+  return real * realPixelScale;
+};
+
+export const transformPaper = (
+  real: number,
+  paperKey: PaperKey,
+  toPaperKey: PaperKey
+) => {
+  const scale = paperConfigs[paperKey].scale / paperConfigs[toPaperKey].scale;
+  return real * scale;
+};
+
+export const getCoverPaperScale = (cover: ImageData, paperKey: PaperKey) => {
+  let pixelScale = (cover.widthRaw! / cover.width) * cover.proportion!.scale;
+  const realPixelScale = paperConfigs[paperKey].scale;
+  return Math.round(realPixelScale * pixelScale);
+};
+
+export const setCoverPaperScale = (cover: ImageData, paperKey: PaperKey, scale: number) => {
+  const realPixelScale = paperConfigs[paperKey].scale;
+  cover.width = cover.widthRaw! / ((scale / realPixelScale) /cover.proportion!.scale)
+  cover.height = (cover.heightRaw! / cover.widthRaw!) * cover.width
+}
+
+export const genTabulationData = async (paperKey: PaperKey, cover?: TabCover | null) => {
+  const { margin, size } = getPaperConfig(
+    paperConfigs[paperKey].size,
+    paperConfigs[paperKey].scale
+  );
+
+  console.log(size, margin, paperKey)
+  const getTable = () => {
+    const w1 = getRealPixel(25, paperKey);
+    const w2 = getRealPixel(55, paperKey);
+    const h = getRealPixel(8, paperKey);
+
+    const nameColl = {
+      width: w1,
+      height: h,
+      padding: getRealPixel(2, paperKey),
+      content: "",
+      align: "center",
+      fontSize: getRealPixel(4, paperKey),
+      fontColor: "#000000",
+    };
+    const valueColl = { ...nameColl, width: w2 };
+
+    const data = {
+      ...getBaseItem(),
+      content: [
+        [{ ...nameColl, content: "案发时间" }, valueColl],
+        [{ ...nameColl, content: "案发地点" }, valueColl],
+        [{ ...nameColl, content: "绘图单位" }, valueColl],
+        [{ ...nameColl, content: "绘图人" }, valueColl],
+        [{ ...nameColl, content: "绘图时间" }, valueColl],
+      ],
+      key: tableTableKey,
+      width: nameColl.width + valueColl.width,
+      height: nameColl.height * 5,
+      mat: [1, 0, 0, 1, 0, 0],
+    };
+
+    const pos = getFixPosition(
+      {
+        right: getRealPixel(15, paperKey) + margin[1],
+        bottom: getRealPixel(15, paperKey) + margin[2],
+      },
+      data,
+      size
+    );
+
+    return matResponse({ data, mat: new Transform().translate(pos.x, pos.y) });
+  };
+
+  const getCover = async () => {
+    if (!cover) return;
+    const image = await getImage(cover.url);
+    const rectScale = image.width / image.height;
+    const tableCoverScale = tableCoverWidth / tableCoverHeight;
+
+    let width: number, height: number;
+    if (rectScale > tableCoverScale) {
+      width = transformPaper(tableCoverWidth, paperKey, "a4");
+      height = width / rectScale;
+    } else {
+      height = transformPaper(tableCoverHeight, paperKey, "a4");
+      width = rectScale * height;
+    }
+
+    const coverData = {
+      ...getBaseItem(),
+      cornerRadius: 0,
+      strokeWidth: 0,
+      url: cover.url,
+      key: tableCoverKey,
+      proportion: cover.proportion,
+      disableTransformer: true,
+      widthRaw: cover.width,
+      showScale: true,
+      heightRaw: cover.height,
+      zIndex: -1,
+      width,
+      height,
+      mat: [1, 0, 0, 1, 0, 0],
+    };
+    const pos = getFixPosition(
+      {
+        left: coverData.width / 2 + margin[3] + getRealPixel(15, paperKey),
+        bottom: -coverData.height / 2 + margin[2] + getRealPixel(15, paperKey),
+      },
+      coverData,
+      size
+    );
+    coverData.mat[4] = pos.x;
+    coverData.mat[5] = pos.y;
+
+    console.log(pos, coverData, size)
+    const scale = getCoverPaperScale(coverData, paperKey);
+    setCoverPaperScale(coverData, paperKey, Math.round(scale / 10) * 10)
+    return coverData;
+  };
+
+  const getCoverScale = (cover: ImageData) => {
+    const scale = (cover.widthRaw! / cover.width) * cover.proportion!.scale;
+    const text = {
+      ...getBaseItem(),
+      content: `1:${scale}`,
+      width: getRealPixel(30, paperKey),
+      heihgt: getRealPixel(4.8, paperKey),
+      fontSize: getRealPixel(4, paperKey),
+      disableEditText: true,
+      key: tableCoverScaleKey,
+      disableDelete: true,
+      align: "center",
+      mat: [1, 0, 0, 1, 0, 0],
+    };
+    const x = cover.mat[4] - (text.width || 0) / 2;
+    const y = cover.mat[5] + cover.height / 2 + getRealPixel(5, paperKey);
+    text.mat[4] = x;
+    text.mat[5] = y;
+    return text;
+  };
+
+  const getTitle = () => {
+    const title = {
+      ...getBaseItem(),
+      content: "默认标题",
+      width: getRealPixel(90, paperKey),
+      heihgt: getRealPixel(14.4, paperKey),
+      fontSize: getRealPixel(12, paperKey),
+      key: tableTitleKey,
+      align: "center",
+      mat: [1, 0, 0, 1, 0, 0],
+    };
+    const pos = {
+      x: (size.width - margin[3]) / 2 - getRealPixel(40, paperKey) + margin[3],
+      y: getRealPixel(15, paperKey) + margin[0],
+    };
+    title.mat[4] = pos.x;
+    title.mat[5] = pos.y;
+    return title;
+  };
+
+  const data: DrawData = {
+    table: [getTable()],
+    text: [getTitle()]
+  }
+  const image = await getCover()
+  if (image) {
+    data.image = [image]
+    data.text!.push(getCoverScale(image))
+  }
+
+  return data
+};
+
+export const repTabulationStore = async (paperKey: PaperKey, cover: TabCover, store?: StoreData) => {
+  const repData = await genTabulationData(paperKey, cover)
+  const layer = store?.layers && store?.layers[defaultLayer];
+
+  if (!layer) {
+    return {
+      ...(store || {}),
+      config: {
+        ...getEmptyStoreData(),
+        ...(store?.config || {}),
+      },
+      layers: {
+        ...(store?.layers || {}),
+        [defaultLayer]: repData
+      }
+    }
+  }
+
+  if (repData.image?.length) {
+    const imageData = repData.image[0]
+    const images = layer.image || []
+    const imageNdx = images.findIndex(item => item.key === imageData.key)
+    if (~imageNdx) {
+      images[imageNdx] = {
+        ...imageData,
+        mat: images[imageNdx].mat
+      }
+    } else {
+      images.push(imageData)
+    }
+    layer.image = images
+
+
+    const scaleData = repData.text!.find(item => item.key === tableCoverScaleKey)!
+    const texts = layer.text || []
+    const textNdx = texts.findIndex(item => item.key === scaleData.key)
+    if (~textNdx) {
+      texts[textNdx].content = scaleData.content
+    } else {
+      texts.push(scaleData)
+    }
+    layer.text = texts
+  }
+  store.layers[defaultLayer] = layer
+
+  return store;
+}

+ 0 - 277
src/example/fuse/views/tabulation/gen.ts

@@ -1,277 +0,0 @@
-import { defaultLayer } from "@/constant";
-import { getBaseItem } from "@/core/components/util";
-import { getEmptyStoreData, StoreData } from "@/core/store/store";
-import { getFixPosition } from "@/utils/bound";
-import { Size } from "@/utils/math";
-import { getImage } from "@/utils/resource";
-import { TabCover, tableCoverHeight, tableCoverKey, tableCoverScaleKey, tableCoverWidth, tableTableKey, tableTitleKey } from "../../store";
-import { matResponse, TableData } from "@/core/components/table";
-import { TextData } from "@/core/components/text";
-import { ImageData } from "@/core/components/image";
-import { Transform } from "konva/lib/Util";
-import { paperConfigs } from "@/example/components/slide/actions";
-
-const setCoverPosition = (
-  size: Size,
-  margin: number | number[],
-  cover: ImageData
-) => {
-  if (!Array.isArray(margin)) {
-    margin = [margin, margin, margin, margin];
-  }
-  const imagePos = getFixPosition(
-    {
-      left: cover.width / 2 + margin[3] + 70,
-      bottom: -cover.height / 2 + margin[2] + 70,
-    },
-    cover,
-    size
-  );
-  cover.mat[4] = imagePos.x;
-  cover.mat[5] = imagePos.y;
-};
-
-const setTablePosition = (
-  size: Size,
-  margin: number | number[],
-  table: TableData
-) => {
-  if (!Array.isArray(margin)) {
-    margin = [margin, margin, margin, margin];
-  }
-  const tablePos = getFixPosition(
-    { right: 40 + margin[1], bottom: 40 + margin[2] },
-    table,
-    size
-  );
-  table.mat[4] = tablePos.x;
-  table.mat[5] = tablePos.y;
-};
-
-const setTitlePosition = (
-  size: Size,
-  margin: number | number[],
-  title: TextData
-) => {
-  if (!Array.isArray(margin)) {
-    margin = [margin, margin, margin, margin];
-  }
-
-  const titlePos = {
-    x: (size.width - margin[3]) / 2 - 150 + margin[3],
-    y: 40 + margin[0],
-  };
-  title.mat[4] = titlePos.x;
-  title.mat[5] = titlePos.y;
-};
-const setCoverScaleTextPosition = (cover: ImageData, title: TextData) => {
-  const x = cover.mat[4] - (title.width || 0) / 2;
-  const y = cover.mat[5] + cover.height / 2 + 10;
-  title.mat[4] = x;
-  title.mat[5] = y;
-};
-
-export const getCoverPaperScale = (cover: ImageData, paperKey: string) => {
-  let pixelScale =
-    (cover.widthRaw! / cover.width) * cover.proportion!.scale;
-  const realPixelScale = paperConfigs[paperKey as "a4"].scale;
-  return Math.round(realPixelScale * pixelScale);
-}
-export const setCoverPaperScale = (cover: ImageData, paperKey: string, scale: number) => {
-  const realPixelScale = paperConfigs[paperKey as "a4"].scale;
-  cover.width = cover.widthRaw! / ((scale / realPixelScale) /cover.proportion!.scale)
-  cover.height = (cover.heightRaw! / cover.widthRaw!) * cover.width
-  console.log(cover.width / cover.height)
-}
-
-const genDefaultCover = async (cover: TabCover, paperKey: string) => {
-  const image = await getImage(cover.url);
-  const rectScale = image.width / image.height
-  const tableCoverScale = tableCoverWidth / tableCoverHeight
-
-  let width: number, height: number;
-  if (rectScale > tableCoverScale) {
-    width = tableCoverWidth;
-    height = width / rectScale;
-  } else {
-    height = tableCoverHeight;
-    width = rectScale * height;
-  }
-  console.log(width / height, rectScale, cover.width / cover.height)
-
-  const coverData = {
-    ...getBaseItem(),
-    cornerRadius: 0,
-    strokeWidth: 0,
-    url: cover.url,
-    key: tableCoverKey,
-    proportion: cover.proportion,
-    disableTransformer: true,
-    widthRaw: cover.width,
-    showScale: true,
-    heightRaw: cover.height,
-    zIndex: -1,
-    width,
-    height,
-    mat: [1, 0, 0, 1, 0, 0],
-  };
-
-  // 缩放比例默认给10的倍数
-  let wScale = getCoverPaperScale(coverData, paperKey)
-  setCoverPaperScale(coverData, paperKey, Math.round(wScale / 10) * 10)
-  return coverData;
-};
-
-const genDefaultCoverScaleText = (cover: ImageData) => {
-  const scale = (cover.widthRaw! / cover.width) * cover.proportion!.scale;
-  return {
-    ...getBaseItem(),
-    content: `1:${scale}`,
-    width: 100,
-    heihgt: 16,
-    fontSize: 12,
-    disableEditText: true,
-    key: tableCoverScaleKey,
-    disableDelete: true,
-    align: "center",
-    mat: [1, 0, 0, 1, 0, 0],
-  };
-}
-
-const genDefaultTable = () => {
-  const nameColl = {
-    width: 140,
-    height: 40,
-    padding: 13,
-    content: "",
-    align: "center",
-    fontSize: 14,
-    fontColor: "#000000",
-  };
-  const valueColl = { ...nameColl, width: 200 };
-  
-  const data = {
-    ...getBaseItem(),
-    content: [
-      [{ ...nameColl, content: "案发时间" }, valueColl],
-      [{ ...nameColl, content: "案发地点" }, valueColl],
-      [{ ...nameColl, content: "绘图单位" }, valueColl],
-      [{ ...nameColl, content: "绘图人" }, valueColl],
-      [{ ...nameColl, content: "绘图时间" }, valueColl],
-    ],
-    key: tableTableKey,
-    width: nameColl.width + valueColl.width,
-    height: nameColl.height * 5,
-    mat: [1, 0, 0, 1, 0, 0],
-  };
-  return matResponse({ data, mat: new Transform() })
-};
-
-export const genDefaultTitle = () => {
-  return {
-    ...getBaseItem(),
-    content: "默认标题",
-    width: 300,
-    heihgt: 42,
-    fontSize: 38,
-    key: tableTitleKey,
-    align: "center",
-    mat: [1, 0, 0, 1, 0, 0],
-  };
-};
-
-export const getRepTabulationStore = async (
-  store: StoreData,
-  size: Size,
-  margin: number | number[],
-  paperKey: string,
-  cover?: TabCover | null,
-  place = true
-) => {
-  const layers = store?.layers;
-  let layer = layers && layers[defaultLayer];
-
-  if (!layer) {
-    const titleData = genDefaultTitle();
-    const tableData = genDefaultTable();
-    setTitlePosition(size, margin, titleData);
-    setTablePosition(size, margin, tableData);
-    layer = { text: [titleData], table: [tableData] };
-
-    if (cover) {
-      const imageData = await genDefaultCover(cover, paperKey);
-      setCoverPosition(size, margin, imageData);
-      layer.image = [imageData];
-      layer.text!.push(genDefaultCoverScaleText(imageData))
-    }
-  } else {
-    layer.image = layer.image || [];
-    const coverNdx = layer.image.findIndex(
-      (item) => item.key === tableCoverKey
-    );
-    layer.text = layer.text || []
-    const coverScaleNdx = layer.text.findIndex(
-      (item) => item.key === tableCoverScaleKey
-    );
-
-    if (cover) {
-      const imageData = await genDefaultCover(cover, paperKey);
-      const imageScaleData = genDefaultCoverScaleText(imageData)
-      if (!~coverNdx) {
-        setCoverPosition(size, margin, imageData);
-        layer.image.push(imageData);
-      } else {
-        layer.image[coverNdx].url = imageData.url
-        layer.image[coverNdx].width = imageData.width
-        layer.image[coverNdx].height = imageData.height
-        layer.image[coverNdx].widthRaw = imageData.widthRaw
-        layer.image[coverNdx].heightRaw = imageData.heightRaw
-        layer.image[coverNdx].proportion = imageData.proportion
-      }
-
-      if (!~coverScaleNdx) {
-        setCoverScaleTextPosition(imageData, imageScaleData)
-      } else {
-        layer.text[coverScaleNdx].content = imageScaleData.content
-        setCoverScaleTextPosition(imageData, layer.text[coverScaleNdx])
-      }
-    } else {
-      ~coverNdx && layer.image.splice(coverNdx, 1);
-      ~coverScaleNdx && layer.image.splice(coverScaleNdx, 1);
-    }
-  }
-
-  if (place) {
-    const imageData = layer.image?.find(item => item.key === tableCoverKey)
-    imageData && setCoverPosition(size, margin, imageData)
-    const imageScaleData = layer.text?.find(item => item.key === tableCoverScaleKey)
-    imageScaleData && imageData && setCoverScaleTextPosition(imageData, imageScaleData)
-    const titleData = layer.text?.find(item => item.key === tableTitleKey)
-    titleData && setTitlePosition(size, margin, titleData)
-    const tableData = layer.table?.find(item => item.key === tableTableKey)
-    tableData && setTablePosition(size, margin, tableData)
-  }
-
-  if (layers) {
-    return {
-      ...store,
-      layers: {
-        ...layers,
-        [defaultLayer]: layer,
-      },
-      config: {
-        ...getEmptyStoreData(),
-        ...store.config,
-      }
-    };
-  } else {
-    return {
-      ...store,
-      config: {
-        ...getEmptyStoreData(),
-        ...store.config,
-      },
-      layers: { [defaultLayer]: layer },
-    };
-  }
-};

+ 7 - 6
src/example/fuse/views/tabulation/index.vue

@@ -30,9 +30,9 @@ import {
 } from "../../store";
 import { ImageData } from "@/core/components/image";
 import { TextData } from "@/core/components/text";
-import { getCoverPaperScale, setCoverPaperScale } from "./gen";
+import { getCoverPaperScale, setCoverPaperScale } from "./gen-tab";
 
-const uploadResourse = window.platform.uploadResourse
+const uploadResourse = window.platform.uploadResourse;
 const full = ref(false);
 const draw = ref<Draw>();
 
@@ -105,10 +105,11 @@ watch(cover, (cover, _, onCleanup) => {
   });
 });
 
-const coverScaleText = computed(
-  () =>
-    draw.value?.store.items.find((item) => item.key === tableCoverScaleKey) as TextData
-);
+const coverScaleText = computed(() => {
+  return draw.value?.store.items.find(
+    (item) => item.key === tableCoverScaleKey
+  ) as TextData;
+});
 watch(
   () => {
     return {

+ 0 - 223
src/example/fuse/views/tabulation/slide-icons.vue

@@ -1,223 +0,0 @@
-<template>
-  <ElCollapse class="icon-layout" v-model="activeGroups">
-    <ElCollapseItem
-      v-for="group in searchGroups"
-      :name="group.name"
-      v-if="searchGroups.length"
-    >
-      <template #title>
-        <h2>{{ group.name }}</h2>
-      </template>
-
-      <div class="type-children" v-for="typeChildren in group.children">
-        <h3 v-if="typeChildren.name">{{ typeChildren.name }}</h3>
-        <div class="icon-items">
-          <div
-            v-for="item in typeChildren.children"
-            @click="drawIcon(`/icons/${item.icon}.svg`)"
-          >
-            <Icon :name="item.icon" size="32px" />
-            <span>{{ item.name }}</span>
-          </div>
-        </div>
-      </div>
-    </ElCollapseItem>
-    <el-empty description="暂无数据" v-else />
-  </ElCollapse>
-</template>
-
-<script lang="ts" setup>
-import { themeColor } from "@/constant/help-style";
-import { computed, ref } from "vue";
-import { ElCollapse, ElCollapseItem, ElEmpty } from "element-plus";
-import { getSvgContent, parseSvgContent } from "@/utils/resource";
-import { Draw } from "../../../components/container/use-draw.ts";
-
-const props = defineProps<{ draw: Draw }>();
-const emit = defineEmits<{ (e: "exit"): void }>();
-
-const drawIcon = async (url: string) => {
-  const svgContent = parseSvgContent(await getSvgContent(url));
-  const addWidth = 100;
-  const addHeight = (addWidth / svgContent.width) * svgContent.height;
-
-  props.draw.enterDrawShape("icon", {
-    url,
-    width: addWidth,
-    height: addHeight,
-    fill: themeColor,
-  });
-  emit("exit");
-};
-
-const groups = [
-  {
-    name: "常用名称",
-    children: [
-      {
-        name: "门",
-        children: [
-          { icon: "cad-men", name: "门" },
-          { icon: "cad-shuangkaimen", name: "双开门" },
-          { icon: "cad-yimen", name: "移门" },
-          { icon: "cad-yakou", name: "哑口" },
-        ],
-      },
-      {
-        name: "窗",
-        children: [
-          { icon: "cad-chuang", name: "窗" },
-          { icon: "cad-piaochuang", name: "飘窗" },
-          { icon: "cad-luodichuang", name: "落地窗" },
-        ],
-      },
-      {
-        name: "构件",
-        children: [
-          { icon: "cad-zhuzi", name: "柱子" },
-          { icon: "cad-yandao", name: "烟道" },
-          { icon: "cad-loudao", name: "楼道" },
-        ],
-      },
-    ],
-  },
-  {
-    name: "家具",
-    children: [
-      {
-        name: "客餐厅",
-        children: [
-          { icon: "TV", name: "电视柜" },
-          { icon: "CombinationSofa", name: "组合沙发" },
-          { icon: "SingleSofa", name: "单人沙发" },
-          { icon: "TeaTable", name: "茶几" },
-          { icon: "Carpet", name: "地毯" },
-          { icon: "Plant", name: "植物" },
-          { icon: "DiningTable", name: "餐桌" },
-        ],
-      },
-      {
-        name: "卧室",
-        children: [
-          { icon: "DoubleBed", name: "双人床" },
-          { icon: "SingleBed", name: "单人床" },
-          { icon: "Wardrobe", name: "衣柜" },
-          { icon: "Dresser", name: "梳妆台" },
-          { icon: "BedsideCupboard", name: "床头柜" },
-          { icon: "Pillow", name: "抱枕" },
-        ],
-      },
-      {
-        name: "厨卫",
-        children: [
-          { icon: "GasStove", name: "燃气灶" },
-          { icon: "Cupboard", name: "橱柜" },
-          { icon: "Bathtub", name: "浴缸" },
-          { icon: "Closestool", name: "马桶" },
-          { icon: "Washstand", name: "洗漱台" },
-        ],
-      },
-      {
-        name: "其他",
-        children: [
-          { icon: "Desk", name: "书桌" },
-          { icon: "BalconyChair", name: "阳台椅" },
-          { icon: "Elevator", name: "电梯" },
-        ],
-      },
-    ],
-  },
-  {
-    name: "痕迹物证",
-    children: [
-      {
-        name: "",
-        children: [
-          { icon: "zhiwen_o", name: "手印" },
-          { icon: "zuozuji_o", name: "脚印" },
-          { icon: "youzuji_o", name: "脚印" },
-          { icon: "xieyin_o", name: "鞋印" },
-          { icon: "chelunhenji_o", name: "车轮印" },
-          { icon: "dantou_o", name: "弹头" },
-          { icon: "danke_o", name: "弹壳" },
-          { icon: "shouqiang_o", name: "手枪" },
-          { icon: "buqiang_o", name: "步枪" },
-          { icon: "xuepo_o", name: "血泊" },
-          { icon: "xueji_o", name: "血迹" },
-          { icon: "shitiz_o", name: "尸体正面" },
-          { icon: "shitib_o", name: "尸体背面" },
-          { icon: "shitifuhao_o", name: "尸体" },
-        ],
-      },
-    ],
-  },
-];
-
-const activeGroups = ref(groups.map((item) => item.name));
-const keyword = ref("");
-const searchGroups = computed(() => {
-  return groups
-    .map((typeChildren) => {
-      const filterTypeChildren = typeChildren.children
-        .map((type) => {
-          const children = type.children.filter((item) =>
-            item.name.includes(keyword.value)
-          );
-          return {
-            ...type,
-            children,
-          };
-        })
-        .filter((type) => type.children.length > 0);
-      return {
-        ...typeChildren,
-        children: filterTypeChildren,
-      };
-    })
-    .filter((typeChildren) => typeChildren.children.length > 0);
-});
-</script>
-
-<style lang="scss" scoped>
-.icon-layout {
-  width: 320px;
-  height: 100%;
-  background-color: var(--el-menu-bg-color);
-  border-right: solid 1px var(--el-menu-border-color);
-  overflow-y: auto;
-  padding: 0 20px;
-  font-size: 14px;
-  color: #333;
-
-  h2,
-  h3 {
-    font-size: inherit;
-    color: inherit;
-  }
-
-  // .type-children:not(:first-child) {
-  //   margin-top: 20px;
-  // }
-  h3 {
-    margin-bottom: 20px;
-  }
-}
-
-.icon-items {
-  display: flex;
-  flex-wrap: wrap;
-
-  > div {
-    width: 25%;
-    text-align: center;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: space-between;
-    margin-bottom: 20px;
-    span {
-      margin-top: 10px;
-    }
-  }
-}
-</style>

+ 34 - 17
src/example/fuse/views/tabulation/slide.vue

@@ -10,38 +10,55 @@ import {
   serial,
   table,
   test,
+  PaperKey,
 } from "../../../components/slide/actions.ts";
 import { useDraw } from "../../../components/container/use-draw.ts";
-import { nextTick, reactive, watch } from "vue";
+import { computed, nextTick, reactive, watch } from "vue";
 import { tabulationData } from "../../store";
-import { getRepTabulationStore } from "./gen.ts";
+import { ElMessageBox } from "element-plus";
+import { genTabulationData } from "./gen-tab.ts";
+import { defaultLayer } from "@/constant/index.ts";
 
 const draw = useDraw();
-const paper = {
+const paper = reactive({
   ...paperRaw,
   children: paperRaw.children.map((item) => ({
     ...item,
-    handler: () => (tabulationData.value.paperKey = item.key),
+    active: computed(() => tabulationData.value.paperKey === item.key),
+    handler: () => (tabulationData.value.paperKey = item.key as PaperKey),
   })),
-};
+});
 const menus = reactive([paper, text, serial, table]);
 
-const setPaperAfterHandler = async (paperKey: string, oldPaperKey?: string) => {
+const setPaperAfterHandler = async (paperKey: PaperKey, oldPaperKey?: PaperKey) => {
+  let isUpdate = false;
+  // if (oldPaperKey) {
+  //   try {
+  //     await ElMessageBox.confirm(
+  //       "纸张已变化, 是否需要重新生成模板数据(会清空已有数据)?",
+  //       {
+  //         type: "info",
+  //         confirmButtonText: "生成",
+  //         cancelButtonText: "取消",
+  //       }
+  //     );
+  //     isUpdate = true;
+  //   } catch {
+  //     isUpdate = false;
+  //   }
+  // }
   paperRaw.children.find((item) => item.key === paperKey)!.handler(draw);
-  return;
-  if (!oldPaperKey) return;
   await nextTick();
-  const data = await getRepTabulationStore(
-    draw.store.data,
-    draw.viewer.viewSize!,
-    draw.config.margin || 0,
-    paperKey,
-    tabulationData.value.cover,
-    true
-  );
+  if (!isUpdate) return;
+
+  const data = await genTabulationData(paperKey, tabulationData.value.cover);
 
   draw.history.clear();
-  draw.store.setStore(data);
+  draw.store.setStore({
+    layers: {
+      [defaultLayer]: data,
+    },
+  });
 };
 
 watch(() => tabulationData.value?.paperKey, setPaperAfterHandler, { immediate: true });

+ 41 - 16
src/example/platform/platform-draw.ts

@@ -1,9 +1,10 @@
-import { genBound } from "@/utils/shared";
+import { genBound, onlyId } from "@/utils/shared";
 import { AIExposeData } from "../dialog/ai";
 import { Draw } from "../components/container/use-draw";
 import { SceneFloor } from "./platform-resource";
 import { getBaseItem } from "@/core/components/util";
 import { LineData, defaultStyle } from "@/core/components/line";
+import { getInitCtx } from "@/core/components/line/use-draw";
 import { defaultStyle as iconDefaultStyle } from "@/core/components/icon";
 import { Transform } from "konva/lib/Util";
 import { MathUtils } from "three";
@@ -121,19 +122,43 @@ const drawLayerResource = (
   const images: any[] = [];
   const createTime = Date.now();
 
-  const geos: LineData[] = [];
-  layerResource.geos.forEach((item, ndx) => {
+  let sGeo = draw.store.getTypeItems('line')[0]
+  const geo: LineData = sGeo ? sGeo : {
+    ...getBaseItem(),
+    lines: [],
+    points: [],
+    polygon: [],
+    attitude: [1, 0, 0, 1, 0, 0],
+    createTime: createTime,
+  }
+  const geoCtx = getInitCtx()
+
+  layerResource.geos.forEach((item) => {
     bound.update(item);
-    geos.push({
-      ...getBaseItem(),
-      ...defaultStyle,
-      createTime: createTime + ndx,
-      lock: import.meta.env.DEV ? false : true,
-      attitude: [1, 0, 0, 1, 0, 0],
-      points: item,
-    });
+    if (item.length < 1) return;
+
+    let a: string = onlyId(), b: string
+    let i = 0
+    for (i = 0; i < item.length - 1; i++) {
+      b = onlyId()
+      const lId = onlyId()
+      const l = {  id: onlyId(), a, b, ...defaultStyle }
+      const p = { id: a, ...item[i] }
+      geoCtx.add.points[a] = p
+      geoCtx.add.lines[lId] = l
+      geo.points.push(p)
+      geo.lines.push(l)
+      a = b
+    }
+    const p = { id: b!, ...item[i] }
+    geoCtx.add.points[b!] = p
+    geo.points.push(p)
   });
-  draw.store.addItems('sequentLine', geos);
+  if (sGeo) {
+    draw.store.setItem('line', {id: geo.id, value: geo });  
+  } else {
+    draw.store.addItem('line', geo);
+  }
 
   if (layerResource.thumb) {
     const box = layerResource.box;
@@ -187,10 +212,10 @@ const drawLayerResource = (
     images.filter((item) => !item.url.includes(".svg"))
   );
 
-  draw.store.addItem("group", {
-    ...getBaseItem(),
-    ids: [...images, ...geos].map((item) => item.id),
-  });
+  // draw.store.addItem("group", {
+  //   ...getBaseItem(),
+  //   ids: [...images, geo.id].map((item) => item.id),
+  // });
 
   return bound.get();
 };

+ 8 - 7
src/utils/colors.ts

@@ -1,11 +1,7 @@
+import { themeColor } from "@/constant";
 import { Color, HSL } from "three";
 
 const offset = {
-  pub: {
-    h: 0,
-    s: 0,
-    l: 0,
-  },
   hover: {
     h: -0.012,
     s: 0,
@@ -27,10 +23,14 @@ const offset = {
     l: -0.15,
   },
 };
-type keys = keyof typeof offset;
+type keys = keyof typeof offset | 'pub';
 
 export const getMouseColors = (color16: string) => {
-  const theme = new Color(color16);
+  let theme = new Color(color16);
+  // if (theme.r === theme.g && theme.g === theme.b && theme.g === 0) {
+  //   theme = new Color(themeColor)
+  // }
+
   const themeHSL = theme.getHSL({} as HSL);
   const temp = new Color();
   const colors = Object.entries(offset).reduce((t, [k, o]) => {
@@ -39,6 +39,7 @@ export const getMouseColors = (color16: string) => {
       .getHexString();
     return t;
   }, {} as Record<keys, string>);
+  colors.pub = color16
 
   return {
     theme: '#' + theme.getHexString(),

+ 122 - 0
src/utils/polygon.ts

@@ -118,3 +118,125 @@ export function extractConnectedSegments(
 
   return result;
 }
+
+
+
+/**
+ * 从无向线段中提取所有闭合环(多边形)和开放路径
+ * @param segments 线段数组,每条线段由两个点组成(无方向性)
+ * @returns 返回所有闭合环和开放路径,每个路径用点数组表示
+ */
+export function extractConnectedNoDirectionSegments(
+  segments: { a: number; b: number }[]
+): number[][] {
+  // 1. 构建无向图邻接表
+  const graph: Record<number, number[]> = {};
+  for (const { a, b } of segments) {
+    if (!graph[a]) graph[a] = [];
+    if (!graph[b]) graph[b] = [];
+    graph[a].push(b);
+    graph[b].push(a);
+  }
+
+  // 2. 边访问记录(确保 (a,b) 和 (b,a) 被视为同一条边)
+  const visitedEdges = new Set<string>();
+  const edgeKey = (a: number, b: number) => (a < b ? `${a},${b}` : `${b},${a}`);
+
+  // 3. 结果收集
+  const result: number[][] = [];
+  const polygonSet = new Set<string>(); // 用于去重
+
+  // 4. 标准化多边形(按点集排序去重)
+  const normalizePolygon = (polygon: number[]) => {
+    const points = [...new Set(polygon.slice(0, -1))].sort((a, b) => a - b);
+    return JSON.stringify(points);
+  };
+
+  // 5. DFS 查找闭合环(无向图)
+  function findPolygons(start: number) {
+    const stack: {
+      current: number;
+      path: number[];
+      visitedEdges: Set<string>;
+    }[] = [{ current: start, path: [start], visitedEdges: new Set() }];
+
+    while (stack.length > 0) {
+      const { current, path, visitedEdges } = stack.pop()!;
+
+      for (const neighbor of graph[current]) {
+        const edge = edgeKey(current, neighbor);
+
+        // 跳过已访问的边
+        if (visitedEdges.has(edge)) continue;
+
+        // 发现闭合环(路径长度 > 2 且回到起点)
+        if (neighbor === start && path.length > 2) {
+          const closedPath = [...path, start];
+          const polyKey = normalizePolygon(closedPath);
+
+          if (!polygonSet.has(polyKey)) {
+            polygonSet.add(polyKey);
+            result.push(closedPath);
+          }
+          continue;
+        }
+
+        // 避免重复访问点(起点除外)
+        if (neighbor !== start && path.includes(neighbor)) continue;
+
+        const newVisitedEdges = new Set(visitedEdges);
+        newVisitedEdges.add(edge);
+        stack.push({
+          current: neighbor,
+          path: [...path, neighbor],
+          visitedEdges: newVisitedEdges,
+        });
+      }
+    }
+  }
+
+  // 6. 查找所有闭合环
+  for (const node in graph) {
+    const nodeId = Number(node);
+    if (graph[nodeId].length >= 2) {
+      findPolygons(nodeId);
+    }
+  }
+
+  // 7. 查找开放路径(度数为1的点)
+  const visitedNodes = new Set<number>();
+  for (const node in graph) {
+    const nodeId = Number(node);
+    if (visitedNodes.has(nodeId)) continue;
+
+    // 开放路径必须从端点开始(度数为1的点)
+    if (graph[nodeId].length === 1) {
+      const path: number[] = [];
+      let current: number | null = nodeId;
+      let prev: number | null = null;
+
+      while (current !== null) {
+        visitedNodes.add(current);
+        path.push(current);
+
+        // 找到下一个未访问的相邻点
+        let next: number | null = null;
+        for (const neighbor of graph[current]) {
+          if (neighbor !== prev && !visitedNodes.has(neighbor)) {
+            next = neighbor;
+            break;
+          }
+        }
+
+        prev = current;
+        current = next;
+      }
+
+      if (path.length >= 2) {
+        result.push(path);
+      }
+    }
+  }
+
+  return result;
+}

+ 19 - 1
src/utils/shared.ts

@@ -383,4 +383,22 @@ export const genBound = () => {
 };
 
 
-export const validNum = (num: any) => typeof num === 'number' && !Number.isNaN(num)
+export const validNum = (num: any) => typeof num === 'number' && !Number.isNaN(num)
+
+
+// 检测字体是否可用(返回第一个支持的字体)
+export function getSupportedFont(fonts: string[]) {
+  const canvas = document.createElement('canvas');
+  const ctx = canvas.getContext('2d')!;
+  const defaultFont = ctx.font;
+
+  for (const font of fonts) {
+    ctx.font = `12px ${font}`;
+    if (ctx.font.includes(font)) {
+      ctx.font = defaultFont; // 恢复默认
+      return font;
+    }
+  }
+  ctx.font = defaultFont;
+  return fonts[fonts.length - 1]; // 返回最后一个回退字体
+}