浏览代码

fix: Merge branch 'new-dev' into dev

bill 2 年之前
父节点
当前提交
657fbbe2cc
共有 89 个文件被更改,包括 2666 次插入943 次删除
  1. 1020 350
      public/lib/potree/potree.js
  2. 1 1
      public/lib/potree/potree.js.map
  3. 2 0
      src/api/constant.ts
  4. 12 8
      src/api/fuse-model.ts
  5. 2 0
      src/api/guide-path.ts
  6. 17 20
      src/api/instance.ts
  7. 2 2
      src/api/mesasure.ts
  8. 5 3
      src/api/record.ts
  9. 22 3
      src/api/scene.ts
  10. 13 12
      src/api/setup.ts
  11. 10 2
      src/api/sys.ts
  12. 4 3
      src/api/tagging-position.ts
  13. 7 1
      src/api/tagging-style.ts
  14. 10 2
      src/app.vue
  15. 9 5
      src/components/actions/index.vue
  16. 1 1
      src/components/bill-ui/assets/scss/components/_input.scss
  17. 1 0
      src/components/bill-ui/assets/scss/components/_slide.scss
  18. 2 2
      src/components/bill-ui/components/slide/index.vue
  19. 1 1
      src/components/control-panl/style.scss
  20. 1 1
      src/components/list/index.vue
  21. 20 34
      src/components/static-preview/index.vue
  22. 54 0
      src/components/static-preview/sign.vue
  23. 12 4
      src/components/static-preview/style.scss
  24. 8 2
      src/components/tagging/sign.vue
  25. 10 5
      src/env/index.ts
  26. 6 0
      src/layout/edit/fuse-edit.vue
  27. 2 4
      src/layout/edit/header/index.vue
  28. 53 11
      src/layout/edit/scene-select.vue
  29. 3 8
      src/layout/model-list/index.vue
  30. 28 16
      src/layout/model-list/sign.vue
  31. 10 0
      src/layout/model-list/style.scss
  32. 27 2
      src/layout/right-fill-pano.vue
  33. 30 8
      src/layout/scene-list/index.vue
  34. 42 10
      src/layout/show/index.vue
  35. 53 22
      src/layout/show/slide-menu.vue
  36. 47 1
      src/main.ts
  37. 20 9
      src/model/app.vue
  38. 9 1
      src/model/index.ts
  39. 2 1
      src/model/platform.ts
  40. 2 3
      src/router/config.ts
  41. 6 1
      src/router/constant.ts
  42. 27 14
      src/router/index.ts
  43. 64 28
      src/sdk/association.ts
  44. 92 44
      src/sdk/cover/index.js
  45. 2 2
      src/sdk/sdk.ts
  46. 42 21
      src/store/fuse-model.ts
  47. 16 1
      src/store/guide-path.ts
  48. 10 2
      src/store/measure.ts
  49. 16 5
      src/store/record.ts
  50. 1 1
      src/store/scene.ts
  51. 18 4
      src/store/sys.ts
  52. 2 0
      src/store/tagging-style.ts
  53. 15 12
      src/store/tagging.ts
  54. 6 1
      src/store/view.ts
  55. 1 1
      src/utils/loading.ts
  56. 10 0
      src/utils/stack.ts
  57. 0 2
      src/utils/store-help.ts
  58. 1 0
      src/utils/video-cover.ts
  59. 39 25
      src/views/folder/index.vue
  60. 1 2
      src/views/guide/edit-paths.vue
  61. 15 9
      src/views/guide/index.vue
  62. 2 3
      src/views/guide/show.vue
  63. 1 1
      src/views/guide/sign.vue
  64. 1 1
      src/views/measure/edit.vue
  65. 17 9
      src/views/measure/index.vue
  66. 2 6
      src/views/measure/show.vue
  67. 70 16
      src/views/measure/sign.vue
  68. 13 8
      src/views/merge/index.vue
  69. 7 2
      src/views/proportion/index.vue
  70. 15 18
      src/views/record/index.vue
  71. 95 0
      src/views/record/shot-imitate.vue
  72. 93 40
      src/views/record/shot.vue
  73. 5 3
      src/views/record/show.vue
  74. 28 10
      src/views/record/sign.vue
  75. 6 1
      src/views/record/style.scss
  76. 40 8
      src/views/registration/index.vue
  77. 18 5
      src/views/sign-model/index.vue
  78. 22 19
      src/views/summary/index.vue
  79. 3 1
      src/views/tagging-position/index.vue
  80. 47 17
      src/views/tagging/edit.vue
  81. 25 13
      src/views/tagging/index.vue
  82. 1 4
      src/views/tagging/show.vue
  83. 5 2
      src/views/tagging/sign.vue
  84. 8 5
      src/views/tagging/styles.vue
  85. 109 0
      src/views/test/index.vue
  86. 23 7
      src/views/view/index.vue
  87. 2 3
      src/views/view/show.vue
  88. 30 8
      src/views/view/sign.vue
  89. 14 0
      src/views/view/style.scss

文件差异内容过多而无法显示
+ 1020 - 350
public/lib/potree/potree.js


文件差异内容过多而无法显示
+ 1 - 1
public/lib/potree/potree.js.map


+ 2 - 0
src/api/constant.ts

@@ -12,6 +12,7 @@ export const UPLOAD_HEADS = {
   'Content-Type': 'multipart/form-data'
   'Content-Type': 'multipart/form-data'
 }
 }
 
 
+export const CASE_INFO = `/fusion/case/getInfo`
 
 
 // 融合模型列表
 // 融合模型列表
 export const FUSE_MODEL_LIST = `/fusion/caseFusion/list`
 export const FUSE_MODEL_LIST = `/fusion/caseFusion/list`
@@ -19,6 +20,7 @@ export const FUSE_INSERT_MODEL = `/fusion/caseFusion/add`
 export const FUSE_UPDATE_MODEL = `/fusion/caseFusion/update`
 export const FUSE_UPDATE_MODEL = `/fusion/caseFusion/update`
 export const FUSE_DELETE_MODEL = `/fusion/caseFusion/delete`
 export const FUSE_DELETE_MODEL = `/fusion/caseFusion/delete`
 // 场景列表
 // 场景列表
+export const SCENE_LIST_ALL = `/fusion/api/scene/list`
 export const MODEL_LIST = `/fusion/case/sceneList`
 export const MODEL_LIST = `/fusion/case/sceneList`
 export const MODEL_SIGN = `/fusion/model/getInfo`
 export const MODEL_SIGN = `/fusion/model/getInfo`
 
 

+ 12 - 8
src/api/fuse-model.ts

@@ -7,7 +7,7 @@ import {
 } from './constant'
 } from './constant'
 import { params } from '@/env'
 import { params } from '@/env'
 
 
-import { Scene, SceneType } from './scene'
+import { Scene, SceneStatus, SceneType } from './scene'
 
 
 
 
 export interface FuseModelAttrs {
 export interface FuseModelAttrs {
@@ -26,8 +26,10 @@ export interface FuseModel extends FuseModelAttrs {
   url: string
   url: string
   title: string
   title: string
   fusionId: number,
   fusionId: number,
+  modelType: string,
   type: SceneType
   type: SceneType
   size: number,
   size: number,
+  status: SceneStatus,
   time: string
   time: string
 }
 }
 
 
@@ -46,21 +48,23 @@ interface ServiceFuseModel {
 }
 }
 
 
 const serviceToLocal = (serviceModel: ServiceFuseModel): FuseModel => ({
 const serviceToLocal = (serviceModel: ServiceFuseModel): FuseModel => ({
-  show: !serviceModel.hide,
+  show: true,
   scale: serviceModel.transform.scale[0],
   scale: serviceModel.transform.scale[0],
   opacity: serviceModel.opacity || 1,
   opacity: serviceModel.opacity || 1,
   bottom: serviceModel.bottom || 0,
   bottom: serviceModel.bottom || 0,
   fusionNumId: serviceModel.fusionNumId,
   fusionNumId: serviceModel.fusionNumId,
+  modelType: serviceModel.sceneData?.modelDateType,
   position: serviceModel.transform.position,
   position: serviceModel.transform.position,
   rotation: serviceModel.transform.rotation,
   rotation: serviceModel.transform.rotation,
   id: serviceModel.fusionNumId.toString(),
   id: serviceModel.fusionNumId.toString(),
-  url: serviceModel.sceneData.type === SceneType.SWSS ? serviceModel.sceneData.num : serviceModel.sceneData.modelGlbUrl,
-  title: serviceModel.sceneData.name || serviceModel.sceneData.sceneName || serviceModel.sceneData.modelTitle,
-  modelId: serviceModel.sceneData.modelId,
+  url: serviceModel.sceneData ? (serviceModel.sceneData.type === SceneType.SWSS ? serviceModel.sceneData.num : serviceModel.sceneData.modelGlbUrl) : '',
+  title: serviceModel.sceneData ? (serviceModel.sceneData?.name || serviceModel.sceneData.sceneName || serviceModel.sceneData.modelTitle) : '-',
+  modelId: serviceModel.sceneData?.modelId,
   fusionId: serviceModel.fusionId,
   fusionId: serviceModel.fusionId,
-  type: serviceModel.sceneData.type,
-  size: serviceModel.sceneData.modelSize,
-  time: serviceModel.sceneData.createTime
+  type: serviceModel.sceneData?.type,
+  size: serviceModel.sceneData?.modelSize,
+  time: serviceModel.sceneData?.createTime,
+  status: serviceModel.sceneData ? serviceModel.sceneData.status : SceneStatus.ERR
 })
 })
 
 
 const localToService = (model: FuseModel): Omit<ServiceFuseModel, 'sceneData'> => ({
 const localToService = (model: FuseModel): Omit<ServiceFuseModel, 'sceneData'> => ({

+ 2 - 0
src/api/guide-path.ts

@@ -15,6 +15,7 @@ interface ServiceGuidePath {
   position: string
   position: string
   target: string
   target: string
   time: number
   time: number
+  sort: number
   speed: number
   speed: number
   cover: string
   cover: string
 }
 }
@@ -25,6 +26,7 @@ export interface GuidePath {
   position: SceneLocalPos
   position: SceneLocalPos
   target: SceneLocalPos
   target: SceneLocalPos
   time: number
   time: number
+  sort: number
   speed: number
   speed: number
   cover: string
   cover: string
 }
 }

+ 17 - 20
src/api/instance.ts

@@ -2,7 +2,7 @@ import { axiosFactory } from './setup'
 import { Message } from 'bill/index'
 import { Message } from 'bill/index'
 import { showLoad, hideLoad } from '@/utils'
 import { showLoad, hideLoad } from '@/utils'
 import * as URL from './constant'
 import * as URL from './constant'
-import { ResCodeDesc } from './constant'
+import { ResCode, ResCodeDesc } from './constant'
 import { baseURL, params } from '@/env'
 import { baseURL, params } from '@/env'
 
 
 const instance = axiosFactory()
 const instance = axiosFactory()
@@ -10,6 +10,7 @@ const instance = axiosFactory()
 export const {
 export const {
   axios,
   axios,
   addUnsetTokenURLS,
   addUnsetTokenURLS,
+  delUnsetTokenURLS,
   addReqErrorHandler,
   addReqErrorHandler,
   addResErrorHandler,
   addResErrorHandler,
   delReqErrorHandler,
   delReqErrorHandler,
@@ -23,17 +24,28 @@ export const {
   setHook
   setHook
 } = instance
 } = instance
 
 
+const gotoLogin = () => {
+  const loginHref = import.meta.env.DEV ? 'http://localhost:3000' : '/'
+  location.href = loginHref + '?redirect=' + escape(location.href) + '#/login'
+}
+
 addReqErrorHandler(err => {
 addReqErrorHandler(err => {
-  Message.error(err.message)
+  // Message.error(err.message)
+  hideLoad()
+  gotoLogin()
 })
 })
 
 
 addResErrorHandler(
 addResErrorHandler(
   (response, data) => {
   (response, data) => {
-    if (response.status !== 200) {
+    if (response && response.status !== 200) {
       Message.error(response.statusText)
       Message.error(response.statusText)
     } else if (data) {
     } else if (data) {
       const msg = data.code && ResCodeDesc[data.code] ? ResCodeDesc[data.code] : (data?.message || data?.msg)
       const msg = data.code && ResCodeDesc[data.code] ? ResCodeDesc[data.code] : (data?.message || data?.msg)
-      Message.error(msg)
+      if (data.code === ResCode.TOKEN_INVALID) {
+        gotoLogin()
+      } else {
+        Message.error(msg)
+      }
     }
     }
   }
   }
 )
 )
@@ -45,28 +57,13 @@ addHook({
     }
     }
   }, 
   }, 
   after: (config) => {
   after: (config) => {
-    if (config.url !== URL.RECORD_STATUS) {
+    if (!config || config.url !== URL.RECORD_STATUS) {
       hideLoad()
       hideLoad()
     }
     }
   } 
   } 
 })
 })
 
 
-addUnsetTokenURLS(
-  // URL.FUSE_MODEL_LIST,
-  // URL.FUSE_UPDATE_MODEL,
-  // URL.FUSE_INSERT_MODEL,
-  // URL.FUSE_DELETE_MODEL,
-  // URL.TAGGING_LIST,
-  // URL.DELETE_TAGGING,
-  // URL.INSERT_TAGGING,
-  // URL.UPDATE_TAGGING,
-  // URL.TAGGING_POINT_LIST,
-  // URL.INSERT_TAGGING_POINT,
-  // URL.UPDATE_TAGGING_POINT,
-  // URL.DELETE_TAGGING_POINT,
-)
 setDefaultURI(baseURL)
 setDefaultURI(baseURL)
 params.token && setToken(params.token)
 params.token && setToken(params.token)
 
 
-
 export default axios
 export default axios

+ 2 - 2
src/api/mesasure.ts

@@ -39,7 +39,7 @@ const toLocal = (serviceMeasure: ServiceMeasure) : Measure => ({
   fusionId: serviceMeasure.fusionId,
   fusionId: serviceMeasure.fusionId,
   title: serviceMeasure.meterTitle,
   title: serviceMeasure.meterTitle,
   desc: serviceMeasure.length.toString(),
   desc: serviceMeasure.length.toString(),
-  positions: JSON.parse(serviceMeasure.position),
+  positions: JSON.parse(serviceMeasure.position).map((pos: any) => ({modelId: pos.fusionNumId, ...pos})),
   type: serviceMeasure.meterType === ServiceMeasureType.area 
   type: serviceMeasure.meterType === ServiceMeasureType.area 
     ? MeasureType.area
     ? MeasureType.area
     : serviceMeasure.meterType === ServiceMeasureType.vertical
     : serviceMeasure.meterType === ServiceMeasureType.vertical
@@ -56,7 +56,7 @@ const toService = (measure: Measure, isUpdate = true): PartialProps<ServiceMeasu
     : measure.type === MeasureType.vertical
     : measure.type === MeasureType.vertical
     ? ServiceMeasureType.vertical
     ? ServiceMeasureType.vertical
     : ServiceMeasureType.free,
     : ServiceMeasureType.free,
-  position: JSON.stringify(measure.positions),
+  position: JSON.stringify(measure.positions.map(item => ({...item, modelId: undefined, fusionNumId: item.modelId}))),
   length: Number(measure.desc),
   length: Number(measure.desc),
 
 
 })
 })

+ 5 - 3
src/api/record.ts

@@ -14,7 +14,7 @@ export interface Record {
   id: string
   id: string
   cover: string
   cover: string
   title: string
   title: string
-  url: string
+  url: string | null
   sort: number
   sort: number
   status: RecordStatus
   status: RecordStatus
 }
 }
@@ -23,7 +23,7 @@ interface ServiceRecord {
   videoFolderId: number,
   videoFolderId: number,
   videoFolderName: string,
   videoFolderName: string,
   videoFolderCover: string,
   videoFolderCover: string,
-  videoMergeUrl: string,
+  videoMergeUrl: string | null,
   sort: number,
   sort: number,
   uploadStatus: RecordStatus
   uploadStatus: RecordStatus
 }
 }
