bill 8 meses atrás
pai
commit
d9c14aef4b

+ 30 - 7
src/api/path.ts

@@ -1,6 +1,7 @@
 import axios from "./instance";
 import { params } from "@/env";
 import { PATH_LIST, DELETE_PATH, INSERT_PATH, UPDATE_PATH } from "./constant";
+import { createPath } from "@/store";
 
 interface ServerPath {
   id: number;
@@ -10,18 +11,22 @@ interface ServerPath {
 export interface Path {
   id: string;
   name: string;
+  showName: boolean
   linePosition?: {
     loc: SceneLocalPos;
     modelId: string;
+    normal: SceneLocalPos
   };
   lineWidth: number;
   lineColor: string;
-  lineAltitudeAboveGround: number
+  lineAltitudeAboveGround: number;
   fontSize: number;
-  autoAdjust: boolean;
+  showDirection: boolean;
+  reverseDirection: boolean
   globalVisibility: boolean;
+  visibilityRange: number
   points: {
-    name?: string;
+    name: string;
     position: {
       loc: SceneLocalPos;
       modelId: string;
@@ -42,10 +47,28 @@ const localToService = (path: Path): ServerPath => ({
 });
 
 export const fetchPaths = async () => {
-  const staggings = await axios.get<ServerPath[]>(PATH_LIST, {
-    params: { caseId: params.caseId },
-  });
-  return staggings.map(serviceToLocal);
+  const paths: Paths = [
+    createPath({ name: "路径1" }),
+    createPath({
+      name: "路径2",
+      points: [
+        {
+          name: "点1",
+          position: {
+            loc: { x: 0, y: 0, z: 0 },
+            modelId: "1167",
+          },
+        },
+      ],
+    }),
+    createPath({ name: "路径3" }),
+  ];
+
+  return paths;
+  // const staggings = await axios.get<ServerPath[]>(PATH_LIST, {
+  //   params: { caseId: params.caseId },
+  // });
+  // return staggings.map(serviceToLocal);
 };
 
 export const postAddPath = async (path: Path) => {

+ 5 - 1
src/app.vue

@@ -20,7 +20,11 @@
 
     <template v-for="needMount in needMounts">
       <Teleport :to="needMount[0]">
-        <component :is="needMount[1]" v-bind="needMount[2]" />
+        <component
+          :is="needMount[1]"
+          v-bind="needMount[2]"
+          :ref="(v: any) => needMount[3] && needMount[3](v)"
+        />
       </Teleport>
     </template>
   </ConfigProvider>

+ 3 - 0
src/assets/style/global.css

@@ -30,3 +30,6 @@ textarea {
   background: rgba(255, 255, 255, 0.1) !important;
   border: 1px solid rgba(255, 255, 255, 0.2) !important;
 }
+.ant-input-number {
+  background: var(--colors-normal-back) !important;
+}

+ 3 - 0
src/assets/style/global.less

@@ -40,4 +40,7 @@ button, input, select, textarea {
 .ant-slider .ant-slider-rail {
   background: rgba(255,255,255,0.1) !important;
   border: 1px solid rgba(255,255,255,0.2) !important;
+}
+.ant-input-number {
+  background: var(--colors-normal-back) !important;
 }

+ 78 - 67
src/components/bill-ui/components/input/number.vue

@@ -1,80 +1,91 @@
 <template>
-    <UIText
-        :key="key"
-        class="number ready"
-        :class="{ ctrl }"
-        type="number"
-        :right="right"
-        :modelValue="tempValue"
-        :placeholder="placeholder"
-        @update:modelValue="updateTempValue"
-        :other="{ min, max, step }"
-        @blur="blurHandler"
-        :readonly="!inInput"
-    >
-        <template v-for="(slot, name) in $slots" v-slot:[name]="raw">
-            <slot :name="name" v-bind="raw" />
-        </template>
-        <template #icon v-if="ctrl">
-            <div class="ctrls">
-                <Icon type="up-a" ctrl class="up" @click="updateModelValue(normValue(modelValue) + step)" />
-                <Icon type="d-r" ctrl class="down" @click="updateModelValue(normValue(modelValue) - step)" />
-            </div>
-        </template>
-    </UIText>
+  <UIText
+    :key="key"
+    class="number ready"
+    :class="{ ctrl }"
+    type="number"
+    :right="right"
+    :modelValue="tempValue"
+    :placeholder="placeholder"
+    @update:modelValue="updateTempValue"
+    :other="{ min, max, step }"
+    @blur="blurHandler"
+    :readonly="!inInput"
+  >
+    <template v-for="(slot, name) in $slots" v-slot:[name]="raw">
+      <slot :name="name" v-bind="raw" />
+    </template>
+    <template #icon v-if="ctrl">
+      <div class="ctrls">
+        <Icon
+          type="up-a"
+          ctrl
+          class="up"
+          @click="updateModelValue(normValue(modelValue) + step)"
+        />
+        <Icon
+          type="d-r"
+          ctrl
+          class="down"
+          @click="updateModelValue(normValue(modelValue) - step)"
+        />
+      </div>
+    </template>
+  </UIText>
 </template>
 
 <script setup>
-import UIText from './text'
-import { numberPropsDesc } from './state'
-import { computed, watchEffect, ref } from 'vue'
-import { toRawType } from '../../utils'
-import Icon from '../icon'
+import UIText from "./text";
+import { numberPropsDesc } from "./state";
+import { computed, watchEffect, ref } from "vue";
+import { toRawType } from "../../utils";
+import Icon from "../icon";
 
-const emit = defineEmits(['update:modelValue'])
-const props = defineProps(numberPropsDesc)
+const emit = defineEmits(["update:modelValue"]);
+const props = defineProps(numberPropsDesc);
 
-const isNumber = raw => !(toRawType(raw) === 'Number' ? isNaN(raw) : isNaN(Number(raw)))
-const tempValue = ref(props.modelValue)
+const isNumber = (raw) =>
+  !(toRawType(raw) === "Number" ? isNaN(raw) : isNaN(Number(raw)));
+const tempValue = ref(props.modelValue);
 
 watchEffect(() => {
-    tempValue.value = props.modelValue
-})
-const updateTempValue = val => {
-    tempValue.value = val
-    const tval = Number(val)
-    if (!isNaN(tval) && tval !== props.modelValue) {
-        updateModelValue(tval)
-    }
-}
+  tempValue.value = props.modelValue;
+});
+const updateTempValue = (val) => {
+  tempValue.value = val;
+  const tval = Number(val);
+  if (!isNaN(tval) && tval !== props.modelValue) {
+    updateModelValue(tval);
+  }
+};
 
-const key = ref(0)
+const key = ref(0);
 const blurHandler = () => {
-    if (props.modelValue) {
-        tempValue.value = props.modelValue.toString()
-    }
-    key.value++
-}
+  if (props.modelValue) {
+    tempValue.value = props.modelValue.toString();
+  }
+  key.value++;
+};
 
-const normValue = val => {
-    val = Number(val)
-    if (isNaN(val)) {
-        return props.min || 0
-    } else {
-        return val
-    }
-}
+const normValue = (val) => {
+  val = Number(val);
+  if (isNaN(val)) {
+    return props.min || 0;
+  } else {
+    return val;
+  }
+};
 
-const updateModelValue = val => {
-    val = normValue(val)
-    if (isNumber(props.min)) {
-        let min = Number(props.min)
-        val = val < min ? min : val
-    }
-    if (isNumber(props.max)) {
-        let max = Number(props.max)
-        val = val > max ? max : val
-    }
-    emit('update:modelValue', val)
-}
+const updateModelValue = (val) => {
+  val = normValue(val);
+  if (isNumber(props.min)) {
+    let min = Number(props.min);
+    val = val < min ? min : val;
+  }
+  if (isNumber(props.max)) {
+    let max = Number(props.max);
+    val = val > max ? max : val;
+  }
+  emit("update:modelValue", val);
+};
 </script>

+ 3 - 1
src/components/bill-ui/utils/index.js

@@ -168,7 +168,9 @@ export const os = (function () {
     }
 })()
 
-export const inRevise = (raw1, raw2) => _inRevise(raw1, raw2, new Set())
+export const inRevise = (raw1, raw2) => {
+    return _inRevise(raw1, raw2, new Set())
+}
 
 export * from './dom'
 export * from './zindex'

+ 2 - 2
src/components/path/list.vue

@@ -1,12 +1,12 @@
 <template>
   <template v-for="(path, index) in paths" :key="path.id">
-    <Sign
+    <!-- <Sign
       v-if="getPathIsShow(path) && path.points.length"
       @delete="deletePath(path)"
       @changePosition="(data: any) => updatePosition(index, data)"
       :path="path"
       :key="path.id"
-    />
+    /> -->
   </template>
 </template>
 

+ 25 - 48
src/components/path/sign.vue

@@ -1,5 +1,5 @@
 <template>
-  <template v-if="show">
+  <!-- <template v-if="show">
     <div :style="linePixel">
       {{ path.name }}
     </div>
@@ -8,16 +8,14 @@
         {{ path.points[i].name }}
       </span>
     </template>
-  </template>
+</template> -->
 </template>
 
 <script lang="ts" setup>
 import { computed, ref, watch, watchEffect } from "vue";
-import { router, RoutesName } from "@/router";
 import { sdk } from "@/sdk";
 
 import type { Path } from "@/store";
-import { usePixel, usePixels } from "@/hook/use-pixel";
 
 export type SignProps = { path: Path };
 
@@ -25,44 +23,47 @@ const props = defineProps<SignProps>();
 const emit = defineEmits<{
   (e: "delete"): void;
   (e: "updatePoints", val: Path["points"]): void;
-  (e: "updateLinePosition", val: SceneLocalPos): void;
 }>();
 
+const getLineProps = () => ({
+  width: props.path.lineWidth,
+  color: props.path.lineColor,
+  altitudeAboveGround: props.path.lineAltitudeAboveGround,
+  position: props.path.linePosition?.loc || props.path.points[0].position.loc,
+  normal: props.path.linePosition?.normal || { x: 0, y: 0, z: 1 },
+  modelId: props.path.linePosition?.modelId || props.path.points[0].position.modelId,
+});
+
 const path = sdk.createPath({
-  line: {
-    width: props.path.lineWidth,
-    color: props.path.lineColor,
-    altitudeAboveGround: props.path.lineAltitudeAboveGround,
-    position: props.path.linePosition?.loc || props.path.points[0].position.loc,
-    modelId: props.path.linePosition?.modelId || props.path.points[0].position.modelId,
-  },
+  name: props.path.name,
+  showName: props.path.showName,
+  fontSize: props.path.fontSize,
+  showDirection: props.path.showDirection,
+  reverseDirection: props.path.reverseDirection,
+  line: getLineProps(),
   points: props.path.points.map((item) => ({
+    name: item.name,
     position: item.position.loc,
     modelId: item.position.modelId,
   })),
 });
 
-watchEffect(() => {
-  path.changeCanEdit(router.currentRoute.value.name === RoutesName.paths);
-});
-
 watch(
-  () => ({
-    width: props.path.lineWidth,
-    color: props.path.lineColor,
-    altitudeAboveGround: props.path.lineAltitudeAboveGround,
-    position: props.path.linePosition?.loc || props.path.points[0].position.loc,
-    modelId: props.path.linePosition?.modelId || props.path.points[0].position.modelId,
-  }),
+  getLineProps,
   (val) => {
     path.changeLine(val);
   },
   { deep: true }
 );
+watchEffect(() => path.visibilityName(props.path.showName));
 
 const toCameraDistance = ref(path.toCameraDistance);
 path.bus.on("toCameraDistanceChange", (v) => (toCameraDistance.value = v));
-const show = computed(() => props.path.globalVisibility || toCameraDistance.value <= 30);
+const show = computed(
+  () =>
+    props.path.globalVisibility ||
+    toCameraDistance.value <= Math.pow(props.path.visibilityRange, 2)
+);
 watchEffect(() => path.visibility(show.value));
 
 path.bus.on("changePoints", (points) => {
@@ -82,30 +83,6 @@ path.bus.on("changePoints", (points) => {
     }))
   );
 });
-
-const isHover = ref(false);
-path.bus.on("enter", () => {
-  isHover.value = true;
-});
-path.bus.on("leave", () => {
-  isHover.value = false;
-});
-
-const [linePixel] = usePixel(() => ({
-  localPos: props.path.linePosition?.loc || props.path.points[0].position.loc,
-  modelId: props.path.linePosition?.modelId || props.path.points[0].position.modelId,
-}));
-
-const [pointPixels, pathPoints] = usePixels(() => {
-  return props.path.points.map((item) => ({
-    localPos: item.position.loc,
-    modelId: item.position.modelId,
-  }));
-});
-
-path.bus.on("lineTopPositionChange", (pos) => {
-  emit("updateLinePosition", pos);
-});
 </script>
 
 <style lang="scss" scoped></style>

+ 10 - 5
src/components/tagging/list.vue

@@ -5,6 +5,7 @@
       @delete="deletePosition(pos)"
       @changePosition="(data: any) => updatePosition(index, data)"
       :tagging="tagging"
+      :ref="(node: any) => nodes[index] = ({ node, id: pos.id })"
       :scene-pos="pos"
       :key="pos.id"
     />
@@ -12,14 +13,16 @@
 </template>
 
 <script lang="ts" setup>
-import { computed } from "vue";
+import { computed, reactive } from "vue";
 import Sign from "./sign-new.vue";
 import { getTaggingPositions, taggingPositions, getTaggingPositionIsShow } from "@/store";
 
 import type { Tagging, TaggingPosition } from "@/store";
+import { TaggingNode } from "@/sdk";
 
 const props = defineProps<{ tagging: Tagging }>();
 const positions = computed(() => getTaggingPositions(props.tagging));
+const nodes = reactive([]) as TaggingNode;
 
 const deletePosition = (pos: TaggingPosition) => {
   const index = taggingPositions.value.indexOf(pos);
@@ -27,10 +30,12 @@ const deletePosition = (pos: TaggingPosition) => {
 };
 const updatePosition = (
   ndx: number,
-  data: { pos: SceneLocalPos; modelId: string; normal: SceneLocalPos }
+  data: { position: SceneLocalPos; modelId: string; normal: SceneLocalPos }
 ) => {
-  taggingPositions.value[ndx].localPos = data.pos;
-  taggingPositions.value[ndx].modelId = data.modelId;
-  taggingPositions.value[ndx].normal = data.normal;
+  positions.value[ndx].localPos = data.position;
+  positions.value[ndx].modelId = data.modelId;
+  positions.value[ndx].normal = data.normal;
 };
+
+defineExpose(nodes);
 </script>

+ 52 - 24
src/components/tagging/sign-new.vue

@@ -20,10 +20,7 @@
           :in-full="true"
           @pull="(index) => (pullIndex = index)"
         />
-        <div
-          class="edit-hot"
-          v-if="router.currentRoute.value.name === RoutesName.tagging"
-        >
+        <div class="edit-hot" v-if="showDelete">
           <span @click="$emit('delete')" class="fun-ctrl">
             <ui-icon type="del" />
             删除
@@ -42,19 +39,19 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onUnmounted, ref, watch, watchEffect } from "vue";
-import { router, RoutesName } from "@/router";
+import { computed, markRaw, onUnmounted, ref, watch, watchEffect } from "vue";
 import UIBubble from "bill/components/bubble/index.vue";
 import Images from "@/views/tagging/images.vue";
-import Preview, { MediaType } from "../static-preview/index.vue";
+import Preview from "../static-preview/index.vue";
 import { getTaggingStyle } from "@/store";
 import { getFileUrl } from "@/utils";
-import { sdk } from "@/sdk";
+import { sdk, TaggingPositionNode } from "@/sdk";
 import { custom, getResource } from "@/env";
 
 import type { Tagging, TaggingPosition } from "@/store";
 import { useCameraChange, usePixel } from "@/hook/use-pixel";
 import { useViewStack } from "@/hook";
+import { inRevise } from "bill/utils";
 
 export type SignProps = { tagging: Tagging; scenePos: TaggingPosition; show?: boolean };
 
@@ -63,7 +60,7 @@ const emit = defineEmits<{
   (e: "delete"): void;
   (
     e: "changePosition",
-    val: { pos: SceneLocalPos; modelId: string; normal: SceneLocalPos }
+    val: { position: SceneLocalPos; modelId: string; normal: SceneLocalPos }
   ): void;
 }>();
 
@@ -75,22 +72,27 @@ const queryItems = computed(() =>
   }))
 );
 const taggingStyle = computed(() => getTaggingStyle(props.tagging.styleId));
