bill 1 年之前
父節點
當前提交
73d6ac52f1

+ 56 - 49
src/api/constant.ts

@@ -12,81 +12,88 @@ export const UPLOAD_HEADS = {
   'Content-Type': 'multipart/form-data'
 }
 
-export const CASE_INFO = `/fusion/case/getInfo`
+export const CASE_INFO = `/fusion-xj/case/getInfo`
 // 校验密码
-export const AUTH_PWD = `/fusion/web/fireProject/getDetailWithoutAuth`
+export const AUTH_PWD = `/fusion-xj/web/fireProject/getDetailWithoutAuth`
 
 // 融合模型列表
-export const FUSE_MODEL_LIST = `/fusion/caseFusion/list`
-export const FUSE_INSERT_MODEL = `/fusion/caseFusion/add`
-export const FUSE_UPDATE_MODEL = `/fusion/caseFusion/update`
-export const FUSE_DELETE_MODEL = `/fusion/caseFusion/delete`
+export const FUSE_MODEL_LIST = `/fusion-xj/caseFusion/list`
+export const FUSE_INSERT_MODEL = `/fusion-xj/caseFusion/add`
+export const FUSE_UPDATE_MODEL = `/fusion-xj/caseFusion/update`
+export const FUSE_DELETE_MODEL = `/fusion-xj/caseFusion/delete`
 // 场景列表
-export const SCENE_LIST_ALL = `/fusion/api/scene/list`
-export const MODEL_LIST = `/fusion/case/sceneList`
-export const MODEL_SIGN = `/fusion/model/getInfo`
+export const SCENE_LIST_ALL = `/fusion-xj/api/scene/list`
+export const MODEL_LIST = `/fusion-xj/case/sceneList`
+export const MODEL_SIGN = `/fusion-xj/model/getInfo`
 
 // 标注列表
-export const TAGGING_LIST = `/fusion/caseTag/allList`
-export const INSERT_TAGGING = `/fusion/caseTag/add`
-export const UPDATE_TAGGING = `/fusion/caseTag/update`
-export const DELETE_TAGGING = `/fusion/caseTag/delete`
+export const TAGGING_LIST = `/fusion-xj/caseTag/allList`
+export const INSERT_TAGGING = `/fusion-xj/caseTag/add`
+export const UPDATE_TAGGING = `/fusion-xj/caseTag/update`
+export const DELETE_TAGGING = `/fusion-xj/caseTag/delete`
 
 // 标注放置列表
-export const TAGGING_POINT_LIST = `/fusion/caseTagPoint/allList`
-export const INSERT_TAGGING_POINT = `/fusion/caseTagPoint/place`
-export const UPDATE_TAGGING_POINT = `/fusion/caseTagPoint/update`
-export const DELETE_TAGGING_POINT = `/fusion/caseTagPoint/delete`
+export const TAGGING_POINT_LIST = `/fusion-xj/caseTagPoint/allList`
+export const INSERT_TAGGING_POINT = `/fusion-xj/caseTagPoint/place`
+export const UPDATE_TAGGING_POINT = `/fusion-xj/caseTagPoint/update`
+export const DELETE_TAGGING_POINT = `/fusion-xj/caseTagPoint/delete`
 
 // 标注样式类型列表
-export const TAGGING_STYLE_LIST = '/fusion/edit/hotIcon/list'
-export const INSERT_TAGGING_STYLE = '/fusion/edit/hotIcon/add'
-export const DELETE_TAGGING_STYLE = '/fusion/edit/hotIcon/delete'
+export const TAGGING_STYLE_LIST = '/fusion-xj/edit/hotIcon/list'
+export const INSERT_TAGGING_STYLE = '/fusion-xj/edit/hotIcon/add'
+export const DELETE_TAGGING_STYLE = '/fusion-xj/edit/hotIcon/delete'
 
 // 测量线
-export const MESASURE_LIST = `/fusion/fusionMeter/allList`
-export const INSERT_MESASURE = `/fusion/fusionMeter/add`
-export const UPDATE_MESASURE = `/fusion/fusionMeter/updateMeter`
-export const DELETE_MESASURE = `/fusion/fusionMeter/delete`
+export const MESASURE_LIST = `/fusion-xj/fusionMeter/allList`
+export const INSERT_MESASURE = `/fusion-xj/fusionMeter/add`
+export const UPDATE_MESASURE = `/fusion-xj/fusionMeter/updateMeter`
+export const DELETE_MESASURE = `/fusion-xj/fusionMeter/delete`
 
 // 导览
-export const GUIDE_LIST = `/fusion/fusionGuide/allList`
-export const INSERT_GUIDE = `/fusion/fusionGuide/add`
-export const UPDATE_GUIDE = `/fusion/fusionGuide/update`
-export const DELETE_GUIDE = `/fusion/fusionGuide/delete`
+export const GUIDE_LIST = `/fusion-xj/fusionGuide/allList`
+export const INSERT_GUIDE = `/fusion-xj/fusionGuide/add`
+export const UPDATE_GUIDE = `/fusion-xj/fusionGuide/update`
+export const DELETE_GUIDE = `/fusion-xj/fusionGuide/delete`
 
 // 导览路线
-export const GUIDE_PATH_LIST = `/fusion/fusionGuidePath/allList`
-export const INSERT_GUIDE_PATH = `/fusion/fusionGuidePath/add`
-export const UPDATE_GUIDE_PATH = `/fusion/fusionGuidePath/update`
-export const DELETE_GUIDE_PATH = `/fusion/fusionGuidePath/delete`
+export const GUIDE_PATH_LIST = `/fusion-xj/fusionGuidePath/allList`
+export const INSERT_GUIDE_PATH = `/fusion-xj/fusionGuidePath/add`
+export const UPDATE_GUIDE_PATH = `/fusion-xj/fusionGuidePath/update`
+export const DELETE_GUIDE_PATH = `/fusion-xj/fusionGuidePath/delete`
 
 // 屏幕录制
-export const RECORD_LIST = `/fusion/caseVideoFolder/allList`
-export const RECORD_STATUS = `/fusion/caseVideo/uploadAddVideoProgress`
-export const INSERT_RECORD = `/fusion/caseVideo/uploadAddVideo`
-export const MERGE_RECORD = `/fusion/caseVideo/uploadAddVideo`
-export const UPDATE_RECORD = `/fusion/caseVideoFolder/updateNameOrSort`
-export const DELETE_RECORD = `/fusion/caseVideoFolder/delete`
+export const RECORD_LIST = `/fusion-xj/caseVideoFolder/allList`
+export const RECORD_STATUS = `/fusion-xj/caseVideo/uploadAddVideoProgress`
+export const INSERT_RECORD = `/fusion-xj/caseVideo/uploadAddVideo`
+export const MERGE_RECORD = `/fusion-xj/caseVideo/uploadAddVideo`
+export const UPDATE_RECORD = `/fusion-xj/caseVideoFolder/updateNameOrSort`
+export const DELETE_RECORD = `/fusion-xj/caseVideoFolder/delete`
 
 // 录制片段
-export const RECORD_FRAGMENT_LIST = `/fusion/caseVideo/allList`
-export const DELETE_RECORD_FRAGMENT = `/fusion/caseVideo/delete`
+export const RECORD_FRAGMENT_LIST = `/fusion-xj/caseVideo/allList`
+export const DELETE_RECORD_FRAGMENT = `/fusion-xj/caseVideo/delete`
 
 // 视图提取
-export const VIEW_LIST = `/fusion/caseView/allList`
-export const INSERT_VIEW = `/fusion/caseView/add`
-export const UPDATE_VIEW = `/fusion/caseView/updateNameOrSort`
-export const DELETE_VIEW = `/fusion/caseView/delete`
+export const VIEW_LIST = `/fusion-xj/caseView/allList`
+export const INSERT_VIEW = `/fusion-xj/caseView/add`
+export const UPDATE_VIEW = `/fusion-xj/caseView/updateNameOrSort`
+export const DELETE_VIEW = `/fusion-xj/caseView/delete`
+
+export const GET_SETTING = `/fusion-xj/caseSettings/info`
+export const UPDATE_SETTING = `/fusion-xj/caseSettings/saveOrUpdate`
+
+
+export const GET_SETTING_RESOURCES = `/fusion-xj/settingsResource/info`
+export const UPDATE_SETTING_RESOURCE = `/fusion-xj/settingsResource/saveOrUpdate`
+export const DEL_SETTING_RESOURCE = `/fusion-xj/settingsResource/delete`
+export const ADD_SETTING_RESOURCE = `/fusion-xj/settingsResource/saveOrUpdate`
 
-export const GET_SETTING = `/fusion/caseSettings/info`
-export const UPDATE_SETTING = `/fusion/caseSettings/saveOrUpdate`
 
 // 卷宗类型
-export const FOLDER_TYPE_LIST = `/fusion/caseFilesType/allList`
+export const FOLDER_TYPE_LIST = `/fusion-xj/caseFilesType/allList`
 
 // 卷宗
-export const FLODER_LIST = `/fusion/caseFiles/allList`
+export const FLODER_LIST = `/fusion-xj/caseFiles/allList`
 
 // 文件上传
-export const UPLOAD_FILE = `/fusion/upload/file`
+export const UPLOAD_FILE = `/fusion-xj/upload/file`

+ 75 - 0
src/api/setting-resource.ts

