Browse Source

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

xzw 10 months ago
parent
commit
e82347372b

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

@@ -51,7 +51,8 @@ export const postAddTaggingStyle = async (props: {file: Blob, iconTitle: string}
     url: INSERT_TAGGING_STYLE, 
     data: jsonToForm({
       file: new File([props.file], `${props.iconTitle}.png`),
-      iconTitle: props.iconTitle
+      iconTitle: props.iconTitle,
+      caseId: params.caseId
     })
   })
   return toLocal(data)

+ 60 - 0
src/hook/notice.ts

@@ -0,0 +1,60 @@
+import { params } from "@/env";
+import { ref } from "vue";
+import type {ResData} from '@/api'
+
+console.log('scoke 测试')
+const socketUrl = `wss://test-mix3d.4dkankan.com/fusion/ws/${params.caseId}`;
+
+let websocket: WebSocket | null = null;
+function createWebSocket() {
+  websocket = new WebSocket(socketUrl);
+  websocket.onopen = function() {
+    console.log('WebSocket 连接已打开');
+  };
+
+  websocket.onmessage = function(event) {
+    try {
+      const res = JSON.parse(event.data) as {command: string, content: ResData<any>}
+      switch(res.command) {
+        case 'ping':
+          break;
+
+        case 'notice':
+          break;
+      }
+    } catch {
+      console.log('收到错误消息格式:', event.data);  
+    }
+  };
+
+  websocket.onerror = function(event) {
+    console.error('WebSocket 出错:', event);
+  };
+
+  websocket.onclose = function(event) {
+    console.log('WebSocket 连接已关闭,将在 5 秒后重连');
+    setTimeout(createWebSocket, 5000);
+  };
+}
+
+export const notity = ref({})
+const handler = {
+  ping() {
+
+  },
+  notity() {
+
+  }
+}
+
+// 发送数据到服务器
+function sendData(command: string, message: string) {
+  if (websocket && websocket.readyState === WebSocket.OPEN) {
+    websocket.send(message);
+  } else {
+    console.log('WebSocket 连接未打开,无法发送消息');
+  }
+}
+
+// 初始创建 WebSocket 连接
+createWebSocket();

+ 31 - 12
src/layout/model-list/index.vue

@@ -1,23 +1,39 @@
 <template>
-  <List :title="title" rawKey="id" class="scene-model-list" :data="modelList" :showContent="showContent">
+  <List
+    :title="title"
+    rawKey="id"
+    class="scene-model-list"
+    :data="modelList"
+    :showContent="showContent"
+  >
     <template #action>
       <slot name="action" />
     </template>
     <template #atom="{ item }">
-      <ModelSign :canChange="canChange" :model="item.raw" @delete="modelDelete(item.raw)"
-        @click="(mode: any) => modelChangeSelect(item.raw, mode)" />
+      <ModelSign
+        :canChange="canChange"
+        :model="item.raw"
+        @delete="modelDelete(item.raw)"
+        @click="(mode: any) => modelChangeSelect(item.raw, mode, true)"
+      />
     </template>
   </List>
 
   <Teleport to="#left-pano" v-if="panoModel && currentModel === fuseModel">
     <div class="mode-tab strengthen">
-      <div class="mode-icon-layout" @click="modelChangeSelect(panoModel, 'fuse')"
-        :class="{ active: custom.showMode === 'fuse' }">
-        <ui-icon type="show_3d_n" class="icon" ctrl />
+      <div
+        class="mode-icon-layout"
+        @click="modelChangeSelect(panoModel, 'fuse')"
+        :class="{ active: custom.showMode === 'fuse' }"
+      >
+        <ui-icon type="show_3d_n" class="icon" ctrl tip="三维模型" tipV="top" />
       </div>
-      <div class="mode-icon-layout" @click="modelChangeSelect(panoModel, 'pano')"
-        :class="{ active: custom.showMode === 'pano' }">
-        <ui-icon type="show_roaming_n" class="icon" ctrl />
+      <div
+        class="mode-icon-layout"
+        @click="modelChangeSelect(panoModel, 'pano')"
+        :class="{ active: custom.showMode === 'pano' }"
+      >
+        <ui-icon type="show_roaming_n" class="icon" ctrl tip="全景图" tipV="top" />
       </div>
     </div>
   </Teleport>
@@ -56,12 +72,15 @@ const modelList = computed(() =>
   }))
 );
 