@@ -56,8 +56,10 @@ export const fetchRecords = async () => {
 
 
 export const fetchRecordStatus = async (recordId: Record['id']) => {
 export const fetchRecordStatus = async (recordId: Record['id']) => {
   const step = await axios.get<number>(RECORD_STATUS, { params: { folderId: Number(recordId) } })
   const step = await axios.get<number>(RECORD_STATUS, { params: { folderId: Number(recordId) } })
-  if (step === 100) {
+  if (step >= 100) {
     return RecordStatus.SUCCESS
     return RecordStatus.SUCCESS
+  } else if (step < 0) {
+    return RecordStatus.ERR
   } else {
   } else {
     return RecordStatus.RUN
     return RecordStatus.RUN
   }
   }

+ 22 - 3
src/api/scene.ts

@@ -1,19 +1,31 @@
 import axios from './instance'
 import axios from './instance'
-import { MODEL_LIST, MODEL_SIGN } from './constant'
+import { MODEL_LIST, MODEL_SIGN, SCENE_LIST_ALL } from './constant'
 import { params } from '@/env'
 import { params } from '@/env'
+import type { PagingResult } from './index'
 
 
 export enum SceneType {
 export enum SceneType {
   SWKK = 0,
   SWKK = 0,
   SWKJ = 1,
   SWKJ = 1,
   SWSS = 2,
   SWSS = 2,
   SWMX = 3,
   SWMX = 3,
+  SWSSMX = 4
+}
+
+export enum SceneStatus {
+  DEL = -1,
+  RUN = 0,
+  ERR = 1,
+  SUCCESS = 2,
+  ARCHIVE = 3,
+  RERUN = 4,
 }
 }
 
 
 export const SceneTypeDesc: Record<SceneType, string>  = {
 export const SceneTypeDesc: Record<SceneType, string>  = {
   [SceneType.SWKK]: '四维看看',
   [SceneType.SWKK]: '四维看看',
   [SceneType.SWKJ]: '四维看见',
   [SceneType.SWKJ]: '四维看见',
-  [SceneType.SWSS]: '四维深时',
+  [SceneType.SWSS]: '四维深时点云场景',
   [SceneType.SWMX]: '三维模型',
   [SceneType.SWMX]: '三维模型',
+  [SceneType.SWSSMX]: '四维深时obj场景'
 }
 }
 
 
 export interface Scene {
 export interface Scene {
@@ -25,6 +37,7 @@ export interface Scene {
   modelId: number
   modelId: number
   modelObjUrl: string
   modelObjUrl: string
   modelSize: number
   modelSize: number
+  status: SceneStatus
   modelTitle: string
   modelTitle: string
   name: string
   name: string
   num: string
   num: string
@@ -44,10 +57,16 @@ const toLocalScene = (scene: Scene) => ({
 })
 })
 
 
 export const fetchScenes = async () => {
 export const fetchScenes = async () => {
-  const scenes = await axios.post<Scenes>(MODEL_LIST, { caseId: params.caseId })
+  const scenes = await axios.get<Scenes>(MODEL_LIST, { params: { caseId: params.caseId } })
+  return scenes.map(toLocalScene)
+}
+
+export const fetchScenesAll = async (params: {numList: Scene['num'][], type: SceneType}) => {
+  const scenes = await axios.post<Scenes>(SCENE_LIST_ALL, params)
   return scenes.map(toLocalScene)
   return scenes.map(toLocalScene)
 }
 }
 
 
+
 export const fetchScene = async (modelId: Scene['modelId']) => {
 export const fetchScene = async (modelId: Scene['modelId']) => {
   const scene = await axios.get<Scene>(MODEL_SIGN, { params: { modelId } })
   const scene = await axios.get<Scene>(MODEL_SIGN, { params: { modelId } })
   return toLocalScene(scene)
   return toLocalScene(scene)

+ 13 - 12
src/api/setup.ts

@@ -7,8 +7,8 @@ export type ResErrorHandler = <D, T extends ResData<D>>(response: AxiosResponse<
 export type ReqErrorHandler = <T>(err: Error, response: AxiosRequestConfig<T>) => void
 export type ReqErrorHandler = <T>(err: Error, response: AxiosRequestConfig<T>) => void
 export type ResData<T> = { code: ResCode, message: string, data: T, msg: string }
 export type ResData<T> = { code: ResCode, message: string, data: T, msg: string }
 export type Hook = {
 export type Hook = {
-  before: (config: AxiosRequestConfig) => void
-  after: (config: AxiosRequestConfig) => void
+  before?: (config: AxiosRequestConfig) => void
+  after?: (config: AxiosRequestConfig) => void
 }
 }
 
 
 export const axiosFactory = () => {
 export const axiosFactory = () => {
@@ -114,7 +114,7 @@ export const axiosFactory = () => {
   }
   }
 
 
   const matchURL = (urls: string[], config: AxiosRequestConfig<any>) => 
   const matchURL = (urls: string[], config: AxiosRequestConfig<any>) => 
-    config.url && urls.includes(config.url)
+    config && config.url && urls.includes(config.url)
 
 
   const callErrorHandler = (key: 'req' | 'res', ...args: any[]) => {
   const callErrorHandler = (key: 'req' | 'res', ...args: any[]) => {
     Promise.resolve()
     Promise.resolve()
@@ -127,16 +127,17 @@ export const axiosFactory = () => {
   axiosRaw.interceptors.request.use(
   axiosRaw.interceptors.request.use(
     config => {
     config => {
       for (const hook of axiosConfig.hook) {
       for (const hook of axiosConfig.hook) {
-        hook.before(config)
+        hook.before && hook.before(config)
       }
       }
 
 
       if (!matchURL(axiosConfig.unTokenSet, config)) {
       if (!matchURL(axiosConfig.unTokenSet, config)) {
         if (!axiosConfig.token) {
         if (!axiosConfig.token) {
-          // if (!matchURL(axiosConfig.unReqErrorSet, config)) {
-          //   const error = new Error('缺少token')
-          //   callErrorHandler('req', error, config)
-          //   throw error
-          // }
+          if (!matchURL(axiosConfig.unReqErrorSet, config)) {
+            console.log(config.url)
+            const error = new Error('缺少token')
+            callErrorHandler('req', error, config)
+            throw error
+          }
         } else {
         } else {
           config.headers = {
           config.headers = {
             ...config.headers,
             ...config.headers,
@@ -151,7 +152,7 @@ export const axiosFactory = () => {
   axiosRaw.interceptors.response.use(
   axiosRaw.interceptors.response.use(
     (response: AxiosResponse<ResData<any>>) => {
     (response: AxiosResponse<ResData<any>>) => {
       for (const hook of axiosConfig.hook) {
       for (const hook of axiosConfig.hook) {
-        hook.after(response.config)
+        hook.after && hook.after(response.config)
       }
       }
 
 
       if (matchURL(axiosConfig.unResErrorSet, response.config)) {
       if (matchURL(axiosConfig.unResErrorSet, response.config)) {
@@ -173,12 +174,12 @@ export const axiosFactory = () => {
     },
     },
     (err) => {
     (err) => {
       for (const hook of axiosConfig.hook) {
       for (const hook of axiosConfig.hook) {
-        hook.after(err.config)
+        hook.after && hook.after(err.config)
       }
       }
       if (!matchURL(axiosConfig.unResErrorSet, err.config)) {
       if (!matchURL(axiosConfig.unResErrorSet, err.config)) {
         callErrorHandler('res', err.response)
         callErrorHandler('res', err.response)
       }
       }
-      throw new Error(err.response.statusText)
+      throw new Error(err.response && err.response.statusText)
     }
     }
   )
   )
 
 

+ 10 - 2
src/api/sys.ts

@@ -1,6 +1,7 @@
-import { UPLOAD_FILE, UPLOAD_HEADS } from './constant'
+import { UPLOAD_FILE, UPLOAD_HEADS, CASE_INFO } from './constant'
 import { axios } from './instance'
 import { axios } from './instance'
 import { jsonToForm } from '@/utils'
 import { jsonToForm } from '@/utils'
+import { params } from '@/env'
 
 
 type UploadFile = LocalFile | string
 type UploadFile = LocalFile | string
 
 
@@ -19,4 +20,11 @@ export const uploadFile = async (file: UploadFile, suffix = '.png') => {
     })
     })
     return url
     return url
   }
   }
-}
+}
+
+export interface Case {
+  caseTitle: string	
+}
+
+export const getCaseInfo = () => 
+  axios.get<Case>(CASE_INFO, { params: { caseId: params.caseId } })

+ 4 - 3
src/api/tagging-position.ts

@@ -12,7 +12,8 @@ import type { Tagging } from './tagging'
 interface ServicePosition {
 interface ServicePosition {
   "tagPointId": number,
   "tagPointId": number,
   "tagId": number,
   "tagId": number,
-  "modelId": number
+  "fusionNumId": number
+  "modelId"?: number
   "tagPoint": string,
   "tagPoint": string,
 }
 }
 
 
@@ -28,7 +29,7 @@ export type TaggingPositions = TaggingPosition[]
 
 
 const serviceToLocal = (position: ServicePosition, taggingId?: Tagging['id']): TaggingPosition => ({
 const serviceToLocal = (position: ServicePosition, taggingId?: Tagging['id']): TaggingPosition => ({
   id: position.tagPointId.toString(),
   id: position.tagPointId.toString(),
-  modelId: position.modelId ? position.modelId.toString() : '123123',
+  modelId: position.fusionNumId ? position.fusionNumId.toString() : position.modelId ? position.modelId.toString() : '123123',
   taggingId: taggingId || position.tagId.toString(),
   taggingId: taggingId || position.tagId.toString(),
   localPos: JSON.parse(position.tagPoint)
   localPos: JSON.parse(position.tagPoint)
 })
 })
@@ -36,7 +37,7 @@ const serviceToLocal = (position: ServicePosition, taggingId?: Tagging['id']): T
 const localToService = (position: TaggingPosition, update = false): PartialProps<ServicePosition, 'tagPointId'> => ({
 const localToService = (position: TaggingPosition, update = false): PartialProps<ServicePosition, 'tagPointId'> => ({
   "tagPointId": update ? Number(position.id) : undefined,
   "tagPointId": update ? Number(position.id) : undefined,
   "tagId": Number(position.taggingId),
   "tagId": Number(position.taggingId),
-  "modelId": Number(position.modelId),
+  "fusionNumId": Number(position.modelId),
   "tagPoint": JSON.stringify(position.localPos),
   "tagPoint": JSON.stringify(position.localPos),
 })
 })
 
 

+ 7 - 1
src/api/tagging-style.ts

@@ -1,22 +1,26 @@
 import axios from './instance'
 import axios from './instance'
 import { TAGGING_STYLE_LIST, INSERT_TAGGING_STYLE, DELETE_TAGGING_STYLE, UPLOAD_HEADS } from './constant'
 import { TAGGING_STYLE_LIST, INSERT_TAGGING_STYLE, DELETE_TAGGING_STYLE, UPLOAD_HEADS } from './constant'
 import { jsonToForm } from '@/utils'
 import { jsonToForm } from '@/utils'
+import { params } from '@/env'
 interface ServiceStyle {
 interface ServiceStyle {
   iconId: number,
   iconId: number,
   iconTitle: string,
   iconTitle: string,
   iconUrl: string,
   iconUrl: string,
   isSystem: number
   isSystem: number
+  lastUse: 1 | 0
 }
 }
 
 
 export interface TaggingStyle {
 export interface TaggingStyle {
   id: string
   id: string
   icon: string
   icon: string
   name: string
   name: string
+  lastUse: 1 | 0
   default: boolean
   default: boolean
 }
 }
 
 
 const toLocal = (serviceStyle: ServiceStyle) : TaggingStyle => ({
 const toLocal = (serviceStyle: ServiceStyle) : TaggingStyle => ({
   id: serviceStyle.iconId.toString(),
   id: serviceStyle.iconId.toString(),
+  lastUse: serviceStyle.lastUse,
   name: serviceStyle.iconTitle,
   name: serviceStyle.iconTitle,
   icon: serviceStyle.iconUrl,
   icon: serviceStyle.iconUrl,
   default: Boolean(serviceStyle.isSystem)
   default: Boolean(serviceStyle.isSystem)
@@ -25,6 +29,7 @@ const toLocal = (serviceStyle: ServiceStyle) : TaggingStyle => ({
 const toService = (style: TaggingStyle): ServiceStyle => ({
 const toService = (style: TaggingStyle): ServiceStyle => ({
   iconId: Number(style.id),
   iconId: Number(style.id),
   iconTitle: style.name,
   iconTitle: style.name,
+  lastUse: style.lastUse,
   iconUrl: style.icon,
   iconUrl: style.icon,
   isSystem: Number(style.default)
   isSystem: Number(style.default)
 
 
@@ -33,7 +38,8 @@ const toService = (style: TaggingStyle): ServiceStyle => ({
 export type TaggingStyles = TaggingStyle[]
 export type TaggingStyles = TaggingStyle[]
 
 
 export const fetchTaggingStyles = async () => {
 export const fetchTaggingStyles = async () => {
-  const data = await axios.get<ServiceStyle[]>(TAGGING_STYLE_LIST)
+  const reqParams = params.share ? { caseId: params.caseId } : { }
+  const data = await axios.get<ServiceStyle[]>(TAGGING_STYLE_LIST, { params: reqParams })
   return data.map(toLocal)
   return data.map(toLocal)
 }
 }
 
 

+ 10 - 2
src/app.vue

@@ -18,8 +18,10 @@
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { custom } from '@/env'
 import { custom } from '@/env'
-import { computed } from 'vue'
-import { isEdit, appEl } from '@/store'
+import { computed, watchEffect } from 'vue'
+import { isEdit, prefix, appEl } from '@/store'
+import { getCaseInfo } from '@/api'
+import { currentLayout, RoutesName } from './router';
 
 
 const layoutClassNames = computed(() => {
 const layoutClassNames = computed(() => {
   return {
   return {
@@ -42,6 +44,12 @@ const layoutStyles = computed(() => {
   return styles
   return styles
 })
 })
 
 
+watchEffect(() => {
+  if (currentLayout.value && currentLayout.value !== RoutesName.signModel) {
+    getCaseInfo().then(ecase => prefix.value = ecase.caseTitle)  
+  }
+})
+
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 9 - 5
src/components/actions/index.vue

@@ -6,14 +6,15 @@
       :key="action.key || i" 
       :key="action.key || i" 
       @click="clickHandler(action)"
       @click="clickHandler(action)"
     >
     >
-      <ui-icon :type="action.icon" />
+      <ui-icon :type="action.icon" class="icon" />
       {{ action.text }}
       {{ action.text }}
     </span>
     </span>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { ref, toRaw, watchEffect, onBeforeUnmount, nextTick } from 'vue'
+import { useActive } from '@/hook';
+import { ref, toRaw, watchEffect, onBeforeUnmount, nextTick, watch } from 'vue'
 
 
 export type ActionsItem<T = any> = { 
 export type ActionsItem<T = any> = { 
   icon: string, 
   icon: string, 
@@ -34,13 +35,12 @@ const clickHandler = (select: ActionsItem) => {
     nextTick(() => selected.value && clickHandler(selected.value))
     nextTick(() => selected.value && clickHandler(selected.value))
   }
   }
 }
 }
-
-watchEffect((onCleanup) => {
+watch(selected, (_n, _o, onCleanup) => {
   if (selected.value?.action) {
   if (selected.value?.action) {
     const cleanup = selected.value.action()
     const cleanup = selected.value.action()
     cleanup && onCleanup(cleanup)
     cleanup && onCleanup(cleanup)
   }
   }
-})
+}, { flush: 'sync' })
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
   selected.value = null
   selected.value = null
@@ -66,6 +66,10 @@ onBeforeUnmount(() => {
     cursor: pointer;
     cursor: pointer;
     transition: all .3s ease;
     transition: all .3s ease;
 
 
+    .icon {
+      margin-right: 4px;
+    }
+
 
 
     &:hover,
     &:hover,
     &.active {
     &.active {

+ 1 - 1
src/components/bill-ui/assets/scss/components/_input.scss

@@ -278,7 +278,7 @@
         display: flex;
         display: flex;
         padding: 0 10px;
         padding: 0 10px;
         align-items: center;
         align-items: center;
-        justify-content: space-between;
+        justify-content: end;
 
 
       }
       }
 
 

+ 1 - 0
src/components/bill-ui/assets/scss/components/_slide.scss

@@ -49,6 +49,7 @@
       display: inline-flex;
       display: inline-flex;
       align-items: center;
       align-items: center;
       font-size: 10px;
       font-size: 10px;
+      color: rgba(255,255,255,.6);
 
 
       span {
       span {
         color: var(--colors-primary-base);
         color: var(--colors-primary-base);

+ 2 - 2
src/components/bill-ui/components/slide/index.vue

@@ -6,8 +6,8 @@
             </GateContent>
             </GateContent>
         </Gate>
         </Gate>
         <template v-if="showCtrl">
         <template v-if="showCtrl">
-            <span class="left" @click="prevHandler"><UIIcon type="left1" /></span>
-            <span class="right" @click="nextHandler"><UIIcon type="right" /></span>
+            <span class="left fun-ctrl" @click="prevHandler"><UIIcon type="left1" /></span>
+            <span class="right fun-ctrl" @click="nextHandler"><UIIcon type="right" /></span>
         </template>
         </template>
         <slot name="attach" :active="items[index]" />
         <slot name="attach" :active="items[index]" />
 
 

+ 1 - 1
src/components/control-panl/style.scss

@@ -137,7 +137,7 @@
 .ctrl {
 .ctrl {
   margin-bottom: 1px;
   margin-bottom: 1px;
   width: 50px;
   width: 50px;
-  height: 40px;
+  height: 50px;
   border-radius: 4px;
   border-radius: 4px;
   cursor: pointer;
   cursor: pointer;
   transition: background-color .3s ease, color .3s ease;
   transition: background-color .3s ease, color .3s ease;

+ 1 - 1
src/components/list/index.vue

@@ -11,7 +11,7 @@
         v-for="(item, i) in data" 
         v-for="(item, i) in data" 
         :key="rawKey ? item.raw[rawKey] : i" 
         :key="rawKey ? item.raw[rawKey] : i" 
         :class="{select: item.select}"
         :class="{select: item.select}"
-        @click.stop="$emit('changeSelect', item)"
+        @click="$emit('changeSelect', item)"
       >
       >
         <div class="atom-content">
         <div class="atom-content">
           <slot name="atom" :item="item"></slot>
           <slot name="atom" :item="item"></slot>

+ 20 - 34
src/components/static-preview/index.vue

@@ -4,24 +4,19 @@
       <ui-icon type="close" ctrl />
       <ui-icon type="close" ctrl />
     </span>
     </span>
     <div class="pull-preview pc">
     <div class="pull-preview pc">
-      <div class="preview-layer">
-        <div class="pull-meta">
-            <video v-if="type === MediaType.video" controls autoplay playsinline webkit-playsinline>
-              <source :src="staticURL" />
-            </video>
-            <iframe v-else-if="type === MediaType.web" :src="staticURL"></iframe>
-            <div v-if="type === MediaType.img" class="full-img pc">
-              <img :src="staticURL" />
-            </div>
-        </div>
-      </div>
+      <ui-slide v-if="items.length > 1" :currentIndex="current" showCtrl :items="items" showInfos>
+        <template v-slot="{ raw }">
+          <Sign :media="raw" />
+        </template>
+      </ui-slide>
+      <Sign :media="items[0]" v-else />
     </div>
     </div>
   </teleport>
   </teleport>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
-import { ref, watchEffect, defineComponent, PropType } from 'vue'
-import { getResource } from '@/env'
+import { defineComponent, PropType, ref } from 'vue'
+import Sign from './sign.vue'
 
 
 export enum MediaType {
 export enum MediaType {
   video,
   video,
@@ -29,37 +24,28 @@ export enum MediaType {
   web
   web
 }
 }
 
 
+export type MediaItem = {
+  url: Blob | string,
+  type: MediaType
+}
+
 export const Preview =  defineComponent({
 export const Preview =  defineComponent({
   name: 'static-preview',
   name: 'static-preview',
   props: {
   props: {
-    url: {
-      type: String as PropType<Blob | string>,
+    items: {
+      type: Array as PropType<MediaItem[]>,
       required: true
       required: true
     },
     },
-    type: {
-      type: Number as PropType<MediaType>,
-      required: true
+    current: {
+      type: Number,
+      default: 1
     }
     }
   },
   },
   emits: {
   emits: {
     close: () => true
     close: () => true
   },
   },
-  setup(props) {
-    const staticURL = ref('')
-    watchEffect(() => {
-      const data = props.url
-      const url = typeof data === 'string'
-        ? getResource(data)
-        : URL.createObjectURL(data)
-
-      staticURL.value = url
-      return () => URL.revokeObjectURL(url)
-    })
-    
-    return {
-      staticURL,
-      MediaType
-    }
+  components: {
+    Sign
   }
   }
 })
 })
 
 

+ 54 - 0
src/components/static-preview/sign.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="preview-layer">
+    <div class="pull-meta">
+      <video v-if="media.type === MediaType.video" controls autoplay playsinline webkit-playsinline>
+        <source :src="staticURL" />
+      </video>
+      <iframe v-else-if="media.type === MediaType.web" :src="staticURL"></iframe>
+      <img :src="staticURL" v-if="media.type === MediaType.img"/>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { ref, watchEffect, defineComponent, PropType } from 'vue'
+import { getResource } from '@/env'
+import { MediaType } from './index.vue'
+import type { MediaItem } from './index.vue'
+
+
+export const Preview =  defineComponent({
+  name: 'static-preview',
+  props: {
+    media: {
+      type: Object as PropType<MediaItem>,
+        required: true
+    }
+  },
+  emits: {
+    close: () => true
+  },
+  setup(props) {
+    const staticURL = ref('')
+    watchEffect(() => {
+      const data = props.media.url
+      const url = typeof data === 'string'
+        ? getResource(data)
+        : URL.createObjectURL(data)
+
+      staticURL.value = url
+      return () => URL.revokeObjectURL(url)
+    })
+    
+    return {
+      staticURL,
+      MediaType
+    }
+  }
+})
+
+
+export default Preview
+</script>
+
+<style scoped lang="scss" src="./style.scss"></style>

+ 12 - 4
src/components/static-preview/style.scss

@@ -19,7 +19,7 @@
 .pull-preview {
 .pull-preview {
   position: absolute;
   position: absolute;
   z-index: 9999;
   z-index: 9999;
-  display: flex;
+  // display: flex;
   align-items: center;
   align-items: center;
   top: 0;
   top: 0;
   right: 0;
   right: 0;
@@ -61,6 +61,9 @@
       width: 100%;
       width: 100%;
       overflow-y: auto;
       overflow-y: auto;
       flex: 1;
       flex: 1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
 
 
       .content {
       .content {
         margin-bottom: 10px;
         margin-bottom: 10px;
@@ -73,14 +76,19 @@
   
   
       }
       }
 
 
-      iframe,
-      video,
-      img {
+      iframe {
         width: 100%;
         width: 100%;
         height: 100%;
         height: 100%;
         display: block;
         display: block;
       }
       }
 
 
+      video,
+      img {
+        max-width: 100%;
+        max-height: 100%;
+        display: block;
+      }
+
       video, img {
       video, img {
         object-fit: contain;
         object-fit: contain;
       }
       }

+ 8 - 2
src/components/tagging/sign.vue

@@ -44,8 +44,8 @@
 
 
       <Preview 
       <Preview 
         @close="pullIndex = -1"
         @close="pullIndex = -1"
-        :type="MediaType.img" 
-        :url="getResource(getFileUrl(tagging.images[pullIndex]))"
+        :current="pullIndex"
+        :items="queryItems"
         v-if="!!~pullIndex" 
         v-if="!!~pullIndex" 
       />
       />
     </div>
     </div>
@@ -97,6 +97,12 @@ const taggingStyle = getTaggingStyle(props.tagging.styleId)
 
 
 const pullIndex = ref(-1)
 const pullIndex = ref(-1)
 const isHover = ref(false)
 const isHover = ref(false)
+const queryItems = computed(() => 
+  props.tagging.images.map(image => ({
+    type: MediaType.img, 
+    url: getResource(getFileUrl(image))
+  }))
+)
 
 
 const iconClickHandler = () => {
 const iconClickHandler = () => {
   if (custom.showTaggingPositions.has(props.scenePos)) {
   if (custom.showTaggingPositions.has(props.scenePos)) {

+ 10 - 5
src/env/index.ts

@@ -1,7 +1,7 @@
 import { stackFactory, flatStacksValue, strToParams } from '@/utils'
 import { stackFactory, flatStacksValue, strToParams } from '@/utils'
 import { ref } from 'vue'
 import { ref } from 'vue'
 
 
-import type { FuseModel, TaggingPosition } from '@/store'
+import type { FuseModel, TaggingPosition, View } from '@/store'
 
 
 export const viewModeStack = stackFactory(ref<'full' | 'auto'>('auto'))
 export const viewModeStack = stackFactory(ref<'full' | 'auto'>('auto'))
 export const showToolbarStack = stackFactory(ref<boolean>(false))
 export const showToolbarStack = stackFactory(ref<boolean>(false))
@@ -10,14 +10,15 @@ export const showRightPanoStack = stackFactory(ref<boolean>(true))
 export const showLeftPanoStack = stackFactory(ref<boolean>(false))
 export const showLeftPanoStack = stackFactory(ref<boolean>(false))
 export const showLeftCtrlPanoStack = stackFactory(ref<boolean>(true))
 export const showLeftCtrlPanoStack = stackFactory(ref<boolean>(true))
 export const showRightCtrlPanoStack = stackFactory(ref<boolean>(true))
 export const showRightCtrlPanoStack = stackFactory(ref<boolean>(true))
-export const showBottomBarStack = stackFactory(ref<boolean>(false))
+export const showBottomBarStack = stackFactory(ref<boolean>(false), true)
 export const bottomBarHeightStack = stackFactory(ref<string>('60px'))
 export const bottomBarHeightStack = stackFactory(ref<string>('60px'))
 export const showTaggingsStack = stackFactory(ref<boolean>(true))
 export const showTaggingsStack = stackFactory(ref<boolean>(true))
-export const showMeasuresStack = stackFactory(ref<boolean>(false))
+export const showMeasuresStack = stackFactory(ref<boolean>(true))
 export const currentModelStack = stackFactory(ref<FuseModel | null>(null))
 export const currentModelStack = stackFactory(ref<FuseModel | null>(null))
-export const showModelsMapStack = stackFactory(ref<Map<FuseModel, boolean>>(new Map), true)
+export const showModelsMapStack = stackFactory(ref<WeakMap<FuseModel, boolean>>(new WeakMap()), true)
 export const modelsChangeStoreStack = stackFactory(ref<boolean>(false))
 export const modelsChangeStoreStack = stackFactory(ref<boolean>(false))
 export const showTaggingPositionsStack = stackFactory(ref<WeakSet<TaggingPosition>>(new WeakSet()))
 export const showTaggingPositionsStack = stackFactory(ref<WeakSet<TaggingPosition>>(new WeakSet()))
+export const currentViewStack = stackFactory(ref<View>())
 
 
 export const custom = flatStacksValue({
 export const custom = flatStacksValue({
   viewMode: viewModeStack,
   viewMode: viewModeStack,
@@ -34,16 +35,20 @@ export const custom = flatStacksValue({
   showTaggingPositions: showTaggingPositionsStack,
   showTaggingPositions: showTaggingPositionsStack,
   showBottomBar: showBottomBarStack,
   showBottomBar: showBottomBarStack,
   bottomBarHeight: bottomBarHeightStack,
   bottomBarHeight: bottomBarHeightStack,
-  showHeadBar: showHeadBarStack
+  showHeadBar: showHeadBarStack,
+  currentView: currentViewStack
 })
 })
 
 
 
 
 export const params = strToParams(location.search) as unknown as Params
 export const params = strToParams(location.search) as unknown as Params
 params.caseId = Number(params.caseId)
 params.caseId = Number(params.caseId)
+params.share = Boolean(Number(params.share))
 export type Params = { 
 export type Params = { 
   caseId: number,
   caseId: number,
   baseURL?: string,
   baseURL?: string,
   modelId?: string,
   modelId?: string,
+  m?: string
+  share: boolean,
   token?: string
   token?: string
 }
 }
 
 

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

@@ -83,6 +83,12 @@ watch(router.currentRoute, (_n, _, onClean) => {
     ]))
     ]))
   }
   }
 }, { flush: 'post', immediate: true })
 }, { flush: 'post', immediate: true })
+
+watchEffect((onCleanup) => {
+  if (!fuseModels.value.length) {
+    onCleanup(showRightPanoStack.push(ref(false)))
+  }
+})
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>

+ 2 - 4
src/layout/edit/header/index.vue

@@ -22,13 +22,11 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { computed, watchEffect } from 'vue'
+import { computed } from 'vue'
 import { isEdit, title, isOld, leave, save } from '@/store'
 import { isEdit, title, isOld, leave, save } from '@/store'
 
 
 const props = defineProps<{ title?: string }>()
 const props = defineProps<{ title?: string }>()
-const sysTitle = computed(() => props.title || title)
-
-watchEffect(() => (document.title = sysTitle.value))
+const sysTitle = computed(() => props.title || title.value)
 </script>
 </script>
 
 
 <style lang="sass" scoped>
 <style lang="sass" scoped>

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

@@ -11,20 +11,30 @@
   >
   >
     <div>
     <div>
       <div className='model-header'>
       <div className='model-header'>
+        <p class="header-desc">已选择数据<span>( {{ selectIds.length }} )</span></p>
         <Search
         <Search
           className='content-header-search'
           className='content-header-search'
           placeholder="输入名称搜索" 
           placeholder="输入名称搜索" 
+          v-model:value="keyword"
+          allow-clear
           style="width: 264px"
           style="width: 264px"
-          @search="val => keyword = val"
         />
         />
       </div>
       </div>
       <Table 
       <Table 
+        v-if="filterScenes.length"
         :row-key="record => record.modelId"
         :row-key="record => record.modelId"
         :columns="cloumns"
         :columns="cloumns"
         :rowSelection="rowSelection"
         :rowSelection="rowSelection"
         :data-source="filterScenes"
         :data-source="filterScenes"
         :pagination="false"
         :pagination="false"
       />
       />
+      <div style="padding: 1px;" v-else>
+        <Empty 
+          :description="keyword.length ? '暂无搜索结果': '暂无结果'" 
+          :image="Empty.PRESENTED_IMAGE_SIMPLE"
+          className="ant-empty ant-empty-normal" 
+        />
+      </div>
     </div>
     </div>
   </Modal>
   </Modal>
 
 
@@ -34,11 +44,11 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { Modal, Input, Table } from 'ant-design-vue'
-import { computed, nextTick, ref, watchEffect } from 'vue';
-import { scenes, save } from '@/store'
+import { Modal, Input, Table, Empty } from 'ant-design-vue'
+import { computed, nextTick, ref, watch, watchEffect } from 'vue';
+import { scenes, save, SceneTypeDesc } from '@/store'
 import { createLoadPack } from '@/utils'
 import { createLoadPack } from '@/utils'
-import { fuseModels, createFuseModels, addFuseModel, fuseModelsLoaded } from '@/store'
+import { fuseModels, createFuseModels, addFuseModel, fuseModelsLoaded, initialScenes } from '@/store'
 
 
 import type { Scene } from '@/api'
 import type { Scene } from '@/api'
 
 
@@ -50,7 +60,12 @@ const selectIds = computed(() => fuseModels.value.map(item => item.modelId))
 const visible = ref(false)
 const visible = ref(false)
 const keyword = ref('')
 const keyword = ref('')
 const filterScenes = computed(
 const filterScenes = computed(
-  () => scenes.value.filter(item => item.name && item.modelId && item.name.includes(keyword.value))
+  () => scenes.value
+    .filter(item => item.name && item.modelId && item.name.includes(keyword.value))
+    .map(scene => ({
+      ...scene,
+      type: SceneTypeDesc[scene.type]
+    }))
 )
 )
 const selects = ref<Key[]>(selectIds.value)
 const selects = ref<Key[]>(selectIds.value)
 const rowSelection: any = ref({
 const rowSelection: any = ref({
@@ -64,12 +79,18 @@ const rowSelection: any = ref({
 })
 })
 const cloumns = [
 const cloumns = [
   {
   {
+    width: "400px",
     title: '名称',
     title: '名称',
     dataIndex: 'name',
     dataIndex: 'name',
     key: 'name',
     key: 'name',
   },
   },
   {
   {
-    title: '拍摄时间',
+    title: '类型',
+    dataIndex: 'type',
+    key: 'type',
+  },
+  {
+    title: '拍摄/创建时间',
     dataIndex: 'createTime',
     dataIndex: 'createTime',
     key: 'createTime',
     key: 'createTime',
   }
   }
@@ -82,27 +103,39 @@ const okHandler = createLoadPack(async () => {
     .map(addFuseModel)
     .map(addFuseModel)
 
 
   await Promise.all(addPromises)
   await Promise.all(addPromises)
-
   await new Promise<void>((resolve) => {
   await new Promise<void>((resolve) => {
     nextTick(() => {
     nextTick(() => {
       const stop = watchEffect(() => {
       const stop = watchEffect(() => {
         if (fuseModelsLoaded.value) {
         if (fuseModelsLoaded.value) {
-          nextTick(() => stop())
-          resolve()
+          nextTick(() => {
+            stop()
+            resolve()
+          })
         }
         }
+        
       })
       })
     })
     })
   })
   })
   await save()
   await save()
   visible.value = false
   visible.value = false
 })
 })
+
+watch(visible, (visible, oldvisible) => {
+  if (visible !== oldvisible) {
+    keyword.value = ''
+    selects.value = selectIds.value
+
+    visible && initialScenes()
+  }
+})
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
 .model-header {
 .model-header {
   display: flex;
   display: flex;
-  justify-content: flex-end;
+  justify-content: space-between;
   padding-bottom: 24px;
   padding-bottom: 24px;
+  align-items: center;
 }
 }
 </style>
 </style>
 
 
@@ -115,5 +148,14 @@ const okHandler = createLoadPack(async () => {
   // .ant-table-tbody > tr.ant-table-row-selected > td {
   // .ant-table-tbody > tr.ant-table-row-selected > td {
   //   background: none;
   //   background: none;
   // }
   // }
+  .model-header .header-desc {
+    margin-bottom: 0;
+  }
+  .model-header .header-desc span {
+    color: @primary-color;
+  }
+}
+.ant-modal-root .ant-table-tbody > tr > td {
+  word-break: break-all;
 }
 }
 </style>
 </style>

+ 3 - 8
src/layout/model-list/index.vue

@@ -23,6 +23,7 @@ import ModelSign from './sign.vue'
 import { fuseModels, getFuseModelShowVariable } from '@/store'
 import { fuseModels, getFuseModelShowVariable } from '@/store'
 
 
 import type { FuseModel } from '@/store'
 import type { FuseModel } from '@/store'
+import { currentModel, fuseModel } from '@/model'
 
 
 export type ModelListProps = { 
 export type ModelListProps = { 
   title?: string, 
   title?: string, 
@@ -41,19 +42,13 @@ defineEmits<{
 const modelList = computed(() => 
 const modelList = computed(() => 
   fuseModels.value.map(model => ({
   fuseModels.value.map(model => ({
     raw: model,
     raw: model,
-    select: custom.currentModel === model
+    select: custom.currentModel === model && currentModel.value === fuseModel
   }))
   }))
 )
 )
 
 
 const modelChangeSelect = (model: FuseModel) => {
 const modelChangeSelect = (model: FuseModel) => {
   if (getFuseModelShowVariable(model).value) {
   if (getFuseModelShowVariable(model).value) {
-    if (custom.currentModel !== model) {
-      getSceneModel(model)?.changeSelect(true)
-      custom.currentModel = model
-    } else {
-      getSceneModel(custom.currentModel)?.changeSelect(false)
-      custom.currentModel = null
-    }
+    custom.currentModel = custom.currentModel !== model ? model : null
   }
   }
 }
 }
 
 

+ 28 - 16
src/layout/model-list/sign.vue

@@ -1,31 +1,43 @@
 <template>
 <template>
-  <div class="model-header" @click.stop="!model.error && $emit('click')">
-    <p>{{ model.title }}</p>
-    <div class="model-action" @click.stop>
-      <ui-input type="checkbox" v-model="show" :class="{disabled: model.error}"/>
-      <ui-icon 
-        v-if="custom.modelsChangeStore" 
-        type="del" 
-        ctrl 
-        @click="$emit('delete')" 
-      />
+  <div @click="!model.error && $emit('click')" class="sign-layout">
+    <div class="model-header">
+      <p>{{ model.title }}</p>
+      <div class="model-action">
+        <ui-input 
+          type="checkbox" 
+          v-model="show" 
+          @click.stop 
+          :class="{disabled: model.error}"
+        />
+        <ui-icon 
+          v-if="custom.modelsChangeStore" 
+          type="del" 
+          ctrl 
+          @click="$emit('delete')" 
+        />
+      </div>
+    </div>
+    <div class="model-desc"  v-if="active">
+      <p><span>数据来源:</span>{{ SceneTypeDesc[model.type] }}</p>
+      <p v-if="model.type !== SceneType.SWSS"><span>数据大小:</span>{{ model.size }}</p>
+      <p><span>拍摄时间:</span>{{ model.time }}</p>
     </div>
     </div>
-  </div>
-  <div class="model-desc" @click="$emit('click')" v-if="custom.currentModel === model">
-    <p><span>数据来源:</span>{{ SceneTypeDesc[model.type] }}</p>
-    <p><span>数据大小:</span>{{ model.size }}</p>
-    <p><span>拍摄时间:</span>{{ model.time }}</p>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { getFuseModelShowVariable, SceneTypeDesc } from '@/store'
+import { getFuseModelShowVariable, SceneTypeDesc, SceneType } from '@/store'
 import { custom } from '@/env'
 import { custom } from '@/env'
 
 
 import type { FuseModel } from '@/store'
 import type { FuseModel } from '@/store'
+import { computed } from 'vue';
+import { currentModel, fuseModel } from '@/model';
 
 
 type ModelProps = { model: FuseModel, canChange?: boolean }
 type ModelProps = { model: FuseModel, canChange?: boolean }
 const props = defineProps<ModelProps>()
 const props = defineProps<ModelProps>()
+const active = computed(() => 
+  custom.currentModel === props.model && currentModel.value === fuseModel
+)
 
 
 type ModelEmits = {
 type ModelEmits = {
   (e: 'changeSelect', selected: boolean): void
   (e: 'changeSelect', selected: boolean): void

+ 10 - 0
src/layout/model-list/style.scss

@@ -7,6 +7,10 @@
   p {
   p {
     font-size: 14px;
     font-size: 14px;
     color: #fff;
     color: #fff;
+    height: 1.5em;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
   }
   }
 }
 }
 
 
@@ -19,7 +23,13 @@
 .model-action {
 .model-action {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
+  flex: none;
   > * {
   > * {
     margin-left: 20px;
     margin-left: 20px;
   }
   }
 }
 }
+
+.sign-layout {
+  margin: -20px 0;
+  padding: 20px 0;
+}

+ 27 - 2
src/layout/right-fill-pano.vue

@@ -6,13 +6,22 @@
     :class="{ active: custom.showRightPano }">
     :class="{ active: custom.showRightPano }">
     <ui-icon type="extend" class="icon"></ui-icon>
     <ui-icon type="extend" class="icon"></ui-icon>
   </span>
   </span>
-  <ui-editor-toolbox :toolbox="true" disabledAnimation>
-    <slot></slot>
+  <ui-editor-toolbox :toolbox="true" disabledAnimation :class="{ flex: $slots.header }">
+    <template v-if="$slots.header">
+      <div class="header">
+        <slot name="header"></slot>
+      </div>
+      <div class="content">
+        <slot></slot>
+      </div>
+    </template>
+    <slot v-else></slot>
   </ui-editor-toolbox>
   </ui-editor-toolbox>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { custom } from '@/env'
 import { custom } from '@/env'
+
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -54,4 +63,20 @@ import { custom } from '@/env'
   }
   }
 }
 }
 
 
+.flex {
+  overflow-y: hidden;
+  display: flex;
+  flex-direction: column;
+
+  .header {
+    flex: none;
+  }
+
+  .content {
+    flex: 1;
+    overflow-y: auto;
+    margin: 0 -20px;
+    padding: 0 20px;
+  }
+}
 </style>
 </style>

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

@@ -4,25 +4,30 @@
       <slot name="action" />
       <slot name="action" />
     </template>
     </template>
     <template #atom="{ item }">
     <template #atom="{ item }">
-      <div 
-        v-if="item.raw === fuseModel" 
+      <div v-if="item.raw === fuseModel" 
         @click="updateCurrent(item.raw)"
         @click="updateCurrent(item.raw)"
       >
       >
         <ModelList
         <ModelList
           class="scene-model-list"
           class="scene-model-list"
           :class="{active: current === fuseModel}"
           :class="{active: current === fuseModel}"
           :title="getModelTypeDesc(fuseModel as any)"
           :title="getModelTypeDesc(fuseModel as any)"
-          :show-content="current === fuseModel"
+          :show-content="showModelList"
         >
         >
           <template #action>
           <template #action>
             <ui-icon 
             <ui-icon 
-              :type="`pull-${current === fuseModel ? 'up' : 'down'}`" 
+              :type="`pull-${showModelList ? 'up' : 'down'}`" 
+              @click="showModelList = !showModelList"
               ctrl 
               ctrl 
             />
             />
           </template>
           </template>
         </ModelList>
         </ModelList>
       </div>
       </div>
-      <div class="scene" @click="updateCurrent(item.raw)" v-else>
+      <div 
+        class="scene" 
+        :class="{disabled: item.raw.status !== SceneStatus.SUCCESS}" 
+        @click="updateCurrent(item.raw)" 
+        v-else
+      >
         <p>{{ item.raw.name }}</p>
         <p>{{ item.raw.name }}</p>
         <p>{{ SceneTypeDesc[item.raw.type as SceneType] }}</p>
         <p>{{ SceneTypeDesc[item.raw.type as SceneType] }}</p>
       </div>
       </div>
@@ -31,8 +36,8 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { computed } from 'vue'
-import { scenes, SceneType, SceneTypeDesc } from '@/store'
+import { computed, nextTick, ref, watch } from 'vue'
+import { scenes, SceneType, SceneTypeDesc, fuseModels, SceneStatus } from '@/store'
 import List from '@/components/list/index.vue'
 import List from '@/components/list/index.vue'
 import ModelList from '../model-list/index.vue'
 import ModelList from '../model-list/index.vue'
 import { fuseModel, getModelTypeDesc } from '@/model'
 import { fuseModel, getModelTypeDesc } from '@/model'
@@ -42,6 +47,7 @@ import type { Scene } from '@/store'
 
 
 const emit = defineEmits<{ (e: 'update:current', data: ModelType): void }>()
 const emit = defineEmits<{ (e: 'update:current', data: ModelType): void }>()
 const props = defineProps<{ current: ModelType }>()
 const props = defineProps<{ current: ModelType }>()
+const showModelList = ref(true)
 
 
 const list = computed(() => {
 const list = computed(() => {
   const sceneList = scenes.value.map(scene => ({
   const sceneList = scenes.value.map(scene => ({
@@ -50,7 +56,11 @@ const list = computed(() => {
       && (props.current.num === scene.num )
       && (props.current.num === scene.num )
       && props.current.type === scene.type
       && props.current.type === scene.type
   }))
   }))
-  return [{ raw: fuseModel }, ...sceneList]
+  if (fuseModels.value.length) {
+    return [{ raw: fuseModel }, ...sceneList]
+  } else {
+    return sceneList
+  }
 })
 })
 
 
 const updateCurrent = (scene: FuseModelType | Scene) => {
 const updateCurrent = (scene: FuseModelType | Scene) => {
@@ -61,6 +71,12 @@ const updateCurrent = (scene: FuseModelType | Scene) => {
   }
   }
 }
 }
 
 
+const stopWatch = watch(list, () => {
+  if (!list.value.some(model => model.raw === fuseModel)) {
+    updateCurrent(list.value[0].raw as any)
+    nextTick(() => stopWatch())
+  }
+}, { immediate: true })
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
@@ -70,6 +86,12 @@ const updateCurrent = (scene: FuseModelType | Scene) => {
 
 
 .scene {
 .scene {
   padding: 0 20px;
   padding: 0 20px;
+
+  p {
+    height: 1.5em;
+    overflow: hidden;
+    text-overflow: ellipsis
+  }
 }
 }
 
 
 .scene-model-list.list {
 .scene-model-list.list {

+ 42 - 10
src/layout/show/index.vue

@@ -1,34 +1,61 @@
 <template>
 <template>
   <template v-if="loaded">
   <template v-if="loaded">
-    <SlideMenu />
-    
-    <router-view v-slot="{ Component }">
-      <keep-alive>
-        <component :is="Component" />
-      </keep-alive>
-    </router-view>
+    <div :class="{ hideLeft: !custom.showLeftPano }">
+      <SlideMenu 
+        :activeName="(router.currentRoute.value.name as RoutesName)" 
+        @change-item="item => router.push({ name: item.name })"
+      />
+
+      <router-view v-slot="{ Component }">
+        <keep-alive>
+          <component :is="Component" />
+        </keep-alive>
+      </router-view>
+
+    </div>
   </template>
   </template>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { custom } from '@/env'
 import { custom } from '@/env'
 import { ref } from 'vue'
 import { ref } from 'vue'
+import { router, RoutesName } from '@/router'
 import { loadModel, fuseModel } from '@/model'
 import { loadModel, fuseModel } from '@/model'
 import SlideMenu from './slide-menu.vue'
 import SlideMenu from './slide-menu.vue'
-import { initialFuseModels, initialScenes } from '@/store'
-
+import { 
+  initialFloders, 
+  initialFloderTypes, 
+  initialFuseModels, 
+  initialRecords, 
+  initialScenes, 
+  initialViews,
+  defTitle,
+  initialTaggingStyles,
+  initialTaggings,
+  initialMeasures,
+  initialGuides
+} from '@/store'
 
 
 const loaded = ref(false)
 const loaded = ref(false)
 const initialSys = async () => {
 const initialSys = async () => {
   await Promise.all([
   await Promise.all([
     initialFuseModels(),
     initialFuseModels(),
-    initialScenes()
+    initialScenes(),
+    initialViews(),
+    initialRecords(),
+    initialFloders(),
+    initialFloderTypes(),
+    initialTaggingStyles(),
+    initialTaggings(),
+    initialGuides()
   ])
   ])
+  await initialMeasures()
   loaded.value = true
   loaded.value = true
   loadModel(fuseModel)
   loadModel(fuseModel)
   custom.showLeftPano = true
   custom.showLeftPano = true
 }
 }
 initialSys()
 initialSys()
+defTitle.value = ''
 </script>
 </script>
 
 
 <style>
 <style>
@@ -36,4 +63,9 @@ initialSys()
   --editor-menu-width: 80px;
   --editor-menu-width: 80px;
   --editor-head-height: 0px;
   --editor-head-height: 0px;
 }
 }
+
+.hideLeft {
+  --editor-menu-left: calc(-1 * var(--editor-menu-width));
+  --left-pano-left: calc(var(--editor-menu-left) + var(--editor-menu-width) - var(--left-pano-width)) !important
+}
 </style>
 </style>

+ 53 - 22
src/layout/show/slide-menu.vue

@@ -2,9 +2,9 @@
   <div class="slide-menu">
   <div class="slide-menu">
     <div 
     <div 
       v-for="item in items" 
       v-for="item in items" 
-      :class="{active: item.name === router.currentRoute.value.name}" 
+      :class="{active: item.name === activeName}" 
       :key="item.name"
       :key="item.name"
-      @click="router.push({ name: item.name })"
+      @click="$emit('changeItem', item as any)"
     >
     >
       <ui-icon :type="item.icon" :tip="item.title" class="icon" />
       <ui-icon :type="item.icon" :tip="item.title" class="icon" />
     </div>
     </div>
@@ -12,26 +12,56 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { metas, RoutesName, router } from '@/router'
-
-const items = [
-  {
-    name: RoutesName.summaryShow,
-    ...metas[RoutesName.summaryShow]
-  },
-  {
-    name: RoutesName.viewShow,
-    ...metas[RoutesName.viewShow]
-  },
-  {
-    name: RoutesName.recordShow,
-    ...metas[RoutesName.recordShow]
-  },
-  {
-    name: RoutesName.folderShow,
-    ...metas[RoutesName.folderShow]
+import { metas, RoutesName, router, getRouteConfig } from '@/router'
+import { views, records, floders } from '@/store';
+import { computed } from 'vue';
+
+import type { RouteRaw } from '@/router'
+
+export type MenuItem = {
+  name: RoutesName,
+  config: RouteRaw,
+} & (typeof metas)[keyof typeof metas]
+
+defineProps<{ activeName: RoutesName }>()
+defineEmits<{ (e: 'changeItem', item: MenuItem): void }>()
+
+
+const items = computed(() => {
+  const items = [
+    {
+      name: RoutesName.summaryShow,
+      config: getRouteConfig(RoutesName.summaryShow),
+      ...metas[RoutesName.summaryShow]
+    }
+  ]
+
+  if (views.value.length) {
+    items.push({
+      name: RoutesName.viewShow,
+      config: getRouteConfig(RoutesName.viewShow),
+      ...metas[RoutesName.viewShow]
+    }) 
+  }
+
+  if (records.value.length) {
+    items.push({
+      name: RoutesName.recordShow,
+      config: getRouteConfig(RoutesName.recordShow),
+      ...metas[RoutesName.recordShow]
+    }) 
   }
   }
-]
+
+  if (floders.value.length) {
+    items.push({
+      name: RoutesName.folderShow,
+      config: getRouteConfig(RoutesName.folderShow),
+      ...metas[RoutesName.folderShow]
+    }) 
+  }
+
+  return items
+})
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -42,11 +72,12 @@ const items = [
   background-color: var(--editor-menu-back);
   background-color: var(--editor-menu-back);
   position: fixed;
   position: fixed;
   left: var(--editor-menu-left);
   left: var(--editor-menu-left);
-  top: var(--editor-head-height);
+  top: calc(var(--editor-head-height) + var(--header-top));
   bottom: 0;
   bottom: 0;
   z-index: 2000;
   z-index: 2000;
   overflow: hidden;
   overflow: hidden;
   backdrop-filter: blur(4px);
   backdrop-filter: blur(4px);
+  transition: all .3s ease;
 
 
   > div {
   > div {
     height: 70px;
     height: 70px;

+ 47 - 1
src/main.ts

@@ -1,12 +1,58 @@
-import { createApp } from 'vue'
+import { createApp, watchEffect } from 'vue'
 import './style.scss'
 import './style.scss'
 import App from './app.vue'
 import App from './app.vue'
 import Components from 'bill/index'
 import Components from 'bill/index'
 import router from './router'
 import router from './router'
+import { params } from '@/env'
+import { addHook, addUnsetTokenURLS, delHook, delUnsetTokenURLS } from '@/api'
+import { currentLayout, RoutesName } from './router';
+import * as URL from '@/api/constant'
 
 
 const app = createApp(App)
 const app = createApp(App)
 app.use(Components)
 app.use(Components)
 app.use(router)
 app.use(router)
 app.mount('#app')
 app.mount('#app')
 
 
+
+watchEffect((onCleanup) => {
+  if ([RoutesName.show, RoutesName.signModel].includes(currentLayout.value!)) {
+    const untokenURLS = params.share 
+      ? [
+          URL.FUSE_MODEL_LIST,
+          URL.MODEL_LIST,
+          URL.TAGGING_LIST,
+          URL.TAGGING_POINT_LIST,
+          URL.TAGGING_STYLE_LIST,
+          URL.MESASURE_LIST,
+          URL.GUIDE_LIST,
+          URL.GUIDE_PATH_LIST,
+          URL.RECORD_LIST,
+          URL.RECORD_FRAGMENT_LIST,
+          URL.VIEW_LIST,
+          URL.FOLDER_TYPE_LIST,
+          URL.FLODER_LIST,
+          URL.MODEL_SIGN,
+          URL.CASE_INFO
+        ]
+      : []
+      
+    const apiHook = {
+      before(config: any) {
+        const isShare = Number(params.share)
+        if (config.headers) {
+          config.headers.share = isShare
+        } else {
+          config.headers = { share: isShare }
+        }
+      }
+    }
+    addHook(apiHook)
+    addUnsetTokenURLS(...untokenURLS)
+    onCleanup(() => {
+      delHook(apiHook)
+      delUnsetTokenURLS(...untokenURLS)
+    })
+  }
+}, { flush: 'sync' })
+
 export default app
 export default app

+ 20 - 9
src/model/app.vue

@@ -1,14 +1,16 @@
 <template>
 <template>
   <iframe class="external" :src="url" ref="iframeRef" v-if="url"></iframe>
   <iframe class="external" :src="url" ref="iframeRef" v-if="url"></iframe>
   <div class="laser-layer" v-show="!url">
   <div class="laser-layer" v-show="!url">
-    <div class="scene-canvas" ref="fuseRef"></div>
+    <div class="scene-canvas" ref="fuseRef">
+      <div id="direction"></div>
+    </div>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
-import { defineComponent, ref, watchEffect, computed, watch, onMounted, nextTick } from 'vue'
+import { defineComponent, ref, watchEffect, computed, watch, nextTick } from 'vue'
 import { SceneType } from '@/store'
 import { SceneType } from '@/store'
-import { params, showModelsMapStack } from '@/env'
+import { params } from '@/env'
 import { fuseModel, modelProps } from './index'
 import { fuseModel, modelProps } from './index'
 import { modelSDKFactory } from './platform'
 import { modelSDKFactory } from './platform'
 
 
@@ -38,7 +40,8 @@ export const Model = defineComponent({
         [SceneType.SWKK]: `/swkk/spg.html?m=${scene.value.num}`,
         [SceneType.SWKK]: `/swkk/spg.html?m=${scene.value.num}`,
         [SceneType.SWKJ]: `/swkk/spg.html?m=${scene.value.num}`,
         [SceneType.SWKJ]: `/swkk/spg.html?m=${scene.value.num}`,
         [SceneType.SWSS]: `/swss/index.html?m=${scene.value.num}`,
         [SceneType.SWSS]: `/swss/index.html?m=${scene.value.num}`,
-        [SceneType.SWMX]: `index.html?caseId=${params.caseId}&modelId=${scene.value.num}&token=${params.token}#sign-model`
+        [SceneType.SWSSMX]: `/swkk/spg.html?m=${scene.value.num}`,
+        [SceneType.SWMX]: `index.html?caseId=${params.caseId}&modelId=${scene.value.num}&share=1#sign-model`
       }
       }
       return urls[type]
       return urls[type]
     })
     })
@@ -50,10 +53,6 @@ export const Model = defineComponent({
       async (type, oldType, onCleanup) => {
       async (type, oldType, onCleanup) => {
         const callback = modelProps.callback
         const callback = modelProps.callback
 
 
-        if (oldType === fuseModel) {
-          onCleanup(showModelsMapStack.push(ref(new Map())))
-        }
-
         await nextTick()
         await nextTick()
         const { typePromise, typeCleanup } = typeChange()
         const { typePromise, typeCleanup } = typeChange()
         const modelPromise = modelSDKFactory(type, type === fuseModel ? fuseRef.value! : iframeRef.value!)
         const modelPromise = modelSDKFactory(type, type === fuseModel ? fuseRef.value! : iframeRef.value!)
@@ -81,7 +80,6 @@ export default Model
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-.external,
 .laser-layer {
 .laser-layer {
   position: absolute;
   position: absolute;
   z-index: 1;
   z-index: 1;
@@ -98,6 +96,19 @@ export default Model
 }
 }
 
 
 .external {
 .external {
+  position: absolute;
+  z-index: 1;
   border: none;
   border: none;
+  top: calc(var(--editor-head-height) + var(--header-top));
+  left: 0;
+  height: calc(100% - (var(--editor-head-height) + var(--header-top)));
+  width: 100%;
+}
+
+#direction {
+  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;
 }
 }
 </style>
 </style>

+ 9 - 1
src/model/index.ts

@@ -1,5 +1,5 @@
 import App from './app.vue'
 import App from './app.vue'
-import { appEl, SceneTypeDesc } from '@/store'
+import { appEl, SceneTypeDesc, scenes } from '@/store'
 import { mount, deepIsRevise } from '@/utils'
 import { mount, deepIsRevise } from '@/utils'
 import { reactive, ref } from 'vue'
 import { reactive, ref } from 'vue'
 
 
@@ -22,6 +22,14 @@ export const getModelTypeDesc = (model: ModelType) => {
     return SceneTypeDesc[model.type]
     return SceneTypeDesc[model.type]
   }
   }
 }
 }
+export const getModelDesc = (model: ModelType) => {
+  console.log(model)
+  if (model === fuseModel) {
+    return '融合场景'
+  } else {
+    return scenes.value.find(scene => scene.num === model.num && scene.type === model.type)?.name
+  }
+}
 
 
 const _loadModel = (() => {
 const _loadModel = (() => {
   let oldModelType: ModelType
   let oldModelType: ModelType

+ 2 - 1
src/model/platform.ts

@@ -10,6 +10,7 @@ export async function modelSDKFactory (
   type: ModelType, 
   type: ModelType, 
   dom: HTMLDivElement | HTMLIFrameElement
   dom: HTMLDivElement | HTMLIFrameElement
 ): Promise<ModelExpose> {
 ): Promise<ModelExpose> {
+  console.log(type, dom)
   if (type === fuseModel) {
   if (type === fuseModel) {
     if (!fuseInitialed) {
     if (!fuseInitialed) {
       await initialSDK({ layout: dom })
       await initialSDK({ layout: dom })
@@ -146,7 +147,7 @@ export async function exposeFactory(type: ModelType, win?: any): Promise<ModelEx
       }
       }
     }
     }
   }
   }
-  platforms[SceneType.SWKJ] = platforms[SceneType.SWKK]
+  platforms[SceneType.SWSSMX] = platforms[SceneType.SWKJ] = platforms[SceneType.SWKK]
   platforms[SceneType.SWMX] = {
   platforms[SceneType.SWMX] = {
     getSDK: async () => findObjectAttr(win , '__sdk'),
     getSDK: async () => findObjectAttr(win , '__sdk'),
     expose: platforms[fuseModel].expose
     expose: platforms[fuseModel].expose

+ 2 - 3
src/router/config.ts

@@ -1,8 +1,7 @@
 import { RoutesName, paths, metas } from './constant'
 import { RoutesName, paths, metas } from './constant'
 
 
-import type { RouteRecordRaw } from 'vue-router'
-
-export const routes: RouteRecordRaw[] = [
+export type RouteRaw = (typeof routes)[number]
+export const routes = [
   {
   {
     path: paths[RoutesName.fuseEdit],
     path: paths[RoutesName.fuseEdit],
     name: RoutesName.fuseEdit,
     name: RoutesName.fuseEdit,

+ 6 - 1
src/router/constant.ts

@@ -81,6 +81,10 @@ export const metas = {
     title: '测量'
     title: '测量'
   },
   },
 
 
+  [RoutesName.view]: { sysTitle: '视图提取' },
+  [RoutesName.record]: { sysTitle: '屏幕录制' },
+  [RoutesName.show]: { sysTitle: '' },
+
 
 
   [RoutesName.summaryShow]: {
   [RoutesName.summaryShow]: {
     icon: 'list-view',
     icon: 'list-view',
@@ -88,7 +92,8 @@ export const metas = {
   },
   },
   [RoutesName.viewShow]: {
   [RoutesName.viewShow]: {
     icon: 'list-scene',
     icon: 'list-scene',
-    title: '视图'
+    title: '视图',
+    
   },
   },
   [RoutesName.recordShow]: {
   [RoutesName.recordShow]: {
     icon: 'list-record',
     icon: 'list-record',

+ 27 - 14
src/router/index.ts

@@ -4,34 +4,47 @@ import { computed } from 'vue'
 import { RoutesName } from './constant'
 import { RoutesName } from './constant'
 import { metas } from './constant'
 import { metas } from './constant'
 
 
-import type { RouteRecordRaw, RouteRecordName } from 'vue-router'
+import { RouteRaw } from './config'
 
 
 export const history = createWebHashHistory()
 export const history = createWebHashHistory()
 export const router = createRouter({ history, routes })
 export const router = createRouter({ history, routes })
 
 
-export const getRouteNames = (name: RouteRecordName, routes: RouteRecordRaw[]): void | RouteRecordName[] => {
-  for (const route of routes) {
+export const getRouteTree = (name: RoutesName, raw: RouteRaw[] = routes): void | RouteRaw => {
+  for (const route of raw) {
     if (route.name === name) {
     if (route.name === name) {
-      return [route.name]
+      return route
     } else if (route.children) {
     } else if (route.children) {
-      const baseNames = getRouteNames(name, route.children)
-      if (baseNames) {
-        return [route.name as string, ...baseNames]
+      const children = getRouteTree(name, route.children)
+      if (children) {
+        return {...route, children: [children] }
       }
       }
     }
     }
   }
   }
 }
 }
 
 
-export const currentRouteNames = computed(() => {
-  const currentName = router.currentRoute.value.name
-  return currentName 
-    ? getRouteNames(currentName, routes) || []
-    : []
-})
+export const getRouteNames = (name: RoutesName, raw: RouteRaw[] = routes): RoutesName[] => {
+  let current = getRouteTree(name, raw)
+  const names: RoutesName[] = []
+  while (current) {
+    names.push(current.name!)
+    current = current.children && current.children[0]
+  }
+  return names
+}
+
+export const getRouteConfig = (name: RoutesName, raw: RouteRaw[] = routes): RouteRaw | void => {
+  let current = getRouteTree(name, raw)
+  while (current?.children && current.children[0]) {
+    current = current.children[0]
+  }
+  return current
+}
+
+export const currentRouteNames = computed(() => getRouteNames(router.currentRoute.value.name as RoutesName, routes))
 
 
 export const currentLayout = computed(() => {
 export const currentLayout = computed(() => {
   const names = currentRouteNames.value
   const names = currentRouteNames.value
-  const layoutNames = [RoutesName.fuseEditSwitch] as const
+  const layoutNames = [RoutesName.fuseEditSwitch, RoutesName.show, RoutesName.sceneEdit, RoutesName.signModel] as const
   return layoutNames.find(name => names.includes(name))
   return layoutNames.find(name => names.includes(name))
 })
 })
 
 

+ 64 - 28
src/sdk/association.ts

@@ -18,6 +18,7 @@ import {
   togetherCallback
   togetherCallback
 } from '@/utils'
 } from '@/utils'
 import { 
 import { 
+  dynamicAddedModelIds,
   fuseModels, 
   fuseModels, 
   taggings, 
   taggings, 
   isEdit, 
   isEdit, 
@@ -28,8 +29,9 @@ import {
   measures,
   measures,
   fuseModelsLoaded,
   fuseModelsLoaded,
   getMeasureIsShow,
   getMeasureIsShow,
-  unSetModelUpdate
+  SceneStatus
 } from '@/store'
 } from '@/store'
+import { currentLayout, RoutesName } from '@/router'
 
 
 import TaggingComponent from '@/components/tagging/list.vue'
 import TaggingComponent from '@/components/tagging/list.vue'
 
 
@@ -72,14 +74,30 @@ const associationModels = (sdk: SDK) => {
         continue;
         continue;
       }
       }
 
 
+      if (item.status !== SceneStatus.SUCCESS) {
+        item.error = true
+        item.loaded = true
+        continue;
+      }
+
       const itemRaw = toRaw(item)
       const itemRaw = toRaw(item)
-      console.error('loaded')
-      const sceneModel = sdk.addModel({
-        ...itemRaw,
-        ...modelRange,
-        type: item.type === SceneType.SWSS ? 'laser' : 'glb',
-        url: item.type === SceneType.SWSS ? item.url : getResource(item.url)
-      })
+      let sceneModel: SceneModel
+      console.error('loaded', itemRaw)
+      try {
+        sceneModel = sdk.addModel({
+          ...itemRaw,
+          ...modelRange,
+          mode: RoutesName.signModel === currentLayout.value! ? 'single' : 'many',
+          isDynamicAdded: dynamicAddedModelIds.value.some(id => itemRaw.id === id),
+          type: item.type === SceneType.SWSS ? 'laser' : item.modelType,
+          url: item.type === SceneType.SWSS ? item.url : getResource(item.url)
+        })
+      } catch(e) {
+        console.error('模型加载失败', e)
+        item.error = true
+        return;
+      }
+
       sceneModelMap.set(itemRaw, sceneModel)
       sceneModelMap.set(itemRaw, sceneModel)
 
 
       let changeId: NodeJS.Timeout
       let changeId: NodeJS.Timeout
@@ -102,9 +120,10 @@ const associationModels = (sdk: SDK) => {
               z: round(transform.position.z, 5),
               z: round(transform.position.z, 5),
             }
             }
           }
           }
-          if (transform.bottom) {
-            transform.bottom = round(transform.bottom, 2)
-          }
+          delete transform.bottom
+          // if (transform.bottom) {
+          //   transform.bottom = round(transform.bottom, 2)
+          // }
           if (transform.scale) {
           if (transform.scale) {
             transform.scale = round(transform.scale, 2)
             transform.scale = round(transform.scale, 2)
           }
           }
@@ -116,19 +135,19 @@ const associationModels = (sdk: SDK) => {
           }
           }
           
           
           if (deepIsRevise(update, transform)) {
           if (deepIsRevise(update, transform)) {
-
-          console.error(update, transform)
             unSet(() => Object.assign(item, transform))
             unSet(() => Object.assign(item, transform))
           }
           }
         }, 16)
         }, 16)
       })
       })
 
 
       sceneModel.bus.on('changeSelect', select => {
       sceneModel.bus.on('changeSelect', select => {
-        if (custom.currentModel === item && !select) {
-          custom.currentModel = null
-        } else if (custom.currentModel !== item && select) {
-          custom.currentModel = item
-        }
+        unSet(() => {
+          if (custom.currentModel === item && !select) {
+            custom.currentModel = null
+          } else if (custom.currentModel !== item && select) {
+            custom.currentModel = item
+          }
+        })
       })
       })
       showLoad()
       showLoad()
       sceneModel.bus.on('loadDone', () => {
       sceneModel.bus.on('loadDone', () => {
@@ -138,6 +157,7 @@ const associationModels = (sdk: SDK) => {
       sceneModel.bus.on('loadError', () => {
       sceneModel.bus.on('loadError', () => {
         item.error = true
         item.error = true
         item.show = false
         item.show = false
+        
         custom.showModelsMap.delete(item)
         custom.showModelsMap.delete(item)
         hideLoad()
         hideLoad()
       })
       })
@@ -158,50 +178,66 @@ const associationModels = (sdk: SDK) => {
           watch(
           watch(
             () => item.bottom, 
             () => item.bottom, 
             () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom), 
             () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom), 
-            { immediate: true }
+            // { immediate: true }
           )
           )
           watch(
           watch(
             () => item.opacity, 
             () => item.opacity, 
             () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity), 
             () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity), 
-            { immediate: true }
+            // { immediate: true }
           )
           )
           watch(
           watch(
             () => item.scale, 
             () => item.scale, 
             () => isUnSet || getSceneModel(item)?.changeScale(item.scale), 
             () => isUnSet || getSceneModel(item)?.changeScale(item.scale), 
-            { immediate: true }
+            // { immediate: true }
           )
           )
           watch(
           watch(
             () => item.position, 
             () => item.position, 
             () => {
             () => {
               if (!isUnSet) {
               if (!isUnSet) {
-                console.log('set position', item.position)
                 getSceneModel(item)?.changePosition(item.position)
                 getSceneModel(item)?.changePosition(item.position)
               }
               }
             }, 
             }, 
-            { immediate: true }
+            // { immediate: true }
           )
           )
           watch(
           watch(
             () => item.rotation, 
             () => item.rotation, 
             () => {
             () => {
               if (!isUnSet) {
               if (!isUnSet) {
-                console.log('set rotation', item.rotation)
                 getSceneModel(item)?.changeRotation(item.rotation)
                 getSceneModel(item)?.changeRotation(item.rotation)
               }
               }
             }, 
             }, 
-            { immediate: true }
+            // { immediate: true }
           )
           )
           watch(
           watch(
             () => modelShow.value, 
             () => modelShow.value, 
-            () => isUnSet || getSceneModel(item)?.changeShow(modelShow.value), 
+            () => {
+              const sceneModel = getSceneModel(item)
+              if (!isUnSet && sceneModel) {
+                sceneModel.changeSelect(false)
+                sceneModel.changeShow(modelShow.value)
+              }
+            }, 
             { immediate: true }
             { immediate: true }
           )
           )
 
 
+          watch(
+            () => custom.currentModel === item,
+            (selected) => {
+              isUnSet || console.log(item.title, selected, getSceneModel(item))
+              isUnSet || getSceneModel(item)?.changeSelect(selected)
+            }
+          )
+
           stopLoadedWatch()
           stopLoadedWatch()
         }
         }
       },
       },
       // { immediate: true }
       // { immediate: true }
     )
     )
   })
   })
+
+  watch(() => custom.currentModel, () => {
+
+  })
 }
 }
 
 
 
 
@@ -319,7 +355,7 @@ const fullView = async (fn: () => void) => {
 }
 }
 
 
 export const isScenePlayIng = ref(false)
 export const isScenePlayIng = ref(false)
-export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallback?: (index: number) => void) => {
+export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallback?: (index: number) => void, forceFull = false) => {
   if (isScenePlayIng.value) {
   if (isScenePlayIng.value) {
     throw new Error('导览正在播放')
     throw new Error('导览正在播放')
   }
   }
@@ -330,7 +366,7 @@ export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallbac
   changeIndexCallback && sceneGuide.bus.on('changePoint', changeIndexCallback)
   changeIndexCallback && sceneGuide.bus.on('changePoint', changeIndexCallback)
 
 
   const quitHandler = () => (isScenePlayIng.value = false)
   const quitHandler = () => (isScenePlayIng.value = false)
-  const clearHandler = isEdit.value ? null : await fullView(quitHandler)
+  const clearHandler = !forceFull && isEdit.value ? null : await fullView(quitHandler)
   if (!clearHandler) {
   if (!clearHandler) {
     sysBus.on('leave', quitHandler, { last: true })
     sysBus.on('leave', quitHandler, { last: true })
     sysBus.on('save', quitHandler, { last: true })
     sysBus.on('save', quitHandler, { last: true })

+ 92 - 44
src/sdk/cover/index.js

@@ -9,9 +9,18 @@ export const enter = (dom, isLocal) => {
     //Potree.fileServer = axios 
     //Potree.fileServer = axios 
     Potree.settings.libsUrl = './lib/'
     Potree.settings.libsUrl = './lib/'
     
     
-    const tagLimitDis = 8;
+    if(location.host === 'mix3d.4dkankan.com' ){//正式环境
+        Potree.settings.urls.prefix = Potree.settings.urls.prefix6  
+        Potree.settings.webSite = 'datav1' 
+    }
+    
     
     
+    Potree.settings.notAdditiveBlending = true
     
     
+    const tagLimitDis = 8;
+
+    Potree.settings.showCompass = true
+    Potree.settings.compassDom = dom.querySelector('#direction')
     
     
     let {THREE} = Potree.mergeEditStart(dom)
     let {THREE} = Potree.mergeEditStart(dom)
     let MergeEditor = viewer.modules.MergeEditor
     let MergeEditor = viewer.modules.MergeEditor
@@ -249,11 +258,11 @@ export const enter = (dom, isLocal) => {
             const camera = viewer.scene.getActiveCamera()
             const camera = viewer.scene.getActiveCamera()
             const target = viewer.scene.view.getPivot().clone()
             const target = viewer.scene.view.getPivot().clone()
             const position = viewer.scene.view.position.clone() 
             const position = viewer.scene.view.position.clone() 
-            console.log('getPose',position, target)
+            //console.log('getPose',position, target)
             return { position, target }
             return { position, target }
         }, 
         }, 
         comeTo(o = {}) {
         comeTo(o = {}) {
-            console.log('comeTo',o.position, o.target)
+            //console.log('comeTo',o.position, o.target)
             //飞到某个点  
             //飞到某个点  
             if(o.modelId){ 
             if(o.modelId){ 
                 ['position','target'].forEach(e=>{
                 ['position','target'].forEach(e=>{
@@ -410,7 +419,7 @@ export const enter = (dom, isLocal) => {
             } 
             } 
             */
             */
             
             
-            console.log('enterSceneGuide',pathArr)
+            //console.log('enterSceneGuide',pathArr)
             
             
             let data = {
             let data = {
                 duration: pathArr.slice(0,pathArr.length-1).reduce(function(total, currentValue ){return total+currentValue.time}, 0), //总时长(要去掉最后一个,因为已到终点,该点time无意义)
                 duration: pathArr.slice(0,pathArr.length-1).reduce(function(total, currentValue ){return total+currentValue.time}, 0), //总时长(要去掉最后一个,因为已到终点,该点time无意义)
@@ -469,15 +478,16 @@ export const enter = (dom, isLocal) => {
         //scaleRange: { min, max }, opacityRange: { min, max }, bottomRange: { min, max } })
         //scaleRange: { min, max }, opacityRange: { min, max }, bottomRange: { min, max } })
         
         
         addModel(props){ 
         addModel(props){ 
-            
+ 
+            //console.log(props.isDynamicAdded, props.mode)
+ 
             let bus = mitt()  
             let bus = mitt()  
             //console.log('addModel',props)
             //console.log('addModel',props)
-            props.isFirstLoad = props.bottom == void 0 //在编辑时用户添加的
-            if(props.opacity == void 0)  props.opacity = 1
+            props.isFirstLoad = isLocal ? props.bottom == void 0 : (props.isDynamicAdded || props.mode == 'single')  // 在编辑时用户添加的 或 展示单个模型 (props.mode='single'模型展示页, props.mode='many'融合页)
+            if(props.opacity == void 0)  props.opacity = 1 
+            if(props.type == 'obj') props.type = 'glb'
             props.scale /= 100
             props.scale /= 100
             
             
-            
-            
             if(!props.isFirstLoad){ 
             if(!props.isFirstLoad){ 
                 if(autoLoads.length == 0){ //首次加载
                 if(autoLoads.length == 0){ //首次加载
                     setTimeout(()=>{
                     setTimeout(()=>{
@@ -494,7 +504,7 @@ export const enter = (dom, isLocal) => {
                 autoLoads.push(props)
                 autoLoads.push(props)
                 readyToAddModel = false               
                 readyToAddModel = false               
             }else{
             }else{
-                readyToAddModel = true
+                readyToAddModel = true 
             }
             }
              
              
             
             
@@ -543,6 +553,7 @@ export const enter = (dom, isLocal) => {
                 if(!props.isFirstLoad){
                 if(!props.isFirstLoad){
                     model.visible = false//先不显示,防止卡顿
                     model.visible = false//先不显示,防止卡顿
                 }
                 }
+                
                 props.opacity < 100 && result.changeOpacity(props.opacity) 
                 props.opacity < 100 && result.changeOpacity(props.opacity) 
                 
                 
                 model.addEventListener('changeSelect',(e)=>{
                 model.addEventListener('changeSelect',(e)=>{
@@ -553,10 +564,20 @@ export const enter = (dom, isLocal) => {
                         position : model.position.clone(),
                         position : model.position.clone(),
                         scale: model.scale.x * 100,
                         scale: model.scale.x * 100,
                         rotation: model.rotation.clone(),
                         rotation: model.rotation.clone(),
-                        bottom: model.btmHeight
+                        //bottom: model.btmHeight
                     })
                     })
                 }) 
                 }) 
                 spliceFromArr(model,true)
                 spliceFromArr(model,true)
+                
+                                
+                if(props.mode == 'single'){//模型查看页
+                    MergeEditor.noNeedSelection = true
+                    setTimeout(()=>{
+                        MergeEditor.focusOn([model], 1000, true, true)
+                    },1) 
+                }
+
+                
                 bus.emit('loadDone')
                 bus.emit('loadDone')
                 
                 
                 //console.log('loadDone' )
                 //console.log('loadDone' )
@@ -618,40 +639,53 @@ export const enter = (dom, isLocal) => {
                         model.dispatchEvent("scale_changed")
                         model.dispatchEvent("scale_changed")
                     }
                     }
                 },
                 },
-                changeOpacity(opacity){ 
+                changeOpacity(opacity){ //见笔记:透明物体的材质设置
                     if(opacity == void 0)opacity = 100
                     if(opacity == void 0)opacity = 100
                     opacity/=100
                     opacity/=100
                     
                     
-                    let setOp = (mesh)=>{//见笔记:透明物体的材质设置
-                        if(model.isPointcloud){
-                            mesh.changePointOpacity(opacity)  
-                        }else{
-                            mesh.material.opacity = opacity
-                        }
-                        
-                        if(opacity<1){
-                            mesh.material.transparent = true
-                            mesh.renderOrder = Potree.config.renderOrders.model+1 
-                            mesh.material.depthWrite = false
-                        }else{
-                            mesh.material.transparent = false
-                            mesh.renderOrder = Potree.config.renderOrders.model
-                            mesh.material.depthWrite = true
-                        }
-                    }
+                  
                     
                     
                     if(model){
                     if(model){
-                        if(model.isPointcloud){
-                            setOp(model)  
+                        if(model.isPointcloud){ 
+                            model.changePointOpacity(opacity) 
+                            //MergeEditor.updateEdgeStrength()
                         }else{
                         }else{
-                            model.traverse(e=>e.material && setOp(e, opacity))
+                            //model.traverse(e=>e.material && setOp(e, opacity))
+                            model.traverse(mesh=>{
+                                if(mesh.material){ 
+                                
+                                    mesh.material.opacity = opacity
+                                    if(opacity<1){
+                                        mesh.material.transparent = true 
+                                        if(model.isPointcloud){
+                                            mesh.changePointOpacity(opacity)  
+                                        }else{
+                                            mesh.material.opacity = opacity
+                                        }
+                                        
+                                        mesh.renderOrder = Potree.config.renderOrders.model+1 
+                                        mesh.material.depthWrite = false
+                                    }else{
+                                        mesh.material.transparent = false
+                                        mesh.renderOrder = Potree.config.renderOrders.model
+                                        mesh.material.depthWrite = true
+                                    }
+                                
+                                }
+                            })
                         }
                         }
+                            
+                            
+                            
+                            
+                         
+                        model.opacity = opacity//记录在最外层
                     }
                     }
                      
                      
                 },
                 },
                 changeBottom(z){
                 changeBottom(z){
-                    model && MergeEditor.setModelBtmHeight(model,z)
-                    model.dispatchEvent('transformChanged') //改了position
+                    /* model && MergeEditor.setModelBtmHeight(model,z)
+                    model.dispatchEvent('transformChanged') //改了position */
                 },
                 },
                 changePosition(pos){//校准取消时执行
                 changePosition(pos){//校准取消时执行
                     //if(MergeEditor.selected == model){
                     //if(MergeEditor.selected == model){
@@ -673,38 +707,49 @@ export const enter = (dom, isLocal) => {
                     if(model){
                     if(model){
                         if(MergeEditor.split){//分屏校准
                         if(MergeEditor.split){//分屏校准
                             MergeEditor.setTransformState('rotate')
                             MergeEditor.setTransformState('rotate')
-                        }else{
-                            MergeEditor.transformControls.attach(model)
-                            MergeEditor.transformControls.mode = 'rotate'
+                            MergeEditor.transformControls2.attach(model)
+                            MergeEditor.transformControls2.mode = 'rotate'
                         } 
                         } 
+                         
+                        
+                        MergeEditor.transformControls.attach(model)
+                        MergeEditor.transformControls.mode = 'rotate'
+                         
                     }
                     }
                 }, 
                 }, 
                 enterMoveMode(){
                 enterMoveMode(){
                     if(model){ 
                     if(model){ 
                         if(MergeEditor.split){//分屏校准
                         if(MergeEditor.split){//分屏校准
                             MergeEditor.setTransformState('translate')
                             MergeEditor.setTransformState('translate')
-                        }else{
-                            MergeEditor.transformControls.attach(model)
-                            MergeEditor.transformControls.mode = 'translate'
+                            MergeEditor.transformControls2.attach(model)
+                            MergeEditor.transformControls2.mode = 'translate'
                         } 
                         } 
+                        MergeEditor.transformControls.attach(model)
+                        MergeEditor.transformControls.mode = 'translate'
+                         
                     }
                     }
                 },
                 },
-                leaveTransform(){
-                    
+                leaveTransform(){ 
                     
                     
                     if(MergeEditor.split){//分屏校准
                     if(MergeEditor.split){//分屏校准
                         MergeEditor.setTransformState(null)
                         MergeEditor.setTransformState(null)
                     }else{ 
                     }else{ 
                         MergeEditor.transformControls.detach()
                         MergeEditor.transformControls.detach()
+                        MergeEditor.transformControls2.detach()
                     }
                     }
                 },
                 },
                 
                 
                 enterAlignment(){//开始校准
                 enterAlignment(){//开始校准
-                    MergeEditor.enterSplit()
                     result.leaveTransform()
                     result.leaveTransform()
+                    MergeEditor.enterSplit()
+                    
+                    
                     //console.log('enterAlignment',model.position, model.rotation)
                     //console.log('enterAlignment',model.position, model.rotation)
                     let bus = new mitt()
                     let bus = new mitt()
                     
                     
+                    /* MergeEditor.transformControls.attach(model)
+                    MergeEditor.transformControls.mode = 'translate' */
+                    
                     return { 
                     return { 
                         bus  
                         bus  
                     } 
                     } 
@@ -712,6 +757,9 @@ export const enter = (dom, isLocal) => {
                 leaveAlignment(){
                 leaveAlignment(){
                     //console.log('leaveAlignment',model.position, model.rotation)
                     //console.log('leaveAlignment',model.position, model.rotation)
                     MergeEditor.leaveSplit() 
                     MergeEditor.leaveSplit() 
+                    
+                    MergeEditor.transformControls.detach()
+                    MergeEditor.transformControls2.detach()
                 },
                 },
                 
                 
                 enterScaleSet(){//设置比例
                 enterScaleSet(){//设置比例
@@ -989,7 +1037,7 @@ export const enter = (dom, isLocal) => {
     }
     }
      
      
     
     
-    console.log('版本: 2022.8.29-1')
+    console.log('版本: 2022.9.20-1')
      
      
     return sdk 
     return sdk 
 }
 }

+ 2 - 2
src/sdk/sdk.ts

@@ -49,7 +49,7 @@ export type ModelAttrRange = {
 
 
 export type AddModelProps = Pick<FuseModel, 'url' | 'id'> 
 export type AddModelProps = Pick<FuseModel, 'url' | 'id'> 
   & FuseModelAttrs 
   & FuseModelAttrs 
-  & { type: 'laser' | 'glb' }
+  & { type: string, isDynamicAdded: boolean, mode: 'many' | 'single' }
   & ModelAttrRange
   & ModelAttrRange
 
 
 
 
@@ -133,7 +133,7 @@ export const initialSDK = async (props: InialSDKProps) => {
   await Promise.all(libs.map(loadLib))
   await Promise.all(libs.map(loadLib))
   await loadLib(`./lib/potree/potree.js`)
   await loadLib(`./lib/potree/potree.js`)
 
 
-  const localSdk = cover(props.layout, true) as unknown as SDK
+  const localSdk = cover(props.layout, false) as unknown as SDK
 
 
   sdk = localSdk
   sdk = localSdk
   sdk.layout = props.layout
   sdk.layout = props.layout

+ 42 - 21
src/store/fuse-model.ts

@@ -1,4 +1,4 @@
-import { computed, ref, watch, watchEffect, watchPostEffect } from 'vue'
+import { computed, reactive, ref, watch, watchEffect, watchPostEffect } from 'vue'
 import { autoSetModeCallback, unSetModelUpdate, createTemploraryID } from './sys'
 import { autoSetModeCallback, unSetModelUpdate, createTemploraryID } from './sys'
 import { custom } from '@/env'
 import { custom } from '@/env'
 import { 
 import { 
@@ -6,15 +6,14 @@ import {
   postAddFuseModel, 
   postAddFuseModel, 
   postDeleteFuseModel,
   postDeleteFuseModel,
   postUpdateFuseModels,
   postUpdateFuseModels,
-  SceneType
+  SceneType,
+  SceneStatus
 } from '@/api'
 } from '@/api'
 import { 
 import { 
   deleteStoreItem, 
   deleteStoreItem, 
-  addStoreItem, 
   updateStoreItem, 
   updateStoreItem, 
   fetchStoreItems,
   fetchStoreItems,
   saveStoreItems,
   saveStoreItems,
-  recoverStoreItems,
   deepIsRevise
   deepIsRevise
 } from '@/utils'
 } from '@/utils'
 import { initialTaggings } from './tagging'
 import { initialTaggings } from './tagging'
@@ -27,6 +26,8 @@ export type FuseModels = FuseModel[]
 export type { FuseModelAttrs } from '@/api'
 export type { FuseModelAttrs } from '@/api'
 
 
 export const fuseModels = ref<FuseModels>([])
 export const fuseModels = ref<FuseModels>([])
+export const dynamicAddedModelIds = ref<FuseModel['id'][]>([])
+
 export const defaultFuseModelAttrs: FuseModelAttrs = {
 export const defaultFuseModelAttrs: FuseModelAttrs = {
   show: true,
   show: true,
   scale: 100,
   scale: 100,
@@ -42,7 +43,9 @@ export const createFuseModels = (model: Partial<FuseModel> = {}): FuseModel => s
   url: '',
   url: '',
   fusionId: 0,
   fusionId: 0,
   title: '',
   title: '',
+  modelType: 'glb',
   type: SceneType.SWMX,
   type: SceneType.SWMX,
+  status: SceneStatus.SUCCESS,
   size: 0,
   size: 0,
   time: new Date().toString(),
   time: new Date().toString(),
   ...defaultFuseModelAttrs,
   ...defaultFuseModelAttrs,
@@ -50,21 +53,29 @@ export const createFuseModels = (model: Partial<FuseModel> = {}): FuseModel => s
 })
 })
 
 
 export const getFuseModel = (modelId: FuseModel['id']) => fuseModels.value.find(model => model.id === modelId)
 export const getFuseModel = (modelId: FuseModel['id']) => fuseModels.value.find(model => model.id === modelId)
+let setModel: FuseModel
 export const getFuseModelShowVariable = (model: FuseModel) => 
 export const getFuseModelShowVariable = (model: FuseModel) => 
   computed({
   computed({
-    get: () => custom.modelsChangeStore 
+    get: () => {
+      return false && custom.modelsChangeStore 
       ? model.show 
       ? model.show 
-      : custom.showModelsMap.get(model) || false,
+      : custom.showModelsMap.get(model) || false
+    },
     set: (show: boolean) => {
     set: (show: boolean) => {
-      if (custom.modelsChangeStore) {
+      if (false && custom.modelsChangeStore) {
         model.show = show
         model.show = show
       } else {
       } else {
+        setModel = model
         custom.showModelsMap.set(model, show)
         custom.showModelsMap.set(model, show)
       }
       }
     }
     }
   })
   })
   
   
 
 
+watchEffect(() => {
+  fuseModels.value.forEach(item => custom.showModelsMap.get(item))
+}, { flush: 'sync' })
+
 export const fuseModelsLoaded = ref(false)
 export const fuseModelsLoaded = ref(false)
 watchPostEffect(() => {
 watchPostEffect(() => {
   const loaded = fuseModels.value
   const loaded = fuseModels.value
@@ -73,7 +84,6 @@ watchPostEffect(() => {
   fuseModelsLoaded.value = loaded
   fuseModelsLoaded.value = loaded
 })
 })
 
 
-
 let bcModels: FuseModels = []
 let bcModels: FuseModels = []
 export const getBackupFuseModels = () => bcModels
 export const getBackupFuseModels = () => bcModels
 export const backupFuseModels = () => {
 export const backupFuseModels = () => {
@@ -82,8 +92,11 @@ export const backupFuseModels = () => {
     rotation: {...model.rotation},
     rotation: {...model.rotation},
     position: {...model.position},
     position: {...model.position},
   }))
   }))
+  for (const model of fuseModels.value) {
+    initFuseModel(model)
+  }
 }
 }
-watchEffect(() => {
+watch(fuseModels, () => {
   for (const model of bcModels) {
   for (const model of bcModels) {
     const newModel = getFuseModel(model.id)
     const newModel = getFuseModel(model.id)
     if (newModel) {
     if (newModel) {
@@ -92,7 +105,7 @@ watchEffect(() => {
       model.loaded = newModel.loaded
       model.loaded = newModel.loaded
     }
     }
   }
   }
-})
+}, { deep: true })
 
 
 const serviceToLocal = (model: SModel): FuseModel => ({
 const serviceToLocal = (model: SModel): FuseModel => ({
   ...model, 
   ...model, 
@@ -102,7 +115,7 @@ const serviceToLocal = (model: SModel): FuseModel => ({
 })
 })
 
 
 const initFuseModel = (model: FuseModel) => {
 const initFuseModel = (model: FuseModel) => {
-  custom.showModelsMap.set(model, model.show)
+  custom.showModelsMap.has(model) || custom.showModelsMap.set(model, model.show)
 }
 }
 
 
 export const recoverFuseModels = () => {
 export const recoverFuseModels = () => {
@@ -111,17 +124,25 @@ export const recoverFuseModels = () => {
     const model = fuseModels.value.find(item => item.id === oldItem.id)
     const model = fuseModels.value.find(item => item.id === oldItem.id)
     return model ? Object.assign(model, oldItem) : serviceToLocal(oldItem)
     return model ? Object.assign(model, oldItem) : serviceToLocal(oldItem)
   })
   })
+  console.log(backupItems)
 }
 }
 
 
 export const updateFuseModel = updateStoreItem(fuseModels, postUpdateFuseModels)
 export const updateFuseModel = updateStoreItem(fuseModels, postUpdateFuseModels)
-export const deleteFuseModel = deleteStoreItem(fuseModels, postDeleteFuseModel)
+export const deleteFuseModel = deleteStoreItem(fuseModels, async (model) => {
+  await postDeleteFuseModel(model)
+  const index = dynamicAddedModelIds.value.indexOf(model.id)
+  ~index && dynamicAddedModelIds.value.splice(index, 1)
+})
+
 export const addFuseModel = async (model: FuseModel) => {
 export const addFuseModel = async (model: FuseModel) => {
-  const addModel = serviceToLocal(await postAddFuseModel(model))
+  const addModel = reactive(serviceToLocal(await postAddFuseModel(model)))
   initFuseModel(addModel)
   initFuseModel(addModel)
-  unSetModelUpdate(() => fuseModels.value.push(addModel))
+  unSetModelUpdate(() => {
+    fuseModels.value.push(addModel)
+    dynamicAddedModelIds.value.push(addModel.id)
+  })
 }
 }
 
 
-
 export const initialFuseModels = fetchStoreItems(
 export const initialFuseModels = fetchStoreItems(
   fuseModels, 
   fuseModels, 
   fetchFuseModels, 
   fetchFuseModels, 
@@ -154,12 +175,12 @@ export const autoSaveFuseModels = autoSetModeCallback(fuseModels, {
   save: async () => {
   save: async () => {
     await saveFuseModels()
     await saveFuseModels()
 
 
-    for (const model of bcModels) {
-      const currentModel = getFuseModel(model.id)
-      if (currentModel && currentModel.show !== model.show) {
-        custom.showModelsMap.set(currentModel, currentModel.show)
-      }
-    }
+    // for (const model of bcModels) {
+    //   const currentModel = getFuseModel(model.id)
+    //   if (currentModel && currentModel.show !== model.show) {
+    //     custom.showModelsMap.set(currentModel, currentModel.show)
+    //   }
+    // }
     
     
     await Promise.all([
     await Promise.all([
       initialTaggings(),
       initialTaggings(),

+ 16 - 1
src/store/guide-path.ts

@@ -31,6 +31,7 @@ export const createGuidePath = (path: Partial<GuidePath> = {}): GuidePath => ({
   guideId: '',
   guideId: '',
   cover: '',
   cover: '',
   time: 1,
   time: 1,
+  sort: 999,
   speed: 1,
   speed: 1,
   position: {x: 0, y: 0, z: 0},
   position: {x: 0, y: 0, z: 0},
   target: {x: 0, y: 0, z: 0},
   target: {x: 0, y: 0, z: 0},
@@ -61,7 +62,7 @@ export const initialGuidePathsByGuide = async (guide: Guide) => {
   backupGuidePaths()
   backupGuidePaths()
 }
 }
 
 
-export const saveGuidePaths = saveStoreItems(
+const _saveGuidePaths = saveStoreItems(
   guidePaths,
   guidePaths,
   getBackupGuidePaths,
   getBackupGuidePaths,
   {
   {
@@ -70,6 +71,20 @@ export const saveGuidePaths = saveStoreItems(
     delete: deleteGuidePath,
     delete: deleteGuidePath,
   }
   }
 )
 )
+
+export const saveGuidePaths = async () => {
+  await _saveGuidePaths()
+  backupGuidePaths()
+  const sortMaps = new Map<GuidePath['guideId'], number>()
+
+  guidePaths.value.forEach((path) => {
+    const sort = (sortMaps.get(path.guideId) || 0) + 1
+    path.sort = sort
+    sortMaps.set(path.guideId, sort)
+  })
+  console.log(guidePaths.value)
+  await _saveGuidePaths()
+}
 export const autoSaveGuidePaths = autoSetModeCallback(guidePaths, {
 export const autoSaveGuidePaths = autoSetModeCallback(guidePaths, {
   backup: backupGuidePaths,
   backup: backupGuidePaths,
   recovery: recoverGuidePaths,
   recovery: recoverGuidePaths,

+ 10 - 2
src/store/measure.ts

@@ -20,6 +20,7 @@ import {
 } from '@/api'
 } from '@/api'
 
 
 import type { Measure as SMeasure } from '@/api'
 import type { Measure as SMeasure } from '@/api'
+import { Message } from 'bill/index'
 
 
 export type Measure<T extends MeasureType = MeasureType> = SMeasure<T> & { selected?: boolean }
 export type Measure<T extends MeasureType = MeasureType> = SMeasure<T> & { selected?: boolean }
 export type Measures = Measure[]
 export type Measures = Measure[]
@@ -41,7 +42,7 @@ export const getMeasureIsShow = (measure: Measure) =>
 export const createMeasure = (measure: Partial<Measure> = {}): Measure => ({
 export const createMeasure = (measure: Partial<Measure> = {}): Measure => ({
   id: createTemploraryID(),
   id: createTemploraryID(),
   fusionId: fuseModels.value[0].fusionId,
   fusionId: fuseModels.value[0].fusionId,
-  title: `测量${measures.value.length + 1}`,
+  title: MeasureTypeMeta[measure.type || MeasureType.free].unitDesc,
   positions: [],
   positions: [],
   desc: '',
   desc: '',
   type: MeasureType.free,
   type: MeasureType.free,
@@ -82,7 +83,14 @@ export const autoSaveMeasures = autoSetModeCallback(measures, {
   },
   },
   backup: backupMeasures,
   backup: backupMeasures,
   recovery: recoverMeasures,
   recovery: recoverMeasures,
-  save: saveMeasures
+  save: async () => {
+    if (!measures.value.every(record => record.title)) {
+      Message.warning('测量名称不可为空')
+      throw '测量名称不可为空'
+    }
+
+    await saveMeasures()
+  }
 })
 })
 
 
 export type { MeasurePosition } from '@/api'
 export type { MeasurePosition } from '@/api'

+ 16 - 5
src/store/record.ts

@@ -31,6 +31,7 @@ import {
 } from './record-fragment'
 } from './record-fragment'
 
 
 import type { Record as SRecord } from '@/api'
 import type { Record as SRecord } from '@/api'
+import { Message } from "bill/index";
 
 
 export type Record = LocalMode<SRecord, 'cover'> 
 export type Record = LocalMode<SRecord, 'cover'> 
 export type Records = Record[]
 export type Records = Record[]
@@ -39,7 +40,7 @@ export const records = ref<Records>([])
 
 
 export const createRecord = (record: Partial<Record> = {}): Record => ({
 export const createRecord = (record: Partial<Record> = {}): Record => ({
   id: createTemploraryID(),
   id: createTemploraryID(),
-  title: '讲解视频',
+  title: '讲解视频' + (records.value.length + 1),
   cover: '',
   cover: '',
   url: '',
   url: '',
   status: RecordStatus.UN,
   status: RecordStatus.UN,
@@ -55,12 +56,17 @@ export const backupRecords = () => {
 }
 }
 export const recoverRecords = recoverStoreItems(records, getBackupRecords)
 export const recoverRecords = recoverStoreItems(records, getBackupRecords)
 
 
+const refreshRecords: NodeJS.Timeout[] = []
 const refreshRecordStatus = async (record: Record) => {
 const refreshRecordStatus = async (record: Record) => {
   const status = await fetchRecordStatus(record.id)
   const status = await fetchRecordStatus(record.id)
   if (status === RecordStatus.SUCCESS) {
   if (status === RecordStatus.SUCCESS) {
-    unSetModelUpdate(() => record.status = RecordStatus.SUCCESS)
+    refreshRecords.forEach(clearTimeout)
+    refreshRecords.length = 0
+    initialRecords()
   } else {
   } else {
-    setTimeout(refreshRecordStatus.bind(null, record), 3000)
+    refreshRecords.push(
+      setTimeout(refreshRecordStatus.bind(null, record), 3000)
+    )
   }
   }
 }
 }
 
 
@@ -86,14 +92,14 @@ export const initialRecords = async () => {
 }
 }
 
 
 export const addRecord = addStoreItem(records, async (record) => {
 export const addRecord = addStoreItem(records, async (record) => {
-  const cover = await uploadFile(record.cover)
+  const cover = record.cover ? await uploadFile(record.cover) : record.cover
   const serviceRecord = await postAddRecord({ ...record, cover }, getRecordMergeFiles(record))
   const serviceRecord = await postAddRecord({ ...record, cover }, getRecordMergeFiles(record))
   record.id = serviceRecord.id
   record.id = serviceRecord.id
   await postUpdateRecord(record as SRecord)
   await postUpdateRecord(record as SRecord)
   return record
   return record
 })
 })
 export const updateRecord = updateStoreItem(records, async (record) => {
 export const updateRecord = updateStoreItem(records, async (record) => {
-  const cover = await uploadFile(record.cover)
+  const cover = record.cover ? await uploadFile(record.cover) : record.cover
   return await postUpdateRecord({ ...record, cover })
   return await postUpdateRecord({ ...record, cover })
 })
 })
 export const deleteRecord = deleteStoreItem(records, async record => {
 export const deleteRecord = deleteStoreItem(records, async record => {
@@ -115,6 +121,11 @@ export const autoSaveRecords = autoSetModeCallback(
     backup: togetherCallback([backupRecordFragments, backupRecords]),
     backup: togetherCallback([backupRecordFragments, backupRecords]),
     recovery: togetherCallback([recoverRecordFragments, recoverRecords]),
     recovery: togetherCallback([recoverRecordFragments, recoverRecords]),
     save: async () => {
     save: async () => {
+      if (!records.value.every(record => record.title)) {
+        Message.warning('视频名称不可为空')
+        throw '视频名称不可为空'
+      }
+
       for (let i = 0; i < records.value.length; i++) {
       for (let i = 0; i < records.value.length; i++) {
         records.value[i].sort = i
         records.value[i].sort = i
       }
       }

+ 1 - 1
src/store/scene.ts

@@ -15,4 +15,4 @@ export const initialScenes = fetchStoreItems(
   fetchScenes, 
   fetchScenes, 
 )
 )
 
 
-export { SceneType, SceneTypeDesc } from '@/api'
+export { SceneType, SceneTypeDesc, SceneStatus } from '@/api'

+ 18 - 4
src/store/sys.ts

@@ -1,9 +1,11 @@
-import { ref, computed, watch, nextTick } from 'vue'
+import { ref, computed, watch, nextTick, watchEffect } from 'vue'
 import { asyncBusFactory } from '@/utils'
 import { asyncBusFactory } from '@/utils'
 import { Dialog } from 'bill/index'
 import { Dialog } from 'bill/index'
 import { useViewStack } from '@/hook'
 import { useViewStack } from '@/hook'
 
 
 import type { UnwrapRef } from 'vue'
 import type { UnwrapRef } from 'vue'
+import { getCaseInfo } from '@/api'
+import { currentMeta } from '@/router'
 
 
 const Flags = {
 const Flags = {
   EDIT: 0b10,
   EDIT: 0b10,
@@ -18,14 +20,27 @@ export const isEdit = computed(() => !!(mode.value & Flags.EDIT))
 export const isLogin = computed(() => !!(mode.value & Flags.LOGIN))
 export const isLogin = computed(() => !!(mode.value & Flags.LOGIN))
 export const isOld = computed(() => !(mode.value & Flags.NOW))
 export const isOld = computed(() => !(mode.value & Flags.NOW))
 export const isNow = computed(() => !!(mode.value & Flags.NOW))
 export const isNow = computed(() => !!(mode.value & Flags.NOW))
-export const title = '多元融合'
 export const appEl = ref<HTMLDivElement | null>(null)
 export const appEl = ref<HTMLDivElement | null>(null)
+export const prefix = ref('')
+
+export const defTitle = ref('多元融合')
+export const title = computed(() => {
+  const last = currentMeta.value && 'sysTitle' in currentMeta.value
+    ? currentMeta.value.sysTitle
+    : defTitle.value
+
+  if (prefix.value && last) {
+    return prefix.value + ' | ' + last
+  } else {
+    return prefix.value + last
+  }
+})
+watchEffect(() => (document.title = title.value))
 
 
 let currentTempIndex = 0
 let currentTempIndex = 0
 export const isTemploraryID = (id: string) => id.includes('__currentTempIndex__')
 export const isTemploraryID = (id: string) => id.includes('__currentTempIndex__')
 export const createTemploraryID = () => `__currentTempIndex__${currentTempIndex++}`
 export const createTemploraryID = () => `__currentTempIndex__${currentTempIndex++}`
 
 
-
 export const sysBus = asyncBusFactory<{ save: void; leave: void }>()
 export const sysBus = asyncBusFactory<{ save: void; leave: void }>()
 
 
 // 进入编辑界面
 // 进入编辑界面
@@ -79,7 +94,6 @@ export type AutoSetModeSetting<T> = {
 
 
 let isUnset = false
 let isUnset = false
 export const unSetModelUpdate = (run: () => void) => {
 export const unSetModelUpdate = (run: () => void) => {
-  console.error('unset')
   isUnset = true
   isUnset = true
   run()
   run()
   nextTick(() => isUnset = false)
   nextTick(() => isUnset = false)

+ 2 - 0
src/store/tagging-style.ts

@@ -21,10 +21,12 @@ export type TaggingStyles = TaggingStyle[]
 
 
 export const taggingStyles = ref<TaggingStyles>([])
 export const taggingStyles = ref<TaggingStyles>([])
 export const defaultStyle = computed(() => taggingStyles.value.find(style => style.default))
 export const defaultStyle = computed(() => taggingStyles.value.find(style => style.default))
+export const lastUseStyle = computed(() => taggingStyles.value.find(style => style.lastUse))
 
 
 export const createTaggingStyle = (style: Partial<TaggingStyle> = {}): TaggingStyle => ({
 export const createTaggingStyle = (style: Partial<TaggingStyle> = {}): TaggingStyle => ({
   id: createTemploraryID(),
   id: createTemploraryID(),
   icon: '',
   icon: '',
+  lastUse: 0,
   default: false,
   default: false,
   name: '',
   name: '',
   ...style
   ...style

+ 15 - 12
src/store/tagging.ts

@@ -1,7 +1,7 @@
 import { ref } from 'vue'
 import { ref } from 'vue'
 import { togetherCallback } from '@/utils'
 import { togetherCallback } from '@/utils'
 import { autoSetModeCallback, createTemploraryID } from './sys'
 import { autoSetModeCallback, createTemploraryID } from './sys'
-import { defaultStyle, getTaggingStyle } from './tagging-style'
+import { defaultStyle, getTaggingStyle, initialTaggingStyles, lastUseStyle } from './tagging-style'
 import { 
 import { 
   initTaggingPositionsByTagging, 
   initTaggingPositionsByTagging, 
   saveTaggingPositions,
   saveTaggingPositions,
@@ -46,17 +46,19 @@ export const getTagging = (id: Tagging['id']) => taggings.value.find(tagging =>
 export const getTaggingIsShow = (tagging: Tagging) => 
 export const getTaggingIsShow = (tagging: Tagging) => 
   getTaggingPositions(tagging).some(getTaggingPositionIsShow)
   getTaggingPositions(tagging).some(getTaggingPositionIsShow)
 
 
-export const createTagging = (tagging: Partial<Tagging> = {}): Tagging => ({
-  id: createTemploraryID(),
-  title: ``,
-  styleId: defaultStyle.value?.id || '',
-  desc: '',
-  part: '',
-  method: '',
-  principal: '',
-  images: [],
-  ...tagging
-})
+export const createTagging = (tagging: Partial<Tagging> = {}): Tagging => {
+  return {
+    id: createTemploraryID(),
+    title: ``,
+    styleId: lastUseStyle.value?.id || defaultStyle.value?.id || '',
+    desc: '',
+    part: '',
+    method: '',
+    principal: '',
+    images: [],
+    ...tagging
+  }
+}
 
 
 
 
 let bcTaggings: Taggings = []
 let bcTaggings: Taggings = []
@@ -122,5 +124,6 @@ export const autoSaveTaggings = autoSetModeCallback([taggings, taggingPositions,
     }
     }
     await saveTaggings()
     await saveTaggings()
     await saveTaggingPositions()
     await saveTaggingPositions()
+    await initialTaggingStyles()
   },
   },
 })
 })

+ 6 - 1
src/store/view.ts

@@ -20,6 +20,7 @@ import { fuseModel } from '@/model'
 
 
 import type { View as SView } from '@/api'
 import type { View as SView } from '@/api'
 import type { ModelType } from '@/model'
 import type { ModelType } from '@/model'
+import { Message } from "bill/expose-common";
 
 
 export type View = LocalMode<SView, 'cover'>
 export type View = LocalMode<SView, 'cover'>
 export type Views = View[]
 export type Views = View[]
@@ -29,7 +30,7 @@ export const views = ref<Views>([])
 export const createView = (view: Partial<View> = {}): View => {
 export const createView = (view: Partial<View> = {}): View => {
   const base = {
   const base = {
     id: createTemploraryID(),
     id: createTemploraryID(),
-    title: '视图',
+    title: '视图' + (views.value.length + 1),
     cover: 'https://4dkk.4dage.com/scene_view_data/KK-t-F8e5M46wcQ/images/floor_0.png?t=1659422513133?v=0&rnd=0.9219648338739086&x-oss-process=image/resize,m_fill,w_80,h_60/quality,q_70&rnd=0.25420557086595965',
     cover: 'https://4dkk.4dage.com/scene_view_data/KK-t-F8e5M46wcQ/images/floor_0.png?t=1659422513133?v=0&rnd=0.9219648338739086&x-oss-process=image/resize,m_fill,w_80,h_60/quality,q_70&rnd=0.25420557086595965',
     flyData: '',
     flyData: '',
     num: null,
     num: null,
@@ -102,6 +103,10 @@ export const autoSaveViews = autoSetModeCallback(views, {
   backup: backupViews,
   backup: backupViews,
   recovery: recoverViews,
   recovery: recoverViews,
   save: async () => {
   save: async () => {
+    if (!views.value.every(view => view.title)) {
+      Message.warning('视图名称不可为空')
+      throw '视图名称不可为空'
+    }
     for (let i = 0; i < views.value.length; i++) {
     for (let i = 0; i < views.value.length; i++) {
       views.value[i].sort = i
       views.value[i].sort = i
     }
     }

+ 1 - 1
src/utils/loading.ts

@@ -3,7 +3,7 @@ import { defineAsyncComponent } from 'vue'
 
 
 import type { AsyncComponentLoader, Component } from 'vue'
 import type { AsyncComponentLoader, Component } from 'vue'
 
 
-export const loadPack = <T, K extends (...args: any) => Promise<T>>(fn: K | Promise<T>): Promise<T> => {
+export const loadPack = <T, K extends (...args: any[]) => Promise<T>>(fn: K | Promise<T>): Promise<T> => {
     Loading.show()
     Loading.show()
     const ret = typeof fn === 'function' ? fn() : fn
     const ret = typeof fn === 'function' ? fn() : fn
     ret.finally(() => Loading.hide())
     ret.finally(() => Loading.hide())

+ 10 - 0
src/utils/stack.ts

@@ -19,13 +19,23 @@ export const stackFactory = <T>(initVal?: T, debug?: boolean): Stack<T> => {
   return {
   return {
     push(raw: T) {
     push(raw: T) {
       stack.push(raw);
       stack.push(raw);
+      if (debug) {
+        console.warn('push', raw)
+      }
       return () => {
       return () => {
         const index = stack.indexOf(raw);
         const index = stack.indexOf(raw);
         ~index && stack.splice(index, 1);
         ~index && stack.splice(index, 1);
+        if (debug) {
+          console.warn('pop', raw)
+          console.warn('current', stack)
+        }
       };
       };
     },
     },
     pop() {
     pop() {
       let ret = stack[stack.length-- - 1];
       let ret = stack[stack.length-- - 1];
+      if (debug) {
+        console.warn('pop current', stack)
+      }
       return ret;
       return ret;
     },
     },
     current: computed(() => {
     current: computed(() => {

+ 0 - 2
src/utils/store-help.ts

@@ -152,8 +152,6 @@ export const saveStoreItems = <T extends {id: any}>(
   }
   }
 ) => () => {
 ) => () => {
   const oldItems = getOldItem()
   const oldItems = getOldItem()
-  console.error('save save')
-  console.log(oldItems)
   const {
   const {
     deleted,
     deleted,
     updated,
     updated,

+ 1 - 0
src/utils/video-cover.ts

@@ -38,6 +38,7 @@ export const getVideoCover = (data: string | Blob,  seekTo: number = 0.0, width?
         const dataURL = canvas.toDataURL('image/jpeg') 
         const dataURL = canvas.toDataURL('image/jpeg') 
 
 
         if (typeof data !== 'string') {
         if (typeof data !== 'string') {
+          video.pause()
           URL.revokeObjectURL(url)
           URL.revokeObjectURL(url)
         }
         }
         resolve(dataURL)
         resolve(dataURL)

+ 39 - 25
src/views/folder/index.vue

@@ -1,42 +1,39 @@
 <template>
 <template>
   <LeftPano>
   <LeftPano>
-    <div v-for="item in types" :key="item.id" class="types">
-      <h2 @click="item.show.value = !item.show.value">
-        {{item.title}}
-        <ui-icon :type="`pull-${item.show.value ? 'up' : 'down'}`" class="icon" ctrl />
-      </h2>
+    <template v-for="item in types">
+      <div :key="item.id" class="types" v-if="item.floders.length">
+        <h2 @click="item.show.value = !item.show.value">
+          {{item.title}}
+          <ui-icon :type="`pull-${item.show.value ? 'up' : 'down'}`" class="icon" ctrl />
+        </h2>
 
 
-      <div class="floders"  v-if="item.show.value">
-        <div 
-          v-for="floder in item.floders" 
-          :key="floder.filesId" 
-          class="fun-ctrl" 
-          @click="preview(floder)"
-        >
-          <ui-icon :type="typeIcons[floder.metaType]" v-if="floder.metaType" />
-          <p>{{ floder.filesTitle }}</p>
+        <div class="floders"  v-if="item.show.value">
+          <div 
+            v-for="floder in item.floders" 
+            :key="floder.filesId" 
+            class="fun-ctrl" 
+            @click="preview(floder)"
+          >
+            <ui-icon :type="typeIcons[floder.metaType]" v-if="floder.metaType" />
+            <p>{{ floder.filesTitle }}</p>
+          </div>
         </div>
         </div>
       </div>
       </div>
-    </div>
+    </template>
   </LeftPano>
   </LeftPano>
+
+  <Preview :items="[currentFile]" v-if="currentFile" @close="currentFile = null" />
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { LeftPano } from '@/layout'
 import { LeftPano } from '@/layout'
 import { computed, ref } from 'vue';
 import { computed, ref } from 'vue';
 import { getUrlType, MetaType } from '@/utils'
 import { getUrlType, MetaType } from '@/utils'
-import { 
-  initialFloderTypes, 
-  initialFloders,
-  floderTypes,
-  getFloderByType,
-} from '@/store'
+import { Preview, MediaItem, MediaType } from '@/components/static-preview/index.vue'
+import { floderTypes, getFloderByType } from '@/store'
 
 
 import type { Floder } from '@/store'
 import type { Floder } from '@/store'
 
 
-initialFloderTypes()
-initialFloders()
-
 const types = computed(() => 
 const types = computed(() => 
   floderTypes.value.map(type => ({
   floderTypes.value.map(type => ({
     show: ref(true),
     show: ref(true),
@@ -56,7 +53,24 @@ const typeIcons = {
   [MetaType.other]: 'nav-edit'
   [MetaType.other]: 'nav-edit'
 }
 }
 
 
-const preview = (floder: Floder) => window.open(floder.filesUrl)
+const currentFile = ref<MediaItem | null>(null)
+const preview = (floder: Floder) => {
+  const type = getUrlType(floder.filesUrl)
+  const mediaType = type === MetaType.image 
+      ? MediaType.img 
+      : type === MetaType.video
+        ? MediaType.video
+        : null
+  
+  if (!mediaType) {
+    window.open(floder.filesUrl)
+  } else {
+    currentFile.value = {
+      type: mediaType,
+      url: floder.filesUrl
+    }
+  }
+}
 
 
 </script>
 </script>
 
 

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

@@ -63,7 +63,6 @@
               type="number" 
               type="number" 
               width="54px" 
               width="54px" 
               height="26px" 
               height="26px" 
-              v-model="path.time" 
               :modelValue="path.time" 
               :modelValue="path.time" 
               @update:modelValue="(val: number) => updatePathInfo(i, { time: val })"
               @update:modelValue="(val: number) => updatePathInfo(i, { time: val })"
               :ctrl="false" 
               :ctrl="false" 
@@ -229,7 +228,7 @@ watchEffect(async () => {
 
 
   .meta {
   .meta {
     font-size: 12px;
     font-size: 12px;
-    border-bottom: 1px solid rgba(255,255,255,.6);
+    border-bottom: 1px solid rgba(255,255,255,.16);
     padding: 10px 20px;
     padding: 10px 20px;
     display: flex;
     display: flex;
     justify-content: space-between;
     justify-content: space-between;

+ 15 - 9
src/views/guide/index.vue

@@ -1,14 +1,16 @@
 <template>
 <template>
   <RightFillPano>
   <RightFillPano>
-    <ui-group borderBottom>
-      <template #header>
-        <ui-button @click="edit(createGuide())">
-          <ui-icon type="add" />
-          新增 
-        </ui-button>
-      </template>
-    </ui-group>
-    <ui-group title="导览列表">
+    <template #header>
+      <ui-group borderBottom>
+        <template #header>
+          <ui-button @click="edit(createGuide())">
+            <ui-icon type="add" />
+            新增 
+          </ui-button>
+        </template>
+      </ui-group>
+    </template>
+    <ui-group title="路径列表" class="guide-list">
       <GuideSign 
       <GuideSign 
         v-for="guide in guides" 
         v-for="guide in guides" 
         :key="guide.id" 
         :key="guide.id" 
@@ -62,4 +64,8 @@ useViewStack(autoSaveGuides)
   display: block;
   display: block;
 }
 }
 
 
+.guide-list {
+  padding-bottom: 30px;
+}
+
 </style>
 </style>

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

@@ -1,5 +1,5 @@
 <template>
 <template>
-  <ui-group title="导览列表" class="show-guides">
+  <ui-group title="路径列表" class="show-guides">
     <GuideSign 
     <GuideSign 
       v-for="guide in guides" 
       v-for="guide in guides" 
       :key="guide.id" 
       :key="guide.id" 
@@ -11,9 +11,8 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import GuideSign from '@/views/guide/sign.vue'
 import GuideSign from '@/views/guide/sign.vue'
-import { guides, initialGuides } from '@/store'
+import { guides } from '@/store'
 
 
-initialGuides()
 </script>
 </script>
 
 
 
 

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

@@ -7,7 +7,7 @@
           type="preview" 
           type="preview" 
           class="icon" 
           class="icon" 
           ctrl 
           ctrl 
-          @click="playSceneGuide(paths)" 
+          @click="playSceneGuide(paths, undefined, true)" 
           v-if="paths.length" 
           v-if="paths.length" 
         />
         />
       </div>
       </div>

+ 1 - 1
src/views/measure/edit.vue

@@ -4,7 +4,7 @@
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { ref, reactive } from 'vue'
 import { ref, reactive } from 'vue'
-import { enterEdit, enterOld, sysBus, giveupLeave, MeasureType } from '@/store'
+import { enterEdit, enterOld, sysBus, giveupLeave } from '@/store'
 import { useViewStack } from '@/hook'
 import { useViewStack } from '@/hook'
 import { togetherCallback } from '@/utils'
 import { togetherCallback } from '@/utils'
 import { showRightCtrlPanoStack, showRightPanoStack } from '@/env'
 import { showRightCtrlPanoStack, showRightPanoStack } from '@/env'

+ 17 - 9
src/views/measure/index.vue

@@ -1,11 +1,13 @@
 <template>
 <template>
   <RightFillPano>
   <RightFillPano>
-    <ui-group borderBottom>
-      <template #header>
-        <Actions class="edit-header" :items="options" single />
-      </template>
-    </ui-group>
-    <ui-group title="测量列表">
+    <template #header>
+      <ui-group borderBottom>
+        <template #header>
+          <Actions class="edit-header" :items="options" single />
+        </template>
+      </ui-group>
+    </template>
+    <ui-group title="测量列表" class="measure-list">
       <template #icon>
       <template #icon>
         <ui-icon 
         <ui-icon 
           ctrl
           ctrl
@@ -25,6 +27,7 @@
         :key="measure.id" 
         :key="measure.id" 
         :measure="measure" 
         :measure="measure" 
         @delete="deleteMeasure(measure)"
         @delete="deleteMeasure(measure)"
+        @updateTitle="title => measure.title = title"
       />
       />
     </ui-group>
     </ui-group>
   </RightFillPano>
   </RightFillPano>
@@ -43,7 +46,7 @@ import Actions from '@/components/actions/index.vue'
 import EditMeasure from './edit.vue'
 import EditMeasure from './edit.vue'
 import { RightFillPano } from '@/layout'
 import { RightFillPano } from '@/layout'
 import { computed, ref } from 'vue';
 import { computed, ref } from 'vue';
-import { custom, showMeasuresStack } from '@/env'
+import { custom } from '@/env'
 import { measures, MeasureTypeMeta, MeasureType, createMeasure, autoSaveMeasures } from '@/store'
 import { measures, MeasureTypeMeta, MeasureType, createMeasure, autoSaveMeasures } from '@/store'
 import { useViewStack } from '@/hook'
 import { useViewStack } from '@/hook'
 
 
@@ -81,6 +84,11 @@ const deleteMeasure = (measure: Measure) => {
   measures.value.splice(index, 1)
   measures.value.splice(index, 1)
 }
 }
 
 
-useViewStack(() => showMeasuresStack.push(ref(true)))
 useViewStack(autoSaveMeasures)
 useViewStack(autoSaveMeasures)
-</script>
+</script>
+
+<style scoped>
+  .measure-list {
+    padding-bottom: 30px;
+  }
+</style>

+ 2 - 6
src/views/measure/show.vue

@@ -18,13 +18,9 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import MeasureSign from '@/views/measure/sign.vue'
 import MeasureSign from '@/views/measure/sign.vue'
-import { ref } from 'vue'
-import { measures, initialMeasures } from '@/store'
-import { custom, showMeasuresStack } from '@/env'
-import { useViewStack } from '@/hook'
+import { measures } from '@/store'
+import { custom } from '@/env'
 
 
-initialMeasures() 
-useViewStack(() => showMeasuresStack.push(ref(true)))
 </script>
 </script>
 
 
 
 

+ 70 - 16
src/views/measure/sign.vue

@@ -1,19 +1,36 @@
 <template>
 <template>
   <ui-group-option 
   <ui-group-option 
     class="sign-measure" 
     class="sign-measure" 
+    :class="{ active: measure.selected }"
     @mouseenter="measure.selected = true"
     @mouseenter="measure.selected = true"
     @mouseleave="measure.selected = false"
     @mouseleave="measure.selected = false"
   >
   >
     <div class="info">
     <div class="info">
       <ui-icon :type="MeasureTypeMeta[measure.type].icon" class="type" />
       <ui-icon :type="MeasureTypeMeta[measure.type].icon" class="type" />
-      <div>
-        <p>{{ desc }} {{ MeasureTypeMeta[measure.type].unit }}</p>
-        <span>{{ MeasureTypeMeta[measure.type].unitDesc }}</span>
+      <div v-show="!isEditTitle">
+        <p>{{ measure.title || MeasureTypeMeta[measure.type].unitDesc }}</p>
+        <span>{{ desc }} {{ MeasureTypeMeta[measure.type].unit }}</span>
       </div>
       </div>
+      <ui-input 
+        class="view-title-input"
+        type="text" 
+        :modelValue="measure.title" 
+        :maxlength="15"
+        @update:modelValue="(title: string) => $emit('updateTitle', title.trim())"
+        v-show="isEditTitle" 
+        ref="inputRef" 
+        height="28px" 
+      />
     </div>
     </div>
     <div class="actions" @click.stop>
     <div class="actions" @click.stop>
-      <ui-icon type="del" ctrl @click.stop="$emit('delete')" v-if="edit" />
+      <!-- <ui-icon type="del" ctrl @click.stop="$emit('delete')" v-if="edit" /> -->
       <ui-icon type="pin" ctrl @click.stop="fly" :class="{disabled: !getMeasureIsShow(measure)}" />
       <ui-icon type="pin" ctrl @click.stop="fly" :class="{disabled: !getMeasureIsShow(measure)}" />
+      <ui-more 
+        v-if="edit"
+        :options="menus" 
+        style="margin-left: 20px" 
+        @click="(action: keyof typeof actions) => actions[action]()" 
+      />
     </div>
     </div>
   </ui-group-option>
   </ui-group-option>
 </template>
 </template>
@@ -21,29 +38,53 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { MeasureTypeMeta, getMeasureIsShow } from '@/store'
 import { MeasureTypeMeta, getMeasureIsShow } from '@/store'
 import { getSceneMeasure, getSceneMeasureDesc } from '@/sdk'
 import { getSceneMeasure, getSceneMeasureDesc } from '@/sdk'
+import { useFocus } from 'bill/hook/useFocus'
 
 
 import type { Measure } from '@/store'
 import type { Measure } from '@/store'
-import { computed } from 'vue';
+import { computed, ref, watch, watchEffect } from 'vue';
+import { Message } from 'bill/index';
+import { custom } from '@/env';
 
 
 const props = withDefaults(
 const props = withDefaults(
   defineProps<{ measure: Measure, edit?: boolean }>(),
   defineProps<{ measure: Measure, edit?: boolean }>(),
   { edit: true }
   { edit: true }
 )
 )
+const emit = defineEmits<{ 
+  (e: 'delete'): void,
+  (e: 'updateTitle', title: string): void,
+}>()
 
 
-defineEmits<{ (e: 'delete'): void }>()
+const inputRef = ref()
+const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root))
+const menus = [
+  { label: '重命名', value: 'rename' },
+  { label: '删除', value: 'delete' },
+]
+const actions = {
+  delete: () => emit('delete'),
+  rename: () => isEditTitle.value = true
+}
+
+watchEffect(() => {
+  if (!isEditTitle.value && !props.measure.title.length) {
+    isEditTitle.value = true
+    Message.warning('测量名称不可为空')
+  }
+})
 
 
 const fly = () => {
 const fly = () => {
   getSceneMeasure(props.measure)?.fly()
   getSceneMeasure(props.measure)?.fly()
 }
 }
-const desc = computed(() => {
-  const smeasure = getSceneMeasure(props.measure)
-  console.log(smeasure)
-  if (smeasure) {
-    return getSceneMeasureDesc(smeasure, props.measure)
-  } else {
-    return '-'
-  }
-})
+const desc = ref('-')
+watch(
+  () => [props.measure, custom.showMeasures, custom.showModelsMap], 
+  () => {
+    const smeasure = getSceneMeasure(props.measure)
+    desc.value = smeasure ? getSceneMeasureDesc(smeasure, props.measure) : '-'
+  }, 
+  { deep: true, flush: 'post', immediate: true }
+)
+
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -56,6 +97,15 @@ const desc = computed(() => {
   border-bottom: 1px solid var(--colors-border-color);
   border-bottom: 1px solid var(--colors-border-color);
   position: relative;
   position: relative;
 
 
+  &.active::after {
+    content: '';
+    position: absolute;
+    pointer-events: none;
+    inset: 0 -20px;
+    background-color: rgba(0, 200, 175, 0.16);
+    z-index: -1;
+  }
+
   .info {
   .info {
     flex: 1;
     flex: 1;
 
 
@@ -96,6 +146,10 @@ const desc = computed(() => {
     }
     }
   }  
   }  
 }
 }
+</style>
 
 
-
+<style>
+.view-title-input.ui-input .text.suffix input {
+  padding-right: 50px;
+}
 </style>
 </style>

+ 13 - 8
src/views/merge/index.vue

@@ -7,7 +7,7 @@
       <ui-group-option label="等比缩放">
       <ui-group-option label="等比缩放">
         <template #icon>
         <template #icon>
           <a class="set-prop" 
           <a class="set-prop" 
-            :class="{disabled: isOld}"
+            :class="{disabled: isOld || currentItem}"
             @click="router.push({ 
             @click="router.push({ 
               name: RoutesName.proportion, 
               name: RoutesName.proportion, 
               params: { id: custom.currentModel!.id, save: '1' },
               params: { id: custom.currentModel!.id, save: '1' },
@@ -24,7 +24,7 @@
           <template #icon>%</template>
           <template #icon>%</template>
         </ui-input>
         </ui-input>
       </ui-group-option>
       </ui-group-option>
-      <ui-group-option label="离地高度">
+      <!-- <ui-group-option label="离地高度">
         <ui-input 
         <ui-input 
           type="range" 
           type="range" 
           v-model="custom.currentModel.bottom" 
           v-model="custom.currentModel.bottom" 
@@ -34,7 +34,7 @@
         >
         >
           <template #icon>m</template>
           <template #icon>m</template>
         </ui-input>
         </ui-input>
-      </ui-group-option>
+      </ui-group-option> -->
       <ui-group-option label="模型不透明度">
       <ui-group-option label="模型不透明度">
         <ui-input 
         <ui-input 
           type="range" 
           type="range" 
@@ -70,8 +70,8 @@ import { autoSaveFuseModels, defaultFuseModelAttrs, isOld } from '@/store'
 import { togetherCallback } from '@/utils'
 import { togetherCallback } from '@/utils'
 import { getSceneModel, modelRange } from '@/sdk'
 import { getSceneModel, modelRange } from '@/sdk'
 import { useViewStack, useActive } from '@/hook'
 import { useViewStack, useActive } from '@/hook'
-import { showLeftCtrlPanoStack, showLeftPanoStack, custom, modelsChangeStoreStack } from '@/env'
-import { ref, nextTick, watchEffect } from 'vue'
+import { showLeftPanoStack, custom, modelsChangeStoreStack, showRightPanoStack } from '@/env'
+import { ref, nextTick, watchEffect, computed } from 'vue'
 import { Dialog } from 'bill/expose-common'
 import { Dialog } from 'bill/expose-common'
 
 
 import Actions from '@/components/actions/index.vue'
 import Actions from '@/components/actions/index.vue'
@@ -92,11 +92,17 @@ const actionItems: ActionsProps['items'] = [
     icon: 'flip',
     icon: 'flip',
     text: '旋转',
     text: '旋转',
     action: () => {
     action: () => {
+      console.log('enter')
       getSceneModel(custom.currentModel)?.enterRotateMode()
       getSceneModel(custom.currentModel)?.enterRotateMode()
-      return () => getSceneModel(custom.currentModel)?.leaveTransform()
+      return () => {
+        console.log('leave la ')
+        getSceneModel(custom.currentModel)?.leaveTransform()
+      }
     }
     }
   },
   },
 ]
 ]
+
+
 const currentItem = ref<ActionsItem | null>(null)
 const currentItem = ref<ActionsItem | null>(null)
 watchEffect(() => {
 watchEffect(() => {
   if (!custom.currentModel) {
   if (!custom.currentModel) {
@@ -111,14 +117,13 @@ const reset = async () => {
     custom.currentModel && (custom.currentModel.bottom = 0)
     custom.currentModel && (custom.currentModel.bottom = 0)
   }
   }
 }
 }
-
 useViewStack(() => togetherCallback([
 useViewStack(() => togetherCallback([
   showLeftPanoStack.push(ref(true)),
   showLeftPanoStack.push(ref(true)),
+  showRightPanoStack.push(computed(() => !!custom.currentModel)),
   modelsChangeStoreStack.push(ref(true)),
   modelsChangeStoreStack.push(ref(true)),
   () => currentItem.value = null
   () => currentItem.value = null
 ]))
 ]))
 useViewStack(autoSaveFuseModels)
 useViewStack(autoSaveFuseModels)
-
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">

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

@@ -18,11 +18,13 @@
 import { Message } from 'bill/index'
 import { Message } from 'bill/index'
 import { useViewStack } from '@/hook'
 import { useViewStack } from '@/hook'
 import { router, RoutesName } from '@/router'
 import { router, RoutesName } from '@/router'
-import { ref, computed, watch, nextTick, watchEffect } from 'vue'
+import { ref, computed, watch, watchEffect } from 'vue'
 import { getSceneModel } from '@/sdk'
 import { getSceneModel } from '@/sdk'
-import { autoSaveFuseModels, getFuseModel, leave } from '@/store'
+import { autoSaveFuseModels, FuseModel, getFuseModel, leave } from '@/store'
+import { currentModelStack } from '@/env'
 
 
 import type { ScaleSet } from '@/sdk'
 import type { ScaleSet } from '@/sdk'
+import { round } from '@/utils'
 
 
 const isCurrent = computed(() => router.currentRoute.value.name === RoutesName.proportion)
 const isCurrent = computed(() => router.currentRoute.value.name === RoutesName.proportion)
 const model = computed(() => {
 const model = computed(() => {
@@ -43,6 +45,7 @@ watch(length, () => {
   const len = length.value
   const len = length.value
   if (len !== null) {
   if (len !== null) {
     scaleSet?.setLength(len)
     scaleSet?.setLength(len)
+    length.value = round(len, 2)
   }
   }
 })
 })
 
 
@@ -56,10 +59,12 @@ watchEffect((onCleanup) => {
   if (smodel) {
   if (smodel) {
     scaleSet = smodel.enterScaleSet()
     scaleSet = smodel.enterScaleSet()
     scaleSet.startMeasure()
     scaleSet.startMeasure()
+    const pop = currentModelStack.push(model as any)
 
 
     onCleanup(() => {
     onCleanup(() => {
       smodel.leaveScaleSet()
       smodel.leaveScaleSet()
       scaleSet = null
       scaleSet = null
+      pop()
     })
     })
   } else if (isCurrent.value) {
   } else if (isCurrent.value) {
     leave()
     leave()

+ 15 - 18
src/views/record/index.vue

@@ -1,17 +1,10 @@
 <template>
 <template>
   <RightFillPano>
   <RightFillPano>
-    <div class="btns header-btns">
-      <ui-button class="start" @click="start" type="primary">开始录制</ui-button>
-      <ui-input 
-        class="unit" 
-        type="multiple" 
-        :options="setOptions" 
-        v-model="setting" 
-        width="120px" 
-        placeholder="显示设置"
-      >
-      </ui-input>
-    </div>
+    <template #header>
+      <div class="btns header-btns">
+        <ui-button class="start" @click="start" type="primary">开始录制</ui-button>
+      </div>
+    </template>
 
 
     <ui-group title="全部视频" class="tree" >
     <ui-group title="全部视频" class="tree" >
       <Draggable :list="records" draggable=".sign" itemKey="id">
       <Draggable :list="records" draggable=".sign" itemKey="id">
@@ -35,18 +28,22 @@ import { showMeasuresStack, showTaggingsStack } from '@/env'
 import { useViewStack } from '@/hook'
 import { useViewStack } from '@/hook'
 import { diffArrayChange, togetherCallback } from '@/utils'
 import { diffArrayChange, togetherCallback } from '@/utils'
 import { RecordProcess } from './help'
 import { RecordProcess } from './help'
-import { records, createRecord, Record, RecordStatus, autoSaveRecords, initialRecords } from '@/store'
+import { records, createRecord, Record, RecordStatus, autoSaveRecords, initialRecords, getRecordFragmentBlobs, isTemploraryID } from '@/store'
 import { RightFillPano } from '@/layout'
 import { RightFillPano } from '@/layout'
 import Draggable from 'vuedraggable'
 import Draggable from 'vuedraggable'
 import Sign from './sign.vue'
 import Sign from './sign.vue'
+import { Dialog } from 'bill/index'
 
 
 initialRecords()
 initialRecords()
 
 
 const start = () => records.value.push(createRecord())
 const start = () => records.value.push(createRecord())
-const deleteRecord = (record: Record) => {
-  const index = records.value.indexOf(record)
-  if (~index) {
-    records.value.splice(index, 1)
+const deleteRecord = async (record: Record) => {
+  const isTemp = getRecordFragmentBlobs(record).length === 0 && isTemploraryID(record.id)
+  if (isTemp || await Dialog.confirm('确定要删除视频吗?')) {
+    const index = records.value.indexOf(record)
+    if (~index) {
+      records.value.splice(index, 1)
+    }
   }
   }
 }
 }
 
 
@@ -61,7 +58,7 @@ const setOptions = [
 ] as const
 ] as const
 
 
 type SetKey = typeof setOptions[number]['value']
 type SetKey = typeof setOptions[number]['value']
-const setting = ref<SetKey[]>([])
+const setting = ref<SetKey[]>(['tagging', 'measure'])
 watch(setting, (setting, oldSetting = [], onCleanup) => {
 watch(setting, (setting, oldSetting = [], onCleanup) => {
   const { added } = diffArrayChange(setting, oldSetting)
   const { added } = diffArrayChange(setting, oldSetting)
   const pops = added.map(value => {
   const pops = added.map(value => {

+ 95 - 0
src/views/record/shot-imitate.vue

@@ -0,0 +1,95 @@
+<template>
+  <div class="imiate">
+    <div :class="{ hideLeft: !custom.showLeftPano }">
+      <slideMenu :activeName="activeName" @change-item="item => activeName = item.name" />
+      <component :is="component" v-if="component"></component>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { useViewStack } from '@/hook';
+import slideMenu from '@/layout/show/slide-menu.vue';
+import { getRouteConfig, RoutesName } from '@/router'
+import { ref, shallowRef, watch, watchEffect } from 'vue';
+import { togetherCallback } from '@/utils'
+import { 
+  custom,
+  showLeftCtrlPanoStack, 
+  showLeftPanoStack,
+  showRightCtrlPanoStack,
+  showRightPanoStack,
+showTaggingsStack
+} from '@/env'
+import {
+  initialScenes,
+  initialViews,
+  initialRecords,
+  initialFloders,
+  initialFloderTypes
+} from '@/store'
+
+import type { Component } from 'vue'
+import { currentModel } from '@/model';
+
+
+initialScenes(),
+initialViews(),
+initialFloders(),
+initialFloderTypes()
+
+const activeName = ref(RoutesName.summaryShow)
+const component = shallowRef<Component | null>(null)
+
+watchEffect(() => {
+  const route = getRouteConfig(activeName.value)
+  route?.component().then(comp => component.value = comp.default)
+})
+
+const showLeftPano = ref(false)
+
+watch(currentModel, () => {
+  if (currentModel.value) {
+    showLeftPano.value = false
+  }
+})
+
+useViewStack(() => {
+  const style = document.createElement('style')
+  style.type = 'text/css'
+  style.textContent = `
+    #app  {
+      --editor-menu-width: 80px;
+    }
+    #app .edit-mode {
+      --editor-menu-left: 0px;
+    }
+    #app .hideLeft {
+      --editor-menu-left: calc(-1 * var(--editor-menu-width));
+      --left-pano-left: calc(var(--editor-menu-left) + var(--editor-menu-width) - var(--left-pano-width)) !important
+    }
+    #app .ctrl-pano-c,
+    #app .left-pano,
+    #app .ui-editor-toolbox {
+      display: none;
+    }
+    #app .imiate .ctrl-pano-c,
+    #app .imiate .ui-editor-toolbox {
+      display: flex;
+    }
+    #app .imiate .left-pano {
+      display: block;
+    }
+  `
+  document.head.appendChild(style)
+
+  return togetherCallback([
+    showTaggingsStack.push(ref(true)),
+    showLeftCtrlPanoStack.push(ref(true)), 
+    showLeftPanoStack.push(showLeftPano),
+    showRightCtrlPanoStack.push(ref(true)),
+    showRightPanoStack.push(ref(false)),
+    () => document.head.removeChild(style)
+  ])
+})
+</script>

+ 93 - 40
src/views/record/shot.vue

@@ -1,17 +1,17 @@
 <template>
 <template>
   <teleport :to="appEl" v-if="appEl">
   <teleport :to="appEl" v-if="appEl">
-    <div class="countdown strengthen" v-if="!custom.showBottomBar && countdown">
+    <!-- <div class="countdown strengthen" v-if="!custom.showBottomBar && countdown">
       <p class="title"><span>{{countdown}}</span>秒后开始录制</p>
       <p class="title"><span>{{countdown}}</span>秒后开始录制</p>
       <p>按ESC可暂停录制</p>
       <p>按ESC可暂停录制</p>
-    </div>
+    </div> -->
 
 
     <ui-editor-toolbar :toolbar="custom.showBottomBar" class="shot-ctrl">
     <ui-editor-toolbar :toolbar="custom.showBottomBar" class="shot-ctrl">
       <ui-button type="submit" class="btn" @click="close">取消</ui-button>
       <ui-button type="submit" class="btn" @click="close">取消</ui-button>
       <ui-button type="primary" class="btn" @click="complete" :class="{disabled: blobs.length === 0}">合并视频</ui-button>
       <ui-button type="primary" class="btn" @click="complete" :class="{disabled: blobs.length === 0}">合并视频</ui-button>
-      <div class="other">
+      <div class="other" :style="{bottom: barHeight}">
         <ui-icon class="icon" type="video1" ctrl @click="start" tip="继续录制" tipV="top" />
         <ui-icon class="icon" type="video1" ctrl @click="start" tip="继续录制" tipV="top" />
       </div>
       </div>
-      <div class="video-list">
+      <div class="video-list" v-if="videoList.length">
         <div class="layout" :style="{width: `${videoList.length * 130}px`}">
         <div class="layout" :style="{width: `${videoList.length * 130}px`}">
           <div v-for="video in videoList" :key="video.cover" class="cover">
           <div v-for="video in videoList" :key="video.cover" class="cover">
             <img :src="video.cover">
             <img :src="video.cover">
@@ -28,20 +28,22 @@
 
 
     <Preview 
     <Preview 
       v-if="palyUrl" 
       v-if="palyUrl" 
-      :type="MediaType.video" 
-      :url="palyUrl" 
+      :items="[{ type: MediaType.video, url: palyUrl }]"
       @close="palyUrl = null" 
       @close="palyUrl = null" 
     />
     />
+
+    <ShotImiate v-if="!custom.showBottomBar && !countdown" />
   </teleport>
   </teleport>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
-import { ref, defineComponent, onUnmounted, watch, shallowReactive, PropType } from 'vue'
+import { ref, defineComponent, onUnmounted, watch, shallowReactive, PropType, computed, watchEffect } from 'vue'
 import { VideoRecorder } from '@simaq/core';
 import { VideoRecorder } from '@simaq/core';
 import { sdk } from '@/sdk'
 import { sdk } from '@/sdk'
 import { getVideoCover, togetherCallback } from '@/utils'
 import { getVideoCover, togetherCallback } from '@/utils'
 import { MediaType, Preview } from '@/components/static-preview/index.vue'
 import { MediaType, Preview } from '@/components/static-preview/index.vue'
 import { Record, getRecordFragmentBlobs } from '@/store'
 import { Record, getRecordFragmentBlobs } from '@/store'
+import ShotImiate from './shot-imitate.vue'
 import { 
 import { 
   getResource, 
   getResource, 
   showRightCtrlPanoStack, 
   showRightCtrlPanoStack, 
@@ -49,9 +51,13 @@ import {
   showBottomBarStack, 
   showBottomBarStack, 
   custom, 
   custom, 
   bottomBarHeightStack,
   bottomBarHeightStack,
-  showHeadBarStack
+  showHeadBarStack,
+  showLeftPanoStack
 } from '@/env'
 } from '@/env'
 import { appEl } from '@/store';
 import { appEl } from '@/store';
+import { useViewStack } from '@/hook';
+import { currentModel } from '@/model';
+import { Message } from 'bill/expose-common';
 
 
 
 
 export default defineComponent({
 export default defineComponent({
@@ -71,54 +77,82 @@ export default defineComponent({
   setup(props, { emit }) {
   setup(props, { emit }) {
     const config: any = {
     const config: any = {
       uploadUrl: '',
       uploadUrl: '',
-      resolution: '2k',
+      resolution: '4k',
       debug: false,
       debug: false,
     }
     }
   
   
-    console.error('new VideoRecorder')
     const videoRecorder = new VideoRecorder(config);
     const videoRecorder = new VideoRecorder(config);
+    const showLeftPano = ref(false)
+    const showBottomBar = ref(false)
+    const MAX_SIZE = 2 * 1024 * 1024 * 1024
+    const MAX_TIME = 30 * 60 * 1000
 
 
     type VideoItem = { origin: Blob | string, cover: string }
     type VideoItem = { origin: Blob | string, cover: string }
 
 
     const countdown = ref(0)
     const countdown = ref(0)
     let interval: NodeJS.Timer
     let interval: NodeJS.Timer
+    let recordIng = ref(false)
     const start = () => {
     const start = () => {
-      custom.showBottomBar = false
-      countdown.value = 3
-      interval = setInterval(() => {
-        if (--countdown.value === 0) {
+      if (size.value > MAX_SIZE || pauseTime.value < 2000) {
+        return Message.warning('已超出限制大小无法继续录制,可保存后继续录制!')
+      }
+
+      showBottomBar.value = false
+      countdown.value = 2
+      const timeiffe = () => {
+        if (--countdown.value <= 0) {
           clearInterval(interval)
           clearInterval(interval)
           videoRecorder.startRecord()
           videoRecorder.startRecord()
+          recordIng.value = true
+        } else {
+          interval = setTimeout(timeiffe, 300)
         }
         }
-      }, 1000)
+      }
+      timeiffe()
+      // interval = setInterval(() => {
+      //   if (--countdown.value === 0) {
+      //     clearInterval(interval)
+      //     videoRecorder.startRecord()
+      //     recordIng = true
+      //   }
+      // }, 1000)
     }
     }
 
 
     const pause = () => {
     const pause = () => {
-      if (countdown.value === 0 && videoRecorder.status !== 3) {
+      if (countdown.value === 0 && recordIng.value) {
         videoRecorder.endRecord()
         videoRecorder.endRecord()
+        recordIng.value = false
       }
       }
-      
       countdown.value = 0
       countdown.value = 0
-      custom.showBottomBar = true
+      showBottomBar.value = true
       clearInterval(interval)
       clearInterval(interval)
     }
     }
 
 
+    watch(recordIng, (_n, _o, onCleanup) => {
+      if (recordIng.value) {
+        const timeout = setTimeout(() => videoRecorder.endRecord(), pauseTime.value)
+        onCleanup(() => clearTimeout(timeout))
+      }
+    })
+
     const blobs: File[] = shallowReactive([])
     const blobs: File[] = shallowReactive([])
+    const size = computed(() => {
+      console.log(videoList)
+      return videoList.reduce((t, f) => typeof f.origin === 'string' ? t : t + f.origin.size, 0)
+    })
+    const pauseTime = computed(() => (MAX_TIME / MAX_SIZE) * (MAX_SIZE - size.value))
     videoRecorder.off('*')
     videoRecorder.off('*')
     videoRecorder.on('record', blob => {
     videoRecorder.on('record', blob => {
-      blobs.push(new File([blob], '录屏.mp4', { type: 'video/mp4; codecs=h264' }))
-    })
-    videoRecorder.on('cancelRecord', () => {
-      console.error('cancelRecord')
-      pause()
-    })
-    videoRecorder.on('endRecord', () => {
-      console.error('endRecord')
-      pause()
+      if (recordIng.value) {
+        blobs.push(new File([blob], '录屏.mp4', { type: 'video/mp4; codecs=h264' }))
+      }
     })
     })
+    videoRecorder.on('cancelRecord', pause)
+    videoRecorder.on('endRecord', pause)
 
 
     const palyUrl = ref<string | Blob | null>(null)
     const palyUrl = ref<string | Blob | null>(null)
     const videoList: VideoItem[] = shallowReactive([])
     const videoList: VideoItem[] = shallowReactive([])
+    let initial = false
     watch([blobs, props], async () => {
     watch([blobs, props], async () => {
       const existsVideos = []
       const existsVideos = []
 
 
@@ -142,9 +176,14 @@ export default defineComponent({
       if (!props.record.cover && videoList.length) {
       if (!props.record.cover && videoList.length) {
         emit('updateCover', videoList[0].cover)
         emit('updateCover', videoList[0].cover)
       }
       }
+      if (!initial) {
+        initial = true
+        start()
+      }
+      
     }, { immediate: true })
     }, { immediate: true })
 
 
-    const upHandler = (ev: KeyboardEvent) => ev.code === `Escape` && pause()
+    const upHandler = (ev: KeyboardEvent) => ev.code === `Escape` && videoRecorder.endRecord()
     document.body.addEventListener('keyup', upHandler, { capture: true })
     document.body.addEventListener('keyup', upHandler, { capture: true })
 
 
     const complete = () => {
     const complete = () => {
@@ -157,27 +196,40 @@ export default defineComponent({
       emit('close')
       emit('close')
     }
     }
 
 
-    start()
-    const pop = togetherCallback([
-      showHeadBarStack.push(ref(false)),
-      showRightCtrlPanoStack.push(ref(false)),
-      showRightPanoStack.push(ref(false)),
-      showBottomBarStack.push(ref(false)),
-      bottomBarHeightStack.push(ref('180px'))
-    ])
+    const barHeight = computed(() => videoList.length ? '180px' : '60px')
+    
     onUnmounted(() => {
     onUnmounted(() => {
-      close()
-      pop()
-      custom.showBottomBar = false
       document.body.removeEventListener('keyup', upHandler, { capture: true })
       document.body.removeEventListener('keyup', upHandler, { capture: true })
     })
     })
 
 
+
+    watch(currentModel, () => {
+      if (currentModel.value) {
+        showLeftPano.value = false
+      }
+    })
+    useViewStack(() => {
+      return togetherCallback([
+        showHeadBarStack.push(ref(false)),
+        showRightCtrlPanoStack.push(ref(false)),
+        showRightPanoStack.push(ref(false)),
+        showBottomBarStack.push(showBottomBar),
+        bottomBarHeightStack.push(barHeight),
+        showLeftPanoStack.push(showLeftPano),
+        close,
+        () => {
+          console.log('pop', showBottomBarStack.current)
+        }
+      ])
+    })
+
     return {
     return {
       MediaType,
       MediaType,
       complete,
       complete,
       pause,
       pause,
       close,
       close,
       start,
       start,
+      barHeight,
       el: sdk.layout,
       el: sdk.layout,
       blobs,
       blobs,
       countdown,
       countdown,
@@ -188,7 +240,8 @@ export default defineComponent({
     }
     }
   },
   },
   components: {
   components: {
-    Preview
+    Preview,
+    ShotImiate
   }
   }
 })
 })
 
 

+ 5 - 3
src/views/record/show.vue

@@ -3,7 +3,7 @@
   <LeftPano>
   <LeftPano>
     <ui-group class="pano-group">
     <ui-group class="pano-group">
       <Sign 
       <Sign 
-        v-for="record in records" 
+        v-for="record in filterRecords" 
         :record="record"
         :record="record"
         :key="record.id" 
         :key="record.id" 
         :edit="false"
         :edit="false"
@@ -14,10 +14,12 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import Sign from './sign.vue'
 import Sign from './sign.vue'
-import { records, initialRecords } from '@/store'
+import { isTemploraryID, records } from '@/store'
 import { LeftPano } from '@/layout'
 import { LeftPano } from '@/layout'
+import { computed } from 'vue';
+
+const filterRecords = computed(() => records.value.filter(record => !isTemploraryID(record.id)))
 
 
-initialRecords()
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>

+ 28 - 10
src/views/record/sign.vue

@@ -1,5 +1,5 @@
 <template>
 <template>
-  <ui-group-option class="sign">
+  <ui-group-option class="sign record-sign">
     <div class="content">
     <div class="content">
       <span class="cover">
       <span class="cover">
         <img :src="getResource(getFileUrl(record.cover))" alt="" v-if="record.cover">
         <img :src="getResource(getFileUrl(record.cover))" alt="" v-if="record.cover">
@@ -14,10 +14,11 @@
       <ui-input 
       <ui-input 
         type="text" 
         type="text" 
         :modelValue="record.title" 
         :modelValue="record.title" 
-        @update:modelValue="(title: string) => $emit('updateTitle', title)"
+        @update:modelValue="(title: string) => $emit('updateTitle', title.trim())"
         v-show="isEditTitle" 
         v-show="isEditTitle" 
         ref="inputRef" 
         ref="inputRef" 
         height="28px" 
         height="28px" 
+        :maxlength="15"
       />
       />
       <div class="title" v-show="!isEditTitle">
       <div class="title" v-show="!isEditTitle">
         <p>{{ record.title }}</p>
         <p>{{ record.title }}</p>
@@ -42,16 +43,15 @@
       :record="record" />
       :record="record" />
     <Preview 
     <Preview 
       v-if="isPlayVideo" 
       v-if="isPlayVideo" 
-      :type="MediaType.video" 
-      :url="record.url" 
+      :items="[{ type: MediaType.video, url: record.url! }]"
       @close="isPlayVideo = false" 
       @close="isPlayVideo = false" 
     />
     />
   </ui-group-option>
   </ui-group-option>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
-import { defineComponent, ref, computed } from 'vue'
-import { getFileUrl } from '@/utils'
+import { defineComponent, ref, computed, watchEffect } from 'vue'
+import { getFileUrl, getExtname } from '@/utils'
 import { useFocus } from 'bill/hook/useFocus'
 import { useFocus } from 'bill/hook/useFocus'
 import { RecordStatus, createRecordFragment, getRecordFragmentBlobs, recordFragments } from '@/store'
 import { RecordStatus, createRecordFragment, getRecordFragmentBlobs, recordFragments } from '@/store'
 import { saveAs, loadPack } from '@/utils'
 import { saveAs, loadPack } from '@/utils'
@@ -62,6 +62,7 @@ import Shot from './shot.vue'
 import type { PropType } from 'vue'
 import type { PropType } from 'vue'
 import type { RecordProcess } from './help'
 import type { RecordProcess } from './help'
 import { isTemploraryID } from '@/store'
 import { isTemploraryID } from '@/store'
+import { Message } from 'bill/index'
 
 
 export default defineComponent({
 export default defineComponent({
   props: {
   props: {
@@ -87,8 +88,11 @@ export default defineComponent({
         base.push(
         base.push(
           { label: '重命名', value: 'rename' },
           { label: '重命名', value: 'rename' },
           { label: '继续录制', value: 'continue' },
           { label: '继续录制', value: 'continue' },
-          { label: '下载', value: 'download' },
         )
         )
+
+        if (props.record.status === RecordStatus.SUCCESS) {
+          base.push({ label: '下载', value: 'download' },)
+        }
       }
       }
       base.push({ label: '删除', value: 'delete' })
       base.push({ label: '删除', value: 'delete' })
       return base
       return base
@@ -97,6 +101,14 @@ export default defineComponent({
     const isShot = ref<boolean>(false)
     const isShot = ref<boolean>(false)
     const inputRef = ref()
     const inputRef = ref()
     const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root))
     const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root))
+
+    watchEffect(() => {
+      if (!isEditTitle.value && !props.record.title.length) {
+        isEditTitle.value = true
+        Message.warning('视频名称不可为空')
+      }
+    })
+
     const isPlayVideo = ref(false)
     const isPlayVideo = ref(false)
     const actions = {
     const actions = {
       continue: () => isShot.value = true,
       continue: () => isShot.value = true,
@@ -104,9 +116,9 @@ export default defineComponent({
       rename: () => isEditTitle.value = true,
       rename: () => isEditTitle.value = true,
       play: () => isPlayVideo.value = true,
       play: () => isPlayVideo.value = true,
       download() {
       download() {
-        const url = getResource(props.record.url)
-        const paths = url.split('/')
-        loadPack(saveAs(url, paths[paths.length - 1]))
+        const url = getResource(props.record.url!)
+        const ext = getExtname(url) || 'mp4'
+        loadPack(saveAs(url, `${props.record.title}.${ext}`))
       },
       },
     }
     }
     props.record.immediately && actions.continue()
     props.record.immediately && actions.continue()
@@ -149,4 +161,10 @@ export default defineComponent({
 
 
 
 
 <style lang="scss" src="./style.scss" scoped>
 <style lang="scss" src="./style.scss" scoped>
+</style>
+
+<style>
+  .record-sign .ui-input .text.suffix input {
+    padding-right: 60px;
+  }
 </style>
 </style>

+ 6 - 1
src/views/record/style.scss

@@ -18,6 +18,7 @@
 
 
 .tree {
 .tree {
   margin-top: 20px;
   margin-top: 20px;
+  padding-bottom: 100px;
 }
 }
 
 
 .header-btns {
 .header-btns {
@@ -54,6 +55,7 @@
     color: #fff;
     color: #fff;
     font-size: 16px;
     font-size: 16px;
     margin-right: 10px;
     margin-right: 10px;
+    flex: none;
 
 
     img,
     img,
     &::before {
     &::before {
@@ -99,6 +101,8 @@
 .action {
 .action {
   color: #fff;
   color: #fff;
   font-size: 14px;
   font-size: 14px;
+  flex: none;
+  margin-left: 10px;
 }
 }
 
 
 .countdown {
 .countdown {
@@ -130,6 +134,7 @@
 }
 }
 
 
 .shot-ctrl {
 .shot-ctrl {
+  z-index: 2;
   .btn {
   .btn {
     flex: none;
     flex: none;
     width: 160px;
     width: 160px;
@@ -202,4 +207,4 @@
       font-size: 22px;
       font-size: 22px;
     }
     }
   }
   }
-}
+}

+ 40 - 8
src/views/registration/index.vue

@@ -8,10 +8,28 @@
     <ui-floating
     <ui-floating
       v-if="selectOptions.some(({key}) => key === 'opacity')"
       v-if="selectOptions.some(({key}) => key === 'opacity')"
       :refer="opacityOptionEl"
       :refer="opacityOptionEl"
+      class="opacity-range"
       isTransform
       isTransform
       dire="right-center"
       dire="right-center"
     >
     >
-      <div class="floating-range strengthen">
+
+      <div class="right-range floating-range strengthen ">
+        <div class="range-content">
+          <div class="range-layout">
+            <ui-input 
+              type="range" 
+              v-model="model.opacity"
+              v-bind="modelRange.opacityRange" 
+              :moveCallback="changeRange"
+              :ctrl="false" 
+              :input="false"
+              width="100%"
+            />
+            <span class="num" :style="{left: `${model.opacity}%`}">{{parseInt(model.opacity.toString())}}%</span>
+          </div>
+        </div>
+      </div>
+      <!-- <div class="floating-range strengthen">
         <div class="range-content">
         <div class="range-content">
           <ui-input 
           <ui-input 
             type="range" 
             type="range" 
@@ -21,12 +39,12 @@
             :input="false"
             :input="false"
             width="100%"
             width="100%"
           />
           />
-          <span class="num" :style="{left: `${model.opacity}%`}">{{model.opacity}}%</span>
+          <span class="num" :style="{left: `${model.opacity}%`}">{{parseInt(model.opacity.toString())}}%</span>
         </div>
         </div>
-      </div>
+      </div> -->
     </ui-floating>
     </ui-floating>
 
 
-    <div class="right-range floating-range strengthen">
+    <!-- <div class="right-range floating-range strengthen">
       <div class="range-content">
       <div class="range-content">
         <span class="fun-ctrl" @click="model!.bottom += modelRange.bottomRange.step">+</span>
         <span class="fun-ctrl" @click="model!.bottom += modelRange.bottomRange.step">+</span>
         <ui-input 
         <ui-input 
@@ -40,7 +58,7 @@
         />
         />
         <span class="fun-ctrl" @click="model!.bottom -= modelRange.bottomRange.step">-</span>
         <span class="fun-ctrl" @click="model!.bottom -= modelRange.bottomRange.step">-</span>
       </div>
       </div>
-    </div>
+    </div> -->
 
 
     <div class="ui-message tip-left">请在当前窗口调整水平方向位置</div>
     <div class="ui-message tip-left">请在当前窗口调整水平方向位置</div>
     <div class="ui-message tip-right">请在当前窗口调整垂直方向位置</div>
     <div class="ui-message tip-right">请在当前窗口调整垂直方向位置</div>
@@ -55,6 +73,7 @@ import { diffArrayChange } from '@/utils'
 import { useViewStack } from '@/hook'
 import { useViewStack } from '@/hook'
 import { autoSaveFuseModels, getFuseModel, leave } from '@/store'
 import { autoSaveFuseModels, getFuseModel, leave } from '@/store'
 import { router, RoutesName } from '@/router'
 import { router, RoutesName } from '@/router'
+import { currentModelStack } from '@/env'
 
 
 import type { ControlExpose } from '@/components/control-panl'
 import type { ControlExpose } from '@/components/control-panl'
 
 
@@ -105,16 +124,19 @@ watchEffect((onCleanup) => {
   const smodel = sceneModel.value
   const smodel = sceneModel.value
   if (smodel) {
   if (smodel) {
     smodel.enterAlignment()
     smodel.enterAlignment()
+    const pop = currentModelStack.push(model as any)
 
 
     onCleanup(() => {
     onCleanup(() => {
       smodel.leaveTransform()
       smodel.leaveTransform()
       smodel.leaveAlignment()
       smodel.leaveAlignment()
+      pop()
     })
     })
   } else if (isCurrent.value) {
   } else if (isCurrent.value) {
     leave()
     leave()
   }
   }
 })
 })
 
 
+
 useViewStack(() => () => {
 useViewStack(() => () => {
   if (selectOptions.value.length) {
   if (selectOptions.value.length) {
     selectOptions.value = []
     selectOptions.value = []
@@ -138,13 +160,19 @@ useViewStack(autoSaveFuseModels)
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
     height: 100%;
     height: 100%;
-    position: relative;
+    padding: 0 15px;
+    .range-layout {
+      height: 100%;
+      width: 100%;
+      position: relative;
+      padding: 5px 0;
+    }
 
 
     .num {
     .num {
       position: absolute;
       position: absolute;
       color: #fff;
       color: #fff;
-      bottom: 100%;
-      transform: translateX(-50%);
+      top: 100%;
+      transform: translateX(calc(-50% - 10px)) rotate(90deg);
       background: #000000;
       background: #000000;
       border-radius: 4px;
       border-radius: 4px;
       padding: 2px 6px;
       padding: 2px 6px;
@@ -192,10 +220,14 @@ useViewStack(autoSaveFuseModels)
   left: 75%;
   left: 75%;
   transform: translateX(-50%);
   transform: translateX(-50%);
 }
 }
+
 </style>
 </style>
 
 
 <style>
 <style>
 .floating-range .ui-input .range .range-content {
 .floating-range .ui-input .range .range-content {
   --slideSize: calc(var(--height) + 8px) !important;
   --slideSize: calc(var(--height) + 8px) !important;
 }
 }
+.opacity-range {
+  margin-left: 70px;
+}
 </style>
 </style>

+ 18 - 5
src/views/sign-model/index.vue

@@ -5,8 +5,8 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { ref } from 'vue'
 import { ref } from 'vue'
 import { loadModel, fuseModel as FModel } from '@/model'
 import { loadModel, fuseModel as FModel } from '@/model'
-import { fetchScene } from '@/api'
-import { createFuseModels, fuseModels } from '@/store'
+import { fetchScene, fetchScenesAll, Scene, SceneType } from '@/api'
+import { createFuseModels, defTitle, fuseModels, SceneStatus } from '@/store'
 import { params, showModelsMapStack } from '@/env'
 import { params, showModelsMapStack } from '@/env'
 import { Dialog } from 'bill/index'
 import { Dialog } from 'bill/index'
 import { useViewStack, useActive } from '@/hook';
 import { useViewStack, useActive } from '@/hook';
@@ -15,17 +15,31 @@ import { sdk } from '@/sdk'
 const active = useActive()
 const active = useActive()
 let pop: () => void
 let pop: () => void
 const loadSignModel = async () => {
 const loadSignModel = async () => {
-  const scene = await fetchScene(Number(params.modelId))
+  let scene: Scene | undefined
+  if ('modelId' in params) {
+    const mscene = await fetchScene(Number(params.modelId))
+    if (mscene.status !== SceneStatus.SUCCESS) {
+      scene = mscene
+    }
+  } else if ('m' in params) {
+    const scenes = await fetchScenesAll({ numList: [params.m!], type: SceneType.SWSSMX })
+    scene = scenes.find(scene => scene.num === params.m)
+    console.log(scene)
+  } 
+
   if (!scene) {
   if (!scene) {
     return Dialog.alert(`模型不存在!`)
     return Dialog.alert(`模型不存在!`)
   }
   }
   if (active.value) {
   if (active.value) {
+    defTitle.value = scene.title || scene.modelTitle
+    
     const fuseModel = createFuseModels({
     const fuseModel = createFuseModels({
       modelId: scene.modelId,
       modelId: scene.modelId,
       show: true,
       show: true,
       url: scene.modelGlbUrl || scene.modelObjUrl,
       url: scene.modelGlbUrl || scene.modelObjUrl,
       type: scene.type,
       type: scene.type,
-      opacity: 100
+      opacity: 100,
+      modelType: scene.modelDateType
     })
     })
     fuseModels.value.push(fuseModel)
     fuseModels.value.push(fuseModel)
 
 
@@ -35,7 +49,6 @@ const loadSignModel = async () => {
   }
   }
 }
 }
 
 
-
 useViewStack(() => {
 useViewStack(() => {
   const bcModels = fuseModels.value
   const bcModels = fuseModels.value
   fuseModels.value = []
   fuseModels.value = []

+ 22 - 19
src/views/summary/index.vue

@@ -4,16 +4,18 @@
   </LeftPano>
   </LeftPano>
 
 
   <RightFillPano>
   <RightFillPano>
-    <div class="tabs">
-      <span 
-        v-for="tab in tabs"
-        :key="tab.key"
-        :class="{ active: tab.key === current }"
-        @click="current = tab.key"
-      >
-        {{tab.text}}
-      </span>
-    </div>
+    <template #header>
+      <div class="tabs">
+        <span 
+          v-for="tab in tabs"
+          :key="tab.key"
+          :class="{ active: tab.key === current }"
+          @click="current = tab.key"
+        >
+          {{tab.text}}
+        </span>
+      </div>
+    </template>
     <Taggings v-if="current === TabKey.tagging" />
     <Taggings v-if="current === TabKey.tagging" />
     <Guides  v-if="current === TabKey.guide"/>
     <Guides  v-if="current === TabKey.guide"/>
     <Measures  v-if="current === TabKey.measure"/>
     <Measures  v-if="current === TabKey.measure"/>
@@ -21,9 +23,8 @@
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { computed, ref } from 'vue'
+import { ref, watchEffect } from 'vue'
 import { useViewStack } from '@/hook'
 import { useViewStack } from '@/hook'
-import { togetherCallback } from '@/utils'
 import { showRightCtrlPanoStack, showRightPanoStack } from '@/env'
 import { showRightCtrlPanoStack, showRightPanoStack } from '@/env'
 import { currentModel, fuseModel, loadModel } from '@/model'
 import { currentModel, fuseModel, loadModel } from '@/model'
 import { LeftPano, RightFillPano } from '@/layout'
 import { LeftPano, RightFillPano } from '@/layout'
@@ -39,14 +40,16 @@ const tabs = [
   { key: TabKey.guide, text: '路径' },
   { key: TabKey.guide, text: '路径' },
 ]
 ]
 const current = ref(tabs[0].key)
 const current = ref(tabs[0].key)
+const showRightCtrl = ref(true)
 
 
-const showRightPano = computed(() => currentModel.value === fuseModel)
-useViewStack(
-  () => togetherCallback([
-    showRightCtrlPanoStack.push(showRightPano), 
-    showRightPanoStack.push(showRightPano)
-  ])
-)
+watchEffect((onclean) => {
+  const isFuse = currentModel.value === fuseModel
+  if (!isFuse) {
+    onclean(showRightPanoStack.push(ref(false)))
+  }
+  showRightCtrl.value = isFuse
+})
+useViewStack(() => showRightCtrlPanoStack.push(showRightCtrl))
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>

+ 3 - 1
src/views/tagging-position/index.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <RightFillPano>
   <RightFillPano>
-    <ui-group :title="`标注${tagging?.title}`" class="position-group">
+    <ui-group :title="`${tagging?.title}放置位置`" class="position-group">
       <PositionSign 
       <PositionSign 
         v-for="(position, i) in positions" 
         v-for="(position, i) in positions" 
         :key="position.id" 
         :key="position.id" 
@@ -93,7 +93,9 @@ watchEffect((onCleanup) => {
 })
 })
 useViewStack(autoSaveTaggings)
 useViewStack(autoSaveTaggings)
 useViewStack(() => {
 useViewStack(() => {
+  const hide = Message.show({ msg: '请在模型上单击选择标注位置', type: 'warning' })
   enterEdit(() => router.back())
   enterEdit(() => router.back())
+  return hide
 })
 })
 </script>
 </script>
 
 

+ 47 - 17
src/views/tagging/edit.vue

@@ -36,6 +36,7 @@
           placeholder="" 
           placeholder="" 
           type="text" 
           type="text" 
           v-model="tagging.part"
           v-model="tagging.part"
+          :maxlength="60"
         >
         >
           <template #preIcon><span>遗留部位:</span></template>
           <template #preIcon><span>遗留部位:</span></template>
         </ui-input>
         </ui-input>
@@ -45,6 +46,7 @@
           placeholder="" 
           placeholder="" 
           type="text" 
           type="text" 
           v-model="tagging.method"
           v-model="tagging.method"
+          :maxlength="60"
         >
         >
           <template #preIcon><span>提取方法:</span></template>
           <template #preIcon><span>提取方法:</span></template>
         </ui-input>
         </ui-input>
@@ -54,24 +56,26 @@
           type="text" 
           type="text" 
           placeholder=""
           placeholder=""
           v-model="tagging.principal"
           v-model="tagging.principal"
+          :maxlength="60"
         >
         >
           <template #preIcon><span>提取人:</span></template>
           <template #preIcon><span>提取人:</span></template>
         </ui-input>
         </ui-input>
         <ui-input
         <ui-input
-            class="input "
-            type="file"
-            width="100%"
-            height="225px"
-            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"
+          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>
             <template v-slot:valuable>
                 <Images :tagging="tagging" :hideInfo="true">
                 <Images :tagging="tagging" :hideInfo="true">
@@ -96,7 +100,7 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
 import StylesManage from './styles.vue'
 import StylesManage from './styles.vue'
 import Images from './images.vue'
 import Images from './images.vue'
-import { computed, ref } from 'vue';
+import { computed, ref, watchEffect } from 'vue';
 import { Dialog, Message } from 'bill/index';
 import { Dialog, Message } from 'bill/index';
 import { 
 import { 
   taggingStyles,
   taggingStyles,
@@ -104,7 +108,8 @@ import {
   getTaggingStyle, 
   getTaggingStyle, 
   TaggingStyle,
   TaggingStyle,
   taggings,
   taggings,
-  isTemploraryID
+  isTemploraryID,
+defaultStyle
 } from '@/store'
 } from '@/store'
 
 
 export type EditProps = {
 export type EditProps = {
@@ -114,6 +119,13 @@ export type EditProps = {
 const props = defineProps<EditProps>()
 const props = defineProps<EditProps>()
 const emit = defineEmits<{ (e: 'quit'): void, (e: 'save', data: Tagging): void }>()
 const emit = defineEmits<{ (e: 'quit'): void, (e: 'save', data: Tagging): void }>()
 const tagging = ref<Tagging>({...props.data, images: [...props.data.images]})
 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
+  }
+})
 
 
 const submitHandler = () => {
 const submitHandler = () => {
   if (!tagging.value.title.trim()) {
   if (!tagging.value.title.trim()) {
@@ -127,7 +139,8 @@ const submitHandler = () => {
 
 
 const styles = computed(() => 
 const styles = computed(() => 
   [...taggingStyles.value].sort((a, b) => 
   [...taggingStyles.value].sort((a, b) => 
-    a.default ? -1 : b.default ? 1 : isTemploraryID(a.id) ? -1 : isTemploraryID(b.id) ? 1 : 0
+    a.default ? -1 : b.default ? 1 :
+    a.lastUse ? -1 : b.lastUse ? 1 : isTemploraryID(a.id) ? -1 : isTemploraryID(b.id) ? 1 : 0
   )
   )
 )
 )
 
 
@@ -245,4 +258,21 @@ const delImageHandler = async (file: Tagging['images'][number]) => {
   width: 70px;
   width: 70px;
   text-align: right;
   text-align: right;
 }
 }
+
+.del-file {
+  display: inline-block;
+  width: 32px;
+  height: 32px;
+  font-size: 16px;
+  background-color: rgba(0,0,0,0.5);
+  border-radius: 50%;
+  text-align: center;
+  line-height: 32px
+}
+</style>
+
+<style>
+  .edit-hot-layer .input.ui-input .text.suffix input {
+    padding-right: 60px;
+  }
 </style>
 </style>

+ 25 - 13
src/views/tagging/index.vue

@@ -1,21 +1,23 @@
 <template>
 <template>
   <RightFillPano>
   <RightFillPano>
-    <ui-group borderBottom>
-      <template #header>
-        <ui-button @click="editTagging = createTagging()">
-          <ui-icon type="add" />
-          新增
-        </ui-button>
-      </template>
-    </ui-group>
-    <ui-group title="标注">
+    <template #header>
+      <ui-group borderBottom>
+        <template #header>
+          <ui-button @click="editTagging = createTagging()">
+            <ui-icon type="add" />
+            新增
+          </ui-button>
+        </template>
+      </ui-group>
+    </template>
+    <ui-group title="标注列表" class="tagging-list">
       <template #icon>
       <template #icon>
         <ui-icon 
         <ui-icon 
           ctrl
           ctrl
           :class="{active: showSearch}"
           :class="{active: showSearch}"
           type="search" 
           type="search" 
           @click="showSearch = !showSearch" 
           @click="showSearch = !showSearch" 
-          style="margin-right: 10px"
+          style="margin-right: 20px"
         />
         />
         <ui-icon 
         <ui-icon 
           ctrl
           ctrl
@@ -61,14 +63,15 @@ import { router, RoutesName } from '@/router'
 import { custom } from '@/env'
 import { custom } from '@/env'
 import { 
 import { 
   taggings, 
   taggings, 
-  isTemploraryID, 
+  getTaggingStyle, 
   Tagging, 
   Tagging, 
   autoSaveTaggings, 
   autoSaveTaggings, 
   createTagging,
   createTagging,
   getTaggingPositions,
   getTaggingPositions,
   taggingPositions,
   taggingPositions,
   isOld,
   isOld,
-  save
+  save,
+  getTagging
 } from '@/store'
 } from '@/store'
 
 
 const showSearch = ref(false)
 const showSearch = ref(false)
@@ -78,11 +81,16 @@ const filterTaggings = computed(() => taggings.value.filter(tagging => tagging.t
 const editTagging = ref<Tagging | null>(null)
 const editTagging = ref<Tagging | null>(null)
 const saveHandler = (tagging: Tagging) => {
 const saveHandler = (tagging: Tagging) => {
   if (!editTagging.value) return;
   if (!editTagging.value) return;
-  if (isTemploraryID(editTagging.value.id)) {
+  if (!getTagging(editTagging.value.id)) {
     taggings.value.push(tagging)
     taggings.value.push(tagging)
+    const style = getTaggingStyle(tagging.styleId)
+    if (style) {
+      style.lastUse = 1
+    }
   } else {
   } else {
     Object.assign(editTagging.value, tagging)
     Object.assign(editTagging.value, tagging)
   }
   }
+  
   editTagging.value = null
   editTagging.value = null
 }
 }
 
 
@@ -109,4 +117,8 @@ useViewStack(autoSaveTaggings)
   .active {
   .active {
     color: var(--color-main-normal) !important;
     color: var(--color-main-normal) !important;
   }
   }
+
+  .tagging-list {
+    padding-bottom: 30px;
+  }
 </style>
 </style>

+ 1 - 4
src/views/tagging/show.vue

@@ -23,13 +23,10 @@
 import { ref } from 'vue'
 import { ref } from 'vue'
 import { custom } from '@/env'
 import { custom } from '@/env'
 import TaggingSign from './sign.vue'
 import TaggingSign from './sign.vue'
-import { taggings, initialTaggings, initialTaggingStyles } from '@/store'
+import { taggings } from '@/store'
 
 
 import type { Tagging } from '@/store'
 import type { Tagging } from '@/store'
 
 
-initialTaggingStyles()
-initialTaggings() 
-
 const selectTagging = ref<Tagging | null>(null)
 const selectTagging = ref<Tagging | null>(null)
 </script>
 </script>
 
 

+ 5 - 2
src/views/tagging/sign.vue

@@ -5,7 +5,10 @@
     @click="edit && getTaggingIsShow(tagging) && emit('select', true)"
     @click="edit && getTaggingIsShow(tagging) && emit('select', true)"
   >
   >
     <div class="info">
     <div class="info">
-      <img :src="getResource(getFileUrl(tagging.images.length ? tagging.images[0] : style.icon))" v-if="style">
+      <img 
+        :src="getResource(getFileUrl(tagging.images[0]))" 
+        v-if="tagging.images.length"
+      >
       <div>
       <div>
         <p>{{ tagging.title }}</p>
         <p>{{ tagging.title }}</p>
         <span>放置:{{ positions.length }}</span>
         <span>放置:{{ positions.length }}</span>
@@ -20,7 +23,7 @@
         :class="{ disabled: !getTaggingIsShow(tagging) }"
         :class="{ disabled: !getTaggingIsShow(tagging) }"
       />
       />
       <template v-else>
       <template v-else>
-        <ui-icon type="pin1" ctrl @click.stop="$emit('fixed')" />
+        <ui-icon type="pin1" ctrl @click.stop="$emit('fixed')" tip="放置" />
         <ui-more 
         <ui-more 
           :options="menus" 
           :options="menus" 
           style="margin-left: 20px" 
           style="margin-left: 20px" 

+ 8 - 5
src/views/tagging/styles.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <div class="hot-styles">
   <div class="hot-styles">
-    <div class="add item" v-if="!props.all">
+    <div class="add item" v-if="!props.all && styles.length < maxLength">
       <span class="fun-ctrl">
       <span class="fun-ctrl">
         <ui-input 
         <ui-input 
           class="input" 
           class="input" 
@@ -32,7 +32,7 @@
       </span>
       </span>
     </div>
     </div>
     <div 
     <div 
-      v-if="!props.all && props.styles.length > 6"
+      v-if="!props.all && props.styles.length > maxShowLen"
       class="add item style-more" 
       class="add item style-more" 
       @click="showAll = !showAll"
       @click="showAll = !showAll"
     >
     >
@@ -76,6 +76,9 @@ const props = defineProps<{
   all?: boolean
   all?: boolean
 }>()
 }>()
 
 
+const maxLength = 20
+const maxShowLen = computed(() => props.styles.length < maxLength ? 5 : 6)
+
 const emit = defineEmits<{
 const emit = defineEmits<{
   (e: 'change', style: TaggingStyle): void
   (e: 'change', style: TaggingStyle): void
   (e: 'delete', style: TaggingStyle): void
   (e: 'delete', style: TaggingStyle): void
@@ -88,9 +91,9 @@ const styleAll = computed(() => {
   if (props.all) {
   if (props.all) {
     return props.styles
     return props.styles
   } else {
   } else {
-    const styles = props.styles.slice(0, props.styles.length > 6 ? 5 : 6)
-    if (!styles.includes(props.active)) {
-      styles[3] = props.active
+    const styles = props.styles.slice(0, props.styles.length > maxShowLen.value ? maxShowLen.value : maxShowLen.value + 1)
+    if (!styles.includes(props.active) && props.active) {
+      styles[styles.length - 1] = props.active
     }
     }
     return styles
     return styles
   }
   }

+ 109 - 0
src/views/test/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="layout">
+    <TI v-model:attr.number="attr" />
+    123123
+    <button @click="show = !show">切换</button>
+    <input type="text" @change="ev => ev.returnValue">
+    <component :is="cp" v-for="cp in cps"></component>
+
+    <keep-alive>
+      <TIN v-if="show" />
+    </keep-alive>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, h, defineComponent, ref, onMounted, onUnmounted, onUpdated, computed, PropType } from 'vue'
+
+const TP = defineComponent({
+  props: {
+    children: { type: String }
+  },
+  setup(props) {
+    onUpdated(() => {
+      console.log('updated', props.children)
+    }),
+    onMounted(() => {
+      console.log('mounted', props.children)
+    })
+    onUnmounted(() => {
+      console.log('unmounted', props.children)
+    })
+
+    return () => h('p', props.children)
+  }
+})
+const TP1 = () => h(TP, { children: '111' })
+const TP2 = () => h(TP, { children: '222' })
+const TP3 = () => h(TP, { children: '333' })
+const TP4 = () => h(TP, { children: '444' })
+
+const TI = defineComponent({
+  props: {
+    attr: {
+      type: String
+    },
+    'onUpdate:attr': {
+      type: Function as PropType<(attr: string) => void>,
+    }
+  },
+  setup(props) {
+    const change = () => {
+      console.log('-->', props.attr)
+      props['onUpdate:attr']!(' 1' + props.attr + '1 ')
+    }
+    console.log(props)
+
+    return () => h('div', [
+        '内容:' + props.attr,
+        h('button', { onClick: change }, '变化')
+      ])
+  }
+})
+
+const TIN = defineComponent({
+  setup() {
+
+    return () => h('div', {style: { height: '3000px' }}, [
+      h('input')
+    ])
+  }
+})
+
+export default defineComponent({
+  setup() {
+    const data = reactive([
+      { name: '张三' },
+      { name: '李四' },
+    ])
+    const show = ref(false)
+    const cps = computed(() => 
+      show.value ? [TP1, TP2, TP3] : [TP4, TP1, TP2, TP3]
+    )
+    const attr = ref('1')
+
+    return {
+      cps,
+      show,
+      attr
+    }
+  },
+  components: {
+    TI,
+    TIN
+  }
+})
+
+
+</script>
+
+<style scoped>
+  .layout {
+    overflow: auto;
+    height: 100vh;
+    background-color: #ccc;
+  }
+  input {
+    border: 1px solid #000;
+  }
+</style>

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

@@ -1,11 +1,13 @@
 <template>
 <template>
   <RightFillPano>
   <RightFillPano>
-    <div class="btns header-btns">
-      <ui-button class="start" @click="getView">
-        <ui-icon type="add" />
-        视图提取
-      </ui-button>
-    </div>
+    <template #header>
+      <div class="btns header-btns">
+        <ui-button class="start" @click="getView">
+          <ui-icon type="add" />
+          视图提取
+        </ui-button>
+      </div>
+    </template>
 
 
     <ui-group title="全部视图" class="tree" >
     <ui-group title="全部视图" class="tree" >
       <Draggable :list="views" draggable=".sign" itemKey="id">
       <Draggable :list="views" draggable=".sign" itemKey="id">
@@ -30,8 +32,10 @@ import { useViewStack } from '@/hook'
 import Draggable from 'vuedraggable'
 import Draggable from 'vuedraggable'
 import Sign from './sign.vue'
 import Sign from './sign.vue'
 import { loadModel, currentModel, fuseModel } from '@/model'
 import { loadModel, currentModel, fuseModel } from '@/model'
-import { loadPack } from '@/utils'
+import { loadPack, togetherCallback } from '@/utils'
 import { Message } from 'bill/index'
 import { Message } from 'bill/index'
+import { showLeftPanoStack } from '@/env'
+import { ref, watch } from 'vue'
 
 
 import type { View } from '@/store'
 import type { View } from '@/store'
 
 
@@ -67,7 +71,19 @@ const deleteView = (record: View) => {
     views.value.splice(index, 1)
     views.value.splice(index, 1)
   }
   }
 }
 }
+
+const showLeftPano = ref(false)
+watch(currentModel, () => {
+  if (currentModel.value) {
+    showLeftPano.value = false
+  }
+})
+
+
 useViewStack(autoSaveViews)
 useViewStack(autoSaveViews)
+useViewStack(() => togetherCallback([
+  showLeftPanoStack.push(showLeftPano) 
+]))
 </script>
 </script>
 
 
 <style lang="scss" src="./style.scss" scoped>
 <style lang="scss" src="./style.scss" scoped>

+ 2 - 3
src/views/view/show.vue

@@ -13,14 +13,13 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import Sign from './sign.vue'
 import Sign from './sign.vue'
-import { views, initialViews } from '@/store'
+import { views } from '@/store'
 import { LeftPano } from '@/layout'
 import { LeftPano } from '@/layout'
 
 
-initialViews()
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .pano-group {
 .pano-group {
-  padding: 0 20px;
+  padding: 0 20px 40px;
 }
 }
 </style>
 </style>

+ 30 - 8
src/views/view/sign.vue

@@ -1,20 +1,22 @@
 <template>
 <template>
-  <ui-group-option class="sign">
+  <ui-group-option class="sign" :class="{active}">
     <div class="content">
     <div class="content">
       <span class="cover" @click="fly">
       <span class="cover" @click="fly">
         <img :src="getResource(getFileUrl(view.cover))" alt="">
         <img :src="getResource(getFileUrl(view.cover))" alt="">
       </span>
       </span>
       <ui-input 
       <ui-input 
+        class="view-title-input"
         type="text" 
         type="text" 
         :modelValue="view.title" 
         :modelValue="view.title" 
-        @update:modelValue="(title: string) => $emit('updateTitle', title)"
+        :maxlength="15"
+        @update:modelValue="(title: string) => $emit('updateTitle', title.trim())"
         v-show="isEditTitle" 
         v-show="isEditTitle" 
         ref="inputRef" 
         ref="inputRef" 
         height="28px" 
         height="28px" 
       />
       />
       <div class="title" v-show="!isEditTitle" @click="fly">
       <div class="title" v-show="!isEditTitle" @click="fly">
         <p>{{ view.title }}</p>
         <p>{{ view.title }}</p>
-        <span>{{ getModelTypeDesc(modelType as ModelType) }}</span>
+        <span>  {{ getModelDesc(modelType as ModelType) }}</span>
       </div>
       </div>
     </div>
     </div>
     <div class="action" v-if="edit">
     <div class="action" v-if="edit">
@@ -29,14 +31,15 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { ref, computed } from 'vue'
+import { ref, computed, watchEffect } from 'vue'
 import { useFocus } from 'bill/hook/useFocus'
 import { useFocus } from 'bill/hook/useFocus'
-import { getResource } from '@/env'
-import { getFileUrl } from '@/utils'
-import { loadModel, getModelTypeDesc, ModelType } from '@/model'
+import { custom, getResource } from '@/env'
+import { deepIsRevise, getFileUrl } from '@/utils'
+import { loadModel, getModelDesc, ModelType, currentModel } from '@/model'
 import { viewToModelType } from '@/store'
 import { viewToModelType } from '@/store'
 
 
 import type { View } from '@/store'
 import type { View } from '@/store'
+import { Message } from 'bill/expose-common'
 
 
 const props = withDefaults(
 const props = withDefaults(
   defineProps<{ view: View, edit?: boolean }>(),
   defineProps<{ view: View, edit?: boolean }>(),
@@ -49,12 +52,20 @@ const emit = defineEmits<{
 }>()
 }>()
 
 
 const menus = [
 const menus = [
-  { label: '编辑', value: 'rename' },
+  { label: '重命名', value: 'rename' },
   { label: '删除', value: 'delete' },
   { label: '删除', value: 'delete' },
 ]
 ]
 
 
 const inputRef = ref()
 const inputRef = ref()
 const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root))
 const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root))
+
+watchEffect(() => {
+  if (!isEditTitle.value && !props.view.title.length) {
+    isEditTitle.value = true
+    Message.warning('视图名称不可为空')
+  }
+})
+
 const actions = {
 const actions = {
   delete: () => emit('delete'),
   delete: () => emit('delete'),
   rename: () => isEditTitle.value = true
   rename: () => isEditTitle.value = true
@@ -62,10 +73,21 @@ const actions = {
 const modelType = viewToModelType(props.view)
 const modelType = viewToModelType(props.view)
 const fly = async () => {
 const fly = async () => {
   const sdk = await loadModel(modelType)
   const sdk = await loadModel(modelType)
+  custom.currentView = props.view
   sdk.setView(props.view.flyData)
   sdk.setView(props.view.flyData)
 }
 }
+const active = computed(() => {
+  return custom.currentView === props.view && !deepIsRevise(currentModel.value, modelType)
+})
+
 </script>
 </script>
 
 
 
 
 <style lang="scss" src="./style.scss" scoped>
 <style lang="scss" src="./style.scss" scoped>
+</style>
+
+<style>
+  .view-title-input.ui-input .text.suffix input {
+    padding-right: 50px;
+  }
 </style>
 </style>

+ 14 - 0
src/views/view/style.scss

@@ -18,6 +18,7 @@
 
 
 .tree {
 .tree {
   margin-top: 20px;
   margin-top: 20px;
+  padding-bottom: 40px;
 }
 }
 
 
 .header-btns {
 .header-btns {
@@ -34,10 +35,20 @@
   align-items: center;
   align-items: center;
   justify-content: space-between;
   justify-content: space-between;
   margin-bottom: 0 !important;
   margin-bottom: 0 !important;
+  position: relative;
 
 
   &:last-child {
   &:last-child {
     border-bottom: 1px solid rgba(255,255,255,0.1600);
     border-bottom: 1px solid rgba(255,255,255,0.1600);
   }
   }
+
+  &.active::after {
+    content: '';
+    position: absolute;
+    pointer-events: none;
+    inset: 0 -20px;
+    background-color: rgba(0, 200, 175, 0.16);
+    z-index: -1;
+  }
 }
 }
 
 
 .content {
 .content {
@@ -45,6 +56,7 @@
   align-items: center;
   align-items: center;
 
 
   .cover {
   .cover {
+    flex: none;
     display: flex;
     display: flex;
     position: relative;
     position: relative;
     width: 48px;
     width: 48px;
@@ -78,4 +90,6 @@
 .action {
 .action {
   color: #fff;
   color: #fff;
   font-size: 14px;
   font-size: 14px;
+  flex: none;
+  margin-left: 10px;
 }
 }