Преглед на файлове

feat: 添加基本形状的文字绑定

bill преди 6 месеца
родител
ревизия
468e0c70da

+ 18 - 2
src/core/components/circle/circle.vue

@@ -1,5 +1,11 @@
 <template>
-  <TempCircle :data="tData" :ref="(v: any) => shape = v?.shape" :id="data.id" />
+  <TempCircle
+    :data="tData"
+    :ref="(v: any) => shape = v?.shape"
+    :id="data.id"
+    editer
+    @update-content="updateContent"
+  />
   <PropertyUpdate
     :describes="describes"
     :data="data"
@@ -44,6 +50,11 @@ const matToData = (data: CircleData, mat: Transform, initRadius?: Pos) => {
   return data;
 };
 
+const updateContent = (val: string) => {
+  data.value.content = val;
+  emit("updateShape", { ...data.value });
+};
+
 const { shape, tData, data, operateMenus, describes } = useComponentStatus<
   Ellipse,
   CircleData
@@ -87,11 +98,16 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
   propertys: [
     "fill",
     "stroke",
+    "fontColor",
     "strokeWidth",
-    "dash",
+    "fontSize",
     // "ref",
     "opacity",
+    "dash",
     //  "zIndex"
+    "align",
+    "fontStyle",
+    // "ref", "zIndex"
   ],
 });
 </script>

+ 5 - 0
src/core/components/circle/index.ts

@@ -19,6 +19,10 @@ export const defaultStyle = {
   dash: [30, 0],
   stroke: themeMouseColors.theme,
   strokeWidth: 1,
+  fontSize: 16,
+  align: "center",
+  fontStyle: "normal",
+  fontColor: themeMouseColors.theme,
 };
 
 export const addMode = "area";
@@ -55,6 +59,7 @@ export type CircleData = Partial<typeof defaultStyle> &
     mat: number[];
     radiusX: number
     radiusY: number
+    content?: string;
   };
 
 export const dataToConfig = (data: CircleData): CircleConfig => ({

+ 48 - 11
src/core/components/circle/temp-circle.vue

@@ -1,24 +1,38 @@
 <template>
-  <v-ellipse
-    :config="{
-      ...data,
-      ...matConfig,
-      zIndex: undefined,
-      opacity: addMode ? 0.3 : data.opacity,
-    }"
-    ref="shape"
-  >
-  </v-ellipse>
+  <v-group ref="shape" :id="data.id">
+    <v-ellipse
+      name="repShape"
+      :config="{
+        ...data,
+        ...matConfig,
+        zIndex: undefined,
+        opacity: addMode ? 0.3 : data.opacity,
+      }"
+    />
+    <ShareText
+      :config="{ ...textConfig, ...textBound }"
+      :parent-id="data.id"
+      :editer="editer"
+      @update-text="(val) => emit('updateContent', val)"
+      @update:is-edit="(val) => emit('update:isEdit', val)"
+    />
+  </v-group>
 </template>
 
 <script lang="ts" setup>
+import ShareText from "../share/text.vue";
 import { CircleData, defaultStyle } from "./index.ts";
 import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Circle } from "konva/lib/shapes/Circle";
 import { Transform } from "konva/lib/Util";
 