-const modelChangeSelect = (model: FuseModel, mode: "pano" | "fuse") => {
+const modelChangeSelect = (model: FuseModel, mode: "pano" | "fuse", f = false) => {
   if (getFuseModelShowVariable(model).value) {
     if (custom.currentModel === model && mode === custom.showMode) {
-      return;
+      if (!f) return;
+
+      activeModel({ showMode: "fuse", active: undefined });
+    } else {
+      activeModel({ showMode: mode, active: model });
     }
-    activeModel({ showMode: mode, active: model });
   }
 
   if (currentModel.value !== fuseModel) {

+ 36 - 8
src/layout/model-list/sign.vue

@@ -1,14 +1,34 @@
 <template>
-  <div @click="!model.error && $emit('click', 'fuse')" class="sign-layout" :class="{ disabled: model.error }">
+  <div
+    @click="!model.error && $emit('click', 'fuse')"
+    class="sign-layout"
+    :class="{ disabled: model.error }"
+  >
     <div class="model-header">
       <p>{{ model.title }}</p>
       <div class="model-action">
-        <ui-icon ctrl type="show_roaming_n" @click.stop="$emit('click', 'pano')" class="icon"
-          :class="{ active: custom.showMode === 'pano' && active }" v-if="getSceneModel(props.model)?.supportPano()" />
-        <ui-input type="checkbox" v-model="show" @click.stop :class="{
-          disabled: model.error || custom.showMode === 'pano',
-        }" />
-        <ui-icon v-if="custom.modelsChangeStore" type="del" ctrl @click="$emit('delete')" />
+        <ui-icon
+          ctrl
+          type="show_roaming_n"
+          @click.stop="$emit('click', 'pano')"
+          class="icon"
+          :class="{ active: custom.showMode === 'pano' && active }"
+          v-if="supportPano"
+        />
+        <ui-input
+          type="checkbox"
+          v-model="show"
+          @click.stop
+          :class="{
+            disabled: model.error || custom.showMode === 'pano',
+          }"
+        />
+        <ui-icon
+          v-if="custom.modelsChangeStore"
+          type="del"
+          ctrl
+          @click="$emit('delete')"
+        />
       </div>
     </div>
     <div class="model-desc" v-if="active">
@@ -27,7 +47,7 @@ import { custom } from "@/env";
 import { getSceneModel } from "@/sdk";
 
 import type { FuseModel } from "@/store";
-import { computed } from "vue";
+import { computed, ref } from "vue";
 import { currentModel, fuseModel } from "@/model";
 
 type ModelProps = { model: FuseModel; canChange?: boolean };
@@ -36,6 +56,14 @@ const props = defineProps<ModelProps>();
 const active = computed(
   () => custom.currentModel === props.model && currentModel.value === fuseModel
 );
+const sceneModel = getSceneModel(props.model);
+const supportPano = ref(sceneModel?.supportPano());
+if (sceneModel) {
+  sceneModel.bus.on("loadDone", () => {
+    console.error(sceneModel.supportPano());
+    supportPano.value = sceneModel.supportPano();
+  });
+}
 
 type ModelEmits = {
   (e: "changeSelect", selected: boolean): void;

+ 4 - 0
src/main.ts

@@ -14,6 +14,10 @@ app.use(Components)
 app.use(router)
 app.mount('#app')
 
+if (import.meta.env.DEV) {
+  import('@/hook/notice')
+}
+
 appStyleImport[params.app]()
 watchEffect((onCleanup) => {
   if ([RoutesName.show, RoutesName.signModel].includes(currentLayout.value!)) {

+ 6 - 6
src/router/config.ts

@@ -46,15 +46,15 @@ export const routes = [
             name: RoutesName.setting,
             meta: metas.setting,
             component: () => import('@/views/setting/index.vue')
-          }
+          },
+          {
+            path: paths[RoutesName.registration],
+            name: RoutesName.registration,
+            component: () => import('@/views/registration/index.vue')
+          },
         ]
       },
       {
-        path: paths[RoutesName.registration],
-        name: RoutesName.registration,
-        component: () => import('@/views/registration/index.vue')
-      },
-      {
         path: paths[RoutesName.proportion],
         name: RoutesName.proportion,
         component: () => import('@/views/proportion/index.vue')

+ 1 - 1
src/sdk/association/fuseMode.ts

@@ -27,7 +27,7 @@ import { isUnSet, unSet } from "@/utils/unset";
 export const modelRange: ModelAttrRange = {
   opacityRange: { min: 0, max: 100, step: 0.1 },
   bottomRange: { min: -30, max: 70, step: 0.1 },
-  scaleRange: { min: 0, max: 200, step: 0.1 },
+  scaleRange: { min: 0.1, max: 200, step: 0.1 },
 };
 
 export const sceneModelMap = reactive(new Map<FuseModel, SceneModel>());

+ 41 - 38
src/views/measure/edit.vue

@@ -1,42 +1,45 @@
-<template>
-
-</template>
+<template></template>
 
 <script lang="ts" setup>
-import { ref, reactive } from 'vue'
-import { enterEdit, enterOld, sysBus, giveupLeave } from '@/store'
-import { useViewStack } from '@/hook'
-import { togetherCallback } from '@/utils'
-import { showRightCtrlPanoStack, showRightPanoStack } from '@/env'
-import { sdk, associationMessaure } from '@/sdk'
-import { Message } from 'bill/index'
-
-import type { Measure } from '@/store'
-
-const props = defineProps<{ measure: Measure }>()
-const emit = defineEmits<{ 
-  (e: 'close'): void
-  (e: 'submit', measure: Measure): void
-}>()
-
-const measure = reactive(props.measure)
-const modelMeasure = sdk.startMeasure(measure.type)
-associationMessaure(modelMeasure, measure)
-modelMeasure.bus.on('cancel', giveupLeave)
-modelMeasure.bus.on('invalidPoint', Message.error)
-modelMeasure.bus.on('submit', () => {
-  enterOld()
-})
+import { ref, reactive } from "vue";
+import { enterEdit, enterOld, sysBus, giveupLeave } from "@/store";
+import { useViewStack } from "@/hook";
+import { togetherCallback } from "@/utils";
+import { showRightCtrlPanoStack, showRightPanoStack } from "@/env";
+import { sdk, associationMessaure } from "@/sdk";
+import { Message } from "bill/index";
+
+import type { Measure } from "@/store";
+
+const props = defineProps<{ measure: Measure }>();
+const emit = defineEmits<{
+  (e: "close"): void;
+  (e: "complete", measure: Measure): void;
+  (e: "submit", measure: Measure): void;
+}>();
+
+const measure = reactive(props.measure);
+const modelMeasure = sdk.startMeasure(measure.type);
+associationMessaure(modelMeasure, measure);
+modelMeasure.bus.on("cancel", giveupLeave);
+modelMeasure.bus.on("invalidPoint", Message.error);
+let success = false;
+modelMeasure.bus.on("submit", () => {
+  enterOld();
+  success = true;
+  emit("complete", measure);
+});
 
 enterEdit(() => {
-  emit('close')
-  modelMeasure.destroy!()
-})
-sysBus.on('save', () => emit('submit', measure), { pre: true })
-
-
-useViewStack(() => togetherCallback([
-  showRightCtrlPanoStack.push(ref(false)),
-  showRightPanoStack.push(ref(false)),
-]))
-</script>
+  emit("close");
+  modelMeasure.destroy!();
+});
+sysBus.on("save", () => success && emit("submit", measure), { pre: true });
+
+useViewStack(() =>
+  togetherCallback([
+    showRightCtrlPanoStack.push(ref(false)),
+    showRightPanoStack.push(ref(false)),
+  ])
+);
+</script>

+ 68 - 42
src/views/measure/index.vue

@@ -9,10 +9,10 @@
     </template>
     <ui-group title="测量列表" class="measure-list">
       <template #icon>
-        <ui-icon 
+        <ui-icon
           ctrl
-          :type="custom.showMeasures ? 'eye-s' : 'eye-n'" 
-          @click="custom.showMeasures = !custom.showMeasures" 
+          :type="custom.showMeasures ? 'eye-s' : 'eye-n'"
+          @click="custom.showMeasures = !custom.showMeasures"
         />
       </template>
       <!-- <ui-group-option>
@@ -22,73 +22,99 @@
           </template>
         </ui-input>
       </ui-group-option> -->
-      <MeasureSign 
-        v-for="measure in filterMeasures" 
-        :key="measure.id" 
-        :measure="measure" 
+      <MeasureSign
+        v-for="measure in filterMeasures"
+        :key="measure.id"
+        :measure="measure"
         @delete="deleteMeasure(measure)"
-        @updateTitle="title => measure.title = title"
+        @updateTitle="(title) => (measure.title = title)"
       />
     </ui-group>
   </RightFillPano>
 
-  <EditMeasure 
-    v-if="editMeasure" 
-    :measure="editMeasure" 
-    @close="editMeasure = null"
-    @submit="measure => measures.push(measure)"
+  <EditMeasure
+    v-for="editMeasure in editMeasures"
+    :measure="editMeasure"
+    @close="clonseEditMeasure(editMeasure)"
+    @complete="measuresSuccess"
+    @submit="measuresSubmit"
   />
 </template>
 
 <script lang="ts" setup>
-import MeasureSign from './sign.vue'
-import Actions from '@/components/actions/index.vue'
-import EditMeasure from './edit.vue'
-import { RightFillPano } from '@/layout'
-import { computed, ref } from 'vue';
-import { custom } from '@/env'
-import { measures, MeasureTypeMeta, MeasureType, createMeasure, autoSaveMeasures } from '@/store'
-import { useViewStack } from '@/hook'
+import MeasureSign from "./sign.vue";
+import Actions from "@/components/actions/index.vue";
+import EditMeasure from "./edit.vue";
+import { RightFillPano } from "@/layout";
+import { computed, ref, watchEffect } from "vue";
+import { custom } from "@/env";
+import {
+  measures,
+  MeasureTypeMeta,
+  MeasureType,
+  createMeasure,
+  autoSaveMeasures,
+} from "@/store";
+import { useViewStack } from "@/hook";
 
-import type { Measure } from '@/store'
-import type { ActionsItem } from '@/components/actions/index.vue'
+import type { Measure } from "@/store";
+import type { ActionsItem } from "@/components/actions/index.vue";
+import { asyncTimeout } from "@/utils";
 
-const keyword = ref('')
-const filterMeasures = computed(() => measures.value.filter(measure => measure.desc.includes(keyword.value)))
-const editMeasure = ref<Measure | null>(null)
+const keyword = ref("");
+const filterMeasures = computed(() =>
+  measures.value.filter((measure) => measure.desc.includes(keyword.value))
+);
+
+const editMeasures = ref<Measure[]>([]);
 const enterCreateMeasure = (type: MeasureType) => {
-  editMeasure.value = createMeasure({ type })
-}
+  editMeasures.value.push(createMeasure({ type }));
+};
+const measuresSuccess = (measure: Measure) => {
+  enterCreateMeasure(measure.type);
+};
+const measuresSubmit = (measure: Measure) => {
+  measures.value.push(measure);
+};
+const clonseEditMeasure = async (measure: Measure) => {
+  const ndx = editMeasures.value.indexOf(measure);
+  if (~ndx) {
+    await asyncTimeout(100);
+    editMeasures.value.splice(editMeasures.value.indexOf(measure), 1);
+  }
+};
 