@@ -0,0 +1,75 @@
+import { params } from "@/env";
+import axios from "./instance";
+import {
+  ADD_SETTING_RESOURCE,
+  DEL_SETTING_RESOURCE,
+  GET_SETTING_RESOURCES,
+  UPDATE_SETTING_RESOURCE,
+} from "./constant";
+import { ref } from "vue";
+
+export enum SettingResourceType {
+  map = 'map',
+  color = "color",
+  envImage = "img",
+  bottomImage = "bimg",
+  icon = "icon",
+}
+export const settingResourceTypeDesc = {
+  [SettingResourceType.color]: "颜色",
+  [SettingResourceType.envImage]: "环境图",
+  [SettingResourceType.bottomImage]: "地面图",
+  [SettingResourceType.map]: "",
+  [SettingResourceType.icon]: "",
+};
+
+export type SettingResource = {
+  id?: string;
+  resource: string;
+  backType: SettingResourceType;
+  name: string;
+};
+
+export type SettingResources = SettingResource[];
+
+export const settingResources = ref<SettingResources>([]);
+
+export const fetchSettingResources = async () => {
+  settingResources.value = [
+    { name: "无", backType: SettingResourceType.icon, resource: "icon-without" },
+    { name: "影像地图", backType: SettingResourceType.map, resource: "satellite" },
+    { name: "矢量地图", backType: SettingResourceType.map, resource: "standard" },
+    {
+      name: "蓝天白云",
+      backType: SettingResourceType.envImage,
+      resource: (await import("../views/setting/images/pic_ltby@2x.png"))
+        .default,
+    }
+  ];
+  const data = await axios.get<SettingResources>(GET_SETTING_RESOURCES, {
+    params: { caseId: params.caseId },
+  });
+  settingResources.value.push(...data)
+
+  return data;
+};
+
+export const updateSettingResource = async (setting: SettingResource) => {
+  await axios.post(UPDATE_SETTING_RESOURCE, {
+    caseId: params.caseId,
+    ...setting,
+  });
+};
+
+export const delSettingResource = async (setting: SettingResource) => {
+  await axios.post(DEL_SETTING_RESOURCE, {
+    id: setting.id,
+  });
+};
+
+export const addSettingResource = async (setting: SettingResource) => {
+  await axios.post(ADD_SETTING_RESOURCE, {
+    caseId: params.caseId,
+    ...setting,
+  });
+};

+ 42 - 34
src/api/setting.ts

@@ -1,53 +1,61 @@
-import { GET_SETTING, UPDATE_SETTING } from './constant'
-import defaultCover from '@/assets/pic.jpg'
-import { params } from '@/env'
-import axios from './instance'
-import { uploadFile } from './sys'
-
+import { GET_SETTING, UPDATE_SETTING } from "./constant";
+import defaultCover from "@/assets/pic.jpg";
+import { params } from "@/env";
+import axios from "./instance";
+import { SettingResourceType } from "./setting-resource";
 
 type ServeSetting = {
-  settingsId?: string,
-  pose?: string
-  cover?: string,
-  back?: string
-}
+  settingsId?: string;
+  pose?: string;
+  cover?: string;
+  back?: string;
+  backType?: SettingResourceType;
+  mapOpen?: boolean;
+  openCompass?: boolean
+};
 
 export type Setting = {
-  id?: string,
+  id?: string;
   pose?: {
-    position: SceneLocalPos
-    target: SceneLocalPos
-  },
-  cover: string,
-  back: string
-}
-
-
-const toLocal = (serviceSetting: ServeSetting) : Setting => ({
+    position: SceneLocalPos;
+    target: SceneLocalPos;
+  };
+  cover: string;
+  back: string;
+  openCompass: boolean
+  backType: SettingResourceType;
+  mapOpen: boolean;
+};
+
+const toLocal = (serviceSetting: ServeSetting): Setting => ({
+  ...serviceSetting,
   id: serviceSetting.settingsId,
   pose: serviceSetting.pose && JSON.parse(serviceSetting.pose),
   cover: serviceSetting.cover || defaultCover,
-  back: serviceSetting.back || 'none'
-})
+  back: serviceSetting.back || "none",
+  backType: serviceSetting.backType || SettingResourceType.icon,
+  mapOpen: false,
+  openCompass: serviceSetting.openCompass 
+});
 
 const toService = (setting: Setting): ServeSetting => ({
+  ...setting,
   settingsId: setting.id,
   pose: setting.pose && JSON.stringify(setting.pose),
   cover: setting.cover,
-  back: setting.back
-})
-
-
+  back: setting.back,
+});
 
 export const fetchSetting = async () => {
-  const data = await axios.get<ServeSetting[]>(GET_SETTING, { params: { caseId: params.caseId } })
-  return toLocal(data[0] || {})
-}
+  const data = await axios.get<ServeSetting[]>(GET_SETTING, {
+    params: { caseId: params.caseId },
+  });
+  return toLocal(data[0] || {});
+};
 
 export const updateSetting = async (setting: Setting) => {
   await axios.post(UPDATE_SETTING, {
     caseId: params.caseId,
-    ...toService(setting)
-  })
-  
-}
+    ...toService(setting),
+  });
+};

+ 7 - 0
src/api/sys.ts