-const tag = sdk.createTagging({
-  ...props.scenePos,
-  title: props.tagging.title,
-  position: props.scenePos.localPos,
-  canMove: false,
-  image: getFileUrl(taggingStyle.value!.icon),
-});
+const tag = markRaw(
+  sdk.createTagging({
+    ...props.scenePos,
+    title: props.tagging.title,
+    position: props.scenePos.localPos,
+    canMove: false,
+    image: getFileUrl(taggingStyle.value!.icon),
+  })
+) as TaggingPositionNode;
+const showDelete = ref(false);
+tag.showDelete = (show) => {
+  showDelete.value = show;
+};
+
+tag.changeCanMove(false);
 
 const changePos = () => {
   pos.value = { localPos: tag.getImageCenter(), modelId: props.scenePos.modelId };
 };
 
 watch(taggingStyle, (icon) => icon && tag.changeImage(getFileUrl(icon.icon)));
-watchEffect(() => {
-  tag.changeCanMove(router.currentRoute.value.name === RoutesName.tagging);
-});
 watchEffect(() => tag.changeMat(props.scenePos.mat));
 watchEffect(() => tag.changeFontSize(props.scenePos.fontSize));
 watchEffect(() => {
@@ -103,6 +105,35 @@ watchEffect(() => {
   tag.changeType(props.scenePos.type);
   changePos();
 });
+const getPosition = () => ({
+  position: props.scenePos.localPos,
+  normal: props.scenePos.normal,
+  modelId: props.scenePos.modelId,
+});
+let currentPosition = getPosition();
+let changeTimeout: any;
+tag.bus.on("changePosition", (data) => {
+  clearTimeout(changeTimeout);
+  emit(
+    "changePosition",
+    (currentPosition = {
+      position: { ...data.pos },
+      normal: { ...data.normal },
+      modelId: data.modelId,
+    })
+  );
+  changePos();
+});
+
+watch(getPosition, (p) => {
+  changeTimeout = setTimeout(() => {
+    if (inRevise(p, currentPosition)) {
+      console.log("更改当前位置");
+      tag.changePosition(p);
+      currentPosition = p;
+    }
+  }, 16);
+});
 
 const [toCameraDistance] = useCameraChange(() => {
   return tag.getCameraDisSquared && tag.getCameraDisSquared();
@@ -114,11 +145,6 @@ const show = computed(
 );
 watchEffect(() => tag.visibility(show.value));
 
-tag.bus.on("changePosition", (data) => {
-  emit("changePosition", data);
-  changePos();
-});
-
 const isHover = ref(false);
 tag.bus.on("enter", () => {
   isHover.value = true;
@@ -165,6 +191,8 @@ const iconClickHandler = () => {
 onUnmounted(() => {
   tag.destory();
 });
+
+defineExpose(tag);
 </script>
 
 <style lang="scss" scoped>

+ 2 - 0
src/layout/edit/fuse-edit.vue

@@ -33,6 +33,7 @@ import {
   initialGuides,
   initialMeasures,
   fuseModelsLoaded,
+  initialPaths,
 } from "@/store";
 
 import Header from "./header/index.vue";
@@ -45,6 +46,7 @@ const initialSys = async () => {
     initialTaggingStyles(),
     initialTaggings(),
     initialGuides(),
+    initialPaths(),
     initialMeasures(),
   ]);
   await loadModel(fuseModel);

+ 2 - 0
src/layout/show/index.vue

@@ -41,6 +41,7 @@ import {
   scenes,
   fuseModels,
   appEl,
+  initialPaths,
 } from "@/store";
 
 const hasSingle = new URLSearchParams(location.search).has("single");
@@ -57,6 +58,7 @@ const initialSys = async () => {
     initialTaggingStyles(),
     initialTaggings(),
     initialGuides(),
+    initialPaths(),
   ]);
   await initialMeasures();
   await loadModel(fuseModel);

+ 1 - 1
src/sdk/association/guide.ts

@@ -174,5 +174,5 @@ export const playSceneGuide = async (
 
 export const pauseSceneGuide = () => {
   isScenePlayIng.value = ScenePlayIngEnum.stop;
-  pauseRecovery && pauseRecovery();
+  pauseRecovery! && pauseRecovery();
 };

+ 87 - 17
src/sdk/association/tagging.ts

@@ -1,28 +1,98 @@
-import { toRaw } from 'vue'
-import { 
-  mount, 
-  diffArrayChange, 
-  shallowWatchArray, } from '@/utils'
-import { taggings, Tagging } from '@/store'
-import TaggingComponent from '@/components/tagging/list.vue'
-import { SDK } from '../sdk'
+import { reactive, toRaw } from "vue";
+import { mount, diffArrayChange, shallowWatchArray } from "@/utils";
+import { taggings, Tagging, TaggingPosition, taggingPositions } from "@/store";
+import TaggingComponent from "@/components/tagging/list.vue";
+import { SDK, Tagging3D } from "../sdk";
+import { groupProxy } from "@/store/group";
 
+// -----------------标签关联--------------------
+export type TaggingPositionNode = Tagging3D & { showDelete: (show: boolean) => void };
+export type TaggingNode = {
+  node: TaggingPositionNode;
+  id: TaggingPosition["id"];
+}[];
 
+const taggingNodes = reactive(new Map<Tagging, TaggingNode>());
 
-// -----------------标签关联--------------------
+export const getTaggingNode = (
+  tagging: Tagging | Tagging["id"]
+): undefined | TaggingNode => {
+  if (typeof tagging !== "object") {
+    tagging = taggings.value.find((item) => item.id === tagging)!;
+    if (!tagging) return void 0;
+  }
+  return taggingNodes.get(tagging);
+};
+
+export const getTaggingPosNode = (
+  pos: TaggingPosition | TaggingPosition["id"]
+): undefined | TaggingPositionNode => {
+  if (typeof pos !== "object") {
+    pos = taggingPositions.value.find((item) => item.id === pos)!;
+    if (!pos) return void 0;
+  }
+  const taggingNodeItem = getTaggingNode(pos.taggingId);
+  if (!taggingNodeItem) return void 0;
+
+  const taggingPositionNodeItem = taggingNodeItem.find(
+    (item) => item.id === pos.id
+  );
+  if (taggingPositionNodeItem) {
+    return taggingPositionNodeItem.node;
+  }
+};
+
+export const taggingsGroup = groupProxy(() => {
+  const nodes = [] as TaggingPositionNode[];
+  for (const tagging of taggings.value) {
+    const node = getTaggingNode(tagging);
+    if (node) {
+      nodes.push(...node.map(({ node }) => node));
+    }
+  }
+  return nodes;
+});
+
+export const taggingGroup = (tagging: Tagging | Tagging["id"]) => {
+  return groupProxy(() => {
+    const nodes = [] as TaggingPositionNode[];
+    if (typeof tagging !== "object") {
+      tagging = taggings.value.find((item) => item.id === tagging)!;
+      if (tagging) {
+        const node = getTaggingNode(tagging);
+        if (node) {
+          nodes.push(...node.map(({ node }) => node));
+        }
+      }
+    }
+    return nodes;
+  });
+};
 
 export const associationTaggings = (sdk: SDK, el: HTMLDivElement) => {
-  const getTaggings = () => taggings.value
-  const taggingVMs = new WeakMap<Tagging, ReturnType<typeof mount>>()
+  const getTaggings = () => taggings.value;
+  const taggingVMs = new WeakMap<Tagging, ReturnType<typeof mount>>();
 
   shallowWatchArray(getTaggings, (taggings, oldTaggings) => {
-    const { added, deleted } = diffArrayChange(taggings, oldTaggings)
+    const { added, deleted } = diffArrayChange(taggings, oldTaggings);
     for (const item of added) {
-      taggingVMs.set(toRaw(item), mount(el, TaggingComponent, { tagging: item }))
+      const unMount = mount<TaggingNode>(
+        el,
+        TaggingComponent,
+        { tagging: item },
+        (nodes) => {
+          taggingNodes.set(item, nodes);
+        }
+      );
+
+      taggingVMs.set(toRaw(item), () => {
+        unMount();
+        taggingNodes.delete(item);
+      });
     }
     for (const item of deleted) {
-      const unMount = taggingVMs.get(toRaw(item))
-      unMount && unMount()
+      const unMount = taggingVMs.get(toRaw(item));
+      unMount && unMount();
     }
-  })
-}
+  });
+};

+ 24 - 3
src/sdk/sdk.ts

@@ -201,14 +201,27 @@ export interface SDK {
 }
 
 export type PathProps = {
+  // 线段名称
+  name: string,
+  // 是否显示名称,
+  showName: boolean,
+  // 文字大小
+  fontSize: number,
+  // 是否显示方向,
+  showDirection: boolean,
+  // 方向是否反向
+  reverseDirection: boolean,
   line: {
     width: number,
     color: string,
     altitudeAboveGround: number
     position: SceneLocalPos,
+    normal: SceneLocalPos,
     modelId: string
   },
   points: {
+    // 点位名称
+    name: string,
     position: SceneLocalPos,
     modelId: string,
   }[]
@@ -230,12 +243,12 @@ export type Path = {
   }>;
   // 是否可编辑
   changeCanEdit: (canMove: boolean) => void
-  // 标注可见性
+  // 可见性
   visibility: (visibility: boolean) => void
+  // 气泡是否可见
+  visibilityName: (visibility: boolean) => void
   // 更改标题气泡属性
   changeLine: (props: Partial<PathProps['line']>) => void
-  // 顶标注中心像素位置 
-  lineTopPosition: ScreenLocalPos
   // 距离相机位置
   toCameraDistance: number
   // 线段销毁
@@ -276,6 +289,14 @@ export type Tagging3D = {
     // 位置变更
     changePosition: {pos: SceneLocalPos, modelId: string, normal: SceneLocalPos}
   }>;
+  changePosition: (position: { 
+    modelId: string,
+    // 贴地射线获取的位置
+    position: SceneLocalPos
+    normal: SceneLocalPos
+  }) => {
+
+  }
   changeFontSize: (fontSize: number) => void
   changeLineHeight: (lineHeight: number) => void
   // 设置标题

+ 38 - 0
src/store/group.ts

@@ -0,0 +1,38 @@
+export type GroupProxy<T extends object> = {
+  [key in keyof T]: T[key] extends (...args: infer args) => infer res
+    ? (...args: args) => res[]
+    : T[key][];
+};
+
+export const groupProxy = <T extends object>(
+  getter: () => T[]
+): GroupProxy<T> => {
+  return new Proxy(
+    {},
+    {
+      get(_, key) {
+        const group = getter();
+        const res = group.map((item: any) => item[key]) as any[];
+        if (!res.length || typeof res[0] !== "function") {
+          return res;
+        }
+
+        return function (...args: any[]) {
+          const funRes = [] as any[];
+          for (let i = 0; i < group.length; i++) {
+            const fun = res[i];
+            funRes[i] = fun.apply(group[i], args);
+          }
+          return funRes;
+        };
+      },
+      set(_, key, val) {
+        const group = getter() as any[];
+        for (const item of group) {
+          item[key] = val
+        }
+        return true
+      },
+    }
+  ) as GroupProxy<T>;
+};

+ 6 - 2
src/store/path.ts

@@ -43,10 +43,14 @@ export const createPath = (path: Partial<Path> = {}): Path => {
   return {
     id: createTemploraryID(),
     lineColor: 'ff0000',
+    lineAltitudeAboveGround: 10,
+    reverseDirection: false,
     lineWidth: 3,
     name: '',
     fontSize: 1,
-    autoAdjust: false,
+    showDirection: false,
+    showName: true,
+    visibilityRange: 30,
     globalVisibility: false,
     points: [],
     ...path
@@ -63,7 +67,7 @@ export const backupPaths = () => {
   }))
 }
 
-export const initialPath = fetchStoreItems(paths, fetchPaths, backupPaths)
+export const initialPaths = fetchStoreItems(paths, fetchPaths, backupPaths)
 export const recoverPaths = recoverStoreItems(paths, () => bcPaths)
 export const addPath = addStoreItem(paths, postAddPath)
 export const deletePath = deleteStoreItem(paths, path => postDeletePath(path.id))

+ 1 - 1
src/store/sys.ts

@@ -128,7 +128,7 @@ export const autoSetModeCallback = <T extends object>(current: T, setting: AutoS
 
   return () => {
     setting.backup && setting.backup()
-    return watch(current as UnwrapRef<T>, handler, { deep: true })
+    return watch(current as any, handler, { deep: true })
   }
 }
 

+ 0 - 1
src/store/tagging-positions.ts

@@ -44,7 +44,6 @@ export const createTaggingPosition = (position: Partial<TaggingPosition> = {}):
 export const getTaggingPositions = (tagging: Tagging) => 
   taggingPositions.value.filter(position => position.taggingId === tagging.id) || []
 
-
 export const getTaggingPositionIsShow = (position: TaggingPosition) => {
     const model = getFuseModel(position.modelId)
     return custom.showTaggings && model && model.loaded && getFuseModelShowVariable(model).value

+ 5 - 4
src/utils/mount.ts

@@ -2,15 +2,16 @@ import { reactive, markRaw } from "vue";
 
 import type { Component } from "vue";
 
-export type MInfo = [HTMLDivElement, Component, Record<string, any> | undefined]
+export type MInfo<T = any> = [HTMLDivElement, Component, Record<string, any> | undefined, T]
 export const needMounts: MInfo[] = reactive([])
 
-export const mount = (
+export const mount = <T>(
   to: HTMLDivElement,
   Component: Component,
-  props?: Record<string, any>
+  props?: Record<string, any>,
+  ref?: (data: T) => void
 ) => {
-  const info = [to, Component, props] as MInfo
+  const info = [to, Component, props, ref] as MInfo
   markRaw(Component)
   needMounts.push(info)
 

+ 0 - 1
src/views/guide/edit-paths.vue

@@ -145,7 +145,6 @@ useAutoSetMode(
         .filter((path) => !oldPaths.includes(path))
         .concat(paths.value);
       if (isTemploraryID(props.data.id)) {
-        console.error("现在才保存?");
         guides.value.push(props.data);
       }
     },

+ 67 - 0
src/views/guide/guide/edit.vue

@@ -0,0 +1,67 @@
+<template>
+  <ui-group>
+    <template #header>
+      <ui-button @click="edit(createGuide())">
+        <ui-icon type="add" />
+        新增
+      </ui-button>
+    </template>
+  </ui-group>
+  <ui-group>
+    <GuideSign
+      v-for="guide in guides"
+      :key="guide.id"
+      :guide="guide"
+      @edit="edit(guide)"
+      @delete="deleteGuide(guide)"
+    />
+  </ui-group>
+  <Teleport to="#layout-app">
+    <ui-editor-toolbar :toolbar="!!currentGuide" class="video-toolbar">
+      <EditPaths :data="currentGuide" v-if="currentGuide" />
+    </ui-editor-toolbar>
+  </Teleport>
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import GuideSign from "./sign.vue";
+import EditPaths from "./edit-paths.vue";
+import { useViewStack } from "@/hook";
+import {
+  guides,
+  createGuide,
+  enterEdit,
+  sysBus,
+  autoSaveGuides,
+  enterOld,
+} from "@/store";
+
+import type { Guide } from "@/store";
+
+const currentGuide = ref<Guide | null>();
+const leaveEdit = () => (currentGuide.value = null);
+const edit = (guide: Guide) => {
+  currentGuide.value = guide;
+  enterEdit();
+  sysBus.on("leave", leaveEdit);
+};
+
+const deleteGuide = (guide: Guide) => {
+  const index = guides.value.indexOf(guide);
+  guides.value.splice(index, 1);
+};
+
+useViewStack(autoSaveGuides);
+</script>
+
+<style lang="scss" scoped>
+.video-toolbar {
+  height: auto;
+  display: block;
+}
+
+.guide-list {
+  padding-bottom: 30px;
+}
+</style>

+ 28 - 0
src/views/guide/guide/show.vue

@@ -0,0 +1,28 @@
+<template>
+  <ui-group title="路径列表" class="show-guides">
+    <GuideSign 
+      v-for="guide in guides" 
+      :key="guide.id" 
+      :guide="guide" 
+      :edit="false"
+    />
+  </ui-group>
+</template>
+
+<script setup lang="ts">
+import GuideSign from '@/views/guide/sign.vue'
+import { guides } from '@/store'
+
+</script>
+
+
+<style lang="scss">
+.show-guides.ui-group {
+   h3.group-title {
+    margin-bottom: 0;
+  }
+  .sign-guide:first-child {
+    border-top: none;
+  }
+}
+</style>

src/views/guide/sign.vue → src/views/guide/guide/sign.vue


+ 58 - 56
src/views/guide/index.vue

@@ -1,71 +1,73 @@
 <template>
   <RightFillPano>
     <template #header>
-      <ui-group borderBottom>
-        <template #header>
-          <ui-button @click="edit(createGuide())">
-            <ui-icon type="add" />
-            新增 
-          </ui-button>
-        </template>
-      </ui-group>
+      <div class="tabs" :class="{ disabled: isEdit }">
+        <span
+          v-for="tab in tabs"
+          :key="tab.key"
+          :class="{ active: tab.key === current }"
+          @click="current = tab.key"
+        >
+          {{ tab.text }}
+        </span>
+      </div>
     </template>
-    <ui-group title="路径列表" class="guide-list">
-      <GuideSign 
-        v-for="guide in guides" 
-        :key="guide.id" 
-        :guide="guide" 
-        @edit="edit(guide)"
-        @delete="deleteGuide(guide)"
-      />
-    </ui-group>
+    <GuideEdit v-if="current === 'guide'" />
+    <PathEdit v-if="current === 'path'" />
   </RightFillPano>
-
-  <ui-editor-toolbar :toolbar="!!currentGuide" class="video-toolbar">
-    <EditPaths :data="currentGuide" v-if="currentGuide" />
-  </ui-editor-toolbar>
 </template>
 
 <script lang="ts" setup>
-import { RightFillPano } from '@/layout'
-import { ref } from 'vue';
-import GuideSign from './sign.vue'
-import EditPaths from './edit-paths.vue'
-import { useViewStack } from '@/hook'
-import { 
-  guides, 
-  createGuide, 
-  enterEdit, 
-  sysBus, 
-  autoSaveGuides
-} from '@/store'
+import { RightFillPano } from "@/layout";
+import GuideEdit from "./guide/edit.vue";
+import PathEdit from "./path/edit.vue";
+import { ref } from "vue";
+import { isEdit } from "@/store";
 
-import type { Guide } from '@/store'
+const current = ref("path");
+const tabs = [
+  { key: "guide", text: "导览" },
+  { key: "path", text: "路线" },
+];
+</script>
 
-const currentGuide = ref<Guide | null>()
-const leaveEdit = () => currentGuide.value = null
-const edit = (guide: Guide) => {
-  currentGuide.value = guide
-  enterEdit()
-  sysBus.on('leave', leaveEdit)
-}
+<style lang="scss" scoped>
+.tabs {
+  height: 60px;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
+  display: flex;
+  margin: -20px;
+  margin-bottom: 20px;
 
-const deleteGuide = (guide: Guide) => {
-  const index = guides.value.indexOf(guide)
-  guides.value.splice(index, 1)
-}
+  > span {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+    transition: color 0.3s ease;
+    cursor: pointer;
+    font-size: 16px;
 
-useViewStack(autoSaveGuides)
-</script>
+    &::after {
+      content: "";
+      transition: height 0.3s ease;
+      position: absolute;
+      background-color: #00c8af;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      height: 0;
+    }
 
-<style lang="scss" scoped>
-.video-toolbar {
-  height: auto;
-  display: block;
-}
+    &:hover,
+    &.active {
+      color: #00c8af;
+    }
 
-.guide-list {
-  padding-bottom: 30px;
+    &.active::after {
+      height: 3px;
+    }
+  }
 }
-
-</style>
+</style>

+ 185 - 0
src/views/guide/path/edit-path.vue

@@ -0,0 +1,185 @@
+<template>
+  <Teleport to="#layout-app">
+    <RightFillPano class="edit-path-point">
+      <ui-group :title="`${isTemploraryID(data.id) ? '创建' : '编辑'}路线`" borderBottom>
+        <ui-group-option class="item">
+          <span class="label">路径名称</span>
+          <span class="oper"> <Switch v-model:checked="data.showName" /> </span>
+        </ui-group-option>
+        <ui-group-option class="item">
+          <ui-input
+            width="100%"
+            type="text"
+            placeholder="路径名称"
+            v-model="data.name"
+            :maxlength="60"
+          />
+        </ui-group-option>
+        <ui-group-option class="item">
+          <span class="label">路径粗细</span>
+          <span class="oper">
+            <InputNumber
+              style="width: 120px"
+              v-model:value="data.lineWidth"
+              :max="100"
+              :min="1"
+              :controls="false"
+            >
+              <template #addonBefore>
+                <span
+                  class="fun-ctrl"
+                  @click="data.lineWidth = Math.max(1, data.lineWidth - 1)"
+                  >-</span
+                >
+              </template>
+              <template #addonAfter>
+                <span
+                  class="fun-ctrl"
+                  @click="data.lineWidth = Math.min(100, data.lineWidth + 1)"
+                  >+</span
+                >
+              </template>
+            </InputNumber>
+          </span>
+        </ui-group-option>
+        <ui-group-option class="item">
+          <span class="label">路径颜色</span>
+          <span class="oper">
+            <ui-input type="color" width="24px" height="24px" v-model="data.lineColor" />
+          </span>
+        </ui-group-option>
+        <ui-group-option class="item">
+          <span class="label">路径箭头</span>
+          <span class="oper">
+            <Switch v-model:checked="data.showDirection" />
+          </span>
+        </ui-group-option>
+        <ui-group-option class="item" v-if="data.showDirection">
+          <span class="label">箭头反向</span>
+          <span class="oper"> <Switch v-model:checked="data.reverseDirection" /> </span>
+        </ui-group-option>
+      </ui-group>
+      <ui-group borderBottom>
+        <ui-group-option>
+          <span>文字大小</span>
+          <Slider v-model:value="data.fontSize" :min="12" :max="60" :step="0.1" />
+        </ui-group-option>
+      </ui-group>
+      <ui-group borderBottom>
+        <ui-group-option>
+          <SignItem
+            label="可见范围"
+            v-if="!data.globalVisibility"
+            @apply-global="$emit('applyGlobal', 'visibilityRange')"
+          >
+            <Slider
+              v-model:value="data.visibilityRange"
+              :min="1"
+              :max="1000"
+              :step="0.1"
+            />
+          </SignItem>
+        </ui-group-option>
+
+        <ui-group-option>
+          <SignItem @apply-global="$emit('applyGlobal', 'globalVisibility')">
+            <template v-slot:label>
+              <ui-input
+                type="checkbox"
+                label="全部范围可见"
+                :modelValue="!!data.globalVisibility"
+                @update:modelValue="(v: boolean) => data.globalVisibility = v"
+              />
+            </template>
+          </SignItem>
+        </ui-group-option>
+      </ui-group>
+      <Button block type="primary" ghost size="large"> 预览路径 </Button>
+    </RightFillPano>
+
+    <span
+      @click="unKeepAdding ? unKeepAdding() : keepAdding()"
+      class="pin-position strengthen fun-ctrl"
+    >
+      <ui-icon
+        :style="{ color: unKeepAdding ? 'var(--color-main-normal)' : 'currentColor' }"
+        type="pin1"
+        size="22px"
+      />
+    </span>
+  </Teleport>
+</template>
+
+<script setup lang="ts">
+import {
+  showLeftCtrlPanoStack,
+  showLeftPanoStack,
+  showRightCtrlPanoStack,
+  showRightPanoStack,
+} from "@/env";
+import { onUnmounted, ref, shallowRef } from "vue";
+import { isTemploraryID, Path } from "@/store";
+import { RightFillPano } from "@/layout";
+import { useViewStack } from "@/hook";
+import { togetherCallback } from "@/utils";
+import { Switch, Slider, Button, InputNumber } from "ant-design-vue";
+import SignItem from "@/views/tagging-position/sign-item.vue";
+import { Message } from "bill/expose-common";
+
+const props = defineProps<{ data: Path }>();
+
+defineEmits<{
+  (e: "applyGlobal", k: string | string[]): void;
+}>();
+
+let unKeepAdding = shallowRef<() => void>();
+const keepAdding = () => {
+  unKeepAdding.value && unKeepAdding.value();
+  const hide = Message.show({ msg: "请在模型上单击选择路径点位置", type: "warning" });
+
+  unKeepAdding.value = () => {
+    hide();
+    unKeepAdding.value = void 0;
+  };
+};
+onUnmounted(() => unKeepAdding.value && unKeepAdding.value());
+
+useViewStack(() =>
+  togetherCallback([
+    showRightPanoStack.push(ref(false)),
+    showLeftCtrlPanoStack.push(ref(false)),
+    showLeftPanoStack.push(ref(false)),
+    showRightCtrlPanoStack.push(ref(false)),
+  ])
+);
+</script>
+
+<style lang="scss" scoped>
+.edit-path-point {
+  position: absolute;
+  right: 0;
+  height: 100%;
+  top: 0;
+  --editor-menu-right: 0px;
+}
+
+.item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.pin-position {
+  position: absolute;
+  left: 50%;
+  transform: translate(-50%);
+  width: 64px;
+  height: 64px;
+  background: rgba(27, 27, 28, 0.8);
+  border-radius: 50%;
+  bottom: 20px;
+  z-index: 9;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 77 - 0
src/views/guide/path/edit.vue

@@ -0,0 +1,77 @@
+<template>
+  <ui-group>
+    <template #header>
+      <ui-button @click="edit()">
+        <ui-icon type="add" />
+        新增
+      </ui-button>
+    </template>
+  </ui-group>
+  <ui-group>
+    <PathSign
+      v-for="path in paths"
+      :key="path.id"
+      :path="path"
+      @edit="edit(path)"
+      @delete="deletePath(path)"
+    />
+  </ui-group>
+  <EditPath :data="currentPath" v-if="currentPath" @applyGlobal="applyGlobal" />
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import PathSign from "./sign.vue";
+import EditPath from "./edit-path.vue";
+import { useViewStack } from "@/hook";
+import { paths, enterEdit, sysBus, autoSavePaths, createPath, enterOld } from "@/store";
+
+import type { Path } from "@/store";
+import { Dialog } from "bill/expose-common";
+
+const currentPath = ref<Path | null>();
+const leaveEdit = () => (currentPath.value = null);
+const edit = (path?: Path) => {
+  if (!path) {
+    path = createPath();
+    paths.value.unshift(path);
+  }
+  currentPath.value = path;
+  enterEdit();
+  sysBus.on("leave", leaveEdit);
+};
+
+const deletePath = (path: Path) => {
+  const index = paths.value.indexOf(path);
+  paths.value.splice(index, 1);
+};
+const applyGlobal = async (keys: string | string[]) => {
+  if (!(await Dialog.confirm("确定要将此属性应用到所有位置?"))) return;
+  keys = Array.isArray(keys) ? keys : [keys];
+  for (const current of paths.value!) {
+    let val: any = current;
+    let newVal: any = currentPath.value;
+    for (let i = 0; i < keys.length; i++) {
+      if (i === keys.length - 1) {
+        val[keys[i]] = newVal[keys[i]];
+      } else {
+        val = val[keys[i]];
+        newVal = newVal[keys[i]];
+      }
+    }
+  }
+};
+
+useViewStack(autoSavePaths);
+</script>
+
+<style lang="scss" scoped>
+.video-toolbar {
+  height: auto;
+  display: block;
+}
+
+.guide-list {
+  padding-bottom: 30px;
+}
+</style>

+ 107 - 0
src/views/guide/path/sign.vue

@@ -0,0 +1,107 @@
+<template>
+  <ui-group-option class="sign-guide">
+    <div class="info">
+      <div class="guide-cover">
+        <span class="img"></span>
+        <!-- @click="playSceneGuide(paths, undefined, true)" -->
+        <ui-icon type="preview" class="icon" ctrl v-if="path.points.length" />
+      </div>
+      <div>
+        <p>{{ path.name }}</p>
+      </div>
+    </div>
+    <div class="actions" v-if="edit">
+      <ui-more
+        :options="menus"
+        style="margin-left: 20px"
+        @click="(action: keyof typeof actions) => actions[action]()"
+      />
+    </div>
+  </ui-group-option>
+</template>
+
+<script setup lang="ts">
+import { Path } from "@/store";
+import { playSceneGuide } from "@/sdk";
+
+const props = withDefaults(defineProps<{ path: Path; edit?: boolean }>(), {
+  edit: true,
+});
+
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "edit"): void;
+}>();
+
+const menus = [
+  { label: "编辑", value: "edit" },
+  { label: "删除", value: "delete" },
+];
+const actions = {
+  edit: () => emit("edit"),
+  delete: () => emit("delete"),
+};
+</script>
+
+<style lang="scss" scoped>
+.sign-guide {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 0;
+  border-bottom: 1px solid var(--colors-border-color);
+  &:first-child {
+    border-top: 1px solid var(--colors-border-color);
+  }
+
+  .info {
+    flex: 1;
+
+    display: flex;
+    align-items: center;
+
+    .guide-cover {
+      position: relative;
+      &::after {
+        content: "";
+        position: absolute;
+        inset: 0;
+        background: rgba(0, 0, 0, 0.2);
+      }
+
+      .icon {
+        position: absolute;
+        z-index: 1;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        font-size: 16px;
+      }
+
+      .img {
+        width: 48px;
+        height: 48px;
+        object-fit: cover;
+        border-radius: 4px;
+        overflow: hidden;
+        background-color: rgba(255, 255, 255, 0.6);
+        display: block;
+      }
+    }
+
+    div {
+      margin-left: 10px;
+
+      p {
+        color: #fff;
+        font-size: 14px;
+        margin-bottom: 6px;
+      }
+    }
+  }
+
+  .actions {
+    flex: none;
+  }
+}
+</style>

+ 2 - 23
src/views/guide/show.vue

@@ -1,28 +1,7 @@
 <template>
-  <ui-group title="路径列表" class="show-guides">
-    <GuideSign 
-      v-for="guide in guides" 
-      :key="guide.id" 
-      :guide="guide" 
-      :edit="false"
-    />
-  </ui-group>
+  <Guide />
 </template>
 
 <script setup lang="ts">
-import GuideSign from '@/views/guide/sign.vue'
-import { guides } from '@/store'
-
+import Guide from "./guide/show.vue";
 </script>
-
-
-<style lang="scss">
-.show-guides.ui-group {
-   h3.group-title {
-    margin-bottom: 0;
-  }
-  .sign-guide:first-child {
-    border-top: none;
-  }
-}
-</style>

+ 69 - 54
src/views/record/index.vue

@@ -6,15 +6,15 @@
       </div>
     </template>
 
-    <ui-group title="全部视频" class="tree" >
+    <ui-group title="全部视频" class="tree">
       <Draggable :list="records" draggable=".sign" itemKey="id">
         <template #item="{ element: record }">
-          <Sign 
-            :record="getSignRecord(record)" 
-            :key="record.id" 
+          <Sign
+            :record="getSignRecord(record)"
+            :key="record.id"
             @delete="deleteRecord(record)"
-            @updateTitle="title => record.title = title"
-            @updateCover="cover => record.cover = cover"
+            @updateTitle="(title: string) => (record.title = title)"
+            @updateCover="(cover: string) => (record.cover = cover)"
           />
         </template>
       </Draggable>
@@ -23,70 +23,85 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from 'vue'
-import { showMeasuresStack, showTaggingsStack } from '@/env'
-import { useViewStack } from '@/hook'
-import { diffArrayChange, togetherCallback } from '@/utils'
-import { RecordProcess } from './help'
-import { records, createRecord, Record, RecordStatus, autoSaveRecords, initialRecords, getRecordFragmentBlobs, isTemploraryID, initialTaggingStyles } from '@/store'
-import { RightFillPano } from '@/layout'
-import Draggable from 'vuedraggable'
-import Sign from './sign.vue'
-import { Dialog } from 'bill/index'
-import { initialTaggings } from '@/store/tagging'
-import { initialMeasures } from '@/store/measure'
-import { initialGuides } from '@/store/guide'
+import { ref, watch } from "vue";
+import { showMeasuresStack, showTaggingsStack } from "@/env";
+import { useViewStack } from "@/hook";
+import { diffArrayChange, togetherCallback } from "@/utils";
+import { RecordProcess } from "./help";
+import {
+  records,
+  createRecord,
+  Record,
+  RecordStatus,
+  autoSaveRecords,
+  initialRecords,
+  getRecordFragmentBlobs,
+  isTemploraryID,
+  initialTaggingStyles,
+  initialPaths,
+} from "@/store";
+import { RightFillPano } from "@/layout";
+import Draggable from "vuedraggable";
+import Sign from "./sign.vue";
+import { Dialog } from "bill/index";
+import { initialTaggings } from "@/store/tagging";
+import { initialMeasures } from "@/store/measure";
+import { initialGuides } from "@/store/guide";
 
-initialRecords()
-initialTaggingStyles()
-initialTaggings()
-initialMeasures()
-initialGuides()
+initialRecords();
+initialTaggingStyles();
+initialTaggings();
+initialMeasures();
+initialGuides();
+initialPaths();
 
-const start = () => records.value.push(createRecord())
+const start = () => records.value.push(createRecord());
 const deleteRecord = async (record: Record) => {
-  const isTemp = getRecordFragmentBlobs(record).length === 0 && isTemploraryID(record.id)
-  if (isTemp || await Dialog.confirm('确定要删除视频吗?')) {
-    const index = records.value.indexOf(record)
+  const isTemp = getRecordFragmentBlobs(record).length === 0 && isTemploraryID(record.id);
+  if (isTemp || (await Dialog.confirm("确定要删除视频吗?"))) {
+    const index = records.value.indexOf(record);
     if (~index) {
-      records.value.splice(index, 1)
+      records.value.splice(index, 1);
     }
   }
-}
+};
 
 const getSignRecord = (record: Record): RecordProcess => ({
   ...record,
-  immediately: record.status === RecordStatus.UN
-})
+  immediately: record.status === RecordStatus.UN,
+});
 
 const setOptions = [
-  { value: 'tagging', label: '标签' },
-  { value: 'measure', label: '测量' },
-] as const
+  { value: "tagging", label: "标签" },
+  { value: "measure", label: "测量" },
+] as const;
 
-type SetKey = typeof setOptions[number]['value']
-const setting = ref<SetKey[]>(['tagging', 'measure'])
-watch(setting, (setting, oldSetting = [], onCleanup) => {
-  const { added } = diffArrayChange(setting, oldSetting)
-  const pops = added.map(value => {
-    if (value === 'measure') {
-      return showMeasuresStack.push(ref(true))
-    } else {
-      return showTaggingsStack.push(ref(true))
-    }
-  })
-  onCleanup(togetherCallback(pops))
-}, { flush: 'sync' })
+type SetKey = typeof setOptions[number]["value"];
+const setting = ref<SetKey[]>(["tagging", "measure"]);
+watch(
+  setting,
+  (setting, oldSetting = [], onCleanup) => {
+    const { added } = diffArrayChange(setting, oldSetting);
+    const pops = added.map((value) => {
+      if (value === "measure") {
+        return showMeasuresStack.push(ref(true));
+      } else {
+        return showTaggingsStack.push(ref(true));
+      }
+    });
+    onCleanup(togetherCallback(pops));
+  },
+  { flush: "sync" }
+);
 
 useViewStack(() => {
   // const pop = showTaggingsStack.push(ref(false))
   return () => {
-    setting.value = []
+    setting.value = [];
     // pop()
-  }
-})
-useViewStack(autoSaveRecords)
+  };
+});
+useViewStack(autoSaveRecords);
 </script>
 