+watchEffect(() => {
+  console.log(filterMeasures.value, editMeasures.value.length);
+});
 const options: ActionsItem[] = [
   {
     icon: MeasureTypeMeta[MeasureType.free].icon,
     text: MeasureTypeMeta[MeasureType.free].desc,
-    action: enterCreateMeasure.bind(null, MeasureType.free)
+    action: enterCreateMeasure.bind(null, MeasureType.free),
   },
   {
     icon: MeasureTypeMeta[MeasureType.vertical].icon,
     text: MeasureTypeMeta[MeasureType.vertical].desc,
-    action: enterCreateMeasure.bind(null, MeasureType.vertical)
+    action: enterCreateMeasure.bind(null, MeasureType.vertical),
   },
   {
     icon: MeasureTypeMeta[MeasureType.area].icon,
     text: MeasureTypeMeta[MeasureType.area].desc,
-    action: enterCreateMeasure.bind(null, MeasureType.area)
-  }
-]
-
+    action: enterCreateMeasure.bind(null, MeasureType.area),
+  },
+];
 
 const deleteMeasure = (measure: Measure) => {
-  const index = measures.value.indexOf(measure)
-  measures.value.splice(index, 1)
-}
+  const index = measures.value.indexOf(measure);
+  measures.value.splice(index, 1);
+};
 