@@ -14,6 +14,7 @@ export const uploadFile = async (file: UploadFile, suffix = ".png") => {
   } else {
     const uploadFile =
       file.blob instanceof File ? file.blob : blobToFile(file.blob, suffix);
+      console.log(uploadFile)
     const url = await axios<string>({
       method: "POST",
       url: UPLOAD_FILE,
@@ -54,6 +55,12 @@ export type FireProject = {
   statusDesc: string;
   updateTime: string;
   isDelete?: number;
+
+  latlng?: string
+  field1: string;
+  field2: string;
+  field3: string;
+  field4: string;
 };
 
 export interface Case {

+ 19 - 1
src/api/tagging.ts

@@ -21,6 +21,10 @@ interface ServerTagging {
   "leaveBehind": string,
   "tagDescribe": string,
   "tagTitle": string,
+  type: string;
+  cat: string,
+  time: string,
+  tsms: string
 }
 
 export interface Tagging {
@@ -31,6 +35,12 @@ export interface Tagging {
   part: string
   method: string
   principal: string
+
+  mtype: string;
+  cat: string,
+  time: string,
+  tsms: string,
+  type: 'TEXT' | 'IMAGE' | 'AUDIO' | 'VIDEO' | 'WEB',
   images: string[],
 }
 
@@ -46,7 +56,11 @@ const serviceToLocal = (serviceTagging: ServerTagging): Tagging => ({
   part: serviceTagging.leaveBehind,
   method: serviceTagging.getMethod,
   principal: serviceTagging.getUser,
-  images: JSON.parse(serviceTagging.tagImgUrl)
+  images: JSON.parse(serviceTagging.tagImgUrl),
+  type: serviceTagging.type,
+  cat:  serviceTagging.cat,
+  time: serviceTagging.time,
+  tsms: serviceTagging.tsms,
 })
 
 const localToService = (tagging: Tagging, update = false): PartialProps<ServerTagging, 'tagId' | 'hotIconUrl'> & { fusionId: number } => ({
@@ -60,6 +74,10 @@ const localToService = (tagging: Tagging, update = false): PartialProps<ServerTa
   "leaveBehind": tagging.part,
   "tagDescribe": tagging.desc,
   "tagTitle": tagging.title,
+  type: tagging.type,
+  cat:  tagging.cat,
+  time: tagging.time,
+  tsms: tagging.tsms,
 })
 
 

+ 66 - 0
src/components/tagging/metas/metas-mange.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="mates" v-if="hot.type !== 'TEXT' && hot.type !== 'AUDIO'">
+    <ui-slide
+      v-if="hot.meta"
+      :items="hot.meta"
+      :showCtrl="hot.meta.length > 1"
+      :currentIndex="index"
+      @change="(i) => emit('change', i)"
+      :showInfos="hot.meta.length > 1 && !hideInfo"
+    >
+      <template v-slot="{ raw, index }">
+        <div
+          class="meta-item"
+          :class="{ full: inFull }"
+          @click="inFull && emit('pull', index)"
+        >
+          <img :src="getResources(raw.url)" v-if="hot.type === 'IMAGE'" />
+          <video
+            v-else-if="hot.type === 'VIDEO'"
+            class="video"
+            autoplay
+            controls
+            playsinline
+            webkit-playsinline
+          >
+            <source :src="getResources(raw.url)" type="video/mp4" />
+          </video>
+          <div class="iframe" v-else-if="hot.type === 'WEB'">
+            <iframe :src="getResources(raw.url)"> </iframe>
+          </div>
+        </div>
+      </template>
+      <template v-slot:attach="{ active }">
+        <div class="file-mange">
+          <slot name="icons" :active="active" />
+        </div>
+      </template>
+    </ui-slide>
+  </div>
+</template>
+<script setup lang="ts">
+import { defineProps, defineEmits, ref, watchEffect } from "vue";
+import { Tagging } from "@/store";
+import { getResources } from "@/store/app";
+
+defineProps<{
+  hot: Tagging;
+  inFull?: boolean;
+  index?: number;
+  hideInfo?: boolean;
+}>();
+const emit = defineEmits<{
+  (e: "pull", index: number): void;
+  (e: "change", i: number): void;
+}>();
+</script>
+
+<style lang="sass" scoped>
+@import './style.scss'
+</style>
+
+<style>
+.mates {
+  color: #fff;
+}
+</style>

+ 75 - 72
src/components/tagging/sign.vue

@@ -1,40 +1,42 @@
 <template>
-  <div 
+  <div
     v-if="posStyle"
-    class="hot-item pc" 
-    :style="posStyle" 
+    class="hot-item pc"
+    :style="posStyle"
     @mouseenter="isHover = true"
     @mouseleave="isHover = false"
-    :class="{active: showContent}"
+    :class="{ active: showContent }"
   >
     <ui-tip :tip="tagging.title" foreShow tipV="top" class="tag-tip">
-      <img 
+      <img
         class="tag-img"
-        :src="getResource(getFileUrl(taggingStyle.icon))" 
-        @click="iconClickHandler" 
-        v-if="taggingStyle" 
+        :src="getResource(getFileUrl(taggingStyle.icon))"
+        @click="iconClickHandler"
+        v-if="taggingStyle"
       />
     </ui-tip>
     <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">
+        <h2>{{ tagging.title }}</h2>
         <div class="content">
           <p><span>特征描述:</span>{{ tagging.desc }}</p>
           <p><span>遗留部位:</span>{{ tagging.part }}</p>
           <p><span>提取方法:</span>{{ tagging.method }}</p>
+          <p><span>类别:</span>{{ tagging.mtype }}</p>
+          <p><span>类型:</span>{{ tagging.cat }}</p>
+          <p><span>特征描述:</span>{{ tagging.tsms }}</p>
+          <p><span>提取时间:</span>{{ tagging.time }}</p>
           <p><span>提取人:</span>{{ tagging.principal }}</p>
         </div>
-        <Images 
-          :tagging="tagging" 
-          :in-full="true" 
-          @pull="index => (pullIndex = index)" 
+        <Images
+          :tagging="tagging"
+          :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="router.currentRoute.value.name === RoutesName.tagging"
+        >
           <span @click="$emit('delete')" class="fun-ctrl">
             <ui-icon type="del" />
             删除
@@ -42,85 +44,87 @@
         </div>
       </UIBubble>
 
-      <Preview 
+      <Preview
         @close="pullIndex = -1"
         :current="pullIndex"
         :items="queryItems"
-        v-if="!!~pullIndex" 
+        v-if="!!~pullIndex"
       />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, watchEffect, watch, onUnmounted } from 'vue'
-import { router, RoutesName } from '@/router'
-import UIBubble from 'bill/components/bubble/index.vue'
-import Images from '@/views/tagging/images.vue'
-import Preview, { MediaType } from '../static-preview/index.vue'
-import { getTaggingStyle, getFuseModel } from '@/store';
-import { getFileUrl } from '@/utils'
-import { sdk } from '@/sdk'
-import { custom, getResource } from '@/env'
-import { useViewStack } from '@/hook'
+import { computed, ref, watchEffect, watch, onUnmounted } from "vue";
+import { router, RoutesName } from "@/router";
+import UIBubble from "bill/components/bubble/index.vue";
+import Images from "@/views/tagging/images.vue";
+import Preview, { MediaType } from "../static-preview/index.vue";
+import { getTaggingStyle, getFuseModel } from "@/store";
+import { getFileUrl } from "@/utils";
+import { sdk } from "@/sdk";
+import { custom, getResource } from "@/env";
+import { useViewStack } from "@/hook";
 
-import type { Tagging, TaggingPosition } from '@/store';
+import type { Tagging, TaggingPosition } from "@/store";
 
-export type SignProps = { tagging: Tagging, scenePos: TaggingPosition, show?: boolean }
+export type SignProps = { tagging: Tagging; scenePos: TaggingPosition; show?: boolean };
 
-defineEmits<{ (e: 'delete'): void }>()
+defineEmits<{ (e: "delete"): void }>();
 
-const props = defineProps<SignProps>()
-const posStyle = ref<null | { left: string, top: string}>(null)
+const props = defineProps<SignProps>();
+const posStyle = ref<null | { left: string; top: string }>(null);
 const updatePosStyle = () => {
-  const screenPos = sdk.getScreenByPosition(props.scenePos.localPos, props.scenePos.modelId)
+  const screenPos = sdk.getScreenByPosition(
+    props.scenePos.localPos,
+    props.scenePos.modelId
+  );
   if (!screenPos?.trueSide) {
-    posStyle.value = null
+    posStyle.value = null;
   } else {
     posStyle.value = {
-      left: screenPos.pos.x + 'px',
-      top: screenPos.pos.y + 'px',
-    }
+      left: screenPos.pos.x + "px",
+      top: screenPos.pos.y + "px",
+    };
   }
-}
-
+};
 
 useViewStack(() => {
-  sdk.sceneBus.on('cameraChange', updatePosStyle)
+  sdk.sceneBus.on("cameraChange", updatePosStyle);
   return () => {
-    sdk.sceneBus.off('cameraChange', updatePosStyle)
-  }
-})
-watchEffect(updatePosStyle)
-
-const model = getFuseModel(props.scenePos.modelId)
-model && watch(model, updatePosStyle, { deep: true })
+    sdk.sceneBus.off("cameraChange", updatePosStyle);
+  };
+});
+watchEffect(updatePosStyle);
 
+const model = getFuseModel(props.scenePos.modelId);
+model && watch(model, updatePosStyle, { deep: true });
 
 const showContent = computed(() => {
-  return !~pullIndex.value 
-    && (isHover.value || custom.showTaggingPositions.has(props.scenePos))
-})
-
-const taggingStyle = computed(() => getTaggingStyle(props.tagging.styleId)) 
-
-const pullIndex = ref(-1)
-const isHover = ref(false)
-const queryItems = computed(() => 
-  props.tagging.images.map(image => ({
-    type: MediaType.img, 
-    url: getResource(getFileUrl(image))
+  return (
+    !~pullIndex.value &&
+    (isHover.value || custom.showTaggingPositions.has(props.scenePos))
+  );
+});
+
+const taggingStyle = computed(() => getTaggingStyle(props.tagging.styleId));
+
+const pullIndex = ref(-1);
+const isHover = ref(false);
+const queryItems = computed(() =>
+  props.tagging.images.map((image) => ({
+    type: MediaType.img,
+    url: getResource(getFileUrl(image)),
   }))
-)
+);
 
 const iconClickHandler = () => {
   if (custom.showTaggingPositions.has(props.scenePos)) {
-    custom.showTaggingPositions.delete(props.scenePos)
+    custom.showTaggingPositions.delete(props.scenePos);
   } else {
-    custom.showTaggingPositions.add(props.scenePos)
+    custom.showTaggingPositions.add(props.scenePos);
   }
-}
-
+};
 </script>
 
 <style lang="scss" scoped>
@@ -149,7 +153,7 @@ const iconClickHandler = () => {
     h2 {
       font-size: 20px;
       margin-bottom: 10px;
-      color: #FFFFFF;
+      color: #ffffff;
       position: relative;
     }
 
@@ -183,7 +187,6 @@ const iconClickHandler = () => {
     cursor: pointer;
   }
 }
-
 </style>
 
 <style>
@@ -194,4 +197,4 @@ const iconClickHandler = () => {
   padding: 6px 10px !important;
   margin: 5px 0 !important;
 }
-</style>
+</style>

+ 6 - 3
src/env/index.ts

@@ -49,19 +49,22 @@ export enum appType {
   xmfire = "3",
   criminal = "2",
   police = "4",
+  xj = "1"
 
 }
 export const appStyleImport = {
-  [appType.fire]: () => import('../assets/style/fire.less'),
+  // [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'),
+  [appType.xj]: () => import('../assets/style/fire.less'),
 }
 export const appBackRoot = {
-  [appType.fire]: "/fire",
+  // [appType.fire]: "/fire",
   [appType.xmfire]: "/xmfire",
   [appType.criminal]: "/criminal",
-  [appType.police]: "/criminal"
+  [appType.police]: "/criminal",
+  [appType.xj]: "/xj"
 }
 export const routeIncludeFire = (type: appType) => 
   [appType.fire, appType.xmfire].includes(type)

+ 1 - 0
src/main.ts

@@ -20,6 +20,7 @@ watchEffect((onCleanup) => {
 
     const untokenURLS = params.share 
       ? [
+          URL.GET_SETTING_RESOURCES,
           URL.FUSE_MODEL_LIST,
           URL.MODEL_LIST,
           URL.GET_SETTING,

+ 77 - 64
src/model/app.vue

@@ -3,61 +3,62 @@
   <div class="laser-layer" v-show="!url">
     <div class="scene-canvas" ref="fuseRef">
       <div id="direction"></div>
+      <div id="scene-map"></div>
     </div>
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, ref, watchEffect, computed, watch, nextTick } from 'vue'
-import { SceneType } from '@/store'
-import { params } from '@/env'
-import { fuseModel, modelProps } from './index'
-import { modelSDKFactory } from './platform'
+import { defineComponent, ref, watchEffect, computed, watch, nextTick } from "vue";
+import { SceneType } from "@/store";
+import { params } from "@/env";
+import { fuseModel, modelProps } from "./index";
+import { modelSDKFactory } from "./platform";
 
 const typeChange = () => {
-  const oldType = modelProps.type
-  let stopWatch = null as unknown as () => void
+  const oldType = modelProps.type;
+  let stopWatch = (null as unknown) as () => void;
 
   const typePromise = new Promise((_, reject) => {
     stopWatch = watchEffect(() => {
       if (modelProps.type !== oldType) {
-        reject(new Error('当前模型未加载完已切换到下个'))
-        stopWatch!()
+        reject(new Error("当前模型未加载完已切换到下个"));
+        stopWatch!();
       }
-    })
-  })
-  return { typePromise, typeCleanup: stopWatch }
-}
+    });
+  });
+  return { typePromise, typeCleanup: stopWatch };
+};
 
 export const Model = defineComponent({
-  name: 'model',
+  name: "model",
   setup() {
-    const scene = computed(() => modelProps.type !== fuseModel && modelProps.type)
-    const url = ref("")
+    const scene = computed(() => modelProps.type !== fuseModel && modelProps.type);
+    const url = ref("");
     const setUrl = (newURL: string) => {
       if (newURL !== url.value) {
         setTimeout(() => {
-          const hook = (iframeRef.value?.contentWindow as any)?.beforeDestroy
+          const hook = (iframeRef.value?.contentWindow as any)?.beforeDestroy;
           if (hook) {
             try {
-              hook()
-            } catch(e) {
-              console.error(e)
+              hook();
+            } catch (e) {
+              console.error(e);
             }
-            url.value = ""
-            setTimeout(() => url.value = newURL, 300)
+            url.value = "";
+            setTimeout(() => (url.value = newURL), 300);
           } else {
-            url.value = newURL
+            url.value = newURL;
           }
-        })
+        });
       }
-    }
+    };
 
     watchEffect(() => {
       if (!scene.value) {
         return setUrl("");
       }
-      const type = scene.value.type
+      const type = scene.value.type;
       const urls = {
         [SceneType.SWKK]: `/swkk/spg.html?m=${scene.value.num}`,
         [SceneType.SWKJ]: `/swkk/spg.html?m=${scene.value.num}`,
@@ -66,14 +67,12 @@ export const Model = defineComponent({
         [SceneType.SWMX]: `index.html?caseId=${params.caseId}&app=${params.app}&modelId=${scene.value.num}&share=1#sign-model`,
         [SceneType.SWYDSS]: `/swss/index.html?m=${scene.value.num}`,
         [SceneType.SWYDMX]: `/swkk/spg.html?m=${scene.value.num}`,
-      }
-      setUrl(urls[type])
-    })
-
+      };
+      setUrl(urls[type]);
+    });
 
