bill hace 2 años
padre
commit
4715268c22

+ 1 - 0
src/api/constant.ts

@@ -12,6 +12,7 @@ export const UPLOAD_HEADS = {
   'Content-Type': 'multipart/form-data'
 }
 
+export const CASE_INFO = `/fusion/case/getInfo`
 
 // 融合模型列表
 export const FUSE_MODEL_LIST = `/fusion/caseFusion/list`

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

+ 4 - 0
src/api/tagging-style.ts

@@ -6,17 +6,20 @@ interface ServiceStyle {
   iconTitle: string,
   iconUrl: string,
   isSystem: number
+  lastUse: 1 | 0
 }
 
 export interface TaggingStyle {
   id: string
   icon: string
   name: string
+  lastUse: 1 | 0
   default: boolean
 }
 
 const toLocal = (serviceStyle: ServiceStyle) : TaggingStyle => ({
   id: serviceStyle.iconId.toString(),
+  lastUse: serviceStyle.lastUse,
   name: serviceStyle.iconTitle,
   icon: serviceStyle.iconUrl,
   default: Boolean(serviceStyle.isSystem)
@@ -25,6 +28,7 @@ const toLocal = (serviceStyle: ServiceStyle) : TaggingStyle => ({
 const toService = (style: TaggingStyle): ServiceStyle => ({
   iconId: Number(style.id),
   iconTitle: style.name,
+  lastUse: style.lastUse,
   iconUrl: style.icon,
   isSystem: Number(style.default)
 

+ 4 - 2
src/env/index.ts

@@ -1,7 +1,7 @@
 import { stackFactory, flatStacksValue, strToParams } from '@/utils'
 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 showToolbarStack = stackFactory(ref<boolean>(false))
@@ -18,6 +18,7 @@ export const currentModelStack = stackFactory(ref<FuseModel | null>(null))
 export const showModelsMapStack = stackFactory(ref<Map<FuseModel, boolean>>(new Map), true)
 export const modelsChangeStoreStack = stackFactory(ref<boolean>(false))
 export const showTaggingPositionsStack = stackFactory(ref<WeakSet<TaggingPosition>>(new WeakSet()))
+export const currentViewStack = stackFactory(ref<View>())
 
 export const custom = flatStacksValue({
   viewMode: viewModeStack,
@@ -34,7 +35,8 @@ export const custom = flatStacksValue({
   showTaggingPositions: showTaggingPositionsStack,
   showBottomBar: showBottomBarStack,
   bottomBarHeight: bottomBarHeightStack,
-  showHeadBar: showHeadBarStack
+  showHeadBar: showHeadBarStack,
+  currentView: currentViewStack
 })
 
 

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

@@ -22,11 +22,26 @@
 </template>
 
 <script setup lang="ts">
-import { computed, watchEffect } from 'vue'
+import { computed, watchEffect, ref } from 'vue'
 import { isEdit, title, isOld, leave, save } from '@/store'
+import { currentMeta } from '@/router'
+import { getCaseInfo } from '@/api'
+
+const prefix = ref('')
+getCaseInfo()
+  .then(ecase => prefix.value = `${ecase.caseTitle} | `)
 
 const props = defineProps<{ title?: string }>()
-const sysTitle = computed(() => props.title || title)
+const sysTitle = computed(() => {
+  if (props.title) {
+    return props.title
+  } else if (currentMeta.value && 'sysTitle' in currentMeta.value) {
+    return prefix.value + currentMeta.value.sysTitle
+  } else {
+    return prefix.value + title.value
+  }
+})
+
 
 watchEffect(() => (document.title = sysTitle.value))
 </script>

+ 28 - 5
src/layout/edit/scene-select.vue

@@ -35,12 +35,13 @@
 
 <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 { computed, nextTick, ref, watch, watchEffect } from 'vue';
+import { scenes, save, SceneTypeDesc } from '@/store'
 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 { useViewStack } from '@/hook';
 
 type Key = Scene['modelId']
 
@@ -50,7 +51,12 @@ const selectIds = computed(() => fuseModels.value.map(item => item.modelId))
 const visible = ref(false)
 const keyword = ref('')
 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 rowSelection: any = ref({
@@ -69,7 +75,12 @@ const cloumns = [
     key: 'name',
   },
   {
-    title: '拍摄时间',
+    title: '类型',
+    dataIndex: 'type',
+    key: 'type',
+  },
+  {
+    title: '拍摄/创建时间',
     dataIndex: 'createTime',
     key: 'createTime',
   }
@@ -96,6 +107,18 @@ const okHandler = createLoadPack(async () => {
   await save()
   visible.value = false
 })
+
+watch(visible, (visible, oldvisible) => {
+  if (visible !== oldvisible) {
+    keyword.value = ''
+    selects.value = selectIds.value
+    console.log(selects.value)
+  }
+})
+
+useViewStack(() => {
+  initialScenes()
+})
 </script>
 
 <style lang="less" scoped>

+ 7 - 4
src/layout/scene-list/index.vue

@@ -4,8 +4,7 @@
       <slot name="action" />
     </template>
     <template #atom="{ item }">
-      <div 
-        v-if="item.raw === fuseModel" 
+      <div v-if="item.raw === fuseModel" 
         @click="updateCurrent(item.raw)"
       >
         <ModelList
@@ -32,7 +31,7 @@
 
 <script lang="ts" setup>
 import { computed } from 'vue'
-import { scenes, SceneType, SceneTypeDesc } from '@/store'
+import { scenes, SceneType, SceneTypeDesc, fuseModels } from '@/store'
 import List from '@/components/list/index.vue'
 import ModelList from '../model-list/index.vue'
 import { fuseModel, getModelTypeDesc } from '@/model'
@@ -50,7 +49,11 @@ const list = computed(() => {
       && (props.current.num === scene.num )
       && 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) => {

+ 9 - 1
src/model/index.ts

@@ -1,5 +1,5 @@
 import App from './app.vue'
-import { appEl, SceneTypeDesc } from '@/store'
+import { appEl, SceneTypeDesc, scenes } from '@/store'
 import { mount, deepIsRevise } from '@/utils'
 import { reactive, ref } from 'vue'
 
@@ -22,6 +22,14 @@ export const getModelTypeDesc = (model: ModelType) => {
     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 = (() => {
   let oldModelType: ModelType

+ 5 - 1
src/router/constant.ts

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

+ 1 - 0
src/router/index.ts

@@ -37,6 +37,7 @@ export const currentLayout = computed(() => {
 
 export const currentMeta = computed(() => {
   const currentName = router.currentRoute.value.name
+  console.error(currentName, metas)
   if (currentName && currentName in metas) {
     return (metas as any )[currentName] as ((typeof metas)[keyof typeof metas])
   }

+ 25 - 14
src/sdk/association.ts

@@ -73,13 +73,20 @@ const associationModels = (sdk: SDK) => {
       }
 
       const itemRaw = toRaw(item)
+      let sceneModel: SceneModel
       console.error('loaded', itemRaw)
-      const sceneModel = sdk.addModel({
-        ...itemRaw,
-        ...modelRange,
-        type: item.type === SceneType.SWSS ? 'laser' : item.modelType,
-        url: item.type === SceneType.SWSS ? item.url : getResource(item.url)
-      })
+      try {
+        sceneModel = sdk.addModel({
+          ...itemRaw,
+          ...modelRange,
+          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)
 
@@ -103,9 +110,10 @@ const associationModels = (sdk: SDK) => {
               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) {
             transform.scale = round(transform.scale, 2)
           }
@@ -117,8 +125,6 @@ const associationModels = (sdk: SDK) => {
           }
           
           if (deepIsRevise(update, transform)) {
-
-          console.error(update, transform)
             unSet(() => Object.assign(item, transform))
           }
         }, 16)
@@ -139,6 +145,7 @@ const associationModels = (sdk: SDK) => {
       sceneModel.bus.on('loadError', () => {
         item.error = true
         item.show = false
+        
         custom.showModelsMap.delete(item)
         hideLoad()
       })
@@ -175,7 +182,6 @@ const associationModels = (sdk: SDK) => {
             () => item.position, 
             () => {
               if (!isUnSet) {
-                console.log('set position', item.position)
                 getSceneModel(item)?.changePosition(item.position)
               }
             }, 
@@ -185,7 +191,6 @@ const associationModels = (sdk: SDK) => {
             () => item.rotation, 
             () => {
               if (!isUnSet) {
-                console.log('set rotation', item.rotation)
                 getSceneModel(item)?.changeRotation(item.rotation)
               }
             }, 
@@ -193,7 +198,13 @@ const associationModels = (sdk: SDK) => {
           )
           watch(
             () => modelShow.value, 
-            () => isUnSet || getSceneModel(item)?.changeShow(modelShow.value), 
+            () => {
+              const sceneModel = getSceneModel(item)
+              if (!isUnSet && sceneModel) {
+                sceneModel.changeSelect(false)
+                sceneModel.changeShow(modelShow.value)
+              }
+            }, 
             { immediate: true }
           )
 

+ 1 - 2
src/store/sys.ts

@@ -18,14 +18,13 @@ export const isEdit = computed(() => !!(mode.value & Flags.EDIT))
 export const isLogin = computed(() => !!(mode.value & Flags.LOGIN))
 export const isOld = computed(() => !(mode.value & Flags.NOW))
 export const isNow = computed(() => !!(mode.value & Flags.NOW))
-export const title = '多元融合'
+export const title = ref('多元融合')
 export const appEl = ref<HTMLDivElement | null>(null)
 
 let currentTempIndex = 0
 export const isTemploraryID = (id: string) => id.includes('__currentTempIndex__')
 export const createTemploraryID = () => `__currentTempIndex__${currentTempIndex++}`
 
-
 export const sysBus = asyncBusFactory<{ save: void; leave: void }>()
 
 // 进入编辑界面

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

@@ -21,10 +21,12 @@ export type TaggingStyles = TaggingStyle[]
 
 export const taggingStyles = ref<TaggingStyles>([])
 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 => ({
   id: createTemploraryID(),
   icon: '',
+  lastUse: 0,
   default: false,
   name: '',
   ...style

+ 15 - 12
src/store/tagging.ts

@@ -1,7 +1,7 @@
 import { ref } from 'vue'
 import { togetherCallback } from '@/utils'
 import { autoSetModeCallback, createTemploraryID } from './sys'
-import { defaultStyle, getTaggingStyle } from './tagging-style'
+import { defaultStyle, getTaggingStyle, initialTaggingStyles, lastUseStyle } from './tagging-style'
 import { 
   initTaggingPositionsByTagging, 
   saveTaggingPositions,
@@ -46,17 +46,19 @@ export const getTagging = (id: Tagging['id']) => taggings.value.find(tagging =>
 export const getTaggingIsShow = (tagging: Tagging) => 
   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 = []
@@ -122,5 +124,6 @@ export const autoSaveTaggings = autoSetModeCallback([taggings, taggingPositions,
     }
     await saveTaggings()
     await saveTaggingPositions()
+    await initialTaggingStyles()
   },
 })

+ 5 - 0
src/store/view.ts

@@ -20,6 +20,7 @@ import { fuseModel } from '@/model'
 
 import type { View as SView } from '@/api'
 import type { ModelType } from '@/model'
+import { Message } from "bill/expose-common";
 
 export type View = LocalMode<SView, 'cover'>
 export type Views = View[]
@@ -102,6 +103,10 @@ export const autoSaveViews = autoSetModeCallback(views, {
   backup: backupViews,
   recovery: recoverViews,
   save: async () => {
+    if (!views.value.every(view => view.title)) {
+      Message.warning('视图名称不可为空')
+      throw '视图名称不可为空'
+    }
     for (let i = 0; i < views.value.length; 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'
 
-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()
     const ret = typeof fn === 'function' ? fn() : fn
     ret.finally(() => Loading.hide())

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

@@ -63,7 +63,6 @@
               type="number" 
               width="54px" 
               height="26px" 
-              v-model="path.time" 
               :modelValue="path.time" 
               @update:modelValue="(val: number) => updatePathInfo(i, { time: val })"
               :ctrl="false" 

+ 2 - 2
src/views/merge/index.vue

@@ -24,7 +24,7 @@
           <template #icon>%</template>
         </ui-input>
       </ui-group-option>
-      <ui-group-option label="离地高度">
+      <!-- <ui-group-option label="离地高度">
         <ui-input 
           type="range" 
           v-model="custom.currentModel.bottom" 
@@ -34,7 +34,7 @@
         >
           <template #icon>m</template>
         </ui-input>
-      </ui-group-option>
+      </ui-group-option> -->
       <ui-group-option label="模型不透明度">
         <ui-input 
           type="range" 

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

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

+ 4 - 3
src/views/record/index.vue

@@ -2,7 +2,7 @@
   <RightFillPano>
     <div class="btns header-btns">
       <ui-button class="start" @click="start" type="primary">开始录制</ui-button>
-      <ui-input 
+      <!-- <ui-input 
         class="unit" 
         type="multiple" 
         :options="setOptions" 
@@ -10,7 +10,7 @@
         width="120px" 
         placeholder="显示设置"
       >
-      </ui-input>
+      </ui-input> -->
     </div>
 
     <ui-group title="全部视频" class="tree" >
@@ -61,10 +61,11 @@ const setOptions = [
 ] as const
 
 type SetKey = typeof setOptions[number]['value']
-const setting = ref<SetKey[]>([])
+const setting = ref<SetKey[]>(['tagging', 'measure'])
 watch(setting, (setting, oldSetting = [], onCleanup) => {
   const { added } = diffArrayChange(setting, oldSetting)
   const pops = added.map(value => {
+    console.error('???')
     if (value === 'measure') {
       return showMeasuresStack.push(ref(true))
     } else {

+ 9 - 3
src/views/record/shot.vue

@@ -49,9 +49,11 @@ import {
   showBottomBarStack, 
   custom, 
   bottomBarHeightStack,
-  showHeadBarStack
+  showHeadBarStack,
+  showLeftPanoStack
 } from '@/env'
 import { appEl } from '@/store';
+import { useViewStack } from '@/hook';
 
 
 export default defineComponent({
@@ -82,6 +84,7 @@ export default defineComponent({
 
     const countdown = ref(0)
     let interval: NodeJS.Timer
+    let recordIng = false
     const start = () => {
       custom.showBottomBar = false
       countdown.value = 3
@@ -89,15 +92,16 @@ export default defineComponent({
         if (--countdown.value === 0) {
           clearInterval(interval)
           videoRecorder.startRecord()
+          recordIng = true
         }
       }, 1000)
     }
 
     const pause = () => {
-      if (countdown.value === 0 && videoRecorder.status !== 3) {
+      if (countdown.value === 0 && recordIng) {
         videoRecorder.endRecord()
+        recordIng = false
       }
-      
       countdown.value = 0
       custom.showBottomBar = true
       clearInterval(interval)
@@ -172,6 +176,8 @@ export default defineComponent({
       document.body.removeEventListener('keyup', upHandler, { capture: true })
     })
 
+    useViewStack(() => showLeftPanoStack.push(ref(false)))
+
     return {
       MediaType,
       complete,

+ 3 - 1
src/views/registration/index.vue

@@ -21,7 +21,7 @@
             :input="false"
             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>
     </ui-floating>
@@ -55,6 +55,7 @@ import { diffArrayChange } from '@/utils'
 import { useViewStack } from '@/hook'
 import { autoSaveFuseModels, getFuseModel, leave } from '@/store'
 import { router, RoutesName } from '@/router'
+import { currentModelStack } from '@/env'
 
 import type { ControlExpose } from '@/components/control-panl'
 
@@ -107,6 +108,7 @@ watchEffect((onCleanup) => {
   const smodel = sceneModel.value
   if (smodel) {
     smodel.enterAlignment()
+    currentModelStack.push(model as any)
 
     onCleanup(() => {
       smodel.leaveTransform()

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

@@ -58,20 +58,21 @@
           <template #preIcon><span>提取人:</span></template>
         </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>
                 <Images :tagging="tagging" :hideInfo="true">
@@ -127,7 +128,8 @@ const submitHandler = () => {
 
 const styles = computed(() => 
   [...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
   )
 )
 

+ 15 - 1
src/views/view/index.vue

@@ -30,8 +30,10 @@ import { useViewStack } from '@/hook'
 import Draggable from 'vuedraggable'
 import Sign from './sign.vue'
 import { loadModel, currentModel, fuseModel } from '@/model'
-import { loadPack } from '@/utils'
+import { loadPack, togetherCallback } from '@/utils'
 import { Message } from 'bill/index'
+import { showLeftPanoStack } from '@/env'
+import { ref, watch } from 'vue'
 
 import type { View } from '@/store'
 
@@ -67,7 +69,19 @@ const deleteView = (record: View) => {
     views.value.splice(index, 1)
   }
 }
+
+const showLeftPano = ref(false)
+watch(currentModel, () => {
+  if (currentModel.value) {
+    showLeftPano.value = false
+  }
+})
+
+
 useViewStack(autoSaveViews)
+useViewStack(() => togetherCallback([
+  showLeftPanoStack.push(showLeftPano) 
+]))
 </script>
 
 <style lang="scss" src="./style.scss" scoped>

+ 28 - 6
src/views/view/sign.vue

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

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

@@ -18,6 +18,7 @@
 
 .tree {
   margin-top: 20px;
+  padding-bottom: 40px;
 }
 
 .header-btns {
@@ -34,10 +35,20 @@
   align-items: center;
   justify-content: space-between;
   margin-bottom: 0 !important;
+  position: relative;
 
   &:last-child {
     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 {
@@ -45,6 +56,7 @@
   align-items: center;
 
   .cover {
+    flex: none;
     display: flex;
     position: relative;
     width: 48px;
@@ -78,4 +90,6 @@
 .action {
   color: #fff;
   font-size: 14px;
+  flex: none;
+  margin-left: 10px;
 }