-useViewStack(autoSaveMeasures)
+useViewStack(autoSaveMeasures);
 </script>
 
 <style scoped>
-  .measure-list {
-    padding-bottom: 30px;
-  }
-</style>
+.measure-list {
+  padding-bottom: 30px;
+}
+</style>

+ 64 - 59
src/views/measure/sign.vue

@@ -1,6 +1,6 @@
 <template>
-  <ui-group-option 
-    class="sign-measure" 
+  <ui-group-option
+    class="sign-measure"
     :class="{ active: measure.selected }"
     @mouseenter="measure.selected = true"
     @mouseleave="measure.selected = false"
@@ -8,83 +8,88 @@
     <div class="info">
       <ui-icon :type="MeasureTypeMeta[measure.type].icon" class="type" />
       <div v-show="!isEditTitle">
-        <p>{{ measure.title || MeasureTypeMeta[measure.type].unitDesc }}</p>
+        <p @click.stop="edit && (isEditTitle = true)">
+          {{ measure.title || MeasureTypeMeta[measure.type].unitDesc }}
+        </p>
         <span>{{ desc }} {{ MeasureTypeMeta[measure.type].unit }}</span>
       </div>
-      <ui-input 
+      <ui-input
         class="view-title-input"
-        type="text" 
-        :modelValue="measure.title" 
+        type="text"
+        :modelValue="measure.title"
         :maxlength="15"
         @update:modelValue="(title: string) => $emit('updateTitle', title.trim())"
-        v-show="isEditTitle" 
-        ref="inputRef" 
-        height="28px" 
+        v-show="isEditTitle"
+        ref="inputRef"
+        height="28px"
       />
     </div>
     <div class="actions" @click.stop>
       <!-- <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-more 
+      <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]()" 
+        :options="menus"
+        style="margin-left: 20px"
+        @click="(action: keyof typeof actions) => actions[action]()"
       />
     </div>
   </ui-group-option>
 </template>
 
 <script setup lang="ts">
-import { MeasureTypeMeta, getMeasureIsShow } from '@/store'
-import { getSceneMeasure, getSceneMeasureDesc } from '@/sdk'
-import { useFocus } from 'bill/hook/useFocus'
-
-import type { Measure } from '@/store'
-import { computed, ref, watch, watchEffect } from 'vue';
-import { Message } from 'bill/index';
-import { custom } from '@/env';
-
-const props = withDefaults(
-  defineProps<{ measure: Measure, edit?: boolean }>(),
-  { edit: true }
-)
-const emit = defineEmits<{ 
-  (e: 'delete'): void,
-  (e: 'updateTitle', title: string): void,
-}>()
-
-const inputRef = ref()
-const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root))
+import { MeasureTypeMeta, getMeasureIsShow } from "@/store";
+import { getSceneMeasure, getSceneMeasureDesc } from "@/sdk";
+import { useFocus } from "bill/hook/useFocus";
+
+import type { Measure } from "@/store";
+import { computed, ref, watch, watchEffect } from "vue";
+import { Message } from "bill/index";
+import { custom } from "@/env";
+
+const props = withDefaults(defineProps<{ measure: Measure; edit?: boolean }>(), {
+  edit: true,
+});
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "updateTitle", title: string): void;
+}>();
+
+const inputRef = ref();
+const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root));
 const menus = [
-  { label: '重命名', value: 'rename' },
-  { label: '删除', value: 'delete' },
-]
+  { label: "重命名", value: "rename" },
+  { label: "删除", value: "delete" },
+];
 const actions = {
-  delete: () => emit('delete'),
-  rename: () => isEditTitle.value = true
-}
+  delete: () => emit("delete"),
+  rename: () => (isEditTitle.value = true),
+};
 
 watchEffect(() => {
   if (!isEditTitle.value && !props.measure.title.length) {
-    isEditTitle.value = true
-    Message.warning('测量名称不可为空')
+    isEditTitle.value = true;
+    Message.warning("测量名称不可为空");
   }
-})
+});
 
 const fly = () => {
-  getSceneMeasure(props.measure)?.fly()
-}
-const desc = ref('-')
+  getSceneMeasure(props.measure)?.fly();
+};
+const desc = ref("-");
 watch(
-  () => [props.measure, custom.showMeasures, custom.showModelsMap], 
+  () => [props.measure, custom.showMeasures, custom.showModelsMap],
   () => {
-    const smeasure = getSceneMeasure(props.measure)
-    desc.value = smeasure ? getSceneMeasureDesc(smeasure, props.measure) : '-'
-  }, 
-  { deep: true, flush: 'post', immediate: true }
-)
-
+    const smeasure = getSceneMeasure(props.measure);
+    desc.value = smeasure ? getSceneMeasureDesc(smeasure, props.measure) : "-";
+  },
+  { deep: true, flush: "post", immediate: true }
+);
 </script>
 
 <style lang="scss" scoped>