-
-    const fuseRef = ref<HTMLDivElement>()
-    const iframeRef = ref<HTMLIFrameElement>()
+    const fuseRef = ref<HTMLDivElement>();
+    const iframeRef = ref<HTMLIFrameElement>();
 
     watch(
       () => [modelProps.type, url.value],
@@ -81,47 +80,51 @@ export const Model = defineComponent({
         if (type !== fuseModel && !url) {
           return;
         }
-        const callback = modelProps.callback
-
-        await nextTick()
-        const { typePromise, typeCleanup } = typeChange()
-        const modelPromise = modelSDKFactory(type as any, type === fuseModel ? fuseRef.value! : iframeRef.value!)
-        let result: any = null, error = null
+        const callback = modelProps.callback;
+
+        await nextTick();
+        const { typePromise, typeCleanup } = typeChange();
+        const modelPromise = modelSDKFactory(
+          type as any,
+          type === fuseModel ? fuseRef.value! : iframeRef.value!
+        );
+        let result: any = null,
+          error = null;
         try {
-          result = await Promise.race([typePromise, modelPromise])
+          result = await Promise.race([typePromise, modelPromise]);
         } catch (err: any) {
-          error = err
+          error = err;
         }
-        typeCleanup()
-        callback && callback(result, error)
-      }, 
-      { immediate: true, flush: 'post' }
-    )
+        typeCleanup();
+        callback && callback(result, error);
+      },
+      { immediate: true, flush: "post" }
+    );
 
     // 处理iframe 定制页面
     watch(
-      () => [scene.value && scene.value.type, url.value], 
+      () => [scene.value && scene.value.type, url.value],
       ([type], oldType, onCleanup) => {
         if (type === false) {
           // 手动渲染融合场景
           console.log("手动渲染!");
           setTimeout(() => {
-            (window as any).viewer.setDisplay(true)
-          }, 100)
-        };
+            (window as any).viewer.setDisplay(true);
+          }, 100);
+        }
 
         const interval = setInterval(async () => {
-          let doc: Document | undefined
+          let doc: Document | undefined;
           try {
-            doc = iframeRef.value?.contentWindow?.document!
+            doc = iframeRef.value?.contentWindow?.document!;
           } catch {
             clearInterval(interval);
           }
           if (!doc || !doc.querySelector("div")) return;
-          console.error(doc, doc.head)
-          const target = doc.head
+          console.error(doc, doc.head);
+          const target = doc.head;
           clearInterval(interval);
-          ;(window as any).iframeCreated && (window as any).iframeCreated(iframeRef.value);
+          (window as any).iframeCreated && (window as any).iframeCreated(iframeRef.value);
           if (type === SceneType.SWSS) {
             const $style = document.createElement("style");
             $style.type = "text/css";
@@ -135,19 +138,19 @@ export const Model = defineComponent({
           }
         }, 16);
         onCleanup(() => clearInterval(interval));
-      }, 
-      {flush: 'post', immediate: true}
-    )
+      },
+      { flush: "post", immediate: true }
+    );
 
     return {
       iframeRef,
       fuseRef,
-      url
-    }
-  }
-})
+      url,
+    };
+  },
+});
 
-export default Model
+export default Model;
 </script>
 
 <style scoped lang="scss">
@@ -180,6 +183,16 @@ export default Model
   right: calc(var(--editor-menu-right) + var(--editor-toolbox-width)) !important;
   top: calc(var(--header-top) + var(--editor-head-height)) !important;
   margin: 10px;
-  transition: top .3s ease, right .3s ease;
+  transition: top 0.3s ease, right 0.3s ease;
+}
+
+#scene-map {
+  position: absolute;
+  right: calc(var(--editor-menu-right) + var(--editor-toolbox-width)) !important;
+  bottom: 0;
+  width: 320px;
+  height: 200px;
+  background: red;
+  z-index: 99;
 }
 </style>

+ 28 - 2
src/sdk/association.ts

@@ -30,7 +30,8 @@ import {
   fuseModelsLoaded,
   getMeasureIsShow,
   SceneStatus,
-  setting
+  setting,
+  caseProject
 } from '@/store'
 import { currentLayout, RoutesName } from '@/router'
 
@@ -44,6 +45,7 @@ import type {
   ModelAttrRange, 
   Measure as SceneMeasure 
 } from '.'
+import { SettingResourceType } from '@/api/setting-resource'
 
 let isUnSet = false
 const unSet = ((fn: () => void) => {
@@ -421,8 +423,32 @@ export const setupAssociation = (mountEl: HTMLDivElement) => {
       associationTaggings(mountEl)
       associationMessaures(sdk)
       setting.value?.pose && sdk.comeTo(setting.value.pose)
-      setting.value?.back && sdk.setBackdrop(setting.value.back)
+
+      setBackdrop(setting.value!.back, setting.value!.backType);
+      
+
+      ;(document.querySelector('#direction') as HTMLDivElement)!.style.display = setting.value!.openCompass ? 'block' : 'none';
+
       nextTick(() => stopWatch())
     }
   })
 }
+
+
+export const setBackdrop = (back: string, type: SettingResourceType) => {
+  if (type === SettingResourceType.map) {
+    if (!caseProject.value!.tmProject?.latlng) {
+      return;
+    }
+    
+    const latlng = caseProject.value!.tmProject?.latlng.split(',').map(i => Number(i))
+    sdk.enableMap && sdk.enableMap(document.querySelector('#scene-map') as HTMLDivElement, latlng)
+    sdk.switchMapType && sdk.switchMapType(back)
+    // 'satellite' | 'standard'
+
+  } else if (type!== SettingResourceType.icon) {
+    setting.value?.back && sdk.setBackdrop(back, type)
+  } else {
+    sdk.setBackdrop('none', type)
+  }
+}

+ 5 - 1
src/sdk/sdk.ts

@@ -3,6 +3,7 @@ import { createLoadPack, loadLib } from '@/utils'
 
 import { FuseModelAttrs, FuseModel, GuidePath, MeasureType, Measure as StoreMeasure, MeasurePosition } from '@/store'
 import type { Emitter } from 'mitt'
+import { SettingResourceType } from '@/api/setting-resource'
 
 
 type SceneModelAttrs = FuseModelAttrs & { select: boolean }
