浏览代码

Merge branch 'v1.9.0-jm' of http://192.168.0.115:3000/bill/fuse-code into v1.9.0-jm

xzw 8 月之前
父节点
当前提交
6d691039d0

二进制
public/favicon.ico


+ 4 - 4
src/api/constant.ts

@@ -109,7 +109,7 @@ export const FLODER_LIST = `${namespace}/caseFiles/allList`
 export const UPLOAD_FILE = `${namespace}/upload/file`
 
 // 素材库分页
-export const MATERIAL_PAG = `${namespace}/material/allList`
-export const ADD_MATERIAL = `${namespace}/material/add`
-export const DEL_MATERIAL = `${namespace}/material/del`
-export const MATERIAL_GROUP_LIST = `${namespace}/material/allList`
+export const MATERIAL_PAG = `/service/manage/dictFile/pageList/media-library`
+export const ADD_MATERIAL = `/service/manage/common/upload/fileNew`
+export const DEL_MATERIAL = `/service/manage/dictFile/del/media-library`
+export const MATERIAL_GROUP_LIST = `/service/manage/dict/getByKey/media-library`

+ 9 - 2
src/api/fuse-model.ts

@@ -47,9 +47,13 @@ interface ServiceFuseModel {
   sceneData: Scene;
 }
 