@@ -98,7 +103,7 @@ watch(
   position: relative;
 
   &.active::after {
-    content: '';
+    content: "";
     position: absolute;
     pointer-events: none;
     inset: 0 -20px;
@@ -118,7 +123,7 @@ watch(
       border-radius: 4px;
       overflow: hidden;
       display: flex;
-      background: rgba(0,0,0,0.5);
+      background: rgba(0, 0, 0, 0.5);
       font-size: 18px;
       align-items: center;
       justify-content: center;
@@ -133,18 +138,18 @@ watch(
       }
 
       span {
-        color: rgba(255,255,255,0.6);;
+        color: rgba(255, 255, 255, 0.6);
         font-size: 12px;
       }
     }
   }
-  
+
   .actions {
     flex: none;
     > * {
       margin-left: 22px;
     }
-  }  
+  }
 }
 </style>
 
@@ -152,4 +157,4 @@ watch(
 .view-title-input.ui-input .text.suffix input {
   padding-right: 50px;
 }
-</style>
+</style>

+ 87 - 76
src/views/registration/index.vue

@@ -1,31 +1,32 @@
 <template>
   <template v-if="model && sceneModel">
-    <ControlPanl 
-      :group="[{ items: options }]" 
-      v-model="selectOptions" 
+    <ControlPanl
+      :group="[{ items: options }]"
+      v-model="selectOptions"
       ref="selectExpose"
     />
     <ui-floating
-      v-if="selectOptions.some(({key}) => key === 'opacity')"
+      v-if="selectOptions.some(({ key }) => key === 'opacity')"
       :refer="opacityOptionEl"
       class="opacity-range"
       isTransform
       dire="right-center"
     >
-
-      <div class="right-range floating-range strengthen ">
+      <div class="right-range floating-range strengthen">
         <div class="range-content">
           <div class="range-layout">
-            <ui-input 
-              type="range" 
+            <ui-input
+              type="range"
               v-model="model.opacity"
-              v-bind="modelRange.opacityRange" 
+              v-bind="modelRange.opacityRange"
               :moveCallback="changeRange"
-              :ctrl="false" 
+              :ctrl="false"
               :input="false"
               width="100%"
             />
-            <span class="num" :style="{left: `${model.opacity}%`}">{{parseInt(model.opacity.toString())}}%</span>
+            <span class="num" :style="{ left: `${model.opacity}%` }"
+              >{{ parseInt(model.opacity.toString()) }}%</span
+            >
           </div>
         </div>
       </div>
@@ -66,90 +67,100 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, watch, watchEffect } from 'vue'
-import { ControlPanl } from '@/components/control-panl/'
-import { modelRange, getSceneModel, sdk } from '@/sdk'
-import { diffArrayChange } from '@/utils'
-import { useViewStack } from '@/hook'
-import { autoSaveFuseModels, getFuseModel, leave } from '@/store'
-import { router, RoutesName } from '@/router'
-import { currentModelStack, custom, showMeasuresStack } from '@/env'
+import { ref, computed, watch, watchEffect } from "vue";
+import { ControlPanl } from "@/components/control-panl/";
+import { modelRange, getSceneModel, sdk } from "@/sdk";
+import { diffArrayChange } from "@/utils";
+import { useViewStack } from "@/hook";
+import { autoSaveFuseModels, getFuseModel, leave } from "@/store";
+import { router, RoutesName } from "@/router";
+import { currentModelStack, custom, showMeasuresStack } from "@/env";
 
-import type { ControlExpose } from '@/components/control-panl'
+import type { ControlExpose } from "@/components/control-panl";
 