-<style lang="scss" src="./style.scss" scoped>
-</style>
+<style lang="scss" src="./style.scss" scoped></style>

+ 12 - 12
src/views/tagging-position/sign.vue

@@ -46,7 +46,7 @@
         class="item"
         @apply-global="$emit('applyGlobal', 'fontSize')"
       >
-        <Slider v-model:value="position.fontSize" :min="0" :max="360" :step="0.1" />
+        <Slider v-model:value="position.fontSize" :min="12" :max="60" :step="0.1" />
       </SignItem>
       <SignItem
         label="引线高度"
@@ -55,16 +55,6 @@
       >
         <Slider v-model:value="position.lineHeight" :min="0" :max="10" :step="0.1" />
       </SignItem>
-      <SignItem class="item" @apply-global="$emit('applyGlobal', 'globalVisibility')">
-        <template v-slot:label>
-          <ui-input
-            type="checkbox"
-            label="全部范围可见"
-            :modelValue="!!position.globalVisibility"
-            @update:modelValue="(v: boolean) => position.globalVisibility = v"
-          />
-        </template>
-      </SignItem>
       <SignItem
         label="可见范围"
         class="item"
@@ -73,11 +63,21 @@
       >
         <Slider
           v-model:value="position.visibilityRange"
-          :min="10"
+          :min="1"
           :max="1000"
           :step="0.1"
         />
       </SignItem>
