浏览代码

fix: 对接store

bill 3 年之前
父节点
当前提交
40a42a92fe

+ 10 - 1
src/api/constant.ts

@@ -15,12 +15,21 @@ export const UPLOAD_HEADS = {
 
 // 模型列表
 export const MODEL_LIST = ''
+export const INSERT_MODEL = ''
+export const UPDATE_MODEL = ''
+export const DELETE_MODEL = ''
 
 // 标注列表
 export const TAGGING_LIST = ''
+export const INSERT_TAGGING = ''
+export const UPDATE_TAGGING = ''
+export const DELETE_TAGGING = ''
 
 // 标注样式类型列表
 export const TAGGING_STYLE_LIST = ''
 
 // 路径
-export const PATH_LIST = ''
+export const GUIDE_LIST = ''
+export const INSERT_GUIDE = ''
+export const UPDATE_GUIDE = ''
+export const DELETE_GUIDE = ''

+ 54 - 3
src/api/guide.ts

@@ -1,5 +1,10 @@
 import axios from './instance'
-import { PATH_LIST } from './constant'
+import { 
+  GUIDE_LIST,
+  INSERT_GUIDE,
+  UPDATE_GUIDE,
+  DELETE_GUIDE,
+} from './constant'
 
 export interface GuidePath {
   id: string,
@@ -20,5 +25,51 @@ export interface Guide {
 export type Guides = Guide[]
 export type GuidePaths = GuidePath[]
 
-export const getGuides = () => 
-  axios.post<Guides>(PATH_LIST)
+export const fetchGuides = async () => {
+  // axios.post<Guides>(GUIDE_LIST)
+  return [
+    {
+      id: '123',
+      title: '路径1',
+      cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
+      paths: [
+        {
+          id: '123a',
+          position: {x: 1, y: 1, z: 1},
+          target: {x: 1, y: 1, z: 1},
+          cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
+          speed: 1,
+          time: 1
+        },
+        {
+          id: '123b',
+          position: {x: 1, y: 1, z: 1},
+          target: {x: 1, y: 1, z: 1},
+          cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
+          speed: 1,
+          time: 1
+        }
+      ]
+    }
+  ]
+}
+
+
+export const postAddGuide = async (guide: Guide) => {
+  console.log('add')
+  //  axios.post<Guide>(INSERT_GUIDE, guide)
+   return guide
+}
+
+export const postUpdateGuide = async (guide: Guide) => {
+  console.log('update')
+  // return axios.post<undefined>(UPDATE_GUIDE, guide)
+  // return
+}
+
+export const postDeleteGuide = (id: Guide['id']) => {
+  console.log('delete')
+  return axios.post<undefined>(DELETE_GUIDE)
+}
+
+  

+ 55 - 3
src/api/model.ts

@@ -1,6 +1,9 @@
 import axios from './instance'
 import { 
-  MODEL_LIST
+  MODEL_LIST,
+  INSERT_MODEL,
+  UPDATE_MODEL,
+  DELETE_MODEL
 } from './constant'
 
 export enum ModelType {
@@ -36,5 +39,54 @@ export interface Model extends ModelAttrs {
 
 export type Models = Model[]
 
-export const getModels = () => 
-  axios.post<Models>(MODEL_LIST)
+export const fetchModels = async () => {
+  // axios.post<Models>(MODEL_LIST)
+  return [
+    {
+      id: '123',
+      url: 'SS-t-7DUfWAUZ3V',
+      type: ModelType.SWSS,
+      title: 'SS-t-7DUfWAUZ3V',
+      size: 1000,
+      time: '2012-02-05',
+      scale: 1,
+      rotation: { x: 1, y: 1, z: 1},
+      position: { x: 1, y: 1, z: 1},
+      opacity: 0.1,
+      bottom: 1,
+      show: true
+    },
+    {
+      id: '124',
+      url: '/lib/resources/models/glb/coffeemat.glb',
+      type: ModelType.SWMX,
+      title: '某安外',
+      size: 1000,
+      time: '2012-02-05',
+      scale: 1,
+      rotation: { x: 1, y: 1, z: 1},
+      position: { x: 1, y: 1, z: 1},
+      opacity: 0.1,
+      bottom: 1,
+      show: true
+    }
+  ]
+} 
+
+export const postAddModel = (model: Model) => {
+  console.log('add')
+  return axios.post<Model>(INSERT_MODEL, model)
+}
+
+export const postUpdateModels = (model: Model) => {
+  console.log('update')
+  return axios.post<undefined>(UPDATE_MODEL, model)
+}
+  
+
+export const postDeleteModel = (id: Model['id']) => {
+  console.log('delete')
+  return axios.post<undefined>(DELETE_MODEL)
+}
+
+  

+ 62 - 3
src/api/tagging.ts

@@ -1,5 +1,10 @@
 import axios from './instance'
-import { TAGGING_LIST } from './constant'
+import { 
+  TAGGING_LIST,
+  DELETE_TAGGING,
+  INSERT_TAGGING,
+  UPDATE_TAGGING
+} from './constant'
 
 export interface Tagging {
   id: string
@@ -15,5 +20,59 @@ export interface Tagging {
 
 export type Taggings = Tagging[]
 
-export const getTaggings = () => 
-  axios.post<Taggings>(TAGGING_LIST)
+export const fetchTaggings = async () => {
+  // axios.post<Taggings>(TAGGING_LIST)
+  return [
+    {
+      id: '1231',
+      title: 'aaaa',
+      styleId: '1231',
+      desc: '123123',
+      part: '123asd',
+      method: '123123a',
+      principal: 'asdasd',
+      images: [
+        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
+        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png'
+      ],
+      positions: [
+        { x: 1, y: 1, z: 1 }
+      ]
+    },
+
+    {
+      id: '1231a',
+      title: 'aaaa',
+      styleId: '1231',
+      desc: '123123',
+      part: '123asd',
+      method: '123123a',
+      principal: 'asdasd',
+      images: [
+        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
+        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png'
+      ],
+      positions: [
+        { x: 1, y: 1, z: 1 }
+      ]
+    }
+  ]
+}
+
+export const postAddTagging = (tagging: Tagging) => {
+  console.log('add')
+  return axios.post<Tagging>(INSERT_TAGGING, tagging)
+}
+
+export const postUpdateTagging = async (tagging: Tagging) => {
+  console.log('update')
+  // return axios.post<undefined>(UPDATE_TAGGING, tagging)
+}
+  
+
+export const postDeleteTagging = (id: Tagging['id']) => {
+  console.log('delete')
+  return axios.post<undefined>(DELETE_TAGGING)
+}
+
+  

+ 0 - 1
src/app.vue

@@ -3,7 +3,6 @@
 </template>
 
 <script lang="ts" setup>
-import MainCom from '@/layout/main.vue'
 import { computed } from 'vue'
 import { loaded, error, initialStore } from '@/store'
 import { loadComponent, loadPack } from '@/utils'

+ 9 - 6
src/components/tagging/list.vue

@@ -1,15 +1,18 @@
 <template>
-  <Sign 
-    v-for="(pos, index) in tagging.positions" 
-    :key="index"
-    :tagging="tagging"
-    :scene-pos="pos"
-  />
+  <template v-if="custom.showTaggings">
+    <Sign 
+      v-for="(pos, index) in tagging.positions" 
+      :key="index"
+      :tagging="tagging"
+      :scene-pos="pos"
+    />
+  </template>
 </template>
 
 <script lang="ts" setup>
 import { Tagging } from '@/store';
 import Sign from './sign.vue'
+import { custom } from '@/env'
 
 defineProps<{ tagging: Tagging }>()
 

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

@@ -60,7 +60,8 @@ const posStyle = computed(() => {
     top: screenPos.y + 'px',
   }
 })
-const taggingStyle = getTaggingStyle(props.tagging.id)
+
+const taggingStyle = getTaggingStyle(props.tagging.styleId)
 
 const pullIndex = ref(-1)
 

+ 3 - 1
src/env/index.ts

@@ -7,6 +7,7 @@ export const showRightPanoStack = stackFactory(ref<boolean>(true))
 export const showLeftPanoStack = stackFactory(ref<boolean>(false))
 export const showLeftCtrlPanoStack = stackFactory(ref<boolean>(true))
 export const showRightCtrlPanoStack = stackFactory(ref<boolean>(true))
+export const showTaggingsStack = stackFactory(ref<boolean>(true))
 
 export const custom = flatStacksValue({
   viewMode: viewModeStack,
@@ -14,6 +15,7 @@ export const custom = flatStacksValue({
   showRightPano: showRightPanoStack,
   showLeftPano: showLeftPanoStack,
   showLeftCtrlPano: showLeftCtrlPanoStack,
-  shwoRightCtrlPano: showRightCtrlPanoStack
+  shwoRightCtrlPano: showRightCtrlPanoStack,
+  showTaggings: showTaggingsStack
 })
 

+ 1 - 0
src/sdk/index.ts

@@ -79,6 +79,7 @@ export const initialSDK = async (props: InialSDKProps) => {
   await loadLib(`/lib/potree/potree.js`)
 
   sdk = cover(props.layout) as unknown as SDK
+  sdk.layout = props.layout
   setup(sdk, presetViewElement(props.layout))
 }
 

+ 46 - 33
src/store/guide.ts

@@ -1,43 +1,27 @@
 import { ref } from 'vue'
 import { TemploraryID } from './sys'
-import { getGuides, GuidePath, Guide } from '@/api'
+import { autoSetModeCallback } from './sys'
+import { 
+  fetchGuides, 
+  postAddGuide, 
+  postDeleteGuide,
+  postUpdateGuide
+} from '@/api'
+import { 
+  deleteStoreItem, 
+  addStoreItem, 
+  updateStoreItem, 
+  fetchStoreItems,
+  saveStoreItems,
+  recoverStoreItems
+} from '@/utils'
 
-import type { Guides } from '@/api'
+import type { GuidePath, Guide, Guides } from '@/api'
 
 export const guides = ref<Guides>([])
 
-
-export const initialGuides = async () => {
-  // taggings.value = await getTaggingStyles()
-  guides.value = [
-    {
-      id: '123',
-      title: '路径1',
-      cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
-      paths: [
-        {
-          id: '123a',
-          position: {x: 1, y: 1, z: 1},
-          target: {x: 1, y: 1, z: 1},
-          cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
-          speed: 1,
-          time: 1
-        },
-        {
-          id: '123b',
-          position: {x: 1, y: 1, z: 1},
-          target: {x: 1, y: 1, z: 1},
-          cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
-          speed: 1,
-          time: 1
-        }
-      ]
-    }
-  ]
-}
-
 export const createGuide = (guide: Partial<Guide> = {}): Guide => ({
-  id: '',
+  id: TemploraryID,
   title: '',
   cover: '',
   paths: [],
@@ -55,4 +39,33 @@ export const createGuidePath = (path: Partial<GuidePath> = {}): GuidePath => ({
 })
 
 
+let bcGuides: Guides = []
+export const getBackupGuides = () => bcGuides
+export const backupGuides = () => {
+  bcGuides = guides.value.map(guide => ({
+    ...guide,
+    paths: guide.paths.map(path => ({...path}))
+  }))
+}
+
+export const recoverGuides = recoverStoreItems(guides, getBackupGuides)
+export const addGuide = addStoreItem(guides, postAddGuide)
+export const updateGuide = updateStoreItem(guides, postUpdateGuide)
+export const deleteGuide = deleteStoreItem(guides, guide => postDeleteGuide(guide.id))
+export const initialGuides = fetchStoreItems(guides, fetchGuides, backupGuides)
+export const saveGuides = saveStoreItems(
+  guides,
+  getBackupGuides,
+  {
+    add: addGuide,
+    update: updateGuide,
+    delete: deleteGuide,
+  }
+)
+export const autoSaveGuides = autoSetModeCallback(guides, {
+  backup: getBackupGuides,
+  recovery: recoverGuides,
+  save: saveGuides,
+})
+
 export type { Guide, Guides, GuidePath, GuidePaths } from '@/api'

+ 1 - 3
src/store/index.ts

@@ -8,15 +8,13 @@ export const loaded = ref(false)
 export const error = ref(false)
 
 export const initialStore = async () => {
-  const init = Promise.all([
+  await Promise.all([
     initialModels(),
     initialTaggingStyles(),
     initialTaggings(),
     initialGuides()
   ])
-
   try {
-    await init
     loaded.value = true
   } catch {
     error.value = true

+ 43 - 35
src/store/model.ts

@@ -1,46 +1,54 @@
-import { ref } from 'vue'
-import { getModels, ModelType } from '@/api'
+import { ref, watchEffect } from 'vue'
+import { autoSetModeCallback } from './sys'
+import { 
+  fetchModels, 
+  postAddModel, 
+  postDeleteModel,
+  postUpdateModels
+} from '@/api'
+import { 
+  deleteStoreItem, 
+  addStoreItem, 
+  updateStoreItem, 
+  fetchStoreItems,
+  saveStoreItems,
+  recoverStoreItems
+} from '@/utils'
 
 import type { Models, Model } from '@/api'
 
 export const currentModel = ref<Model | null>(null)
 export const models = ref<Models>([])
 
-
-export const initialModels = async () => {
-  // models.value = await getModels()
-  models.value = [
-    {
-      id: '123',
-      url: 'SS-t-7DUfWAUZ3V',
-      type: ModelType.SWSS,
-      title: 'SS-t-7DUfWAUZ3V',
-      size: 1000,
-      time: '2012-02-05',
-      scale: 1,
-      rotation: { x: 1, y: 1, z: 1},
-      position: { x: 1, y: 1, z: 1},
-      opacity: 0.1,
-      bottom: 1,
-      show: true
-    },
-    {
-      id: '124',
-      url: '/lib/resources/models/glb/coffeemat.glb',
-      type: ModelType.SWMX,
-      title: '某安外',
-      size: 1000,
-      time: '2012-02-05',
-      scale: 1,
-      rotation: { x: 1, y: 1, z: 1},
-      position: { x: 1, y: 1, z: 1},
-      opacity: 0.1,
-      bottom: 1,
-      show: true
-    }
-  ]
+let bcModels: Models = []
+export const getBackupModels = () => bcModels
+export const backupModels = () => {
+  bcModels = models.value.map(model => ({
+    ...model,
+    rotation: {...model.rotation},
+    position: {...model.position},
+  }))
 }
 
+export const recoverModels = () => recoverStoreItems(models, getBackupModels)
+export const addModel = addStoreItem(models, postAddModel)
+export const updateModel = updateStoreItem(models, postUpdateModels)
+export const deleteModel = deleteStoreItem(models, model => postDeleteModel(model.id))
+export const initialModels = fetchStoreItems(models, fetchModels, backupModels)
+export const saveModels = saveStoreItems(
+  models,
+  getBackupModels,
+  {
+    add: addModel,
+    update: updateModel,
+    delete: deleteModel,
+  }
+)
+export const autoSaveModels = autoSetModeCallback(models, {
+  backup: backupModels,
+  recovery: recoverModels,
+  save: saveModels,
+})
 
 export { ModelType, ModelTypeDesc } from '@/api'
 export type { Model, Models } from '@/api'

+ 4 - 2
src/store/sys.ts

@@ -48,6 +48,7 @@ export const giveupLeave = () => {
 export const save = async () => {
   await sysBus.emit('save')
   giveupSave()
+  leave()
 }
 
 // 离开
@@ -79,6 +80,7 @@ export const autoSetModeCallback = <T extends object>(current: T, setting: AutoS
     })
   
   const saveCallback = async () => {
+    leaveCallback && sysBus.off('leave', leaveCallback, { last: true })
     isSave = true
     await setting.save()
     setting.backup && setting.backup()
@@ -90,9 +92,9 @@ export const autoSetModeCallback = <T extends object>(current: T, setting: AutoS
     if (!setting.isUpdate || setting.isUpdate(newv, oldv)) {
       isEdit.value || enterEdit()
       isOld.value ||  enterOld()
-      saveCallback && sysBus.on('save', saveCallback, { pre: true })
+      saveCallback && sysBus.on('save', saveCallback, { last: true })
     }
-    leaveCallback && sysBus.on('leave', leaveCallback, { pre: true })
+    leaveCallback && sysBus.on('leave', leaveCallback, { last: true })
   }
 
   return () => {

+ 60 - 40
src/store/tagging.ts

@@ -1,50 +1,70 @@
 import { ref } from 'vue'
-import { getTaggings } from '@/api'
+import { autoSetModeCallback, TemploraryID } from './sys'
+import { 
+  fetchTaggings, 
+  postAddTagging,
+  postDeleteTagging,
+  postUpdateTagging 
+} from '@/api'
+import { 
+  deleteStoreItem, 
+  addStoreItem, 
+  updateStoreItem, 
+  fetchStoreItems,
+  saveStoreItems,
+  recoverStoreItems
+} from '@/utils'
 
-import type { Taggings } from '@/api'
+
+import type { Taggings, Tagging } from '@/api'
 
 export const taggings = ref<Taggings>([])
 
-export const initialTaggings = async () => {
-  // taggings.value = await getTaggings()
-  taggings.value = [
-    {
-      id: '1231',
-      title: 'aaaa',
-      styleId: '1231',
-      desc: '123123',
-      part: '123asd',
-      method: '123123a',
-      principal: 'asdasd',
-      images: [
-        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
-        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png'
-      ],
-      positions: [
-        { x: 1, y: 1, z: 1 }
-      ]
-    },
-
-    {
-      id: '1231a',
-      title: 'aaaa',
-      styleId: '1231',
-      desc: '123123',
-      part: '123asd',
-      method: '123123a',
-      principal: 'asdasd',
-      images: [
-        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
-        'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png'
-      ],
-      positions: [
-        { x: 1, y: 1, z: 1 }
-      ]
-    }
-  ]
-}
+export const createTagging = (tagging: Partial<Tagging> = {}): Tagging => ({
+  id: TemploraryID,
+  title: '',
+  styleId: '',
+  desc: '',
+  part: '',
+  method: '',
+  principal: '',
+  images: [],
+  positions: [],
+  ...tagging
+})
 
 
+let bcTaggings: Taggings = []
+export const getBackupTaggings = () => bcTaggings
+export const backupTaggings = () => {
+  bcTaggings = taggings.value.map(tagging => ({
+    ...tagging,
+    images: [...tagging.images],
+    positions: [...tagging.positions]
+  }))
+}
 
+export const recoverTaggings = recoverStoreItems(taggings, () => bcTaggings)
+export const addTagging = addStoreItem(taggings, postAddTagging)
+export const updateTagging = updateStoreItem(taggings, (newTagging, oldTagging) => {
+  console.log(newTagging, oldTagging)
+  return postUpdateTagging(newTagging)
+})
+export const deleteTagging = deleteStoreItem(taggings, tagging => postDeleteTagging(tagging.id))
+export const initialTaggings = fetchStoreItems(taggings, fetchTaggings, backupTaggings)
+export const saveTaggings = saveStoreItems(
+  taggings, 
+  getBackupTaggings,
+  {
+    add: addTagging,
+    update: updateTagging,
+    delete: deleteTagging,
+  }
+)
+export const autoSaveTaggings = autoSetModeCallback(taggings, {
+  backup: backupTaggings,
+  recovery: recoverTaggings,
+  save: saveTaggings,
+})
 
 export type { Taggings, Tagging } from '@/api'

+ 0 - 1
src/utils/asyncBus.ts

@@ -45,7 +45,6 @@ export const asyncBusFactory = <T extends AsyncEvents>() => {
             for (const key of keys) {
                 if (store[key]) {
                     for (const fn of store[key]) {
-                        console.log(fn)
                         await fn(args)
                     }
                 }

+ 11 - 0
src/utils/basic.ts

@@ -0,0 +1,11 @@
+
+export const objectToString = Object.prototype.toString
+export const toTypeString = (value: unknown): string => objectToString.call(value)
+
+export const toRawType = (value: unknown): string => {
+  // extract "RawType" from strings like "[object RawType]"
+  return toTypeString(value).slice(8, -1)
+}
+
+
+export const isString = <T>(value: T): T extends string ? true : false => (toRawType(value) === 'String') as any

+ 81 - 8
src/utils/diff.ts

@@ -1,22 +1,95 @@
+import { toRawType } from "./basic";
 
-export const diffArrayChange = <T extends Array<any>>(newItems: T, oldItems: T) => {
-  const addedItems = [] as unknown as T
-  const deletedItems = [] as unknown as T
+// 是否修改
+const _deepIsRevise = (
+  raw1: any,
+  raw2: any,
+  readly: Set<[any, any]>
+): boolean => {
+  if (raw1 === raw2) return false;
+
+  const rawType1 = toRawType(raw1);
+  const rawType2 = toRawType(raw2);
+
+  if (rawType1 !== rawType2) {
+    console.log('===', rawType1, rawType2)
+    return true;
+  } else if (
+    rawType1 === "String" ||
+    rawType1 === "Number" ||
+    rawType1 === "Boolean"
+  ) {
+    if (rawType1 === "Number" && isNaN(raw1) && isNaN(raw2)) {
+      return false;
+    } else {
+      return raw1 !== raw2;
+    }
+  }
+
+  const rawsArray = Array.from(readly.values());
+  for (const raws of rawsArray) {
+    if (raws.includes(raw1) && raws.includes(raw2)) {
+      return false;
+    }
+  }
+  readly.add([raw1, raw2]);
+
+  if (rawType1 === "Array") {
+    return (
+      raw1.length !== raw2.length ||
+      raw1.some((item1: any, i: number) =>
+        _deepIsRevise(item1, raw2[i], readly)
+      )
+    );
+  } else if (rawType1 === "Object") {
+    const rawKeys1 = Object.keys(raw1).sort();
+    const rawKeys2 = Object.keys(raw2).sort();
+
+    return (
+      _deepIsRevise(rawKeys1, rawKeys2, readly) ||
+      rawKeys1.some((key) => _deepIsRevise(raw1[key], raw2[key], readly))
+    );
+  } else if (rawType1 === "Map") {
+    const rawKeys1 = Array.from(raw1.keys()).sort();
+    const rawKeys2 = Array.from(raw2.keys()).sort();
+
+    return (
+      _deepIsRevise(rawKeys1, rawKeys2, readly) ||
+      rawKeys1.some((key) =>
+        _deepIsRevise(raw1.get(key), raw2.get(key), readly)
+      )
+    );
+  } else if (rawType1 === "Set") {
+    return deepIsRevise(Array.from(raw1.values()), Array.from(raw2.values()));
+  } else {
+    return raw1 !== raw2;
+  }
+};
+
+export const deepIsRevise = (raw1: any, raw2: any) =>
+  _deepIsRevise(raw1, raw2, new Set());
+
+export const diffArrayChange = <T extends Array<any>>(
+  newItems: T,
+  oldItems: T
+) => {
+  const addedItems = [] as unknown as T;
+  const deletedItems = [] as unknown as T;
 
   for (const item of newItems) {
     if (!oldItems.includes(item)) {
-      addedItems.push(item)
+      addedItems.push(item);
     }
   }
 
   for (const item of oldItems) {
     if (!newItems.includes(item)) {
-      deletedItems.push(item)
+      deletedItems.push(item);
     }
   }
 
   return {
     added: addedItems,
-    deleted: deletedItems
-  }
-}
+    deleted: deletedItems,
+  };
+};

+ 1 - 0
src/utils/index.ts

@@ -37,6 +37,7 @@ export const together = (cbs: (() => void)[]) => {
   cbs.forEach(cb => cb())
 }
 
+export * from './store-help'
 export * from "./stack";
 export * from "./loading";
 export * from "./route";

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

@@ -0,0 +1,121 @@
+import { Ref, toRaw } from 'vue'
+import { deepIsRevise } from './diff'
+
+export const storeSecurityPush = <T extends any>(items: T[], pushItem: T) => {
+  const index = items.indexOf(pushItem)
+  if (!~index) {
+    items.push(pushItem)
+    return true
+  } else {
+    return false
+  }
+} 
+export const storeSecurityDelete = <T extends any>(items: T[], pushItem: T) => {
+  const index = items.indexOf(pushItem)
+  if (~index) {
+    items.splice(index, 1)
+    return true
+  } else {
+    return false
+  }
+} 
+
+export const addStoreItem = <T extends {id: any}>(items: Ref<T[]>, addAction: (item: T) => Promise<T>) => {
+  return async (item: T) => {
+    const newItem = await addAction(item)
+    item.id = newItem.id
+    storeSecurityPush(items.value, item)
+  }
+}
+
+export const updateStoreItem = <T extends {id: any}>(items: Ref<T[]>, updateAction: (item: T, oldItem: T) => Promise<any>) => {
+  return async (item: T, oldItem: T) => {
+    await updateAction(item, oldItem)
+    const storeItem = items.value.find(atom => atom.id === item.id)
+    if (storeItem) {
+      Object.assign(storeItem, item)
+    }
+  }
+}
+
+
+export const deleteStoreItem = <T extends {id: any}>(items: Ref<T[]>, deleteAction: (item: T) => Promise<any>) => {
+  return async (item: T) => {
+    await deleteAction(item)
+    storeSecurityDelete(items.value, item)
+  }
+}
+
+export const fetchStoreItems = <T extends {id: any}>(items: Ref<T[]>, fetchAction: () => Promise<T[]>, callback?: () => void) => {
+  return async () => {
+    const fetchItems = await fetchAction()
+    items.value = fetchItems
+    callback && callback()
+  }
+}
+
+export const saveStoreItems = <T extends {id: any}>(
+  newItems: Ref<T[]>,
+  getOldItem: () => T[],
+  actions: {
+    add: ReturnType<typeof addStoreItem<T>>,
+    update: ReturnType<typeof updateStoreItem<T>>,
+    delete: ReturnType<typeof deleteStoreItem<T>>,
+  }
+) => () => {
+  const oldItems = getOldItem()
+  const {
+    deleted,
+    updated,
+    added
+  } = diffStoreItemsChange(newItems.value, oldItems)
+
+  const promiseAll: Promise<any>[] = []
+  
+  for (const delItem of deleted) {
+    promiseAll.push(actions.delete(delItem))
+  }
+  for (const [newItem, oldItem] of updated) {
+    promiseAll.push(actions.update(newItem, oldItem))
+  }
+  for (const addItem of added) {
+    promiseAll.push(actions.add(addItem))
+  }
+  return Promise.all(promiseAll)
+}
+
+export const diffStoreItemsChange = <T extends Array<{ id: any }>>(newItems: T, oldItems: T) => {
+  const addedItems = [] as unknown as T
+  const deletedItems = [] as unknown as T
+  const updateItems = [] as unknown as [T[number], T[number]][]
+  newItems = toRaw(newItems)
+  for (const newItem of newItems) {
+    const oldItem = oldItems.find(oldItem => newItem.id === oldItem.id)
+    if (!oldItem) {
+      storeSecurityPush(addedItems, newItem)
+    } else if (deepIsRevise(oldItem, newItem)) {
+      storeSecurityPush(updateItems, [newItem, oldItem] as any)
+    }
+  }
+
+  for (const oldItem of oldItems) {
+    const newItem = newItems.find(newItem => newItem.id === oldItem.id)
+    if (!newItem) {
+      storeSecurityPush(deletedItems, oldItem)
+    }
+  }
+
+  return {
+    added: addedItems,
+    deleted: deletedItems,
+    updated: updateItems
+  }
+}
+
+export const recoverStoreItems = <T extends Array<{ id: any }>>(items: Ref<T>, getBackupItems: () => T) => () => {
+  const backupItems = getBackupItems()
+  items.value = backupItems.map(oldItem => {
+    const model = items.value.find(item => item.id === oldItem.id)
+    return model ? Object.assign(model, oldItem) : oldItem
+  }) as T
+}

+ 22 - 6
src/views/guide/edit-paths.vue

@@ -56,19 +56,19 @@
 <script setup lang="ts">
 import { loadPack, togetherCallback } from '@/utils'
 import { sdk, playSceneGuide, pauseSceneGuide, isScenePlayIng } from '@/sdk'
-import { createGuidePath, leave, sysBus, useAutoSetMode } from '@/store'
+import { createGuidePath, TemploraryID, useAutoSetMode, guides, enterOld } from '@/store'
 import { Dialog } from 'bill/index'
 import { useViewStack } from '@/hook'
 import { nextTick, ref, toRaw, watchEffect } from 'vue'
 import { showRightPanoStack, showLeftCtrlPanoStack, showLeftPanoStack, showRightCtrlPanoStack } from '@/env'
 
-import type { GuidePaths, GuidePath } from '@/store'
+import type { Guide, GuidePaths, GuidePath } from '@/store'
 
 type LocalPath = GuidePath & { blob?: Blob }
 type LocalPaths = LocalPath[]
 
-const props = defineProps< { data: GuidePaths }>()
-const paths = ref<LocalPaths>([...props.data])
+const props = defineProps< { data: Guide }>()
+const paths = ref<LocalPaths>([...props.data.paths])
 const current = ref<LocalPath>(paths.value[0])
 
 useViewStack(() => 
@@ -80,12 +80,28 @@ useViewStack(() =>
   ])
 );
 
-useAutoSetMode(paths, { 
+useAutoSetMode(paths, {
   save() {
-    sysBus.on('save', () => setTimeout(leave), { last: true })
+    props.data.paths = paths.value
+    if (props.data.id === TemploraryID) {
+      guides.value.push(props.data)
+    }
   }
 })
 
+setTimeout(() => {
+  paths.value = [{
+          id: '123a',
+          position: {x: 1, y: 1, z: 1},
+          target: {x: 1, y: 1, z: 1},
+          cover: 'https://gw.alicdn.com/tps/TB1W_X6OXXXXXcZXVXXXXXXXXXX-400-400.png',
+          speed: 1,
+          time: 1
+        }]
+}, 1000)
+
+
+
 const addPath = () => {
   loadPack(async () => {
     const dataURL = await sdk.screenshot(260, 160)

+ 6 - 4
src/views/guide/index.vue

@@ -2,9 +2,9 @@
   <RightFillPano>
     <ui-group borderBottom>
       <template #header>
-        <ui-button @click="currentGuide = createGuide()">
+        <ui-button @click="edit(createGuide())">
           <ui-icon type="add" />
-          新增
+          新增 
         </ui-button>
       </template>
     </ui-group>
@@ -20,16 +20,17 @@
   </RightFillPano>
 
   <ui-editor-toolbar :toolbar="!!currentGuide" class="video-toolbar">
-    <EditPaths :data="currentGuide.paths" v-if="currentGuide" />
+    <EditPaths :data="currentGuide" v-if="currentGuide" />
   </ui-editor-toolbar>
 </template>
 
 <script lang="ts" setup>
 import { RightFillPano } from '@/layout'
-import { guides, Guide, createGuide, enterEdit, sysBus } from '@/store'
+import { guides, Guide, createGuide, enterEdit, sysBus, autoSaveGuides } from '@/store'
 import { ref } from 'vue';
 import GuideSign from './sign.vue'
 import EditPaths from './edit-paths.vue'
+import { useViewStack } from '@/hook'
 
 const currentGuide = ref<Guide | null>()
 const leaveEdit = () => currentGuide.value = null
@@ -44,6 +45,7 @@ const deleteGuide = (guide: Guide) => {
   guides.value.splice(index, 1)
 }
 
+useViewStack(autoSaveGuides)
 </script>
 
 <style lang="scss" scoped>

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

@@ -28,7 +28,7 @@
 
 <script lang="ts" setup>
 import { RightPano } from '@/layout'
-import { currentModel } from '@/store'
+import { currentModel, autoSaveModels } from '@/store'
 import Actions from '@/components/actions/index.vue'
 import { getSceneModel } from '@/sdk'
 import { useViewStack } from '@/hook'
@@ -68,5 +68,6 @@ useViewStack(() => {
     pops.forEach(pop => pop())
   }
 })
+useViewStack(autoSaveModels)
 
 </script>

+ 49 - 22
src/views/tagging/index.vue

@@ -2,7 +2,7 @@
   <RightFillPano>
     <ui-group borderBottom>
       <template #header>
-        <ui-button @click="currentTagging = createTagging()">
+        <ui-button @click="editTagging = createTagging()">
           <ui-icon type="add" />
           新增
         </ui-button>
@@ -10,7 +10,11 @@
     </ui-group>
     <ui-group title="标注">
       <template #icon>
-        <ui-icon type="eye-s" />
+        <ui-icon 
+          ctrl
+          :type="custom.showTaggings ? 'eye-s' : 'eye-n'" 
+          @click="custom.showTaggings = !custom.showTaggings" 
+        />
       </template>
       <ui-group-option>
         <ui-input type="text" width="100%" placeholder="搜索">
@@ -23,48 +27,71 @@
         v-for="tagging in taggings" 
         :key="tagging.id" 
         :tagging="tagging" 
-        @edit="currentTagging = tagging"
+        :selected="selectTagging === tagging"
+        @edit="editTagging = tagging"
         @delete="deleteTagging(tagging)"
+        @select="selectTagging = tagging"
       />
     </ui-group>
   </RightFillPano>
 
   <Edit 
-    v-if="currentTagging" 
-    :data="currentTagging" 
-    @quit="currentTagging = null" 
+    v-if="editTagging" 
+    :data="editTagging" 
+    @quit="editTagging = null" 
     @save="saveHandler"
   />
 </template>
 
 <script lang="ts" setup>
 import Edit from './edit.vue'
+import { Message } from 'bill/index'
 import { RightFillPano } from '@/layout'
-import { Tagging, taggings, TemploraryID } from '@/store'
+import { useViewStack } from '@/hook'
+import { taggings, TemploraryID, Tagging, autoSaveTaggings, createTagging } from '@/store'
 import TagingSign from './sign.vue'
-import { ref } from 'vue';
+import { ref, watchEffect } from 'vue';
+import { custom } from '@/env'
+import { sdk } from '@/sdk'
 
 import type { LocalTagging } from './edit.vue'
 
-const createTagging = (): Tagging => ({
-  id: TemploraryID,
-  title: '',
-  styleId: '',
-  desc: '',
-  part: '',
-  method: '',
-  principal: '',
-  images: [],
-  positions: []
-})
-
-const currentTagging = ref<Tagging | null>(null)
+const editTagging = ref<Tagging | null>(null)
 const saveHandler = (tagging: LocalTagging) => {
-  currentTagging.value = null
+  if (!editTagging.value) return;
+  if (editTagging.value.id === TemploraryID) {
+    // taggings.value.push(tagging)
+  } else {
+    Object.assign(editTagging.value, tagging)
+  }
+  editTagging.value = null
 }
 
 const deleteTagging = (tagging: Tagging) => {
   const index = taggings.value.indexOf(tagging)
   taggings.value.splice(index, 1)
 }
+
+
+const selectTagging = ref<Tagging | null>(null)
+watchEffect(() => {
+  if (selectTagging.value) {
+    const handler = (ev: MouseEvent) => {
+      const position = sdk.getPositionByScreen({
+        x: ev.clientX,
+        y: ev.clientY
+      })
+      
+      if (!position) {
+        Message.error('当前位置无法添加')
+      } else {
+        selectTagging.value?.positions.push(position)
+      }
+    }
+    sdk.layout.addEventListener('click', handler, false)
+    return () => sdk.layout.removeEventListener('click', handler, false)
+  }
+})
+
+useViewStack(autoSaveTaggings)
 </script>

+ 6 - 4
src/views/tagging/sign.vue

@@ -1,5 +1,5 @@
 <template>
-  <ui-group-option class="sign-tagging">
+  <ui-group-option class="sign-tagging" :class="{active: selected}" @click="emit('select')">
     <div class="info">
       <img :src="tagging.images[0]">
       <div>
@@ -7,7 +7,7 @@
         <a>放置:{{ tagging.positions.length }}</a>
       </div>
     </div>
-    <div class="actions">
+    <div class="actions" @click.stop>
       <ui-icon type="pin" ctrl />
       <ui-more 
         :options="menus" 
@@ -22,10 +22,12 @@
 import { Tagging } from '@/store'
 
 
-defineProps<{ tagging: Tagging }>()
+defineProps<{ tagging: Tagging, selected?: boolean }>()
+
 const emit = defineEmits<{ 
   (e: 'delete'): void 
-  (e: 'edit'): void 
+  (e: 'edit'): void
+  (e: 'select'): void
 }>()
 
 const menus = [

+ 11 - 0
src/views/tagging/style.scss

@@ -3,7 +3,18 @@
   justify-content: space-between;
   align-items: center;
   padding: 20px 0;
+  margin: 0;
   border-bottom: 1px solid var(--colors-border-color);
+  cursor: pointer;
+  position: relative;
+
+  &.active::after {
+    content: '';
+    position: absolute;
+    pointer-events: none;
+    inset: 0 -20px;
+    background-color: rgba(0, 200, 175, 0.16);
+  }
 
   .info {
     flex: 1;