-const isCurrent = computed(() => router.currentRoute.value.name === RoutesName.registration)
+const isCurrent = computed(
+  () => router.currentRoute.value.name === RoutesName.registration
+);
 
 const model = computed(() => {
-  const modelId = router.currentRoute.value.params.id as string
+  const modelId = router.currentRoute.value.params.id as string;
   if (isCurrent.value && modelId) {
-    return getFuseModel(modelId)
+    return getFuseModel(modelId);
   }
-})
+});
 
-const sceneModel = computed(() => model.value && getSceneModel(model.value))
+const sceneModel = computed(() => model.value && getSceneModel(model.value));
 const options = [
-  { desc: '移动', icon: 'move', key: 'move' },
-  { desc: '旋转', icon: 'flip', key: 'rotate' },
-  { desc: '透明度', icon: 'transparency', key: 'opacity' },
-]
-const selectOptions = ref<typeof options>([])
-const selectExpose = ref<ControlExpose>()
-const opacityOptionEl = computed(
-  () => selectExpose.value?.dom?.querySelector('div[data-key="opacity"]')
-)
-
-const changeRange = (sp: ScreenLocalPos, cp: ScreenLocalPos, info: { start: number, locusWidth: number }) => 
-  info.start + ((sp.y - cp.y) / info.locusWidth)
-
-watch(selectOptions, (nOptions, oOptions = []) => {
-  const { added, deleted } = diffArrayChange(nOptions, oOptions)
-  const setKeys = ['move', 'rotate']
-  const addOptions = added.filter(option => setKeys.includes(option.key))
-  const delOptions = deleted.filter(option => setKeys.includes(option.key))
-
-  if (sceneModel.value) {
-    if (!addOptions.length && delOptions.length) {
-        sceneModel.value.leaveTransform()
-    } else if (addOptions.length) {
-      if (addOptions[0].key === 'move') {
-        sceneModel.value.enterMoveMode()
-      } else {
-        sceneModel.value.enterRotateMode()
+  { desc: "移动", icon: "move", key: "move" },
+  { desc: "旋转", icon: "flip", key: "rotate" },
+  { desc: "透明度", icon: "transparency", key: "opacity" },
+];
+const selectOptions = ref<typeof options>([]);
+const selectExpose = ref<ControlExpose>();
+const opacityOptionEl = computed(() =>
+  selectExpose.value?.dom?.querySelector('div[data-key="opacity"]')
+);
+
+const changeRange = (
+  sp: ScreenLocalPos,
+  cp: ScreenLocalPos,
+  info: { start: number; locusWidth: number }
+) => info.start + (sp.y - cp.y) / info.locusWidth;
+
+watch(
+  selectOptions,
+  (nOptions, oOptions = []) => {
+    const { added, deleted } = diffArrayChange(nOptions, oOptions);
+    const setKeys = ["move", "rotate"];
+    const addOptions = added.filter((option) => setKeys.includes(option.key));
+    const delOptions = deleted.filter((option) => setKeys.includes(option.key));
+
+    if (sceneModel.value) {
+      if (!addOptions.length && delOptions.length) {
+        sceneModel.value.leaveTransform();
+      } else if (addOptions.length) {
+        if (addOptions[0].key === "move") {
+          sceneModel.value.enterMoveMode();
+        } else {
+          sceneModel.value.enterRotateMode();
+        }
       }
     }
-  }
-}, { immediate: true })
+  },
+  { immediate: true }
+);
 
 watchEffect((onCleanup) => {
-  const smodel = sceneModel.value
+  const smodel = sceneModel.value;
   if (smodel) {
-    smodel.enterAlignment()
-    const pop = currentModelStack.push(model as any)
+    smodel.enterAlignment();
+    const pop = currentModelStack.push(model as any);
+
+    selectOptions.value = [options[0]];
 
     onCleanup(() => {
-      smodel.leaveTransform()
-      smodel.leaveAlignment()
-      pop()
-    })
+      smodel.leaveTransform();
+      smodel.leaveAlignment();
+      pop();
+    });
   } else if (isCurrent.value) {
-    leave()
+    leave();
   }
-})
+});
 
 useViewStack(() => {
   showMeasuresStack.push(ref(false));
-  sdk.showGrid()
+  sdk.showGrid();
 
   return () => {
-    showMeasuresStack.pop()
+    showMeasuresStack.pop();
     if (selectOptions.value.length) {
-      selectOptions.value = []
+      selectOptions.value = [];
     }
-    sdk.hideGrid()
-  }
-})
-useViewStack(autoSaveFuseModels)
-
+    sdk.hideGrid();
+  };
+});
+useViewStack(autoSaveFuseModels);
 </script>
 
 <style lang="scss" scoped>
@@ -158,8 +169,8 @@ useViewStack(autoSaveFuseModels)
   transform: translateY(-50%);
   width: 162px;
   height: 32px;
-  background: rgba(27,27,28,0.8);
-  box-shadow: inset 0px 0px 0px 2px rgba(255,255,255,0.1);
+  background: rgba(27, 27, 28, 0.8);
+  box-shadow: inset 0px 0px 0px 2px rgba(255, 255, 255, 0.1);
   border-radius: 16px;
   padding: 0 10px;
   .range-content {
@@ -213,7 +224,8 @@ useViewStack(autoSaveFuseModels)
   }
 }
 