@@ -104,8 +105,11 @@ export type StartMeasure<T extends StoreMeasure['type']> = Measure<T> & {
 export interface SDK {
   layout: HTMLDivElement,
   sceneBus: Emitter<{ 'cameraChange':  SceneLocalPos }>
-  setBackdrop: (drop: string) => void
+  setBackdrop: (drop: string, type: SettingResourceType) => void
+  compassVisibility: (visibility: boolean) => void
   addModel: (props: AddModelProps) => SceneModel
+  enableMap(dom: HTMLDivElement, latlng: number[]): void
+  switchMapType: (type: string) => void
   showGrid: () => void
   hideGrid: () => void
   calcPathInfo: (paths: CalcPathProps[0], info: CalcPathProps[1]) => Required<CalcPathProps[1]>

+ 1 - 1
src/style.scss

@@ -140,4 +140,4 @@ input::-ms-clear,input::-ms-reveal {
 
 .vc-switch {
   z-index: 99999999 !important;
-}
+}

+ 34 - 21
src/views/fire/index.vue

@@ -3,44 +3,59 @@
     <div class="info" v-if="caseProject?.tmProject">
       <h2>案件信息</h2>
       <p>
-        <span>项目编号:</span>
+        <span>事件分类:</span>
         {{ caseProject.tmProject.projectSn }}
       </p>
       <p>
-        <span>起火地址:</span>
+        <span>详细地址:</span>
         {{ caseProject.tmProject.projectAddress }}
       </p>
       <p>
-        <span>起火场所:</span>
+        <span>勘验信息:</span>
         {{ caseProject.tmProject.projectSite }}
       </p>
+
+      <p>
+        <span>勘验地址:</span>
+        {{ caseProject.tmProject.field1 }}
+      </p>
+      <p>
+        <span>全宗名称:</span>
+        {{ caseProject.tmProject.field2 }}
+      </p>
+      <p>
+        <span>勘验人姓名:</span>
+        {{ caseProject.tmProject.field3 }}
+      </p>
+      <p>
+        <span>勘验人单位、职务:</span>
+        {{ caseProject.tmProject.field4 }}
+      </p>
+
       <p>
         <span>承办单位:</span>
         {{ caseProject.tmProject.organizerDeptName }}
       </p>
+
       <p>
-        <span>起火对象:</span>
+        <span>分类登记:</span>
         {{ caseProject.tmProject.projectName }}
       </p>
       <p>
-        <span>承办人员:</span>
+        <span>勘验人员:</span>
         {{ caseProject.tmProject.organizerUsers }}
       </p>
       <p>
-        <span>事故日期:</span>
+        <span>勘验日期:</span>
         {{ caseProject.tmProject.accidentDate }}
       </p>
       <p>
-        <span>火灾原因:</span>
-        {{ caseProject.tmProject.fireReason }}
-      </p>
-      <p>
         <span>项目状态:</span>
         {{ caseProject.tmProject.statusDesc }}
       </p>
       <p>
         <span>教学项目:</span>
-        {{ caseProject.tmProject.isTeached ? '是' : '否' }}
+        {{ caseProject.tmProject.isTeached ? "是" : "否" }}
       </p>
       <p>
         <span>创建人:</span>
@@ -62,16 +77,14 @@
   </LeftPano>
 </template>
 
-
 <script setup lang="ts">
-import { showRightPanoStack } from '@/env';
-import { useViewStack } from '@/hook';
-import { LeftPano } from '@/layout'
-import { caseProject } from '@/store/case'
-import { ref } from 'vue';
-
+import { showRightPanoStack } from "@/env";
+import { useViewStack } from "@/hook";
+import { LeftPano } from "@/layout";
+import { caseProject } from "@/store/case";
+import { ref } from "vue";
 
-useViewStack(() => showRightPanoStack.push(ref(false)))
+useViewStack(() => showRightPanoStack.push(ref(false)));
 </script>
 
 <style lang="scss" scoped>
@@ -100,8 +113,8 @@ useViewStack(() => showRightPanoStack.push(ref(false)))
       width: 70px;
       height: 100%;
       margin-right: 20px;
-      color: rgba(255, 255, 255, 0.70);
+      color: rgba(255, 255, 255, 0.7);
     }
   }
 }
-</style>
+</style>

+ 201 - 70
src/views/guide/sign.vue

@@ -3,12 +3,12 @@
     <div class="info">
       <div class="guide-cover">
         <img :src="getResource(getFileUrl(guide.cover))" />
-        <ui-icon 
-          type="preview" 
-          class="icon" 
-          ctrl 
-          @click="playSceneGuide(paths, undefined, true)" 
-          v-if="paths.length" 
+        <ui-icon
+          type="preview"
+          class="icon"
+          ctrl
+          @click="playSceneGuide(paths, undefined, true)"
+          v-if="paths.length"
         />
       </div>
       <div>
@@ -16,91 +16,148 @@
       </div>
     </div>
     <div class="actions" v-if="edit">
-      <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]()"
       />
     </div>
   </ui-group-option>
+  <Teleport to="body">
+    <div class="edit-add-type" v-if="downloading">
+      <div class="edit-hot-item">
+        <h3 class="edit-title">设置视频参数</h3>
+        <ui-input
+          require
+          class="input"
+          :options="[
+            { value: '1080p', label: '1080p' },
+            { value: '2k', label: '2k' },
+            { value: '4k', label: '4k' },
+          ]"
+          width="100%"
+          placeholder="设置分辨率"
+          type="select"
+          v-model="videoConfig.resolution"
+          maxlength="15"
+        />
+        <ui-input
+          require
+          class="input"
+          :options="[
+            { value: 30, label: '30' },
+            { value: 60, label: '60' },
+            { value: 90, label: '90' },
+          ]"
+          width="100%"
+          placeholder="设置帧率"
+          type="select"
+          v-model="videoConfig.frameRate"
+          maxlength="15"
+        />
+        <div class="edit-hot">
+          <a @click="() => (downloading = false)">
+            <ui-icon type="nav-edit" />
+            确定
+          </a>
+        </div>
+      </div>
+    </div>
+  </Teleport>
 </template>
 
 <script setup lang="ts">
-import { Guide, getGuidePaths } from '@/store'
-import { getFileUrl, saveAs } from '@/utils'
-import { getResource } from '@/env'
-import { computed, watchEffect, nextTick } from 'vue';
-import { playSceneGuide, isScenePlayIng, pauseSceneGuide } from '@/sdk'
-import { VideoRecorder } from '@simaq/core';
-
-const props = withDefaults(
-  defineProps<{ guide: Guide, edit?: boolean }>(),
-  { edit: true }
-)
-
-const emit = defineEmits<{ 
-  (e: 'delete'): void 
-  (e: 'play'): void 
-  (e: 'edit'): void 
-}>()
+import { Guide, getGuidePaths } from "@/store";
+import { getFileUrl, saveAs } from "@/utils";
+import { getResource } from "@/env";
+import { computed, watchEffect, nextTick, ref } from "vue";
+import { playSceneGuide, isScenePlayIng, pauseSceneGuide } from "@/sdk";
+import { VideoRecorder } from "@simaq/core";
+
+const props = withDefaults(defineProps<{ guide: Guide; edit?: boolean }>(), {
+  edit: true,
+});
+
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "play"): void;
+  (e: "edit"): void;
+}>();
 
 const menus = [
-  { label: '编辑', value: 'edit' },
-  { label: '下载', value: 'download' },
-  { label: '删除', value: 'delete' },
-]
+  { label: "编辑", value: "edit" },
+  { label: "下载", value: "download" },
+  { label: "删除", value: "delete" },
+];
+const downloading = ref(false);
+const videoConfig = ref({
+  resolution: "1080p",
+  frameRate: 60,
+});
 const actions = {
-  edit: () => emit('edit'),
-  delete: () => emit('delete'),
-  download: () => {
+  edit: () => emit("edit"),
+  delete: () => emit("delete"),
+  download: async () => {
+    downloading.value = true;
+    await new Promise<void>((resolve) => {
+      const stop = watchEffect(() => {
+        if (downloading.value === false) {
+          stop();
+          resolve();
+        }
+      });
+    });
     const config: any = {
       // uploadUrl: '',
-      // resolution: '4k',
+      // resolution: '1080p' | '2k' | '4k';
+      // frameRate: [30, 60, 90]
       // autoDownload: false,
       // systemAudio: true,
       // debug: true,
-      resolution: '4k',
+      resolution: videoConfig.value.resolution || "1080p",
       autoDownload: false,
-      platform: 'canvas',
-      
+      platform: "canvas",
+
       config: {
-        frameRate: 60,
+        frameRate: videoConfig.value.frameRate || 60,
         canvasId: ".scene-canvas > canvas",
       },
       disbaledAudio: false,
       systemAudio: false,
       debug: false,
-    }
-  
+    };
+
     const videoRecorder = new VideoRecorder(config);
-    videoRecorder.startRecord()
+    videoRecorder.startRecord();
 
-    let stopWatch: () => void
+    let stopWatch: () => void;
     const stopRecord = () => {
-      stopWatch && stopWatch()
-      pauseSceneGuide()
-    }
+      stopWatch && stopWatch();
+      pauseSceneGuide();
+    };
 
-    videoRecorder.on('record', blob => {
-      saveAs(new File([blob], '录屏.mp4', { type: 'video/mp4; codecs=h264' }), props.guide.title + ".mp4")
-    })
+    videoRecorder.on("record", (blob) => {
+      saveAs(
+        new File([blob], "录屏.mp4", { type: "video/mp4; codecs=h264" }),
+        props.guide.title + ".mp4"
+      );
+    });
 
-    videoRecorder.off('*')
-    videoRecorder.on('startRecord', () => {
-      playSceneGuide(paths.value, undefined, true)
+    videoRecorder.off("*");
+    videoRecorder.on("startRecord", () => {
+      playSceneGuide(paths.value, undefined, true);
       stopWatch = watchEffect(() => {
-      if (!isScenePlayIng.value) {
-        videoRecorder.endRecord()
-        nextTick(stopWatch)
-      }
-    })
-    })
-    videoRecorder.on('cancelRecord', stopRecord)
-    videoRecorder.on('endRecord', stopRecord)
-  }
-}
-const paths = computed(() => getGuidePaths(props.guide))
-
+        if (!isScenePlayIng.value) {
+          videoRecorder.endRecord();
+          nextTick(stopWatch);
+        }
+      });
+    });
+    videoRecorder.on("cancelRecord", stopRecord);
+    videoRecorder.on("endRecord", stopRecord);
+  },
+};
+const paths = computed(() => getGuidePaths(props.guide));
 </script>
 
 <style lang="scss" scoped>
@@ -123,10 +180,10 @@ const paths = computed(() => getGuidePaths(props.guide))
     .guide-cover {
       position: relative;
       &::after {
-        content: '';
+        content: "";
         position: absolute;
         inset: 0;
-        background: rgba(0,0,0,.2)
+        background: rgba(0, 0, 0, 0.2);
       }
 
       .icon {
@@ -144,7 +201,7 @@ const paths = computed(() => getGuidePaths(props.guide))
         object-fit: cover;
         border-radius: 4px;
         overflow: hidden;
-        background-color: rgba(255,255,255,.6);
+        background-color: rgba(255, 255, 255, 0.6);
         display: block;
       }
     }