+export const uploadMaterialToModel = async (uploadId: number) => {
+  const model = await axios.post<{modelId: number}>('/fusion/model/addByMediaLibrary', {caseId: params.caseId, uploadId})
+  return model
+}
+
 export const getSceneUrl = (sceneData: Scene) => {
   let url: any = [""];
-  console.log(sceneData, sceneData.type);
   if (
     [SceneType.SWSS, SceneType.SWYDSS].includes(sceneData.type)
   ) {
@@ -60,7 +64,7 @@ export const getSceneUrl = (sceneData: Scene) => {
         url = sceneData.model3dgsUrl;
         break;
       case "shp":
-        url = sceneData.modelShpUrl;
+        url = sceneData.modelGlbUrl;
         break;
       default:
         url = sceneData.modelGlbUrl;
@@ -68,6 +72,9 @@ export const getSceneUrl = (sceneData: Scene) => {
     try {
       url = JSON.parse(url);
     } catch (e) {
+      if (typeof url === 'string') {
+        url = [url]
+      }
       console.error(url, e);
     }
   }

+ 2 - 2
src/api/instance.ts

@@ -3,7 +3,7 @@ import { Message } from "bill/index";
 import { showLoad, hideLoad } from "@/utils";
 import * as URL from "./constant";
 import { ResCode, ResCodeDesc } from "./constant";
-import { appBackRoot, appType, baseURL, params } from "@/env";
+import { baseURL, params } from "@/env";
 import GAxios from "axios";
 
 const instance = axiosFactory();
@@ -60,7 +60,7 @@ addResErrorHandler((response, data) => {
     if (data.code === ResCode.TOKEN_INVALID) {
       gotoLogin();
     } else {
-      Message.error(msg);
+      Message.error(msg || '服务出现异常,请稍后再试');
     }
   }
 });

+ 66 - 17
src/api/material.ts

@@ -1,8 +1,35 @@
 import { asyncTimeout, jsonToForm } from "@/utils";
 import { PagingRequest, PagingResult } from ".";
-import { ADD_MATERIAL, DEL_MATERIAL, MATERIAL_GROUP_LIST, MATERIAL_PAG, UPLOAD_HEADS } from "./constant";
+import {
+  ADD_MATERIAL,
+  DEL_MATERIAL,
+  MATERIAL_GROUP_LIST,
+  MATERIAL_PAG,
+  UPLOAD_HEADS,
+} from "./constant";
 import axios from "./instance";
 
+type ServiceMaterialGroup = {
+  dictKey: string;
+  dictName: string;
+  id: number;
+};
+type ServiceMaterial = {
+  createTime: string;
+  dictId: number;
+  dictName: string;
+  fileFormat: string;
+  fileName: string;
+  fileSize: string;
+  fileType: string;
+  fileUrl: string;
+  id: number;
+  name: string;
+  newFileName: string;
+  typeKey: string;
+  updateTime: string;
+  uploadId: number;
+};
 export type MaterialGroup = {
   id: number;
   name: string;
@@ -16,35 +43,57 @@ export type Material = {
   size: number;
   groupId: number;
   group: string;
-  modelId?: number
+  uploadId?: number;
+  modelId?: number;
 };
 
-export type MaterialPageProps = PagingRequest<Partial<Material> & {groupIds: number[]}>;
+export type MaterialPageProps = PagingRequest<
+  Partial<Material> & { groupIds: number[], formats: string[] }
+>;
 export const fetchMaterialPage = async (params: MaterialPageProps) => {
-  const material = await axios.get<PagingResult<Material[]>>(MATERIAL_PAG, { params })
-  return material
+  //
+  const material = await axios.post<PagingResult<ServiceMaterial[]>>(MATERIAL_PAG, {
+    pageNum: params.pageNum,
+    pageSize: params.pageSize,
+    dictIds: params.groupIds,
+    fileFormats: params.formats
+  });
+  const nm = {
+    ...material,
+    list: material.list.map((item): Material => ({
+      id: item.id,
+      name: item.fileName,
+      format: item.fileFormat,
+      url: item.fileUrl,
+      size: Number(item.fileSize),
+      groupId: item.dictId,
+      group: item.dictName,
+      uploadId: item.uploadId
+    }))
+  }
+  
+  return nm;
 };
 
 export const fetchMaterialGroups = async () => {
-  await asyncTimeout(160)
-  const groups: MaterialGroup[] = [
-    {id: 1, name: '分组1'},
-    {id: 2, name: '分组2'},
-  ]
-  return groups
-  // return axios.get<MaterialGroup[]>(MATERIAL_GROUP_LIST);
+  return (await axios.get<ServiceMaterialGroup[]>(MATERIAL_GROUP_LIST)).map(
+    (item) => ({
+      name: item.dictName,
+      id: item.id,
+    })
+  ) as MaterialGroup[];
 };
 
 export const addMaterial = (file: File) => {
-  console.log(file)
   return axios<string>({
     method: "POST",
     url: ADD_MATERIAL,
     data: jsonToForm({ file }),
     headers: { ...UPLOAD_HEADS },
   });
-}
+  
+};
 
-export const delMaterial = (id: Material['id']) => {
-  return axios.post(DEL_MATERIAL, { id })
-}
+export const delMaterial = (id: Material["id"]) => {
+  return axios.post(DEL_MATERIAL, { id });
+};

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

@@ -0,0 +1,3 @@
+.ant-modal-root .model-header .header-desc span {
+  color: #26559B;
+}

+ 0 - 1
src/assets/style/criminal.less

@@ -1,5 +1,4 @@
 .ant-modal-root {
-  @import 'ant-design-vue/dist/antd.less';
   @primary-color: #26559B;
   @menu-item-active-bg: #E6F7FF;
   @table-selected-row-bg: #E6F7FF;

+ 22 - 22
src/components/bill-ui/components/bubble/index.vue

@@ -1,31 +1,31 @@
 <template>
-    <transition name="fade">
-        <div class="bubble" :class="{ [type]: true, [level]: true }" v-if="show" @click.stop>
-            <div class="bubble-layer">
-                <div class="bubble-arr"></div>
-                <slot />
-            </div>
-        </div>
-    </transition>
+  <transition name="fade">
+    <div class="bubble" :class="{ [type]: true, [level]: true }" v-if="show" @click.stop>
+      <div class="bubble-layer">
+        <div class="bubble-arr"></div>
+        <slot />
+      </div>
+    </div>
+  </transition>
 </template>
 
 <script setup>
 defineProps({
-    type: {
-        type: String,
-        default: 'right',
-    },
-    show: {
-        type: Boolean,
-        default: true,
-    },
-    level: {
-        type: String,
-        require: false,
-    },
-})
+  type: {
+    type: String,
+    default: "right",
+  },
+  show: {
+    type: Boolean,
+    default: true,
+  },
+  level: {
+    type: String,
+    require: false,
+  },
+});
 </script>
 
 <script>
-export default { name: 'ui-bubble' }
+export default { name: "ui-bubble" };
 </script>

+ 17 - 10
src/components/materials/index.vue

@@ -21,7 +21,7 @@
               class="input"
               :accept="ft"
               :maxSize="maxSize"
-              @update:modelValue="addMaterial"
+              @update:modelValue="(file: File) => uploadHandler(file)"
               type="file"
             >
               <template v-slot:replace>
@@ -58,7 +58,7 @@
             </template>
             <template v-else-if="column.key === 'action'">
               <span>
-                <a @click="delHandler(column.id)">删除</a>
+                <a @click="delHandler(record.id)">删除</a>
               </span>
             </template>
           </template>
@@ -113,7 +113,12 @@ const ft = computed(() => {
 });
 
 const Search = Input.Search;
-const params = reactive({ pageNum: 1, pageSize: 12 }) as MaterialPageProps;
+const params = reactive({
+  pageNum: 1,
+  pageSize: 12,
+  groupIds: [],
+  formats: props.format,
+}) as MaterialPageProps;
 const origin = ref<PagingResult<Material[]>>({
   list: [],
   pageNum: 1,
@@ -138,12 +143,6 @@ const rowSelection: any = ref({
     }
   },
   getCheckboxProps: (record: Material) => {
-    console.log(
-      props.format && !props.format.includes(record.format),
-      props.maxSize && record.size > props.maxSize,
-      props.maxSize,
-      record.size
-    );
     return {
       disabled:
         (props.format && !props.format.includes(record.format)) ||
@@ -153,22 +152,24 @@ const rowSelection: any = ref({
 });
 const cloumns = computed(() => [
   {
-    width: "400px",
     title: "名称",
     dataIndex: "name",
     key: "name",
   },
   {
+    width: "100px",
     title: "格式",
     dataIndex: "format",
     key: "format",
   },
   {
+    width: "100px",
     title: "大小",
     dataIndex: "size",
     key: "size",
   },
   {
+    width: "100px",
     title: "分组",
     dataIndex: "group",
     key: "group",
@@ -196,6 +197,11 @@ const refresh = debounceStack(
   160
 );
 
+const uploadHandler = async (file: File) => {
+  await addMaterial(file);
+  refresh();
+};
+
 watch(params, refresh, { immediate: true, deep: true });
 
 const addHandler = async (file: File) => {
@@ -204,6 +210,7 @@ const addHandler = async (file: File) => {
 };
 const delHandler = async (id: Material["id"]) => {
   if (await Dialog.confirm("确定要删除此数据吗?")) {
+    console.error(id);
     await delMaterial(id);
     refresh();
   }

+ 33 - 18
src/components/static-preview/resource.vue

@@ -1,21 +1,29 @@
 <template>
-  <video v-if="type === MetaType.video" controls autoplay playsinline webkit-playsinline>
-    <source :src="url" />
-  </video>
-  <iframe v-else-if="type === MetaType.other" :src="url"></iframe>
-  <iframe
-    v-else-if="type === MetaType.xfile"
-    :src="`./xfile-viewer/index.html?file=${url}&time=${Date.now()}`"
-  ></iframe>
-  <img :src="url" v-if="type === MetaType.image" />
-  <audio
-    :src="url"
-    v-if="type === MetaType.audio"
-    controls
-    autoplay
-    playsinline
-    webkit-playsinline
-  />
+  <div>
+    <video
+      v-if="type === MetaType.video"
+      controls
+      autoplay
+      playsinline
+      webkit-playsinline
+    >
+      <source :src="url" />
+    </video>
+    <iframe v-else-if="type === MetaType.other" :src="url"></iframe>
+    <iframe
+      v-else-if="type === MetaType.xfile"
+      :src="`./xfile-viewer/index.html?file=${url}&time=${Date.now()}`"
+    ></iframe>
+    <img :src="url" v-if="type === MetaType.image" />
+    <audio
+      :src="url"
+      v-if="type === MetaType.audio"
+      controls
+      autoplay
+      playsinline
+      webkit-playsinline
+    />
+  </div>
 </template>
 
 <script lang="ts" setup>
@@ -44,6 +52,13 @@ const type = computed(() => {
 </script>
 
 <style scoped>
+div {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
 audio,
 iframe {
   width: 100%;
@@ -56,7 +71,7 @@ img {
   max-width: 100%;
   max-height: 100%;
   display: block;
-  object-fit: contain;
+  object-fit: cover;
 }
 
 iframe {

+ 27 - 2
src/components/tagging/sign-new.vue

@@ -7,8 +7,24 @@
     @mouseleave="isHover = false"
   >
     <div @click.stop>
-      <UIBubble class="hot-bubble pc" :show="showContent" type="left" level="center">
-        <h2>{{ tagging.title }}</h2>
+      <UIBubble
+        class="hot-bubble pc"
+        :show="showContent"
+        type="left"
+        level="center"
+        @click.stop
+        @pointerdown.stop
+        @pointerup.stop
+      >
+        <h2>
+          {{ tagging.title }}
+          <ui-audio
+            v-if="tagging.audio"
+            class="audio"
+            :src="getResource(getFileUrl(tagging.audio))"
+            ref="audio"
+          />
+        </h2>
         <div class="content">
           <p><span>特征描述:</span>{{ tagging.desc }}</p>
           <p><span>遗留部位:</span>{{ tagging.part }}</p>
@@ -63,6 +79,13 @@ const emit = defineEmits<{
   ): void;
 }>();
 
+const audio = ref();
+watchEffect(() => {
+  audio.value && console.error("准备好了!,");
+  if (props.show && audio.value) {
+    audio.value.play();
+  }
+});
 const [posStyle, pos, pause, recovery] = usePixel(() => undefined);
 
 const queryItems = computed(() =>
@@ -223,6 +246,8 @@ defineExpose(tag);
       margin-bottom: 10px;
       color: #ffffff;
       position: relative;
+      display: flex;
+      justify-content: space-between;
     }
 
     .content {

+ 6 - 0
src/components/tagging/sign.vue

@@ -18,6 +18,12 @@
     <div @click.stop>
       <UIBubble class="hot-bubble pc" :show="showContent" type="left" level="center">
         <h2>{{ tagging.title }}</h2>
+        <ui-audio
+          v-if="tagging.audio"
+          class="audio"
+          :src="getResource(getFileUrl(tagging.audio))"
+          ref="audio"
+        />
         <div class="content">
           <p><span>特征描述:</span>{{ tagging.desc }}</p>
           <p><span>遗留部位:</span>{{ tagging.part }}</p>

+ 3 - 23
src/env/index.ts

@@ -1,7 +1,7 @@
 import { stackFactory, flatStacksValue, strToParams } from '@/utils'
 import { reactive, ref } from 'vue'
 
-import type { FuseModel, TaggingPosition, View } from '@/store'
+import type { FuseModel, Path, TaggingPosition, View } from '@/store'
 export const namespace = '/fusion'
 export const viewModeStack = stackFactory(ref<'full' | 'auto'>('auto'))
 export const showToolbarStack = stackFactory(ref<boolean>(false))
@@ -15,6 +15,7 @@ export const showBottomBarStack = stackFactory(ref<boolean>(false), true)
 export const bottomBarHeightStack = stackFactory(ref<string>('60px'))
 export const showTaggingsStack = stackFactory(ref<boolean>(true))
 export const showPathsStack = stackFactory(ref<boolean>(true))
+export const showPathStack = stackFactory(ref<Path['id']>())
 export const showMeasuresStack = stackFactory(ref<boolean>(true))
 export const currentModelStack = stackFactory(ref<FuseModel | null>(null))
 export const showModelsMapStack = stackFactory(ref<WeakMap<FuseModel, boolean>>(new WeakMap()), true)
@@ -31,6 +32,7 @@ export const custom = flatStacksValue({
   shwoRightCtrlPano: showRightCtrlPanoStack,
   showTaggings: showTaggingsStack,
   showPaths: showPathsStack,
+  showPath: showPathStack,
   showMeasures: showMeasuresStack,
   currentModel: currentModelStack,
   showModelsMap: showModelsMapStack,
@@ -48,27 +50,6 @@ export const params = reactive(strToParams(location.search)) as unknown as Param
 params.caseId = Number(params.caseId)
 params.share = Boolean(Number(params.share))
 params.single = Boolean(Number(params.single))
-export enum appType {
-  fire = "1",
-  xmfire = "3",
-  criminal = "2",
-  police = "4",
-
-}
-export const appStyleImport = {
-  [appType.fire]: () => import('../assets/style/fire.less'),
-  [appType.xmfire]: () => import('../assets/style/fire.less'),
-  [appType.criminal]: () => import('../assets/style/criminal.less'),
-  [appType.police]: () => import('../assets/style/criminal.less'),
-}
-export const appBackRoot = {
-  [appType.fire]: "/fire",
-  [appType.xmfire]: "/xmfire",
-  [appType.criminal]: "/criminal",
-  [appType.police]: "/criminal"
-}
-export const routeIncludeFire = (type: appType) => 
-  [appType.fire, appType.xmfire].includes(type)
 
 export type Params = { 
   caseId: number,
@@ -77,7 +58,6 @@ export type Params = {
   m?: string
   share?: boolean,
   single?: boolean
-  app: appType
   token?: string
 }
 

+ 5 - 1
src/layout/edit/fuse-left-pano.vue

@@ -1,6 +1,10 @@
 <template>
   <LeftPano>
-    <ModelList :can-change="custom.modelsChangeStore" @delete-model="modelDelete" @click-model="modelChangeSelect">
+    <ModelList
+      :can-change="custom.modelsChangeStore"
+      @delete-model="modelDelete"
+      @click-model="modelChangeSelect"
+    >
       <template #action v-if="custom.modelsChangeStore">
         <SelectModel>
           <ui-icon ctrl type="add" />

+ 18 - 3
src/layout/edit/fuse-switch.vue

@@ -1,7 +1,9 @@
 <template>
   <SlideMenu />
-  <!-- <Header></Header> -->
-  <ModelList />
+  <ModelList v-if="!showSceneList" />
+  <LeftPano v-else>
+    <SceneList :current="currentModel" @update:current="loadModel" />
+  </LeftPano>
 
   <router-view v-slot="{ Component }">
     <!-- <keep-alive> -->
@@ -12,6 +14,19 @@
 
 <script lang="ts" setup>
 import SlideMenu from "./fuse-slide-menu.vue";
-import Header from "./header/index.vue";
+import SceneList from "../scene-list/index.vue";
+import { LeftPano } from "@/layout";
 import ModelList from "./fuse-left-pano.vue";
+import { computed, watch, watchEffect } from "vue";
+import router from "@/router";
+import { currentModel, fuseModel, loadModel } from "@/model";
+
+const showSceneList = computed(
+  () => router.currentRoute.value.meta.left === "scene-list"
+);
+watch(showSceneList, (n, o) => {
+  if (!n && o) {
+    loadModel(fuseModel);
+  }
+});
 </script>

+ 11 - 3
src/layout/edit/scene-select.vue

@@ -53,7 +53,7 @@
     </div>
   </Modal>
 
-  <div>
+  <div class="slot-layout">
     <Dropdown placement="bottom">
       <slot></slot>
       <template #overlay>
@@ -89,7 +89,7 @@ import {
   initialScenes,
 } from "@/store";
 
-import type { Scene } from "@/api";
+import { uploadMaterialToModel, type Scene } from "@/api";
 import { getSceneModel } from "@/sdk";
 import { selectMaterials } from "@/components/materials/quisk";
 
@@ -203,7 +203,9 @@ const selectModel = async () => {
     maxSize: 2 * 1024 * 1024 * 1024,
   });
   if (!list?.length) return;
-  const modelIds = list
+
+  const modelList = await Promise.all(list.filter(item => item.uploadId).map(item => uploadMaterialToModel(item.uploadId!)))
+  const modelIds = modelList
     .map((item) => item.modelId!)
     .filter(
       (modelId) => modelId && !fuseModels.value.some((model) => model.modelId === modelId)
@@ -224,12 +226,18 @@ const selectModel = async () => {
   max-height: 500px;
   overflow-y: auto;
 }
+.slot-layout {
+  display: flex;
+  align-items: center;
+  height: 100%;
+}
 </style>
 
 <style lang="less">
 .model-header .header-desc {
   margin-bottom: 0;
 }
+
 .ant-modal-root .ant-table-tbody > tr > td {
   word-break: break-all;
 }

+ 1 - 1
src/layout/left-pano.vue

@@ -41,7 +41,7 @@ import { custom } from "@/env";
   border-radius: 0 6px 6px 0;
   top: 50%;
   transform: translateY(-50%);
-  z-index: 1000;
+  z-index: 100;
   display: flex;
   align-items: center;
   justify-content: center;

+ 1 - 1
src/layout/model-list/style.scss

@@ -70,4 +70,4 @@
 
   }
 
-}
+}

+ 30 - 15
src/layout/scene-list/index.vue

@@ -4,9 +4,12 @@
       <slot name="action" />
     </template>
     <template #atom="{ item }">
-      <div v-if="item.raw === fuseModel" @click="updateCurrent(item.raw)">
+      <div
+        v-if="item.raw === fuseModel"
+        @click="updateCurrent(item.raw)"
+        class="all-scene-model-list"
+      >
         <ModelList
-          class="scene-model-list"
           :class="{ active: current === fuseModel }"
           :title="getModelTypeDesc(fuseModel as any)"
           :show-content="showModelList"
@@ -26,18 +29,22 @@
         @click="updateCurrent(item.raw)"
         v-else
       >
-        <p>{{ item.raw.name }}</p>
-        <p>
-          {{ SceneTypeDesc[item.raw.type as SceneType] }}
-          <span
-            style="float: right"
-            v-if="canSync(item as Scene)"
-            @click.stop="sync(item as Scene)"
-            class="fun-ctrl"
-          >
-            同屏勘验
-          </span>
-        </p>
+        <div>
+          <p>{{ item.raw.name }}</p>
+          <p>
+            {{ SceneTypeDesc[item.raw.type as SceneType] }}
+          </p>
+        </div>
+        <Button
+          size="small"
+          type="primary"
+          ghost
+          style="float: right"
+          v-if="canSync(item as Scene) && !voffline"
+          @click.stop="sync(item as Scene)"
+        >
+          同屏勘验
+        </Button>
       </div>
     </template>
   </List>
@@ -56,6 +63,7 @@ import {
 import List from "@/components/list/index.vue";
 import ModelList from "../model-list/index.vue";
 import { fuseModel, getModelTypeDesc } from "@/model";
+import { Button } from "ant-design-vue";
 
 import type { ModelType, FuseModelType } from "@/model";
 import type { Scene } from "@/store";
@@ -64,6 +72,7 @@ const emit = defineEmits<{ (e: "update:current", data: ModelType): void }>();
 const props = defineProps<{ current: ModelType }>();
 const showModelList = ref(true);
 
+const voffline = offline;
 const canSync = (scene: Scene) =>
   [SceneType.SWKK, SceneType.SWKJ, SceneType.SWSSMX, SceneType.SWYDMX].includes(
     scene.raw.type
@@ -116,6 +125,12 @@ const stopWatch = watch(
 
 .scene {
   padding: 0 20px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  > div {
+    flex: 1;
+  }
 
   p {
     height: 1.5em;
@@ -126,7 +141,7 @@ const stopWatch = watch(
   }
 }
 
-.scene-model-list.list {
+.all-scene-model-list .scene-model-list {
   margin-bottom: -20px;
   margin-top: -20px;
 

+ 1 - 2
src/layout/show/slide-menu.vue

@@ -13,11 +13,10 @@
 
 <script lang="ts" setup>
 import { metas, RoutesName, router, getRouteConfig } from "@/router";
-import { views, records, floders } from "@/store";
+import { views, records } from "@/store";
 import { computed } from "vue";
 
 import type { RouteRaw } from "@/router";
-import { params, routeIncludeFire } from "@/env";
 
 export type MenuItem = {
   name: RoutesName;

+ 1 - 2
src/main.ts

@@ -3,7 +3,7 @@ import './style.scss'
 import App from './app.vue'
 import Components from 'bill/index'
 import router from './router'
-import { appStyleImport, appType, params } from '@/env'
+import { params } from '@/env'
 import { addHook, addUnsetTokenURLS, delHook, delUnsetTokenURLS } from '@/api'
 import { currentLayout, RoutesName } from './router';
 import * as URL from '@/api/constant'
@@ -19,7 +19,6 @@ if (import.meta.env.DEV) {
   // import('@/hook/notice')
 }
 
-appStyleImport[params.app] && appStyleImport[params.app]()
 watchEffect((onCleanup) => {
   if ([RoutesName.show, RoutesName.signModel].includes(currentLayout.value!)) {
 

+ 2 - 2
src/model/app.vue

@@ -65,7 +65,7 @@ export const Model = defineComponent({
             [SceneType.SWKJ]: `/swkk/${scene.value.num}/wwwroot/spg.html?m=${scene.value.num}&lang=zh`,
             [SceneType.SWSS]: `/swss/${scene.value.num}/www/offline.html?m=${scene.value.num}&lang=zh`,
             [SceneType.SWSSMX]: `/swkk/${scene.value.num}/wwwroot/spg.html?m=${scene.value.num}&lang=zh`,
-            [SceneType.SWMX]: `offline.html?caseId=${params.caseId}&app=${params.app}&modelId=${scene.value.num}&share=1#sign-model`,
+            [SceneType.SWMX]: `offline.html?caseId=${params.caseId}&modelId=${scene.value.num}&share=1#sign-model`,
             [SceneType.SWYDSS]: `/swss/${scene.value.num}/www/offline.html?m=${scene.value.num}&lang=zh`,
             [SceneType.SWYDMX]: `/swkk/${scene.value.num}/wwwroot/spg.html?m=${scene.value.num}&lang=zh`,
           }
@@ -87,7 +87,7 @@ export const Model = defineComponent({
           [SceneType.SWSSMX]: `/swkk/spg.html?m=${scene.value.num}&toen=${getToken()}`,
           [SceneType.SWMX]: `index.html?caseId=${params.caseId}&modelId=${
             scene.value.num
-          }#sign-model&toen=${getToken()}`,
+          }&toen=${getToken()}#sign-model`,
           [SceneType.SWYDSS]: `/swss/index.html?m=${scene.value.num}&toen=${getToken()}`,
           [SceneType.SWYDMX]: `/swkk/spg.html?m=${scene.value.num}&toen=${getToken()}`,
         });

+ 2 - 1
src/router/config.ts

@@ -1,5 +1,4 @@
 import { RoutesName, paths, metas } from './constant'
-
 export type RouteRaw = (typeof routes)[number]
 export const routes = [
   {
@@ -15,6 +14,7 @@ export const routes = [
           {
             path: paths[RoutesName.view],
             name: RoutesName.view,
+            meta: metas[RoutesName.view],
             component: () => import('@/views/view/index.vue')
           },
           {
@@ -96,6 +96,7 @@ export const routes = [
       {
         path: paths[RoutesName.viewShow],
         name: RoutesName.viewShow,
+        mate: { left: 'scene-list' },
         component: () => import('@/views/view/show.vue')
       },
       {

+ 1 - 0
src/router/constant.ts

@@ -98,6 +98,7 @@ export const metas = {
     sysTitle: "视图提取",
     icon: "nav-setup",
     title: "视图提取",
+    left: 'scene-list'
   },
   [RoutesName.record]: { sysTitle: "屏幕录制" },
   [RoutesName.show]: { sysTitle: "" },

+ 21 - 4
src/sdk/association/path.ts

@@ -2,10 +2,11 @@ import { diffArrayChange, mount, shallowWatchArray } from "@/utils";
 import TaggingComponent from "@/components/path/list.vue";
 import { Path as PathData, paths } from "@/store/path";
 import { sdk, Path, SDK } from "../sdk";
-import { reactive, watch } from "vue";
+import { reactive, ref, watch, watchEffect } from "vue";
 import { groupProxy } from "@/store/group";
 import { isScenePlayRun, pauseScene, playScene } from "@/utils/full";
 import { analysisPose, setPose } from ".";
+import { custom, showPathsStack, showPathStack } from "@/env";
 
 // -----------------导览线关联--------------------
 export type PathNode = Path;
@@ -23,7 +24,7 @@ export const getPathNode = (
   return pathNodes.get(path);
 };
 
-export const taggingsGroup = groupProxy(() => {
+export const pathsGroup = groupProxy(() => {
   const nodes = [] as PathNode[];
   for (const path of paths.value) {
     const node = getPathNode(path);
@@ -34,16 +35,29 @@ export const taggingsGroup = groupProxy(() => {
   return nodes;
 });
 
+watchEffect(() => {
+  pathsGroup.visibility(custom.showPaths);
+  if (custom.showPath) {
+    const node = getPathNode(custom.showPath);
+    node?.visibility(true);
+    node?.fly()
+    console.log('a1', node, custom.showPath)
+  }
+})
 
-export const playScenePath = (
+export const playScenePath = async (
   path: PathData,
   forceFull = false,
 ) => {
   const node = getPathNode(path)
   if (!node) return null;
 
+  showPathsStack.push(ref(false))
+  showPathStack.push(ref(path.id))
+
+  
   let initPose: any;
-  playScene({
+  await playScene({
     play: () => {
       return new Promise(resolve => {
         initPose = analysisPose(sdk.getPose());
@@ -55,6 +69,9 @@ export const playScenePath = (
       node.pause();
     }
   }, forceFull)
+
+  showPathsStack.pop()
+  showPathStack.pop()
 }
 
 export const pauseScenePath = pauseScene

+ 0 - 1
src/store/path.ts

@@ -28,7 +28,6 @@ export const paths = ref<Paths>([])
 export const getPath = (id: Path['id']) => paths.value.find(path => path.id === id)
 
 export const getPathIsShow = (path: Path) => {
-  if (!custom.showPaths) return false;
   const modelIds = path.points.map(item => item.modelId)
   if (path.linePosition?.modelId) {
     modelIds.push(path.linePosition.modelId)

+ 1 - 1
src/store/scene.ts

@@ -62,7 +62,7 @@ export const getSWKKSyncLink = async (scene: Scene) => {
     // domain: location.href,
     // fromMiniApp: "0",
     role: "leader",
-    avatar: userInfo.avatar,
+    avatar: './favicon.ico',
     redirect: encodeURIComponent(location.href),
     name: userInfo.userName,
     m: scene.num,

+ 1 - 1
src/utils/full.ts

@@ -1,4 +1,4 @@
-import { showLeftPanoStack, viewModeStack } from "@/env";
+import { showLeftPanoStack, showPathsStack, showPathStack, viewModeStack } from "@/env";
 import { togetherCallback } from ".";
 import { ref, watch } from "vue";
 import { isEdit, sysBus } from "@/store";

+ 3 - 4
src/views/folder/floder-view.vue

@@ -8,10 +8,9 @@
       class="solid header"
       :class="{ ['root-header']: index === 1 }"
       :style="{ '--index': index }"
+      @click="showChildren = !showChildren"
     >
-      <span @click="showChildren = !showChildren">
-        {{ root.title }}
-      </span>
+      <span> {{ root.title }} </span>
       <ui-icon
         :type="`pull-${showChildren ? 'up' : 'down'}`"
         class="icon"
@@ -48,7 +47,7 @@
 
   <Modal
     v-if="root.modal"
-    width="800px"
+    width="1200px"
     :title="root.title"
     @cancel="showChildren = false"
     :open="showChildren"

+ 20 - 6
src/views/folder/modal-floder-view.vue

@@ -2,16 +2,21 @@
   <ui-group v-if="floders.length">
     <ui-group-option>
       <span @click="canAll && (showAll = !showAll)" :class="{ ['fun-ctrl']: canAll }">
-        {{ root.title }}
         <template v-if="canAll">
           <UpOutlined v-if="showAll" />
           <DownOutlined v-else />
         </template>
+        {{ root.title }}
       </span>
     </ui-group-option>
     <ui-group-option>
       <div class="items">
-        <div class="img-item" v-for="(_, i) in showLen" :key="floders[i].filesId">
+        <div
+          class="img-item"
+          v-for="(_, i) in showLen"
+          :key="floders[i].filesId"
+          :style="{ '--rawLen': samLen }"
+        >
           <div class="img-item-content">
             <img :src="floders[i].filesUrl" @click="clickHandler(floders[i])" />
           </div>
@@ -20,7 +25,7 @@
     </ui-group-option>
   </ui-group>
 
-  <Tabs v-if="!emptyTabs" v-model:activeKey="activeTab">
+  <Tabs v-if="!emptyTabs" v-model:activeKey="activeTab" class="f-tabs">
     <template v-for="children in root.children">
       <TabPane
         :tab="children.title"
@@ -78,7 +83,7 @@ const children = computed(() => {
 });
 const len = computed(() => floders.value.length);
 const showAll = ref(false);
-const samLen = 3;
+const samLen = 6;
 const showLen = computed(() => (showAll.value ? len.value : Math.min(samLen, len.value)));
 const canAll = computed(() => len.value > samLen);
 </script>
@@ -89,8 +94,8 @@ const canAll = computed(() => len.value > samLen);
   flex-wrap: wrap;
 }
 .img-item {
-  width: 33.33%;
-  padding: 5px;
+  width: calc(100% / var(--rawLen));
+  padding-right: 5px;
   .img-item-content {
     padding-top: 56.25%;
     position: relative;
@@ -112,3 +117,12 @@ const canAll = computed(() => len.value > samLen);
   --swiper-navigation-size: 30px;
 }
 </style>
+
+<style>
+.f-tabs.ant-tabs-top > .ant-tabs-nav {
+  margin-bottom: 30px;
+}
+.f-tabs.ant-tabs-top > .ant-tabs-nav::before {
+  display: none !important;
+}
+</style>

+ 1 - 1
src/views/guide/index.vue

@@ -24,7 +24,7 @@ import PathEdit from "./path/edit.vue";
 import { ref } from "vue";
 import { isEdit } from "@/store";
 
-const current = ref("path");
+const current = ref("guide");
 const tabs = [
   { key: "guide", text: "导览" },
   { key: "path", text: "路线" },

+ 10 - 9
src/views/guide/path/edit-path.vue

@@ -114,7 +114,7 @@
           @click="switchPlay"
           v-if="data.points.length"
         >
-          {{ isScenePathPlayIng ? "停止" : "" }} 预览路径
+          {{ isScenePathPlayIng ? "停止" : "" }}预览路径
         </Button>
       </div>
       <div v-if="~activePointNdx">
@@ -174,9 +174,6 @@ import {
   pauseScenePath,
   playScenePath,
 } from "@/sdk/association/path";
-import { nextTick } from "process";
-import path from "path";
-import { sdk, analysisPose, setPose } from "@/sdk";
 
 const props = defineProps<{ data: Path }>();
 
@@ -185,11 +182,15 @@ defineEmits<{
 }>();
 
 const node = computed(() => getPathNode(props.data.id));
-watch(node, () => {
-  if (props.data.points.length) {
-    node.value?.fly();
-  }
-});
+watch(
+  node,
+  () => {
+    if (props.data.points.length) {
+      node.value?.fly();
+    }
+  },
+  { immediate: true }
+);
 
 const activePointNdx = ref(-1);
 watchEffect((onCleanup) => {

+ 11 - 2
src/views/guide/path/edit.vue

@@ -20,17 +20,24 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from "vue";
+import { computed, ref } from "vue";
 import PathSign from "./sign.vue";
 import EditPath from "./edit-path.vue";
+import { getPathNode, pathsGroup } from "@/sdk/association/path";
 import { useViewStack } from "@/hook";
 import { paths, enterEdit, sysBus, autoSavePaths, createPath, enterOld } from "@/store";
 
 import type { Path } from "@/store";
 import { Dialog } from "bill/expose-common";
+import { showPathsStack, showPathStack } from "@/env";
 
 const currentPath = ref<Path | null>();
-const leaveEdit = () => (currentPath.value = null);
+const leaveEdit = () => {
+  currentPath.value = null;
+  pathsGroup.visibility(true);
+  showPathsStack.pop();
+  showPathStack.pop();
+};
 const edit = (path?: Path) => {
   if (!path) {
     path = createPath();
@@ -39,6 +46,8 @@ const edit = (path?: Path) => {
   currentPath.value = path;
   enterEdit();
   sysBus.on("leave", leaveEdit);
+  showPathsStack.push(ref(false));
+  showPathStack.push(computed(() => path.id));
 };
 
 const deletePath = (path: Path) => {

+ 13 - 8
src/views/tagging/edit.vue

@@ -97,7 +97,9 @@
               <ui-icon type="add" /> 支持 mp3/wav 格式,≤30MB
             </p>
             <p v-else class="rep-val">
-              {{ getFileName(tagging.audio) }}
+              <span>
+                {{ getFileName(tagging.audio) }}
+              </span>
               <ui-icon class="icon" @click.stop="tagging.audio = ''" type="del" ctrl />
             </p>
           </template>
@@ -302,14 +304,17 @@ const imageSelect = async () => {
 
 <style lang="scss" scoped>
 .rep-val {
-  width: 100%;
-  position: relative;
-  text-align: center;
+  width: 80%;
+  display: flex;
+  align-items: center;
+  span {
+    flex: 1;
+    text-align: center;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
   .icon {
-    position: absolute;
-    right: 10px;
-    top: 50%;
-    transform: translateY(-50%);
     pointer-events: all;
   }
 }

+ 73 - 70
src/views/tagging/sign.vue

@@ -1,33 +1,30 @@
 <template>
-  <ui-group-option 
-    class="sign-tagging" 
-    :class="{active: selected, edit}" 
+  <ui-group-option
+    class="sign-tagging"
+    :class="{ active: selected, edit }"
     @click="edit && getTaggingIsShow(tagging) && emit('select', true)"
   >
     <div class="info">
-      <img 
-        :src="getResource(getFileUrl(tagging.images[0]))" 
-        v-if="tagging.images.length"
-      >
+      <img :src="getResource(getFileUrl(findImage))" v-if="findImage" />
       <div>
         <p>{{ tagging.title }}</p>
         <span>放置:{{ positions.length }}</span>
       </div>
     </div>
     <div class="actions" @click.stop>
-      <ui-icon 
-         v-if="!edit"
-        type="pin" 
-        ctrl 
-        @click.stop="$emit('select', true)" 
+      <ui-icon
+        v-if="!edit"
+        type="pin"
+        ctrl
+        @click.stop="$emit('select', true)"
         :class="{ disabled: !getTaggingIsShow(tagging) }"
       />
       <template v-else>
         <ui-icon type="pin1" ctrl @click.stop="$emit('fixed')" tip="放置" />
-        <ui-more 
-          :options="menus" 
-          style="margin-left: 20px" 
-          @click="(action: keyof typeof actions) => actions[action]()" 
+        <ui-more
+          :options="menus"
+          style="margin-left: 20px"
+          @click="(action: keyof typeof actions) => actions[action]()"
         />
       </template>
     </div>
@@ -35,88 +32,94 @@
 </template>
 
 <script setup lang="ts">
-import { getFileUrl } from '@/utils'
-import { computed, ref, watchEffect, nextTick } from 'vue';
-import { getResource, showTaggingPositionsStack } from '@/env'
-import { sdk } from '@/sdk'
-import { 
-  getTaggingStyle, 
-  getTaggingPositions, 
+import { getFileUrl, getUrlType, MetaType } from "@/utils";
+import { computed, ref, watchEffect, nextTick } from "vue";
+import { getResource, showTaggingPositionsStack } from "@/env";
+import { sdk } from "@/sdk";
+import {
+  getTaggingStyle,
+  getTaggingPositions,
   getFuseModel,
   getFuseModelShowVariable,
-  getTaggingIsShow
-} from '@/store'
+  getTaggingIsShow,
+} from "@/store";
 
-import type { Tagging } from '@/store'
+import type { Tagging } from "@/store";
 
 const props = withDefaults(
-  defineProps<{ tagging: Tagging, selected?: boolean, edit?: boolean }>(),
+  defineProps<{ tagging: Tagging; selected?: boolean; edit?: boolean }>(),
   { edit: true }
-)
-const style = computed(() => getTaggingStyle(props.tagging.styleId))
-const positions = computed(() => getTaggingPositions(props.tagging))
+);
+const style = computed(() => getTaggingStyle(props.tagging.styleId));
+const positions = computed(() => getTaggingPositions(props.tagging));
 
-const emit = defineEmits<{ 
-  (e: 'delete'): void 
-  (e: 'edit'): void
-  (e: 'select', selected: boolean): void
-  (e: 'fixed'): void
-}>()
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "edit"): void;
+  (e: "select", selected: boolean): void;
+  (e: "fixed"): void;
+}>();
+
+const findImage = computed(() => {
+  return props.tagging.images.find(
+    (a) => getUrlType(getResource(getFileUrl(a))) === MetaType.image
+  );
+});
 
 const menus = [
-  { label: '编辑', value: 'edit' },
-  { label: '删除', value: 'delete' },
-]
+  { label: "编辑", value: "edit" },
+  { label: "删除", value: "delete" },
+];
 const actions = {
-  edit: () => emit('edit'),
-  delete: () => emit('delete')
-}
+  edit: () => emit("edit"),
+  delete: () => emit("delete"),
+};
 
 const flyTaggingPositions = (tagging: Tagging, callback?: () => void) => {
-  const positions = getTaggingPositions(tagging)
+  const positions = getTaggingPositions(tagging);
 
-  let isStop = false
+  let isStop = false;
   const flyIndex = (i: number) => {
     if (isStop || i >= positions.length) {
-      callback && nextTick(callback)
+      callback && nextTick(callback);
       return;
     }
-    const position = positions[i]
-    const model = getFuseModel(position.modelId)
+    const position = positions[i];
+    const model = getFuseModel(position.modelId);
     if (!model || !getFuseModelShowVariable(model).value) {
-      flyIndex(i + 1)
+      flyIndex(i + 1);
       return;
     }
 
-    const pop = showTaggingPositionsStack.push(ref(new WeakSet([position])))
-    sdk.comeTo({ 
-      position: position.localPos, 
+    const pop = showTaggingPositionsStack.push(ref(new WeakSet([position])));
+    sdk.comeTo({
+      position: position.localPos,
       modelId: position.modelId,
       dur: 300,
-      distance: 3
-    })
-    
+      distance: 3,
+    });
+
     setTimeout(() => {
-      pop()
-      flyIndex(i + 1)
-    }, 2000)
-  }
-  flyIndex(0)
-  return () => isStop = true
-}
+      pop();
+      flyIndex(i + 1);
+    }, 2000);
+  };
+  flyIndex(0);
+  return () => (isStop = true);
+};
 watchEffect((onCleanup) => {
   if (props.selected) {
-    const success = () => emit('select', false)
-    const stop = flyTaggingPositions(props.tagging, success)
-    const keyupHandler = (ev: KeyboardEvent) => ev.code === 'Escape' && success()
+    const success = () => emit("select", false);
+    const stop = flyTaggingPositions(props.tagging, success);
+    const keyupHandler = (ev: KeyboardEvent) => ev.code === "Escape" && success();
 
-    document.documentElement.addEventListener('keyup', keyupHandler, false)
+    document.documentElement.addEventListener("keyup", keyupHandler, false);
     onCleanup(() => {
-      stop()
-      document.documentElement.removeEventListener('keyup', keyupHandler, false)
-    })
+      stop();
+      document.documentElement.removeEventListener("keyup", keyupHandler, false);
+    });
   }
-})
+});
 </script>
 
-<style lang="scss" scoped src="./style.scss"></style>
+<style lang="scss" scoped src="./style.scss"></style>

+ 2 - 7
src/views/view/index.vue

@@ -83,7 +83,7 @@ const deleteView = (record: View) => {
   }
 };
 
-const showLeftPano = ref(false);
+const showLeftPano = ref(true);
 watch(currentModel, () => {
   if (currentModel.value) {
     showLeftPano.value = false;
@@ -91,12 +91,7 @@ watch(currentModel, () => {
 });
 
 useViewStack(autoSaveViews);
-useViewStack(() =>
-  togetherCallback([
-    showLeftPanoStack.push(showLeftPano),
-    showRightPanoStack.push(ref(false)),
-  ])
-);
+useViewStack(() => togetherCallback([showLeftPanoStack.push(showLeftPano)]));
 </script>
 
 <style lang="scss" src="./style.scss" scoped></style>

+ 2 - 7
vite.config.ts

@@ -42,15 +42,10 @@ const proxy = {
     changeOrigin: true,
     rewrite: path => path.replace(/^\/laser-data/, '/laser-data')
   },
-  // '/fdkk': {
-  //   target: `${ip}/`,
-  //   changeOrigin: true,
-  //   rewrite: path => path.replace(/^\/fdkk/, '/fdkk')
-  // },
-  '/laser-data': {
+  '/fdkk': {
     target: `${ip}/`,
     changeOrigin: true,
-    rewrite: path => path.replace(/^\/laser-data/, '/laser-data')
+    rewrite: path => path.replace(/^\/fdkk/, '/fdkk')
   },
   '/service': {
     target: ip,