-.tip-left,.tip-right {
+.tip-left,
+.tip-right {
   top: calc(var(--editor-head-height) + var(--header-top) + 11px);
   z-index: 2;
 }
@@ -226,7 +238,6 @@ useViewStack(autoSaveFuseModels)
   left: 75%;
   transform: translateX(-50%);
 }
-
 </style>
 
 <style>
@@ -236,4 +247,4 @@ useViewStack(autoSaveFuseModels)
 .opacity-range {
   margin-left: 70px;
 }
-</style>
+</style>

+ 10 - 7
src/views/setting/index.vue

@@ -12,9 +12,13 @@
     <ui-group title="设置天空">
       <ui-group-option>
         <div class="back-layout">
-          <div v-for="back in backs" :key="back.value" class="back-item"
+          <div
+            v-for="back in backs"
+            :key="back.value"
+            class="back-item"
             :class="{ [back.type]: true, active: setting!.back === back.value }"
-            @click="setting!.back !== back.value && changeBack(back.value)">
+            @click="setting!.back !== back.value && changeBack(back.value)"
+          >
             <img :src="back.image" v-if="['img', 'map'].includes(back.type)" />
             <i class="iconfont" :class="back.image" v-else-if="back.type === 'icon'" />
             <span :style="{ background: back.image }" v-else></span>
@@ -107,7 +111,7 @@ const enterSetPic = () => {
   });
 };
 
-const initBack = setting.value!.back;
+let initBack = setting.value!.back;
 let isFirst = true;
 const changeBack = (back: string) => {
   setting.value!.back = back;
@@ -122,6 +126,7 @@ const changeBack = (back: string) => {
       isFirst = true;
     });
     enterOld(async () => {
+      initBack = setting.value!.back;
       isSave = true;
       await loadPack(updataSetting());
     });
@@ -164,8 +169,7 @@ const changeBack = (back: string) => {
 }
 
 .back-item {
-
-  >span,
+  > span,
   .iconfont,
   img {
     display: block;
@@ -189,8 +193,7 @@ const changeBack = (back: string) => {
   }
 
   &.active {
-
-    >span,
+    > span,
     .iconfont,
     img {
       outline-color: #00c8af;

+ 5 - 4
src/views/tagging/edit.vue

@@ -65,7 +65,6 @@
         type="file"
         width="100%"
         height="225px"
-        require
         preview
         placeholder="上传图片"
         othPlaceholder="支持JPG、PNG图片格式,单张不超过5MB,最多支持上传9张。"
@@ -130,9 +129,11 @@ watchEffect(() => {
 const submitHandler = () => {
   if (!tagging.value.title.trim()) {
     Message.error("标签标题必须填写!");
-  } else if (!tagging.value.images.length) {
-    Message.error("至少上传一张图片!");
-  } else {
+  }
+  //  else if (!tagging.value.images.length) {
+  //   Message.error("至少上传一张图片!");
+  // }
+  else {
     emit("save", tagging.value);
   }
 };

+ 65 - 65
src/views/tagging/styles.vue

@@ -2,11 +2,11 @@
   <div class="hot-styles">
     <div class="add item" v-if="!props.all && styles.length < maxLength">
       <span class="fun-ctrl">
-        <ui-input 
-          class="input" 
-          preview 
-          accept=".jpg, .jpeg, .png" 
-          @update:modelValue="iconUpload" 
+        <ui-input
+          class="input"
+          preview
+          accept=".jpg, .jpeg, .png"
+          @update:modelValue="iconUpload"
           type="file"
         >
           <template v-slot:replace>
@@ -15,41 +15,36 @@
         </ui-input>
       </span>
     </div>
-    <div 
-      v-for="hotStyle in styleAll" 
-      class="item" 
+    <div
+      v-for="hotStyle in styleAll"
+      class="item"
       :class="{ active: active === hotStyle }"
       @click="clickHandler(hotStyle)"
     >
       <span>
         <img :src="getFileUrl(hotStyle.icon)" />
-        <ui-icon 
+        <ui-icon
           v-if="!hotStyle.default"
-          class="delete" 
-          type="close" 
-          @click.stop="emit('delete', hotStyle)" 
+          class="delete"
+          type="close"
+          @click.stop="emit('delete', hotStyle)"
         />
       </span>
     </div>
-    <div 
+    <div
       v-if="!props.all && props.styles.length > maxShowLen"
-      class="add item style-more" 
+      class="add item style-more"
       @click="showAll = !showAll"
     >
       <span class="fun-ctrl">
         <ui-icon :type="showAll ? 'pull-up' : 'pull-down'" class="icon" />
-        <ui-bubble 
-          class="more-content" 
-          :show="showAll" 
-          @click.stop 
-          type="bottom">
-          
-          <styles 
-            :styles="styles.filter(style => !styleAll.includes(style))" 
-            :active="active" 
+        <ui-bubble class="more-content" :show="showAll" @click.stop type="bottom">
+          <styles
+            :styles="styles.filter((style) => !styleAll.includes(style))"
+            :active="active"
             all
-            @quitMore="showAll = false" 
-            @uploadStyles="(style: TaggingStyle) => emit('uploadStyle', style)" 
+            @quitMore="showAll = false"
+            @uploadStyles="(style: TaggingStyle) => emit('uploadStyle', style)"
             @change="clickHandler"
             @delete="(style: TaggingStyle) => emit('delete', style)"
           />
@@ -64,57 +59,64 @@
 </template>
 
 <script setup lang="ts">
-import { TaggingStyle, TaggingStyles } from '@/store'
-import { createTaggingStyle } from '@/store'
-import { ref, computed, defineEmits } from 'vue'
-import { Cropper } from 'bill/index'
-import { getFileUrl } from '@/utils'
+import { TaggingStyle, TaggingStyles } from "@/store";
+import { createTaggingStyle } from "@/store";
+import { ref, computed, defineEmits } from "vue";
+import { Cropper } from "bill/index";
+import { getFileUrl } from "@/utils";
 
 const props = defineProps<{
-  styles: TaggingStyles
-  active: TaggingStyle
-  all?: boolean
-}>()
+  styles: TaggingStyles;
+  active: TaggingStyle;
+  all?: boolean;
+}>();
 
-const maxLength = 20
-const maxShowLen = computed(() => props.styles.length < maxLength ? 5 : 6)
+const maxLength = 19;
+const maxShowLen = computed(() => (props.styles.length < maxLength ? 5 : 6));
+// const maxShowLen = computed(() => maxLength);
 
 const emit = defineEmits<{
-  (e: 'change', style: TaggingStyle): void
-  (e: 'delete', style: TaggingStyle): void
-  (e: 'uploadStyle', styles: TaggingStyle): void
-  (e: 'quitMore'): void
-}>()
+  (e: "change", style: TaggingStyle): void;
+  (e: "delete", style: TaggingStyle): void;
+  (e: "uploadStyle", styles: TaggingStyle): void;
+  (e: "quitMore"): void;
+}>();
 
-const showAll = ref(false)
+const showAll = ref(false);
 const styleAll = computed(() => {
   if (props.all) {
-    return props.styles
+    return props.styles;
   } else {
-    const styles = props.styles.slice(0, props.styles.length > maxShowLen.value ? maxShowLen.value : maxShowLen.value + 1)
+    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
+      styles[styles.length - 1] = props.active;
     }
-    return styles
+    return styles;
   }
-})
+});
 
-const iconUpload = async ({ file, preview }: { file: File, preview: string }) => {
-  const data = await Cropper.open(preview)
+const iconUpload = async ({ file, preview }: { file: File; preview: string }) => {
+  const data = await Cropper.open(preview);
   if (data) {
-    emit('uploadStyle', createTaggingStyle({ 
-      name: file.name,
-      icon: { url: data[1], blob: data[0] }
-    }))
+    emit(
+      "uploadStyle",
+      createTaggingStyle({
+        name: file.name,
+        icon: { url: data[1], blob: data[0] },
+      })
+    );
   }
-}
+};
 
 const clickHandler = (hotStyle: TaggingStyle) => {
   if (!props.all) {
-    showAll.value = false
+    showAll.value = false;
   }
-  emit('change', hotStyle)
-}
+  emit("change", hotStyle);
+};
 </script>
 
 <style lang="scss" scoped>