-const props = defineProps<{ data: CircleData; addMode?: boolean }>();
+const props = defineProps<{ data: CircleData; addMode?: boolean; editer?: boolean }>();
+const emit = defineEmits<{
+  (e: "updateContent", data: string): void;
+  (e: "update:isEdit", data: boolean): void;
+}>();
+
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 const matConfig = computed(() => {
@@ -26,6 +40,29 @@ const matConfig = computed(() => {
   return mat.decompose();
 });
 
+const textBound = computed(() => {
+  const r1 = props.data.radiusX;
+  const r2 = props.data.radiusY;
+  const width = (2 * r1) / Math.sqrt(2); // 宽度
+  const height = (2 * r2) / Math.sqrt(2); // 高度
+  const matConfig = new Transform(data.value.mat)
+    .translate(-width / 2, -height / 2)
+    .decompose();
+  return {
+    padding: 8,
+    ...matConfig,
+    width,
+    height,
+  };
+});
+const textConfig = computed(() => ({
+  fontSize: data.value.fontSize,
+  align: data.value.align,
+  fontStyle: data.value.fontStyle,
+  fill: data.value.fontColor,
+  text: data.value.content,
+}));
+
 const shape = ref<DC<Circle>>();
 defineExpose({
   get shape() {

+ 5 - 0
src/core/components/rectangle/index.ts

@@ -13,6 +13,10 @@ export const defaultStyle = {
   dash: [30, 0],
   strokeWidth: 1,
   stroke: themeMouseColors.theme,
+  fontSize: 16,
+  align: "center",
+  fontStyle: "normal",
+  fontColor: themeMouseColors.theme,
 };
 
 export const getMouseStyle = (data: RectangleData) => {
@@ -40,6 +44,7 @@ export type RectangleData = Partial<typeof defaultStyle> &
     stroke?: string;
     fill?: string;
     strokeWidth?: number;
+    content?: string
   };
 
 export const interactiveToData = (

+ 18 - 2
src/core/components/rectangle/rectangle.vue

@@ -1,5 +1,10 @@
 <template>
-  <TempLine :data="tData" :ref="(v: any) => shape = v?.shape" :id="data.id" />
+  <TempLine
+    :data="tData"
+    :ref="(v: any) => shape = v?.shape"
+    editer
+    @update-content="updateContent"
+  />
   <PropertyUpdate
     :describes="describes"
     :data="data"
@@ -23,6 +28,11 @@ const emit = defineEmits<{
   (e: "delShape"): void;
 }>();
 
+const updateContent = (val: string) => {
+  data.value.content = val;
+  emit("updateShape", {...data.value});
+};
+
 const { shape, tData, data, operateMenus, describes } = useComponentStatus({
   emit,
   props,
@@ -40,9 +50,15 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus({
   propertys: [
     "fill",
     "stroke",
+    "fontColor",
     "strokeWidth",
-    "dash",
+    "fontSize",
+    // "ref",
     "opacity",
+    "dash",
+    //  "zIndex"
+    "align",
+    "fontStyle",
     // "ref", "zIndex"
   ],
 });

+ 62 - 11
src/core/components/rectangle/temp-rectangle.vue

@@ -1,24 +1,75 @@
 <template>
-  <v-line
-    :config="{
-      ...data,
-      zIndex: undefined,
-      closed: true,
-      points: flatPositions(data.points),
-      opacity: addMode ? 0.3 : data.opacity,
-    }"
-    ref="shape"
-  />
+  <v-group ref="shape" :id="data.id">
+    <v-line
+      name="repShape"
+      :config="{
+        ...data,
+        id: undefined,
+        closed: true,
+        points: flatPositions(data.points),
+        opacity: addMode ? 0.3 : data.opacity,
+      }"
+    />
+    <ShareText
+      :config="{ ...textConfig, ...textBound }"
+      :parent-id="data.id"
+      :editer="editer"
+      @update-text="(val) => emit('updateContent', val)"
+      @update:is-edit="(val) => emit('update:isEdit', val)"
+    />
+  </v-group>
 </template>
 
 <script lang="ts" setup>
+import ShareText from "../share/text.vue";
 import { flatPositions } from "@/utils/shared.ts";
 import { defaultStyle, RectangleData } from "./index.ts";
 import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Line } from "konva/lib/shapes/Line";
-const props = defineProps<{ data: RectangleData; addMode?: boolean }>();
+import { Transform } from "konva/lib/Util";
+import { MathUtils } from "three";
+const props = defineProps<{ data: RectangleData; addMode?: boolean; editer?: boolean }>();
+const emit = defineEmits<{
+  (e: "updateContent", data: string): void;
+  (e: "update:isEdit", data: boolean): void;
+}>();
+
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
+const textBound = computed(() => {
+  const dec = new Transform(props.data.attitude).decompose();
+  const inv = new Transform().rotate(MathUtils.degToRad(-dec.rotation));
+  const invPoints = props.data.points.map((p) => inv.point(p));
+  const move = { ...invPoints[0] };
+
+  let width = invPoints[2].x - invPoints[0].x;
+  let height = invPoints[2].y - invPoints[0].y;
+  if (width < 0) {
+    width *= -1;
+    move.x -= width;
+  }
+
+  if (height < 0) {
+    height *= -1;
+    move.y -= height;
+  }
+  const textTf = new Transform()
+    .rotate(MathUtils.degToRad(dec.rotation))
+    .translate(move.x, move.y);
+  return {
+    padding: 8,
+    width,
+    height,
+    ...textTf.decompose(),
+  };
+});
+const textConfig = computed(() => ({
+  fontSize: data.value.fontSize,
+  align: data.value.align,
+  fontStyle: data.value.fontStyle,
+  fill: data.value.fontColor,
+  text: data.value.content,
+}));
 
 const shape = ref<DC<Line>>();
 defineExpose({

+ 73 - 0
src/core/components/share/text.vue

@@ -0,0 +1,73 @@
+<template>
+  <v-text :config="{ ...config, text }" ref="shape" name="text" />
+  <TextDom
+    v-if="shape && editer"
+    :shape="shape.getNode()"
+    @submit="submitHandler"
+    @show="showHandler"
+    @hide="quitHandler"
+  />
+</template>
+
+<script setup lang="ts">
+import { useMouseShapesStatus } from "@/core/hook/use-mouse-status";
+import { DC } from "@/deconstruction";
+import { Text, TextConfig } from "konva/lib/shapes/Text";
+import { computed, ref } from "vue";
+import TextDom from "./text-area.vue";
+
+const props = withDefaults(
+  defineProps<{
+    config: TextConfig;
+    editer?: boolean;
+    parentId?: string;
+  }>(),
+  { editer: false }
+);
+const emit = defineEmits<{
+  (e: "updateText", data: string): void;
+  (e: "update:isEdit", val: boolean): void;
+}>();
+const shape = ref<DC<Text>>();
+const editText = ref(false);
+const shapesStatus = useMouseShapesStatus();
+const text = computed(() => {
+  const pad = props.config.padding || 0;
+  if (
+    !props.config.text ||
+    !props.config.fontSize ||
+    !props.config.height ||
+    props.config.height - props.config.fontSize < pad
+  ) {
+    return "";
+  } else {
+    return props.config.text;
+  }
+});
+
+const showHandler = () => {
+  editText.value = true;
+  emit("update:isEdit", true);
+  if (!props.parentId) return;
+  const ndx = shapesStatus.actives.findIndex((s) => s.id() === props.parentId);
+  if (~ndx) {
+    shapesStatus.actives = shapesStatus.actives.filter((s) => s.id() !== props.parentId);
+  }
+};
+
+const submitHandler = (val: string) => {
+  if (val !== props.config.text) {
+    emit("updateText", val);
+  }
+};
+const quitHandler = () => {
+  editText.value = false;
+  emit("update:isEdit", false);
+};
+
+defineExpose({
+  get shape() {
+    return shape.value;
+  },
+});
+</script>

+ 47 - 64
src/core/components/table/table.vue

@@ -1,5 +1,11 @@
 <template>
-  <TempTable :data="tData" ref="tableRef" :id="data.id" />
+  <TempTable
+    :data="tData"
+    ref="tableRef"
+    :id="data.id"
+    @update-content="submitInputHandler"
+    editer
+  />
   <PropertyUpdate
     :describes="describes"
     :data="data"
@@ -12,23 +18,10 @@
     @show="menuShowHandler"
     @hide="menuHideHandler"
   />
-
-  <template v-for="(raw, rawNdx) in data.content">
-    <template v-for="(_, colNdx) in raw">
-      <TextDom
-        v-if="tableRef?.texts[rawNdx] && tableRef?.texts[rawNdx][colNdx]?.getNode()"
-        :shape="tableRef.texts[rawNdx][colNdx].getNode()!"
-        @submit="(val) => submitInputHandler(val, rawNdx, colNdx)"
-        @show="showInputHandler"
-        @hide="quitInputHandler"
-      />
-    </template>
-  </template>
 </template>
 
 <script lang="ts" setup>
 import TempTable from "./temp-table.vue";
-import TextDom from "../share/text-area.vue";
 import { TableData, getMouseStyle, defaultStyle, TableCollData } from "./index.ts";
 import { PropertyUpdate, Operate } from "../../propertys";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
@@ -65,9 +58,9 @@ type TableRef = {
   getMouseIntersect: (
     pos?: Pos
   ) => {
-    rawBorderNdx: number;
+    rowBorderNdx: number;
     colBorderNdx: number;
-    rawNdx: number;
+    rowNdx: number;
     colNdx: number;
   };
 };
@@ -75,7 +68,7 @@ const tableRef = ref<TableRef>();
 const status = useMouseShapeStatus(computed(() => tableRef.value?.shape));
 let inter: Pick<
   ReturnType<TableRef["getMouseIntersect"]>,
-  "rawBorderNdx" | "colBorderNdx"
+  "rowBorderNdx" | "colBorderNdx"
 > | null = null;
 const pos = usePointerPos();
 const cursor = useCursor();
@@ -89,9 +82,9 @@ watch(
         () => pos.value && tableRef.value?.getMouseIntersect(pos.value),
         (inter, _, onCleanup) => {
           const $shape = shape.value?.getNode();
-          if ($shape && inter && (~inter.colBorderNdx || ~inter.rawBorderNdx)) {
+          if ($shape && inter && (~inter.colBorderNdx || ~inter.rowBorderNdx)) {
             onCleanup(
-              cursor.push(getResizeCorsur(!~inter.rawBorderNdx, $shape.rotation()))
+              cursor.push(getResizeCorsur(!~inter.rowBorderNdx, $shape.rotation()))
             );
           }
         },
@@ -107,9 +100,9 @@ watch(
     if (!press) return;
     inter = tableRef.value?.getMouseIntersect() || null;
     const $shape = shape.value?.getNode();
-    if ($shape && inter && (~inter.colBorderNdx || ~inter.rawBorderNdx)) {
+    if ($shape && inter && (~inter.colBorderNdx || ~inter.rowBorderNdx)) {
       const pop = cursor.push(
-        getResizeCorsur(!~inter.rawBorderNdx, shape.value?.getNode().rotation())
+        getResizeCorsur(!~inter.rowBorderNdx, shape.value?.getNode().rotation())
       );
       const isActive = shapesStatus.actives.includes($shape);
       if (isActive) {
@@ -139,15 +132,15 @@ const matToData = (data: TableData, mat: Transform, initData?: TableData) => {
     initData = copy(data);
   }
   const dec = mat.decompose();
-  if (!inter || (!~inter.colBorderNdx && !~inter.rawBorderNdx)) {
+  if (!inter || (!~inter.colBorderNdx && !~inter.rowBorderNdx)) {
     const oldData = copy(data);
     data.height = dec.scaleY * initData.height;
     data.width = dec.scaleX * initData.width;
 
     let w = 0;
     let h = 0;
-    data.content.forEach((raw, rndx) => {
-      raw.forEach((col, cndx) => {
+    data.content.forEach((row, rndx) => {
+      row.forEach((col, cndx) => {
         const initCol = initData.content[rndx][cndx];
         const minSize = getColMinSize(initCol);
         col.width = Math.max(minSize.w, data.width * (initCol.width / initData.width));
@@ -190,10 +183,10 @@ const matToData = (data: TableData, mat: Transform, initData?: TableData) => {
     x: dec.x - initDec.x,
     y: dec.y - initDec.y,
   });
-  if (~inter.rawBorderNdx) {
-    const ndxRaw = inter.rawBorderNdx - 1;
-    const ndx = ndxRaw === -1 ? 0 : ndxRaw;
-    let offset = ndxRaw === -1 ? -move.y : move.y;
+  if (~inter.rowBorderNdx) {
+    const ndxrow = inter.rowBorderNdx - 1;
+    const ndx = ndxrow === -1 ? 0 : ndxrow;
+    let offset = ndxrow === -1 ? -move.y : move.y;
     const minSize = getColMinSize(data.content[ndx][0]);
     const h = Math.max(minSize.h, initData.content[ndx][0].height + offset);
     offset = h - initData.content[ndx][0].height;
@@ -203,7 +196,7 @@ const matToData = (data: TableData, mat: Transform, initData?: TableData) => {
     );
     data.height = initData.height + offset;
 
-    if (ndxRaw === -1) {
+    if (ndxrow === -1) {
       const translate = new Transform()
         .rotate(MathUtils.degToRad(dec.rotation))
         .point({ x: 0, y: -offset });
@@ -212,9 +205,9 @@ const matToData = (data: TableData, mat: Transform, initData?: TableData) => {
         .multiply(new Transform(initData.mat)).m;
     }
   } else {
-    const ndxRaw = inter.colBorderNdx - 1;
-    const ndx = ndxRaw === -1 ? 0 : ndxRaw;
-    let offset = ndxRaw === -1 ? -move.x : move.x;
+    const ndxrow = inter.colBorderNdx - 1;
+    const ndx = ndxrow === -1 ? 0 : ndxrow;
+    let offset = ndxrow === -1 ? -move.x : move.x;
     const minSize = getColMinSize(data.content[0][ndx]);
     const w = Math.max(minSize.w, initData.content[0][ndx].width + offset);
     offset = w - initData.content[0][ndx].width;
@@ -222,7 +215,7 @@ const matToData = (data: TableData, mat: Transform, initData?: TableData) => {
       row[ndx].width = initData.content[rowNdx][ndx].width + offset;
     });
     data.width = initData.width + offset;
-    if (ndxRaw === -1) {
+    if (ndxrow === -1) {
       const translate = new Transform()
         .rotate(MathUtils.degToRad(dec.rotation))
         .point({ x: -offset, y: 0 });
@@ -312,8 +305,8 @@ watchEffect((onCleanup) => {
 watch(
   () => data.value.fontSize,
   () => {
-    data.value.content.forEach((raw) => {
-      raw.forEach((col) => {
+    data.value.content.forEach((row) => {
+      row.forEach((col) => {
         col.fontSize = data.value.fontSize;
       });
     });
@@ -328,8 +321,8 @@ watch(
 
 watchEffect(
   () => {
-    data.value.content.forEach((raw) => {
-      raw.forEach((col) => {
+    data.value.content.forEach((row) => {
+      row.forEach((col) => {
         col.fontColor = data.value.fontColor;
         col.fontStyle = data.value.fontStyle;
         col.align = data.value.align;
@@ -347,13 +340,13 @@ const menuShowHandler = () => {
       icon: Plus,
       label: "插入行",
       handler: () => {
-        const tempRaw = data.value.content[config.rawNdx];
+        const temprow = data.value.content[config.rowNdx];
         data.value.content.splice(
-          config.rawNdx,
+          config.rowNdx,
           0,
-          tempRaw.map((item) => ({ ...item, content: "" }))
+          temprow.map((item) => ({ ...item, content: "" }))
         );
-        data.value.height += tempRaw[0].height;
+        data.value.height += temprow[0].height;
         sync(data.value);
         emit("updateShape", { ...data.value });
       },
@@ -362,9 +355,9 @@ const menuShowHandler = () => {
       icon: Minus,
       label: "删除行",
       handler: () => {
-        const tempRaw = data.value.content[config.rawNdx];
-        data.value.content.splice(config.rawNdx, 1);
-        data.value.height -= tempRaw[0].height;
+        const temprow = data.value.content[config.rowNdx];
+        data.value.content.splice(config.rowNdx, 1);
+        data.value.height -= temprow[0].height;
         if (data.value.content.length === 0) {
           emit("delShape");
         } else {
@@ -379,8 +372,8 @@ const menuShowHandler = () => {
       handler: () => {
         const tempCol = data.value.content[0][config.colNdx];
         for (let i = 0; i < data.value.content.length; i++) {
-          const raw = data.value.content[i];
-          raw.splice(config.colNdx, 0, { ...tempCol, content: "" });
+          const row = data.value.content[i];
+          row.splice(config.colNdx, 0, { ...tempCol, content: "" });
         }
         data.value.width += tempCol.width;
         sync(data.value);
@@ -393,8 +386,8 @@ const menuShowHandler = () => {
       handler: () => {
         const tempCol = data.value.content[0][config.colNdx];
         for (let i = 0; i < data.value.content.length; i++) {
-          const raw = data.value.content[i];
-          raw.splice(config.colNdx, 1);
+          const row = data.value.content[i];
+          row.splice(config.colNdx, 1);
         }
         data.value.width -= tempCol.width;
         if (data.value.content[0].length === 0) {
@@ -418,22 +411,12 @@ const menuHideHandler = () => {
   addMenu = [];
 };
 
-const showText = ref(false);
-const showInputHandler = () => {
-  showText.value = true;
-  const ndx = shapesStatus.actives.indexOf(shape.value!.getNode());
-  if (~ndx) {
-    shapesStatus.actives = shapesStatus.actives.filter(
-      (v) => v !== shape.value!.getNode()
-    );
-  }
-};
-const quitInputHandler = () => {
-  showText.value = false;
-};
-const submitInputHandler = (val: string, rawNdx: number, colNdx: number) => {
-  quitInputHandler();
-  data.value.content[rawNdx][colNdx].content = val;
+const submitInputHandler = (playData: {
+  val: string;
+  rowNdx: number;
+  colNdx: number;
+}) => {
+  data.value.content[playData.rowNdx][playData.colNdx].content = playData.val;
   emit("updateShape", data.value);
 };
 </script>

+ 30 - 34
src/core/components/table/temp-table.vue

@@ -19,17 +19,20 @@
         zIndex: undefined,
       }"
     />
-    <template v-for="(raw, rawNdx) in data.content">
-      <template v-for="(col, colNdx) in raw">
-        <v-text
-          :ref="(r: any) => setText(rawNdx, colNdx, r)"
+    <template v-for="(row, rowNdx) in data.content">
+      <template v-for="(col, colNdx) in row">
+        <ShareText
           :config="{
             ...defaultCollData,
             ...col,
-            ...getBound(rawNdx, colNdx),
+            ...getBound(rowNdx, colNdx),
             text: col.content,
             fill: col.fontColor,
           }"
+          :parent-id="data.id"
+          :editer="editer"
+          @update-text="(val) => emit('updateContent', { rowNdx, colNdx, val })"
+          @update:is-edit="(val) => emit('update:isEdit', { rowNdx, colNdx, val })"
         />
       </template>
     </template>
@@ -37,6 +40,7 @@
 </template>
 
 <script lang="ts" setup>
+import ShareText from "../share/text.vue";
 import { defaultStyle, TableData, defaultCollData } from "./index.ts";
 import { computed, ref } from "vue";
 import { DC } from "@/deconstruction.js";
@@ -46,42 +50,37 @@ import { useStage } from "@/core/hook/use-global-vars.ts";
 import { Line } from "konva/lib/shapes/Line";
 import { useViewerInvertTransform, useViewerTransform } from "@/core/hook/use-viewer.ts";
 import { lineLen, Pos } from "@/utils/math.ts";
-import { Text } from "konva/lib/shapes/Text";
 
-const props = defineProps<{ data: TableData; addMode?: boolean }>();
+const props = defineProps<{ data: TableData; addMode?: boolean; editer?: boolean }>();
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
-const texts = ref<DC<Text>[][]>([]);
+const emit = defineEmits<{
+  (e: "updateContent", data: { rowNdx: number; colNdx: number; val: string }): void;
+  (e: "update:isEdit", data: { rowNdx: number; colNdx: number; val: boolean }): void;
+}>();
 
 const shape = ref<DC<Group>>();
 const line = ref<DC<Line>>();
 
-const setText = (rawNdx: number, colNdx: number, text: DC<Text>) => {
-  let raw = texts.value[rawNdx];
-  if (!raw) {
-    raw = texts.value[rawNdx] = [];
-  }
-  raw[colNdx] = text;
-};
-const getBound = (rawNdx: number, colNdx: number) => {
+const getBound = (rowNdx: number, colNdx: number) => {
   let x = 0,
     y = 0;
-  for (let i = 0; i < rawNdx; i++) {
+  for (let i = 0; i < rowNdx; i++) {
     y += data.value.content[i][0].height;
   }
   for (let i = 0; i < colNdx; i++) {
-    x += data.value.content[rawNdx][i].width;
+    x += data.value.content[rowNdx][i].width;
   }
-  const { width, height } = data.value.content[rawNdx][colNdx];
+  const { width, height } = data.value.content[rowNdx][colNdx];
   return { x, y, width, height };
 };
 
 const getBorderPoints = () => {
   const points: number[] = [];
-  const raws = data.value.content;
+  const rows = data.value.content;
   let ry = 0;
   let rx = 0;
-  for (let i = 0; i < raws.length; i++) {
-    const cols = raws[i];
+  for (let i = 0; i < rows.length; i++) {
+    const cols = rows[i];
     for (let j = 0; j < cols.length; j++) {
       const { x, y, width, height } = getBound(i, j);
       points.push(x, y, x + width, y, x + width, y + height, x, y + height, x, y);
@@ -120,9 +119,9 @@ const getMouseIntersect = (pos?: Pos) => {
     viewTransform.value.point({ x: 0, y: 0 })
   );
   const check = Math.max(rang, swPixel);
-  let rawBorderNdx = -1;
+  let rowBorderNdx = -1;
   let colBorderNdx = -1;
-  let rawNdx = -1;
+  let rowNdx = -1;
   let colNdx = -1;
 
   for (let i = 0; i < data.value.content.length; i++) {
@@ -136,16 +135,16 @@ const getMouseIntersect = (pos?: Pos) => {
       continue;
     }
     if (Math.abs(td) < check) {
-      rawBorderNdx = i;
+      rowBorderNdx = i;
     }
     if (Math.abs(bd) < check) {
-      rawBorderNdx = i + 1;
+      rowBorderNdx = i + 1;
     }
-    rawNdx = i;
+    rowNdx = i;
 
-    const raw = data.value.content[i];
+    const row = data.value.content[i];
     let j = 0;
-    for (; j < raw.length; j++) {
+    for (; j < row.length; j++) {
       const cb = getBound(i, j);
       const ld = pos.x - cb.x;
       const rd = pos.x - cb.x - cb.width;
@@ -162,20 +161,17 @@ const getMouseIntersect = (pos?: Pos) => {
       colNdx = j;
       break;
     }
-    if (j !== raw.length) {
+    if (j !== row.length) {
       break;
     }
   }
-  return { rawBorderNdx, colBorderNdx, rawNdx, colNdx };
+  return { rowBorderNdx, colBorderNdx, rowNdx, colNdx };
 };
 
 defineExpose({
   get shape() {
     return shape.value;
   },
-  get texts() {
-    return texts.value;
-  },
   getMouseIntersect,
 });
 </script>

+ 12 - 8
src/core/components/text/temp-text.vue

@@ -1,7 +1,5 @@
 <template>
-  <!-- wrap: 'char',
-   -->
-  <v-text
+  <ShareText
     :config="{
       ...data,
       wrap: 'char',
@@ -11,20 +9,26 @@
       verticalAlign: 'center',
       opacity: addMode ? 0.3 : data.opacity,
     }"
-    ref="shape"
-    name="text"
-  >
-  </v-text>
+    :parent-id="data.id"
+    :ref="(r: any) => shape = r?.shape"
+    :editer="editer"
+    @update-text="(v) => emit('updateContent', v)"
+  />
 </template>
 
 <script lang="ts" setup>
+import ShareText from "../share/text.vue";
 import { defaultStyle, TextData, textNodeMap } from "./index.ts";
 import { computed, ref, watchEffect } from "vue";
 import { DC } from "@/deconstruction.js";
 import { Transform } from "konva/lib/Util";
 import { Text } from "konva/lib/shapes/Text";
 
-const props = defineProps<{ data: TextData; addMode?: boolean }>();
+const props = defineProps<{ data: TextData; addMode?: boolean; editer?: boolean }>();
+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({

+ 7 - 24
src/core/components/text/text.vue

@@ -1,27 +1,23 @@
 <template>
-  <TextDom
-    v-if="$shape"
-    :shape="$shape"
-    @submit="submitHandler"
-    @show="showHandler"
-    @hide="quitHandler"
+  <TempText
+    :data="tData"
+    editer
+    @update-content="submitHandler"
+    :ref="(v: any) => shape = v?.shape"
   />
-  <TempText :data="tData" :ref="(v: any) => shape = v?.shape" :id="data.id" />
   <PropertyUpdate
-    v-if="!editText"
     :describes="describes"
     :data="data"
     :target="shape"
     @change="emit('updateShape', { ...data })"
   />
-  <Operate :target="shape" :menus="operateMenus" v-if="!editText" />
+  <Operate :target="shape" :menus="operateMenus" />
 </template>
 
 <script lang="ts" setup>
 import { TextData, getMouseStyle, defaultStyle } from "./index.ts";
 import { PropertyUpdate, Operate } from "../../propertys";
 import TempText from "./temp-text.vue";
-import TextDom from "../share/text-area.vue";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
 import { cloneRepShape, useCustomTransformer } from "@/core/hook/use-transformer.ts";
 import { Transform } from "konva/lib/Util";
@@ -30,7 +26,6 @@ import { computed, ref, watch } from "vue";
 import { setShapeTransform } from "@/utils/shape.ts";
 import { zeroEq } from "@/utils/math.ts";
 import { MathUtils } from "three";
-import { useMouseShapesStatus } from "@/core/hook/use-mouse-status.ts";
 
 const props = defineProps<{ data: TextData }>();
 const emit = defineEmits<{
@@ -143,22 +138,10 @@ watch(
   }
 );
 
-const shapesStatus = useMouseShapesStatus();
-const $shape = computed(() => shape.value?.getNode());
-const editText = ref(false);
-const showHandler = () => {
-  editText.value = true;
-  const ndx = shapesStatus.actives.indexOf($shape.value!);
-  if (~ndx) {
-    shapesStatus.actives = shapesStatus.actives.filter((v) => v !== $shape.value);
-  }
-};
 const submitHandler = (val: string) => {
   if (val !== data.value.content) {
     data.value.content = val;
+    emit("updateShape", { ...data.value });
   }
 };
-const quitHandler = () => {
-  editText.value = false;
-};
 </script>

+ 13 - 4
src/core/components/triangle/index.ts

@@ -11,6 +11,10 @@ export const shapeName = "三角形";
 export const defaultStyle = {
   stroke: themeMouseColors.theme,
   strokeWidth: 5,
+  fontSize: 16,
+  align: "center",
+  fontStyle: "normal",
+  fontColor: themeMouseColors.theme,
 };
 
 export const addMode = "area";
@@ -32,11 +36,16 @@ export const getMouseStyle = (data: TriangleData) => {
 };
 
 export type TriangleData = Partial<typeof defaultStyle> &
-  BaseItem & { points: Pos[]; attitude: number[]; fill?: string };
+  BaseItem & {
+    points: Pos[];
+    attitude: number[];
+    fill?: string;
+    content?: string;
+  };
 
 export const getSnapPoints = (data: TriangleData) => {
-  return data.points
-}
+  return data.points;
+};
 
 export const getSnapInfos = (data: TriangleData) =>
   generateSnapInfos(getSnapPoints(data), true, false);
@@ -60,7 +69,7 @@ export const interactiveFixData = (
   info: AddMessage<"triangle">
 ) => {
   const area = info.cur!;
- 
+
   data.points[0] = {
     x: area[0].x - (area[1].x - area[0].x),
     y: area[1].y,

+ 71 - 11
src/core/components/triangle/temp-triangle.vue

@@ -1,26 +1,86 @@
 <template>
-  <v-line
-    :config="{
-      ...data,
-      zIndex: undefined,
-      closed: true,
-      points: flatPositions(data.points),
-      opacity: addMode ? 0.3 : data.opacity,
-    }"
-    ref="shape"
-  />
+  <v-group ref="shape" :id="data.id">
+    <v-line
+      name="repShape"
+      :config="{
+        ...data,
+        zIndex: undefined,
+        closed: true,
+        points: flatPositions(data.points),
+        opacity: addMode ? 0.3 : data.opacity,
+      }"
+    />
+    <ShareText
+      :config="{ ...textConfig, ...textBound }"
+      :parent-id="data.id"
+      :editer="editer"
+      @update-text="(val) => emit('updateContent', val)"
+      @update:is-edit="(val) => emit('update:isEdit', val)"
+    />
+  </v-group>
 </template>
 
 <script lang="ts" setup>
+import ShareText from "../share/text.vue";
 import { DC } from "@/deconstruction.js";
 import { defaultStyle, TriangleData } from "./index.ts";
 import { Line } from "konva/lib/shapes/Line";
 import { computed, ref } from "vue";
 import { flatPositions } from "@/utils/shared.ts";
-const props = defineProps<{ data: TriangleData; addMode?: boolean }>();
+import { Transform } from "konva/lib/Util";
+import { MathUtils } from "three";
+import { lineSpeed } from "@/utils/math.ts";
+const props = defineProps<{ data: TriangleData; addMode?: boolean; editer?: boolean }>();
+const emit = defineEmits<{
+  (e: "updateContent", data: string): void;
+  (e: "update:isEdit", data: boolean): void;
+}>();
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
 
 const shape = ref<DC<Line>>();
+const textBound = computed(() => {
+  const dec = new Transform(props.data.attitude).decompose();
+  const inv = new Transform().rotate(MathUtils.degToRad(-dec.rotation));
+  const invPoints = props.data.points.map((p) => inv.point(p));
+
+  const p1 = lineSpeed([invPoints[0], invPoints[1]], 0.6);
+  const p2 = lineSpeed([invPoints[2], invPoints[1]], 0.6);
+  const p3 = { x: p2.x, y: invPoints[2].y };
+  const move = { ...p1 };
+
+  let width = p3.x - p1.x;
+  let height = p3.y - p1.y;
+
+  if (width < 0) {
+    width *= -1;
+    move.x -= width;
+  }
+
+  if (height < 0) {
+    height *= -1;
+    move.y -= height;
+  }
+
+  const textTf = new Transform()
+    .rotate(MathUtils.degToRad(dec.rotation))
+    .translate(move.x, move.y);
+
+  const bound = {
+    padding: 8,
+    width,
+    height,
+    ...textTf.decompose(),
+  };
+  return bound;
+});
+const textConfig = computed(() => ({
+  fontSize: data.value.fontSize,
+  align: data.value.align,
+  fontStyle: data.value.fontStyle,
+  fill: data.value.fontColor,
+  text: data.value.content,
+}));
+
 defineExpose({
   get shape() {
     return shape.value;

+ 18 - 2
src/core/components/triangle/triangle.vue

@@ -1,5 +1,11 @@
 <template>
-  <TempLine :data="tData" :ref="(v: any) => shape = v?.shape" :id="data.id" />
+  <TempLine
+    :data="tData"
+    :ref="(v: any) => shape = v?.shape"
+    :id="data.id"
+    editer
+    @update-content="updateContent"
+  />
   <PropertyUpdate
     :describes="describes"
     :data="data"
@@ -22,6 +28,10 @@ const emit = defineEmits<{
   (e: "addShape", value: TriangleData): void;
   (e: "delShape"): void;
 }>();
+const updateContent = (val: string) => {
+  data.value.content = val;
+  emit("updateShape", { ...data.value });
+};
 
 const { shape, tData, operateMenus, describes, data } = useComponentStatus({
   emit,
@@ -40,9 +50,15 @@ const { shape, tData, operateMenus, describes, data } = useComponentStatus({
   propertys: [
     "fill",
     "stroke",
+    "fontColor",
     "strokeWidth",
-    "dash",
+    "fontSize",
+    // "ref",
     "opacity",
+    "dash",
+    //  "zIndex"
+    "align",
+    "fontStyle",
     // "ref", "zIndex"
   ],
 });

+ 1 - 0
src/core/hook/use-transformer.ts

@@ -583,6 +583,7 @@ export const useLineTransformer = <T extends LineTransformerData>(
       // 顶点更新
       const transfrom = mat.copy().multiply(inverAttitude);
       data.points = tempVs = stableVs.map((v) => transfrom.point(v));
+      data.attitude = mat.m
     },
     callback(data, mat) {
       data.attitude = mat.m;

+ 6 - 0
src/utils/math.ts

@@ -160,6 +160,12 @@ export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) =>
 export const lineCenter = (line: Pos[]) =>
   vector(line[0]).add(line[1]).multiplyScalar(0.5);
 
+export const lineSpeed = (line: Pos[], step: number) => {
+  const p = vector(line[0])
+  const v = vector(line[1]).sub(line[0])
+  return p.add(v.multiplyScalar(step))
+}
+
 export const pointsCenter = (points: Pos[]) => {
   if (points.length === 0) return { x: 0, y: 0 };
   const v = vector(points[0]);