+      <SignItem class="item" @apply-global="$emit('applyGlobal', 'globalVisibility')">
+        <template v-slot:label>
+          <ui-input
+            type="checkbox"
+            label="全部范围可见"
+            :modelValue="!!position.globalVisibility"
+            @update:modelValue="(v: boolean) => position.globalVisibility = v"
+          />
+        </template>
+      </SignItem>
       <Button block type="primary" danger ghost size="large" @click="$emit('delete')">
         删除
       </Button>

+ 2 - 0
src/views/tagging/edit.vue

@@ -163,6 +163,8 @@ import {
 import { styleTypes } from "@/api";
 import { selectMaterials } from "@/components/materials/quisk";
 import { getFileName } from "@/utils";
+import { taggingsGroup } from "@/sdk";
+import { useViewStack } from "@/hook";
 
 export type EditProps = {
   data: Tagging;

+ 17 - 2
src/views/tagging/index.vue

@@ -62,7 +62,7 @@ import TagingSign from "./sign.vue";
 import StyleTypeSelect from "./style-type-select.vue";
 import { RightFillPano } from "@/layout";
 import { useViewStack } from "@/hook";
-import { computed, ref } from "vue";
+import { computed, ref, watchEffect } from "vue";
 import { router, RoutesName } from "@/router";
 import { custom } from "@/env";
 import {
@@ -78,6 +78,7 @@ import {
   getTagging,
   TaggingStyle,
 } from "@/store";
+import { taggingsGroup } from "@/sdk";
 
 const showSearch = ref(false);
 const type = ref<TaggingStyle["typeId"]>(-1);
@@ -124,7 +125,21 @@ const fixedTagging = async (tagging: Tagging) => {
 };
 
 const selectTagging = ref<Tagging | null>(null);
-useViewStack(autoSaveTaggings);
+useViewStack(() => {
+  const stopAuth = autoSaveTaggings();
+  const stop = watchEffect((onCleanup) => {
+    taggingsGroup.changeCanMove(true);
+    taggingsGroup.showDelete(true);
+    onCleanup(() => {
+      taggingsGroup.changeCanMove(false);
+      taggingsGroup.showDelete(false);
+    });
+  });
+  return () => {
+    stop();
+    stopAuth();
+  };
+});
 </script>
 
 <style scoped>

+ 15 - 13
src/views/tagging/style-type-select.vue

@@ -48,19 +48,21 @@ const getTypeCount = (item: any) => {
 };
 
 const getItems = (types = styleTypes): any => {
-  return types.map((item) => {
-    let count = 0;
-    if (props.count) {
-      count = getTypeCount(item);
-    }
-    return {
-      label: item.name + (props.count ? ` (${count}) ` : ""),
-      title: item.name,
-      count,
-      key: item.id,
-      children: "children" in item ? getItems(item.children) : null,
-    };
-  });
+  return types
+    .map((item) => {
+      let count = 0;
+      if (props.count) {
+        count = getTypeCount(item);
+      }
+      return {
+        label: item.name + (props.count ? ` (${count}) ` : ""),
+        title: item.name,
+        count,
+        key: item.id,
+        children: "children" in item ? getItems(item.children) : null,
+      };
+    })
+    .filter((item) => !props.count || item.count);
 };
 const getCurrentItem = (type: number, all = items.value): any => {
   for (const item of all) {