@@ -130,11 +132,11 @@ const clickHandler = (hotStyle: TaggingStyle) => {
 
   .item {
     --un-active-color: rgba(var(--colors-primary-base-fill), 0);
-    --active-transition: .3s ease;
+    --active-transition: 0.3s ease;
     cursor: pointer;
 
     &.disable {
-      opacity: .3;
+      opacity: 0.3;
       pointer-events: none;
       cursor: inherit;
     }
@@ -178,8 +180,7 @@ const clickHandler = (hotStyle: TaggingStyle) => {
         justify-content: center;
         color: #fff;
         opacity: 0;
-        transition: opacity .3s ease;
-
+        transition: opacity 0.3s ease;
       }
     }
 
@@ -225,7 +226,7 @@ const clickHandler = (hotStyle: TaggingStyle) => {
       border: none;
 
       &::before {
-        content: '';
+        content: "";
         position: absolute;
         left: 50%;
         top: 50%;
@@ -234,11 +235,11 @@ const clickHandler = (hotStyle: TaggingStyle) => {
         height: var(--icon-size);
         border-radius: 2px;
         border: 1px solid var(--colors-border-color);
-        transition: border-color .3s ease;
+        transition: border-color 0.3s ease;
       }
 
       &:hover::before {
-        border-color: rgba(255,255,255,1);
+        border-color: rgba(255, 255, 255, 1);
       }
       &:active::before {
         border-color: var(--colors-primary-base) !important;
@@ -264,5 +265,4 @@ const clickHandler = (hotStyle: TaggingStyle) => {
     }
   }
 }
-
 </style>

+ 5 - 0
vite.config.ts

@@ -6,6 +6,11 @@ import mkcert from 'vite-plugin-mkcert'
 import { resolve } from 'path'
 
 const proxy = {
+  '/fusion/ws': {
+    target: 'wss://test-mix3d.4dkankan.com/',
+    ws: true,
+    rewriteWsOrigin: true,
+  },
   '/local': {
     target: 'http://192.168.0.38:8808',
     changeOrigin: true,