@@ -159,11 +216,85 @@ const paths = computed(() => getGuidePaths(props.guide))
       }
     }
   }
-  
+
   .actions {
     flex: none;
-  }  
+  }
+}
+
+.edit-add-type {
+  color: #fff;
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.3);
+  backdrop-filter: blur(4px);
+  z-index: 2000;
+  padding: 20px;
+  overflow-y: auto;
+
+  .edit-hot-item {
+    margin: 100px auto 20px;
+    width: 400px;
+    padding: 20px;
+    background: rgba(27, 27, 28, 0.8);
+    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
+    border-radius: 4px;
+
+    .input {
+      margin-bottom: 10px;
+    }
+  }
 }
 
+.edit-hot {
+  margin-top: 20px;
+  text-align: right;
 
-</style>
+  span {
+    font-size: 14px;
+    color: rgba(255, 255, 255, 0.6);
+    cursor: pointer;
+  }
+}
+
+.edit-close {
+  position: absolute;
+  cursor: pointer;
+  top: calc((100% - 18px) / 2);
+  right: 0;
+  transform: translateY(-50%);
+}
+
+.edit-title {
+  padding-bottom: 18px;
+  margin-bottom: 18px;
+  position: relative;
+  color: #fff;
+
+  &::after {
+    content: "";
+    position: absolute;
+    left: -20px;
+    right: -20px;
+    height: 1px;
+    bottom: 0;
+    background-color: rgba(255, 255, 255, 0.16);
+  }
+}
+
+.edit-title {
+  padding-bottom: 18px;
+  margin-bottom: 18px;
+  position: relative;
+
+  &::after {
+    content: "";
+    position: absolute;
+    left: -20px;
+    right: -20px;
+    height: 1px;
+    bottom: 0;
+    background-color: rgba(255, 255, 255, 0.16);
+  }
+}
+</style>

+ 252 - 66
src/views/setting/index.vue

@@ -3,56 +3,133 @@
     <ui-group title="初始画面" borderBottom>
       <ui-group-option>
         <div class="init-pic" :class="{ disabled: isEdit }">
-          <img :src="getFileUrl(setting!.cover)" class="init-puc-cover">
+          <img :src="getFileUrl(setting!.cover)" class="init-puc-cover" />
           <div class="init-pic-set" @click="enterSetPic">设置</div>
         </div>
       </ui-group-option>
     </ui-group>
 
-    <ui-group title="设置天空">
+    <ui-group title="指北针">
+      <template #icon>
+        <ui-icon
+          ctrl
+          :type="setting?.openCompass ? 'eye-s' : 'eye-n'"
+          @click="changeBack(setting!.back, setting!.backType, !setting!.openCompass)"
+        />
+      </template>
+    </ui-group>
+
+    <ui-group title="设置背景">
       <ui-group-option>
         <div class="back-layout">
-          <div 
-            v-for="back in backs" 
-            :key="back.value" 
-            class="back-item" 
-            :class="{ [back.type]: true, active: setting!.back === back.value}"
-            @click="setting!.back !== back.value && changeBack(back.value)"
+          <div
+            v-for="back in settingResources"
+            :key="back.resource"
+            class="back-item"
+            :class="{ [back.backType]: true, active: setting!.back === back.resource }"
+            @click="setting!.back !== back.resource && changeBack(back.resource, back.backType, setting!.openCompass)"
           >
-            <img :src="back.image" v-if="back.type === 'img'">
-            <i class="iconfont" :class="back.image" v-else-if="back.type === 'icon'" />
-            <span :style="{background: back.image}" v-else></span>
-            <p class="back-item-desc">{{ back.label }}</p>
+            <img :src="back.resource" v-if="back.backType === 'img'" />
+            <i
+              class="iconfont"
+              :class="back.resource"
+              v-else-if="back.backType === 'icon'"
+            />
+            <span :style="{ background: back.resource }" v-else></span>
+            <p class="back-item-desc">
+              {{
+                (settingResourceTypeDesc[back.backType] &&
+                  settingResourceTypeDesc[back.backType] + "-") + back.name
+              }}
+            </p>
           </div>
+          <ui-input
+            class="input"
+            preview
+            accept=".jpg, .jpeg, .png"
+            @update:modelValue="iconUpload"
+            type="file"
+          >
+            <template v-slot:replace>
+              <div class="back-item icon">
+                <i class="iconfont icon-add" />
+              </div>
+            </template>
+          </ui-input>
         </div>
       </ui-group-option>
     </ui-group>
   </RightFillPano>
+
+  <div class="edit-add-type" v-if="addTemp">
+    <div class="edit-hot-item">
+      <h3 class="edit-title">
+        背景图
+        <ui-icon type="close" ctrl @click.stop="addTemp = undefined" class="edit-close" />
+      </h3>
+
+      <ui-input
+        require
+        class="input"
+        width="100%"
+        placeholder="请输入背景图名称标注"
+        type="text"
+        v-model="addTemp.name"
+        maxlength="15"
+      />
+      <ui-input
+        require
+        class="input"
+        width="100%"
+        placeholder="请输入背景图名称标注"
+        type="select"
+        :options="options"
+        v-model="addTemp.backType"
+        maxlength="15"
+      />
+      <div class="edit-hot">
+        <a
+          @click="
+            () => {
+              addSettingResource(addTemp!);
+              addTemp = undefined;
+              fetchSettingResources();
+            }
+          "
+        >
+          <ui-icon type="nav-edit" />
+          确定
+        </a>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script lang="ts" setup>
-import { RightFillPano } from '@/layout'
-import { enterEdit, enterOld, setting, isEdit, updataSetting } from '@/store'
-import { reactive, ref, watchEffect } from 'vue'
-import { togetherCallback, getFileUrl, loadPack } from '@/utils'
-import { showRightPanoStack, showRightCtrlPanoStack } from '@/env'
-import { sdk } from '@/sdk'
-
-const backs = ref<{ label: string, type: string, image: string, value: string}[]>([])
-watchEffect(async () => {
-  backs.value = [
-      { label: '无', type: 'icon', image: 'icon-without', value: 'none' },
-      { label: '蓝天白云', type: 'img', image: (await import("./images/pic_ltby@2x.png")).default, value: (await import("./images/蓝天白云.jpg")).default },
-      { label: '乌云密布', type: 'img', image: (await import("./images/pic_wymb@2x.png")).default, value: (await import("./images/乌云密布.jpg")).default},
-      { label: '夜空', type: 'img', image: (await import("./images/pic_yk@2x.png")).default, value: (await import("./images/夜空.jpg")).default },
-      { label: '草地', type: 'img', image: (await import("./images/pic_cd@2x.png")).default, value: (await import("./images/草地.jpg")).default },
-      { label: '道路', type: 'img', image: (await import("./images/pic_dl@2x.png")).default, value: (await import("./images/道路.jpg")).default },
-      { label: '傍晚', type: 'img', image: (await import("./images/pic_bw@2x.png")).default, value: (await import("./images/傍晚.jpg")).default },
-      { label: '灰色', type: 'color', image: '#333333', value: '#333' },
-      { label: '黑色', type: 'color', image: '#000000', value: '#000' },
-      { label: '白色', type: 'color', image: '#ffffff', value: '#fff' },
-  ]
-})
+import { RightFillPano } from "@/layout";
+import {
+  enterEdit,
+  enterOld,
+  setting,
+  isEdit,
+  updataSetting,
+  caseProject,
+} from "@/store";
+import { ref } from "vue";
+import { togetherCallback, getFileUrl, loadPack } from "@/utils";
+import { showRightPanoStack, showRightCtrlPanoStack } from "@/env";
+import { sdk, setBackdrop } from "@/sdk";
+import {
+  fetchSettingResources,
+  settingResources,
+  settingResourceTypeDesc,
+} from "@/api/setting-resource";
+import { uploadFile } from "@/api";
+import { SettingResource, addSettingResource } from "@/api/setting-resource";
+import { SettingResourceType } from "@/api/setting-resource";
+import { Dialog } from "bill/index";
+
+fetchSettingResources();
 
 const enterSetPic = () => {
   enterEdit(
@@ -60,44 +137,90 @@ const enterSetPic = () => {
       showRightPanoStack.push(ref(false)),
       showRightCtrlPanoStack.push(ref(false)),
     ])
-  )
+  );
   enterOld(async () => {
-    const dataURL = await sdk.screenshot(300, 150)
-    const res = await fetch(dataURL)
-    const blob = await res.blob()
+    const dataURL = await sdk.screenshot(300, 150);
+    const res = await fetch(dataURL);
+    const blob = await res.blob();
     setting.value = {
       ...setting.value!,
       cover: { url: dataURL, blob },
-      pose: sdk.getPose()
-    }
-    await updataSetting()
-  })
-}
+      pose: sdk.getPose(),
+    };
+    await updataSetting();
+  });
+};
+
+const initBack = setting.value!.back;
+const initType = setting.value!.backType;
+const initOpenCompass = setting.value!.openCompass;
+let isFirst = true;
+const changeBack = (back: string, type: SettingResourceType, openCompass: boolean) => {
+  if (type === SettingResourceType.map && !caseProject.value!.tmProject?.latlng) {
+    Dialog.alert("当前案件没绑定经纬度,无法开启地图功能");
+    return;
+  }
 
-const initBack = setting.value!.back
-let isFirst = true
-const changeBack = (back: string) => {
-  setting.value!.back = back
-  sdk.setBackdrop(back)
+  setting.value!.back = back;
+  setting.value!.backType = type;
+  setting.value!.openCompass = openCompass;
+
+  setBackdrop(back, type);
+  (document.querySelector("#direction") as HTMLDivElement)!.style.display = openCompass
+    ? "block"
+    : "none";
 
   if (isFirst) {
-    let isSave = false
-    isFirst = false
+    let isSave = false;
+    isFirst = false;
     enterEdit(() => {
       if (!isSave) {
-        setting.value!.back = initBack
-        sdk.setBackdrop(initBack)
+        setting.value!.back = initBack;
+        setting.value!.backType = initType;
+        setting.value!.openCompass = initOpenCompass;
+
+        setBackdrop(initBack, initType);
+        (document.querySelector(
+          "#direction"
+        ) as HTMLDivElement)!.style.display = initOpenCompass ? "block" : "none";
+
+        if (setting.value?.backType !== SettingResourceType.icon) {
+          setting.value?.back &&
+            sdk.setBackdrop(setting.value.back, setting.value.backType);
+        } else {
+          sdk.setBackdrop("none", setting.value.backType);
+        }
       }
-      isFirst = true
-    })
+      isFirst = true;
+    });
     enterOld(async () => {
-      isSave = true
+      isSave = true;
 
-      await loadPack(updataSetting())
-    })
+      await loadPack(updataSetting());
+    });
   }
-}
+};
+const options = [
+  {
+    value: SettingResourceType.envImage,
+    label: settingResourceTypeDesc[SettingResourceType.envImage],
+  },
+  {
+    value: SettingResourceType.bottomImage,
+    label: settingResourceTypeDesc[SettingResourceType.bottomImage],
+  },
+];
+const addTemp = ref<Omit<SettingResource, "id">>();
+const iconUpload = async (data: any) => {
+  console.log(data);
+  addTemp.value = {
+    resource: await uploadFile({ blob: data.file as any, url: "" }),
+    name: "",
+    backType: SettingResourceType.envImage,
+  };
 
+  console.log(addTemp.value);
+};
 </script>
 
 <style scoped lang="scss">
@@ -111,8 +234,7 @@ const changeBack = (back: string) => {
 .init-puc-cover {
   width: 100%;
   height: 100%;
-  object-fit: cover
-  
+  object-fit: cover;
 }
 
 .init-pic-set {
@@ -120,7 +242,7 @@ const changeBack = (back: string) => {
   bottom: 0;
   left: 0;
   right: 0;
-  background-color: rgba(0,0,0,0.5);
+  background-color: rgba(0, 0, 0, 0.5);
   font-size: 12px;
   color: #fff;
   line-height: 32px;
@@ -136,14 +258,19 @@ const changeBack = (back: string) => {
 }
 
 .back-item {
-  > span, .iconfont, img {
+  > span,
+  .iconfont,
+  img {
     display: block;
     height: 88px;
     cursor: pointer;
     outline: 2px solid transparent;
-    transition: all .3s;
+    transition: all 0.3s;
     border-radius: 4px;
+    width: 88px;
+    object-fit: cover;
   }
+
   .iconfont {
     display: flex;
     align-items: center;
@@ -157,8 +284,10 @@ const changeBack = (back: string) => {
   }
 
   &.active {
-    > span, .iconfont, img {
-      outline-color:  #00C8AF;
+    > span,
+    .iconfont,
+    img {
+      outline-color: #00c8af;
     }
   }
 }
@@ -169,4 +298,61 @@ const changeBack = (back: string) => {
   margin-top: 10px;
   text-align: center;
 }
-</style>
+.edit-add-type {
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.3);
+  backdrop-filter: blur(4px);
+  z-index: 2000;
+  padding: 20px;
+  overflow-y: auto;
+
+  .edit-hot-item {
+    margin: 100px auto 20px;
+    width: 400px;
+    padding: 20px;
+    background: rgba(27, 27, 28, 0.8);
+    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
+    border-radius: 4px;
+
+    .input {
+      margin-bottom: 10px;
+    }
+  }
+}
+
+.edit-hot {
+  margin-top: 20px;
+  text-align: right;
+
+  span {
+    font-size: 14px;
+    color: rgba(255, 255, 255, 0.6);
+    cursor: pointer;
+  }
+}
+
+.edit-close {
+  position: absolute;
+  cursor: pointer;
+  top: calc((100% - 18px) / 2);
+  right: 0;
+  transform: translateY(-50%);
+}
+
+.edit-title {
+  padding-bottom: 18px;
+  margin-bottom: 18px;
+  position: relative;
+
+  &::after {
+    content: "";
+    position: absolute;
+    left: -20px;
+    right: -20px;
+    height: 1px;
+    bottom: 0;
+    background-color: rgba(255, 255, 255, 0.16);
+  }
+}
+</style>

+ 209 - 158
src/views/tagging/edit.vue

@@ -1,199 +1,251 @@
 <template>
   <div class="edit-hot-layer">
-      <div class="edit-hot-item">
-        <h3 class="edit-title">
-          标注
-          <ui-icon type="close" ctrl @click.stop="$emit('quit')" class="edit-close" />
-        </h3>
-        <StylesManage 
-          :styles="styles" 
-          :active="(getTaggingStyle(tagging.styleId) as TaggingStyle)" 
-          @change="style => tagging.styleId = style.id" 
-          @delete="deleteStyle"
-          @uploadStyle="uploadStyle" 
-        />
-        <ui-input 
-          require 
-          class="input" 
-          width="100%" 
-          placeholder="请输入热点标题" 
-          type="text" 
-          v-model="tagging.title"
-          maxlength="15" 
-        />
-        <ui-input
-          class="input"
-          width="100%"
-          height="158px"
-          placeholder="特征描述:"
-          type="richtext"
-          v-model="tagging.desc"
-          :maxlength="200"
-        />
-        <ui-input 
-          class="input preplace" 
-          width="100%" 
-          placeholder="" 
-          type="text" 
-          v-model="tagging.part"
-          :maxlength="60"
-        >
-          <template #preIcon><span>遗留部位:</span></template>
-        </ui-input>
-        <ui-input 
-          class="input preplace" 
-          width="100%" 
-          placeholder="" 
-          type="text" 
-          v-model="tagging.method"
-          :maxlength="60"
-        >
-          <template #preIcon><span>提取方法:</span></template>
-        </ui-input>
-        <ui-input 
-          class="input preplace" 
-          width="100%" 
-          type="text" 
-          placeholder=""
-          v-model="tagging.principal"
-          :maxlength="60"
-        >
-          <template #preIcon><span>提取人:</span></template>
-        </ui-input>
-        <ui-input
-          class="input "
-          type="file"
-          width="100%"
-          height="225px"
-          require
-          preview
-          placeholder="上传图片"
-          othPlaceholder="支持JPG、PNG图片格式,单张不超过5MB,最多支持上传9张。"
-          accept=".jpg, .png"
-          :disable="true"
-          :multiple="true"
-          :maxSize="5 * 1024 * 1024"
-          :maxLen="9"
-          :modelValue="tagging.images"
-          @update:modelValue="fileChange"
-        >
-            <template v-slot:valuable>
-                <Images :tagging="tagging" :hideInfo="true">
-                  <template v-slot:icons="{ active }">
-                    <span @click="delImageHandler(active)" class="del-file">
-                      <ui-icon type="del" ctrl />
-                    </span>
-                  </template>
-                </Images>
+    <div class="edit-hot-item">
+      <h3 class="edit-title">
+        标注
+        <ui-icon type="close" ctrl @click.stop="$emit('quit')" class="edit-close" />
+      </h3>
+      <StylesManage
+        :styles="styles"
+        :active="(getTaggingStyle(tagging.styleId) as TaggingStyle)"
+        @change="(style) => (tagging.styleId = style.id)"
+        @delete="deleteStyle"
+        @uploadStyle="uploadStyle"
+      />
+      <ui-input
+        require
+        class="input"
+        width="100%"
+        placeholder="请输入名称"
+        type="text"
+        v-model="tagging.title"
+        maxlength="15"
+      />
+      <ui-input
+        class="input"
+        width="100%"
+        height="158px"
+        placeholder="特征描述:"
+        type="richtext"
+        v-model="tagging.desc"
+        :maxlength="200"
+      />
+      <ui-input
+        class="input preplace"
+        width="100%"
+        placeholder=""
+        type="text"
+        v-model="tagging.mtype"
+        :maxlength="60"
+      >
+        <template #preIcon><span>类别:</span></template>
+      </ui-input>
+      <ui-input
+        class="input preplace"
+        width="100%"
+        placeholder=""
+        type="text"
+        v-model="tagging.cat"
+        :maxlength="60"
+      >
+        <template #preIcon><span>类型:</span></template>
+      </ui-input>
+      <ui-input
+        class="input preplace"
+        width="100%"
+        placeholder=""
+        type="text"
+        v-model="tagging.part"
+        :maxlength="60"
+      >
+        <template #preIcon><span>遗留部位:</span></template>
+      </ui-input>
+      <ui-input
+        class="input preplace"
+        width="100%"
+        placeholder=""
+        type="text"
+        v-model="tagging.tsms"
+        :maxlength="60"
+      >
+        <template #preIcon><span>特征描述:</span></template>
+      </ui-input>
+      <ui-input
+        class="input preplace"
+        width="100%"
+        placeholder=""
+        type="text"
+        v-model="tagging.method"
+        :maxlength="60"
+      >
+        <template #preIcon><span>提取方法:</span></template>
+      </ui-input>
+      <ui-input
+        class="input preplace"
+        width="100%"
+        placeholder=""
+        type="text"
+        v-model="tagging.time"
+        :maxlength="60"
+      >
+        <template #preIcon><span>提取时间:</span></template>
+      </ui-input>
+      <ui-input
+        class="input preplace"
+        width="100%"
+        type="text"
+        placeholder=""
+        v-model="tagging.principal"
+        :maxlength="60"
+      >
+        <template #preIcon><span>提取人:</span></template>
+      </ui-input>
+      <ui-input
+        class="input"
+        type="file"
+        width="100%"
+        height="225px"
+        require
+        preview
+        placeholder="上传图片"
+        othPlaceholder="支持JPG、PNG图片格式,单张不超过5MB,最多支持上传9张。"
+        accept=".jpg, .png"
+        :disable="true"
+        :multiple="true"
+        :maxSize="5 * 1024 * 1024"
+        :maxLen="9"
+        :modelValue="tagging.images"
+        @update:modelValue="fileChange"
+      >
+        <template v-slot:valuable>
+          <Images :tagging="tagging" :hideInfo="true">
+            <template v-slot:icons="{ active }">
+              <span @click="delImageHandler(active)" class="del-file">
+                <ui-icon type="del" ctrl />
+              </span>
             </template>
-        </ui-input>
-        <div class="edit-hot" >
-          <a @click="submitHandler">
-            <ui-icon type="nav-edit" />
-            确定
-          </a>
-        </div>
+          </Images>
+        </template>
+      </ui-input>
+      <div class="edit-hot">
+        <a @click="submitHandler">
+          <ui-icon type="nav-edit" />
+          确定
+        </a>
       </div>
+    </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import StylesManage from './styles.vue'
-import Images from './images.vue'
-import { computed, ref, watchEffect } from 'vue';
-import { Dialog, Message } from 'bill/index';
-import { 
+import StylesManage from "./styles.vue";
+import Images from "./images.vue";
+import { computed, ref, watchEffect } from "vue";
+import { Dialog, Message } from "bill/index";
+import {
   taggingStyles,
-  Tagging, 
-  getTaggingStyle, 
+  Tagging,
+  getTaggingStyle,
   TaggingStyle,
   taggings,
   isTemploraryID,
-defaultStyle
-} from '@/store'
+  defaultStyle,
+} from "@/store";
 
 export type EditProps = {
-  data: Tagging
-}
+  data: Tagging;
+};
 
-const props = defineProps<EditProps>()
-const emit = defineEmits<{ (e: 'quit'): void, (e: 'save', data: Tagging): void }>()
-const tagging = ref<Tagging>({...props.data, images: [...props.data.images]})
-const activeStyle = computed(() => getTaggingStyle(tagging.value.styleId))
+const props = defineProps<EditProps>();
+const emit = defineEmits<{ (e: "quit"): void; (e: "save", data: Tagging): void }>();
+const tagging = ref<Tagging>({ ...props.data, images: [...props.data.images] });
+const activeStyle = computed(() => getTaggingStyle(tagging.value.styleId));
 
 watchEffect(() => {
   if (!activeStyle.value && defaultStyle.value) {
-    tagging.value.styleId = defaultStyle.value.id
+    tagging.value.styleId = defaultStyle.value.id;
   }
-})
+});
 
 const submitHandler = () => {
   if (!tagging.value.title.trim()) {
-    Message.error('标注标题必须填写!')
+    Message.error("标注标题必须填写!");
   } else if (!tagging.value.images.length) {
-    Message.error('至少上传一张图片!')
+    Message.error("至少上传一张图片!");
   } else {
-    emit('save', tagging.value)
+    emit("save", tagging.value);
   }
-}
+};
 
-const styles = computed(() => 
-  [...taggingStyles.value].sort((a, b) => 
-    a.default ? -1 : b.default ? 1 :
-    a.lastUse ? -1 : b.lastUse ? 1 : isTemploraryID(a.id) ? -1 : isTemploraryID(b.id) ? 1 : 0
+const styles = computed(() =>
+  [...taggingStyles.value].sort((a, b) =>
+    a.default
+      ? -1
+      : b.default
+      ? 1
+      : a.lastUse
+      ? -1
+      : b.lastUse
+      ? 1
+      : isTemploraryID(a.id)
+      ? -1
+      : isTemploraryID(b.id)
+      ? 1
+      : 0
   )
-)
+);
 
 const deleteStyle = (style: TaggingStyle) => {
-    const index = taggingStyles.value.indexOf(style)
-    if (~index) {
-      taggingStyles.value.splice(index, 1)
-      for (const item of taggings.value) {
-        if (item.styleId === style.id) {
-          const defaultIcon = taggingStyles.value.find(({ default: isDefault }) => isDefault)?.id
-          if (defaultIcon) {
-            item.styleId = defaultIcon
-          }
+  const index = taggingStyles.value.indexOf(style);
+  if (~index) {
+    taggingStyles.value.splice(index, 1);
+    for (const item of taggings.value) {
+      if (item.styleId === style.id) {
+        const defaultIcon = taggingStyles.value.find(
+          ({ default: isDefault }) => isDefault
+        )?.id;
+        if (defaultIcon) {
+          item.styleId = defaultIcon;
         }
       }
     }
-}
+  }
+};
 
 const uploadStyle = (style: TaggingStyle) => {
-  taggingStyles.value.push(style)
-  tagging.value.styleId = style.id
-}
+  taggingStyles.value.push(style);
+  tagging.value.styleId = style.id;
+};
 
-type LocalImageFile = { file: File; preview: string } | Tagging['images'][number]
+type LocalImageFile = { file: File; preview: string } | Tagging["images"][number];
 const fileChange = (file: LocalImageFile | LocalImageFile[]) => {
-  const files = Array.isArray(file) ? file : [file]
+  const files = Array.isArray(file) ? file : [file];
 
-  tagging.value.images = files.map(atom => {
-    if (typeof atom === 'string' || 'blob' in atom) {
-      return atom
+  tagging.value.images = files.map((atom) => {
+    if (typeof atom === "string" || "blob" in atom) {
+      return atom;
     } else {
       return {
         blob: atom.file,
-        url: atom.preview
-      }
+        url: atom.preview,
+      };
     }
-  })
-}
+  });
+};
 
-const delImageHandler = async (file: Tagging['images'][number]) => {
-  const index = tagging.value.images.indexOf(file)
+const delImageHandler = async (file: Tagging["images"][number]) => {
+  const index = tagging.value.images.indexOf(file);
   if (~index && (await Dialog.confirm(`确定要删除此数据吗?`))) {
-    tagging.value.images.splice(index, 1)
+    tagging.value.images.splice(index, 1);
   }
-}
-
+};
 </script>
 
 <style lang="scss" scoped>
 .edit-hot-layer {
   position: fixed;
   inset: 0;
-  background: rgba(0,0,0,0.3000);
+  background: rgba(0, 0, 0, 0.3);
   backdrop-filter: blur(4px);
   z-index: 2000;
   padding: 20px;
@@ -205,13 +257,12 @@ const delImageHandler = async (file: Tagging['images'][number]) => {
   width: 400px;
   padding: 20px;
   background: rgba(27, 27, 28, 0.8);
-  box-shadow: 0px 0px 10px 0px rgba(0,0,0, 0.3);
+  box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
   border-radius: 4px;
 
   .input {
     margin-bottom: 10px;
   }
-
 }
 .edit-close {
   position: absolute;
@@ -227,13 +278,13 @@ const delImageHandler = async (file: Tagging['images'][number]) => {
   position: relative;
 
   &::after {
-    content: '';
+    content: "";
     position: absolute;
     left: -20px;
     right: -20px;
     height: 1px;
     bottom: 0;
-    background-color: rgba(255, 255, 255, 0.16);;
+    background-color: rgba(255, 255, 255, 0.16);
   }
 }
 
@@ -249,12 +300,12 @@ const delImageHandler = async (file: Tagging['images'][number]) => {
 }
 </style>
 <style>
-.edit-hot-item .preplace input{
+.edit-hot-item .preplace input {
   padding-left: 76px !important;
 }
 
 .edit-hot-item .preplace .pre-icon {
-  color: rgba(255,255,255,0.6000);
+  color: rgba(255, 255, 255, 0.6);
   width: 70px;
   text-align: right;
 }
@@ -264,15 +315,15 @@ const delImageHandler = async (file: Tagging['images'][number]) => {
   width: 32px;
   height: 32px;
   font-size: 16px;
-  background-color: rgba(0,0,0,0.5);
+  background-color: rgba(0, 0, 0, 0.5);
   border-radius: 50%;
   text-align: center;
-  line-height: 32px
+  line-height: 32px;
 }
 </style>
 
 <style>
-  .edit-hot-layer .input.ui-input .text.suffix input {
-    padding-right: 60px;
-  }
-</style>
+.edit-hot-layer .input.ui-input .text.suffix input {
+  padding-right: 60px;
+}
+</style>

+ 2 - 2
vite.config.ts

@@ -11,8 +11,8 @@ const proxy = {
     changeOrigin: true,
     rewrite: path => path.replace(/^\/local/, '')
   },
-  '/fusion': {
-    target: config.dev ? 'https://test-mix3d.4dkankan.com' : 'https://mix3d.4dkankan.com',
+  '/fusion-xj': {
+    target: config.dev ? 'https://xj-mix3d.4dkankan.com/' : 'https://mix3d.4dkankan.com',
     changeOrigin: true,
     rewrite: path => path.replace(/^\/api/, '')
   },