Browse Source

feat: 制作新功能

bill 5 months ago
parent
commit
8209513811

+ 164 - 3
src/components/bill-ui/components/icon/iconfont/demo_index.html

@@ -55,6 +55,48 @@
           <ul class="icon_lists dib-box">
           
             <li class="dib">
+              <span class="icon iconfont">&#xe79b;</span>
+                <div class="name">view</div>
+                <div class="code-name">&amp;#xe79b;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe799;</span>
+                <div class="name">ratio</div>
+                <div class="code-name">&amp;#xe799;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe79a;</span>
+                <div class="name">1b1</div>
+                <div class="code-name">&amp;#xe79a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe65a;</span>
+                <div class="name">reset</div>
+                <div class="code-name">&amp;#xe65a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe798;</span>
+                <div class="name">menu</div>
+                <div class="code-name">&amp;#xe798;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe797;</span>
+                <div class="name">keys_a</div>
+                <div class="code-name">&amp;#xe797;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe796;</span>
+                <div class="name">add_a</div>
+                <div class="code-name">&amp;#xe796;</div>
+              </li>
+          
+            <li class="dib">
               <span class="icon iconfont">&#xe78a;</span>
                 <div class="name">rectification</div>
                 <div class="code-name">&amp;#xe78a;</div>
@@ -684,9 +726,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1741331539860') format('woff2'),
-       url('iconfont.woff?t=1741331539860') format('woff'),
-       url('iconfont.ttf?t=1741331539860') format('truetype');
+  src: url('iconfont.woff2?t=1741831605477') format('woff2'),
+       url('iconfont.woff?t=1741831605477') format('woff'),
+       url('iconfont.ttf?t=1741831605477') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -713,6 +755,69 @@
         <ul class="icon_lists dib-box">
           
           <li class="dib">
+            <span class="icon iconfont icon-view"></span>
+            <div class="name">
+              view
+            </div>
+            <div class="code-name">.icon-view
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-ratio"></span>
+            <div class="name">
+              ratio
+            </div>
+            <div class="code-name">.icon-ratio
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-a-1b1"></span>
+            <div class="name">
+              1b1
+            </div>
+            <div class="code-name">.icon-a-1b1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-reset"></span>
+            <div class="name">
+              reset
+            </div>
+            <div class="code-name">.icon-reset
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-menu"></span>
+            <div class="name">
+              menu
+            </div>
+            <div class="code-name">.icon-menu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-keys_a"></span>
+            <div class="name">
+              keys_a
+            </div>
+            <div class="code-name">.icon-keys_a
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-add_a"></span>
+            <div class="name">
+              add_a
+            </div>
+            <div class="code-name">.icon-add_a
+            </div>
+          </li>
+          
+          <li class="dib">
             <span class="icon iconfont icon-rectification"></span>
             <div class="name">
               rectification
@@ -1659,6 +1764,62 @@
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-view"></use>
+                </svg>
+                <div class="name">view</div>
+                <div class="code-name">#icon-view</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-ratio"></use>
+                </svg>
+                <div class="name">ratio</div>
+                <div class="code-name">#icon-ratio</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-a-1b1"></use>
+                </svg>
+                <div class="name">1b1</div>
+                <div class="code-name">#icon-a-1b1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-reset"></use>
+                </svg>
+                <div class="name">reset</div>
+                <div class="code-name">#icon-reset</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-menu"></use>
+                </svg>
+                <div class="name">menu</div>
+                <div class="code-name">#icon-menu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-keys_a"></use>
+                </svg>
+                <div class="name">keys_a</div>
+                <div class="code-name">#icon-keys_a</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-add_a"></use>
+                </svg>
+                <div class="name">add_a</div>
+                <div class="code-name">#icon-add_a</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-rectification"></use>
                 </svg>
                 <div class="name">rectification</div>

+ 31 - 3
src/components/bill-ui/components/icon/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 4647199 */
-  src: url('iconfont.woff2?t=1741331539860') format('woff2'),
-       url('iconfont.woff?t=1741331539860') format('woff'),
-       url('iconfont.ttf?t=1741331539860') format('truetype');
+  src: url('iconfont.woff2?t=1741831605477') format('woff2'),
+       url('iconfont.woff?t=1741831605477') format('woff'),
+       url('iconfont.ttf?t=1741831605477') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,34 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-view:before {
+  content: "\e79b";
+}
+
+.icon-ratio:before {
+  content: "\e799";
+}
+
+.icon-a-1b1:before {
+  content: "\e79a";
+}
+
+.icon-reset:before {
+  content: "\e65a";
+}
+
+.icon-menu:before {
+  content: "\e798";
+}
+
+.icon-keys_a:before {
+  content: "\e797";
+}
+
+.icon-add_a:before {
+  content: "\e796";
+}
+
 .icon-rectification:before {
   content: "\e78a";
 }

File diff suppressed because it is too large
+ 1 - 1
src/components/bill-ui/components/icon/iconfont/iconfont.js


+ 49 - 0
src/components/bill-ui/components/icon/iconfont/iconfont.json

@@ -6,6 +6,55 @@
   "description": "",
   "glyphs": [
     {
+      "icon_id": "43623992",
+      "name": "view",
+      "font_class": "view",
+      "unicode": "e79b",
+      "unicode_decimal": 59291
+    },
+    {
+      "icon_id": "43616883",
+      "name": "ratio",
+      "font_class": "ratio",
+      "unicode": "e799",
+      "unicode_decimal": 59289
+    },
+    {
+      "icon_id": "43616882",
+      "name": "1b1",
+      "font_class": "a-1b1",
+      "unicode": "e79a",
+      "unicode_decimal": 59290
+    },
+    {
+      "icon_id": "25654903",
+      "name": "reset",
+      "font_class": "reset",
+      "unicode": "e65a",
+      "unicode_decimal": 58970
+    },
+    {
+      "icon_id": "43615153",
+      "name": "menu",
+      "font_class": "menu",
+      "unicode": "e798",
+      "unicode_decimal": 59288
+    },
+    {
+      "icon_id": "43559284",
+      "name": "keys_a",
+      "font_class": "keys_a",
+      "unicode": "e797",
+      "unicode_decimal": 59287
+    },
+    {
+      "icon_id": "43559283",
+      "name": "add_a",
+      "font_class": "add_a",
+      "unicode": "e796",
+      "unicode_decimal": 59286
+    },
+    {
       "icon_id": "43549167",
       "name": "rectification",
       "font_class": "rectification",

BIN
src/components/bill-ui/components/icon/iconfont/iconfont.ttf


BIN
src/components/bill-ui/components/icon/iconfont/iconfont.woff


BIN
src/components/bill-ui/components/icon/iconfont/iconfont.woff2


+ 16 - 0
src/components/global-search/guide.vue

@@ -0,0 +1,16 @@
+<template>
+  <GuideSign
+    :guide="data"
+    :edit="false"
+    search
+    @click="playSceneGuide(getGuidePaths(data), undefined, true, data)"
+  />
+</template>
+
+<script lang="ts" setup>
+import { playSceneGuide } from "@/sdk";
+import { getGuidePaths, Guide } from "@/store";
+import GuideSign from "@/views/guide/guide/sign.vue";
+
+defineProps<{ data: Guide }>();
+</script>

+ 171 - 0
src/components/global-search/index.vue

@@ -0,0 +1,171 @@
+<template>
+  <div id="global-search">
+    <Select
+      show-search
+      :filter-option="filter"
+      v-model:value="value"
+      size="large"
+      optionLabelProp="label"
+      style="width: 340px"
+      :dropdownMatchSelectWidth="false"
+      popupClassName="global-search-menu"
+      allowClear
+      placeholder="搜索"
+    >
+      <template v-for="item in options" :key="item.key">
+        <SelectOptGroup v-if="item.options.length" class="group-item" :key="item.key">
+          <template #label>
+            <span class="group-item-title">{{ item.name }}</span>
+          </template>
+          <SelectOption
+            v-for="(option, ndx) in item.options"
+            :value="item.key + option.id"
+            :label="item.getLabel(option as any)"
+            :key="item.key + option.id"
+            :class="{ 'last-item': ndx + 1 === item.options.length }"
+          >
+            <component :is="item.comp" :data="(option as any)" />
+          </SelectOption>
+        </SelectOptGroup>
+      </template>
+    </Select>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import TaggingComp from "./tagging.vue";
+import PathComp from "./path.vue";
+import MeasureComp from "./measure.vue";
+import GuideComp from "./guide.vue";
+import ViewComp from "./view.vue";
+import MonitorComp from "./monitor.vue";
+import {
+  Guide,
+  guides,
+  Measure,
+  measures,
+  Monitor,
+  monitors,
+  Path,
+  paths,
+  Tagging,
+  taggings,
+  View,
+  views,
+} from "@/store";
+import { Select, SelectOptGroup, SelectOption } from "ant-design-vue";
+import { computed, ref } from "vue";
+
+const options = computed(() => [
+  {
+    key: "tagging-",
+    name: "标签",
+    options: taggings.value,
+    getLabel: (tag: Tagging) => tag.title,
+    comp: TaggingComp,
+  },
+  {
+    key: "path-",
+    name: "路径",
+    options: paths.value,
+    getLabel: (tag: Path) => tag.name,
+    comp: PathComp,
+  },
+  {
+    key: "measure-",
+    name: "测量",
+    options: measures.value,
+    getLabel: (tag: Measure) => tag.title,
+    comp: MeasureComp,
+  },
+  {
+    key: "guide-",
+    name: "导览",
+    options: guides.value,
+    getLabel: (tag: Guide) => tag.title,
+    comp: GuideComp,
+  },
+  {
+    key: "view-",
+    name: "视图提取",
+    options: views.value,
+    getLabel: (tag: View) => tag.title,
+    comp: ViewComp,
+  },
+  {
+    key: "monitor-",
+    name: "监控",
+    options: monitors.value,
+    getLabel: (tag: Monitor) => tag.title,
+    comp: MonitorComp,
+  },
+]);
+
+const filterOption = (input: string, key: string) => {
+  const option = options.value.find((option) => key.indexOf(option.key) === 0);
+  if (!option) return false;
+  const id = key.substring(option.key.length);
+  const item = option.options.find((item) => item.id.toString() === id)!;
+  if (!item) return false;
+  return option.getLabel(item as any).indexOf(input) >= 0;
+};
+
+const filter = (input: string, option: any) => {
+  if (option.options) {
+    const fOptions = option.options.filter((option: any) =>
+      filterOption(input, option.value)
+    );
+    return fOptions.length === option.options.length;
+  } else {
+    return filterOption(input, option.value);
+  }
+};
+
+const value = ref();
+</script>
+<style lang="scss" scoped>
+#global-search {
+  position: absolute;
+  z-index: 99;
+  left: calc(var(--left-pano-left) + var(--left-pano-width) + 20px);
+  top: calc(var(--editor-head-height) + var(--header-top) + 20px);
+  // background: #000;
+  transition: all 0.3s ease;
+}
+</style>
+
+<style lang="scss">
+#global-search {
+  .ant-select-selector {
+    background-color: var(--editor-toolbox-back);
+    backdrop-filter: blur(4px);
+
+    box-shadow: inset 0px 0px 0px 2px rgba(255, 255, 255, 0.1);
+    border-radius: 4px;
+
+    font-size: 14px;
+  }
+}
+
+.global-search-menu {
+  background-color: var(--editor-toolbox-back);
+  backdrop-filter: blur(4px);
+
+  .ant-empty-description {
+    color: rgba(255, 255, 255, 0.7);
+  }
+  .ant-empty-image * {
+    fill: rgba(255, 255, 255, 0.7);
+  }
+}
+
+.group-item-title {
+  font-weight: bold;
+  font-size: 14px;
+  color: rgba(255, 255, 255, 0.7);
+}
+
+.last-item {
+  border-bottom: 1px solid rgba(var(--colors-primary-fill), 0.16);
+}
+</style>

+ 16 - 0
src/components/global-search/measure.vue

@@ -0,0 +1,16 @@
+<template>
+  <MeasureSign
+    :measure="data"
+    :edit="false"
+    search
+    @click="getSceneMeasure(data)?.fly()"
+  />
+</template>
+
+<script lang="ts" setup>
+import { getSceneMeasure } from "@/sdk";
+import { Measure } from "@/store";
+import MeasureSign from "@/views/measure/sign.vue";
+
+defineProps<{ data: Measure }>();
+</script>

+ 10 - 0
src/components/global-search/monitor.vue

@@ -0,0 +1,10 @@
+<template>
+  <ViewSign :monitor="data" :edit="false" search />
+</template>
+
+<script lang="ts" setup>
+import { Monitor } from "@/store";
+import ViewSign from "@/views/tagging/monitor/sign.vue";
+
+defineProps<{ data: Monitor }>();
+</script>

+ 10 - 0
src/components/global-search/path.vue

@@ -0,0 +1,10 @@
+<template>
+  <PathSign :path="data" :edit="false" search />
+</template>
+
+<script lang="ts" setup>
+import { Path } from "@/store";
+import PathSign from "@/views/guide/path/sign.vue";
+
+defineProps<{ data: Path }>();
+</script>

+ 11 - 0
src/components/global-search/tagging.vue

@@ -0,0 +1,11 @@
+<template>
+  <TaggingSign :tagging="data" :edit="false" @select="flyTagging(data)" search />
+</template>
+
+<script lang="ts" setup>
+import { flyTagging } from "@/hook/use-fly";
+import { Tagging } from "@/store";
+import TaggingSign from "@/views/tagging/hot/sign.vue";
+
+defineProps<{ data: Tagging }>();
+</script>

+ 19 - 0
src/components/global-search/view.vue

@@ -0,0 +1,19 @@
+<template>
+  <ViewSign :view="data" :edit="false" search class="aitem" />
+</template>
+
+<script lang="ts" setup>
+import { View } from "@/store";
+import ViewSign from "@/views/view/sign.vue";
+
+defineProps<{ data: View }>();
+</script>
+
+<style>
+.aitem .content {
+  width: 100%;
+}
+.aitem .content .title {
+  flex: 1;
+}
+</style>

+ 2 - 0
src/env/index.ts

@@ -17,6 +17,7 @@ export const bottomBarHeightStack = stackFactory(ref<string>('60px'))
 export const showTaggingsStack = stackFactory(ref<boolean>(true))
 export const showMonitorsStack = stackFactory(ref<boolean>(true))
 export const showPathsStack = stackFactory(ref<boolean>(true))
+export const showSearchStack = stackFactory(ref<boolean>(true))
 export const showPathStack = stackFactory(ref<Path['id']>())
 export const showMeasuresStack = stackFactory(ref<boolean>(true))
 export const currentModelStack = stackFactory(ref<FuseModel | null>(null))
@@ -48,6 +49,7 @@ export const custom = flatStacksValue({
   showHeadBar: showHeadBarStack,
   currentView: currentViewStack,
   showMode: showModeStack,
+  showSearch: showSearchStack
 })
 
 

+ 38 - 1
src/hook/use-fly.ts

@@ -1,5 +1,42 @@
 import { TaggingPosition } from "@/api";
-import { sdk, getTaggingPosNode, setPose } from "@/sdk";
+import { showTaggingPositionsStack } from "@/env";
+import { sdk, getTaggingPosNode, setPose,  } from "@/sdk";
+import { getFuseModel, getFuseModelShowVariable, getTaggingPositions, Tagging } from "@/store";
+import { nextTick, ref } from "vue";
+
+let stopFlyTagging: (() => void) | null = null
+export const flyTagging = (tagging: Tagging, callback?: () => void) => {
+  stopFlyTagging && stopFlyTagging()
+  const positions = getTaggingPositions(tagging);
+
+  let isStop = false;
+  const flyIndex = (i: number) => {
+    if (isStop || i >= positions.length) {
+      callback && nextTick(callback);
+      return;
+    }
+    const position = positions[i];
+    const model = getFuseModel(position.modelId);
+    if (!model || !getFuseModelShowVariable(model).value) {
+      flyIndex(i + 1);
+      return;
+    }
+
+    const pop = showTaggingPositionsStack.push(ref(new WeakSet([position])));
+    flyTaggingPosition(position);
+
+    setTimeout(() => {
+      pop();
+      flyIndex(i + 1);
+    }, 2000);
+  };
+  flyIndex(0);
+  stopFlyTagging = () => {
+    isStop = true
+    stopFlyTagging = null
+  }
+  return stopFlyTagging
+};
 
 export const flyTaggingPosition = (position: TaggingPosition) => {
   if (position.pose) {

+ 4 - 1
src/layout/edit/fuse-edit.vue

@@ -12,6 +12,8 @@
         <ui-icon type="add" />添加数据
       </ui-button>
     </SelectModel>
+
+    <GlobalSearch />
   </template>
 </template>
 
@@ -21,6 +23,7 @@ import { currentMeta, router, RoutesName } from "@/router";
 import { showLeftPanoStack, showRightPanoStack } from "@/env";
 import { asyncTimeout, togetherCallback } from "@/utils";
 import { loadModel, fuseModel } from "@/model";
+import GlobalSearch from "@/components/global-search/index.vue";
 import {
   enterEdit,
   isOld,
@@ -51,7 +54,7 @@ const initialSys = async () => {
     initialPaths(),
     initialMeasures(),
     initMonitors(),
-    initialAnimationModels()
+    initialAnimationModels(),
   ]);
   await loadModel(fuseModel);
   const stop = watchEffect(() => {

+ 1 - 1
src/router/constant.ts

@@ -110,7 +110,7 @@ export const metas = {
 
   [RoutesName.view]: {
     sysTitle: "视图提取",
-    icon: "nav-setup",
+    icon: "view",
     title: "视图提取",
     left: 'scene-list'
   },

+ 4 - 4
src/views/animation/right/am.vue

@@ -53,19 +53,19 @@
         <ui-group-option class="item">
           <span class="label">加帧</span>
           <span class="oper" @click="$emit('addFrame')">
-            <ui-icon type="keys" ctrl />
+            <ui-icon type="keys_a" ctrl />
           </span>
         </ui-group-option>
         <ui-group-option class="item">
           <span class="label">路径</span>
           <span class="oper">
-            <ui-icon @click="visibleSelectPath = true" type="add" ctrl />
+            <ui-icon @click="visibleSelectPath = true" type="add_a" ctrl />
           </span>
         </ui-group-option>
         <ui-group-option class="item">
           <span class="label">字幕</span>
           <span class="oper">
-            <ui-icon @click="$emit('addSubtitle')" type="add" ctrl />
+            <ui-icon @click="$emit('addSubtitle')" type="add_a" ctrl />
           </span>
         </ui-group-option>
       </ui-group>
@@ -77,7 +77,7 @@
           <span class="label">{{ action.title }}</span>
           <span class="oper">
             <ui-icon
-              type="add"
+              type="add_a"
               ctrl
               @click="$emit('addAction', { key: action.action, name: action.title })"
             />

+ 14 - 4
src/views/guide/guide/sign.vue

@@ -1,5 +1,5 @@
 <template>
-  <ui-group-option class="sign-guide">
+  <ui-group-option class="sign-guide" :class="{ search }">
     <div class="info">
       <div class="guide-cover">
         <img :src="getResource(getFileUrl(guide.cover))" />
@@ -45,9 +45,13 @@ import { VideoRecorder } from "simaqcore";
 import useFocus from "bill/hook/useFocus";
 import { Message } from "bill/expose-common";
 
-const props = withDefaults(defineProps<{ guide: Guide; edit?: boolean }>(), {
-  edit: true,
-});
+const props = withDefaults(
+  defineProps<{ guide: Guide; edit?: boolean; search?: boolean }>(),
+  {
+    edit: true,
+    search: false,
+  }
+);
 
 const inputRef = ref();
 const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root));
@@ -140,6 +144,12 @@ const paths = computed(() => getGuidePaths(props.guide));
     border-top: 1px solid var(--colors-border-color);
   }
 
+  &.search {
+    padding: 0;
+    border: none;
+    margin-bottom: 5px;
+  }
+
   .info {
     flex: 1;
 

+ 25 - 8
src/views/guide/path/sign.vue

@@ -1,8 +1,10 @@
 <template>
   <!-- -->
   <ui-group-option
-    :class="`sign-guide ${hover || focus ? 'active' : ''} `"
-    @click.stop="clickHandler"
+    :class="`sign-guide ${!search && (hover || focus) ? 'active' : ''} ${
+      search ? 'search' : ''
+    }`"
+    @click="clickHandler"
     @mouseenter="enterHandler"
     @mouseleave="leaveHandler"
   >
@@ -16,7 +18,7 @@
           type="preview"
           class="icon"
           ctrl
-          @click.stop="playHandler()"
+          @click="playHandler()"
           v-if="path.points.length"
         />
       </div>
@@ -40,9 +42,13 @@ import { getPathNode, playScenePath } from "@/sdk/association/path";
 import { computed, ref, watch, watchEffect } from "vue";
 import { custom } from "@/env";
 
-const props = withDefaults(defineProps<{ path: Path; edit?: boolean }>(), {
-  edit: true,
-});
+const props = withDefaults(
+  defineProps<{ path: Path; edit?: boolean; search?: boolean }>(),
+  {
+    edit: true,
+    search: false,
+  }
+);
 
 const emit = defineEmits<{
   (e: "delete"): void;
@@ -57,7 +63,9 @@ const actions = {
   edit: () => emit("edit"),
   delete: () => emit("delete"),
 };
+let isCoverClick = false;
 const playHandler = () => {
+  isCoverClick = true;
   node.value?.focus(true);
   playScenePath(props.path, true);
 };
@@ -96,8 +104,11 @@ const enterHandler = () => {
 };
 
 const clickHandler = () => {
-  node.value?.fly();
-  node.value?.focus(true);
+  if (!isCoverClick) {
+    node.value?.fly();
+    node.value?.focus(true);
+  }
+  isCoverClick = false;
 };
 </script>
 
@@ -109,6 +120,12 @@ const clickHandler = () => {
   padding: 20px 0;
   margin-bottom: 0;
 
+  &.search {
+    padding: 0;
+    border-bottom: none;
+    margin-bottom: 5px;
+  }
+
   position: relative;
   cursor: pointer;
   &:first-child {

+ 19 - 8
src/views/measure/sign.vue

@@ -1,14 +1,14 @@
 <template>
   <ui-group-option
     class="sign-measure"
-    :class="{ active: measure.selected }"
+    :class="{ active: measure.selected, search }"
     @mouseenter="measure.selected = true"
     @mouseleave="measure.selected = false"
   >
     <div class="info">
       <ui-icon :type="MeasureTypeMeta[measure.type].icon" class="type" />
       <div v-show="!isEditTitle">
-        <p @click.stop="edit && (isEditTitle = true)">
+        <p @click="edit && (isEditTitle = true)">
           {{ measure.title || MeasureTypeMeta[measure.type].unitDesc }}
         </p>
         <span>{{ desc }} {{ MeasureTypeMeta[measure.type].unit }}</span>
@@ -24,12 +24,12 @@
         height="28px"
       />
     </div>
-    <div class="actions" @click.stop>
+    <div class="actions">
       <!-- <ui-icon type="del" ctrl @click.stop="$emit('delete')" v-if="edit" /> -->
       <ui-icon
         type="pin"
         ctrl
-        @click.stop="fly"
+        @click="fly"
         :class="{ disabled: !getMeasureIsShow(measure) }"
       />
       <ui-more
@@ -52,9 +52,13 @@ 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 props = withDefaults(
+  defineProps<{ measure: Measure; edit?: boolean; search?: boolean }>(),
+  {
+    edit: true,
+    search: false,
+  }
+);
 const emit = defineEmits<{
   (e: "delete"): void;
   (e: "updateTitle", title: string): void;
@@ -102,7 +106,13 @@ watch(
   border-bottom: 1px solid var(--colors-border-color);
   position: relative;
 
-  &.active::after {
+  &.search {
+    padding: 0;
+    border-bottom: none;
+    margin-bottom: 5px;
+  }
+
+  &.active:not(.search)::after {
     content: "";
     position: absolute;
     pointer-events: none;
@@ -124,6 +134,7 @@ watch(
       overflow: hidden;
       display: flex;
       background: rgba(0, 0, 0, 0.5);
+      flex: 0 0 auto;
       font-size: 18px;
       align-items: center;
       justify-content: center;

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

@@ -18,7 +18,7 @@
               name: RoutesName.proportion, 
               params: { id: custom.currentModel!.id, save: '1' },
             })"
-            type="close"
+            type="ratio"
             tip="设置比例"
           />
         </template>
@@ -30,7 +30,7 @@
           :ctrl="false"
           width="100%"
         >
-          <template #preIcon>1:1</template>
+          <template #preIcon><ui-icon type="a-1b1" /></template>
           <template #icon>%</template>
         </ui-input>
       </ui-group-option>
@@ -83,7 +83,7 @@ const actionItems: ActionsProps["items"] = [
     },
   },
   {
-    icon: "flip",
+    icon: "a-rotate",
     text: "旋转",
     action: () => {
       getSceneModel(custom.currentModel)?.enterRotateMode();
@@ -93,7 +93,7 @@ const actionItems: ActionsProps["items"] = [
     },
   },
   {
-    icon: "flip",
+    icon: "a-zoom",
     text: "缩放",
     action: () => {
       getSceneModel(custom.currentModel)?.enterScaleMode();
@@ -106,7 +106,7 @@ const actionItems: ActionsProps["items"] = [
 
 const othActions = reactive([
   {
-    icon: "move",
+    icon: "rectification",
     text: "配准",
     disabled: isOld,
     action: () => {
@@ -117,7 +117,7 @@ const othActions = reactive([
     },
   },
   {
-    icon: "flip",
+    icon: "reset",
     text: "恢复默认",
     action: () => {
       reset();

+ 36 - 32
src/views/summary/index.vue

@@ -6,56 +6,60 @@
   <RightFillPano>
     <template #header>
       <div class="tabs">
-        <span 
+        <span
           v-for="tab in tabs"
           :key="tab.key"
           :class="{ active: tab.key === current }"
           @click="current = tab.key"
         >
-          {{tab.text}}
+          {{ tab.text }}
         </span>
       </div>
     </template>
     <Taggings v-if="current === TabKey.tagging" />
-    <Guides  v-if="current === TabKey.guide"/>
-    <Measures  v-if="current === TabKey.measure"/>
+    <Guides v-if="current === TabKey.guide" />
+    <Measures v-if="current === TabKey.measure" />
   </RightFillPano>
 </template>
 
 <script setup lang="ts">
-import { ref, watchEffect } from 'vue'
-import { useViewStack } from '@/hook'
-import { showRightCtrlPanoStack, showRightPanoStack } from '@/env'
-import { currentModel, fuseModel, loadModel } from '@/model'
-import { LeftPano, RightFillPano } from '@/layout'
-import SceneList from '@/layout/scene-list/index.vue'
-import Taggings from '@/views/tagging/show.vue'
-import Measures from '@/views/measure/show.vue'
-import Guides from '@/views/guide/show.vue'
+import { ref, watchEffect } from "vue";
+import { useViewStack } from "@/hook";
+import { showRightCtrlPanoStack, showRightPanoStack } from "@/env";
+import { currentModel, fuseModel, loadModel } from "@/model";
+import { LeftPano, RightFillPano } from "@/layout";
+import SceneList from "@/layout/scene-list/index.vue";
+import Taggings from "@/views/tagging/hot/show.vue";
+import Measures from "@/views/measure/show.vue";
+import Guides from "@/views/guide/show.vue";
 
-enum TabKey { tagging, measure, guide }
+enum TabKey {
+  tagging,
+  measure,
+  guide,
+}
 const tabs = [
-  { key: TabKey.tagging, text: '标签' },
-  { key: TabKey.measure, text: '测量' },
-  { key: TabKey.guide, text: '路径' },
-]
-const current = ref(tabs[0].key)
-const showRightCtrl = ref(true)
+  { key: TabKey.tagging, text: "标签" },
+  { key: TabKey.measure, text: "测量" },
+  { key: TabKey.guide, text: "路径" },
+];
+const current = ref(tabs[0].key);
+const showRightCtrl = ref(true);
 
 watchEffect((onclean) => {
-  const isFuse = currentModel.value === fuseModel
+  const isFuse = currentModel.value === fuseModel;
   if (!isFuse) {
-    onclean(showRightPanoStack.push(ref(false)))
+    onclean(showRightPanoStack.push(ref(false)));
   }
-  showRightCtrl.value = isFuse
-})
-useViewStack(() => showRightCtrlPanoStack.push(showRightCtrl))
+  showRightCtrl.value = isFuse;
+});
+useViewStack(() => showRightCtrlPanoStack.push(showRightCtrl));
 </script>
 
 <style lang="scss" scoped>
 .tabs {
   height: 60px;
-  border-bottom: 1px solid rgba(255,255,255,0.16);
+  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
   display: flex;
   margin: -20px;
   margin-bottom: 20px;
@@ -66,15 +70,15 @@ useViewStack(() => showRightCtrlPanoStack.push(showRightCtrl))
     align-items: center;
     justify-content: center;
     position: relative;
-    transition: color .3s ease;
+    transition: color 0.3s ease;
     cursor: pointer;
     font-size: 16px;
 
     &::after {
-      content: '';
-      transition: height .3s ease;
+      content: "";
+      transition: height 0.3s ease;
       position: absolute;
-      background-color: #00C8AF;
+      background-color: #00c8af;
       left: 0;
       right: 0;
       bottom: 0;
@@ -83,7 +87,7 @@ useViewStack(() => showRightCtrlPanoStack.push(showRightCtrl))
 
     &:hover,
     &.active {
-      color: #00C8AF;
+      color: #00c8af;
     }
 
     &.active::after {
@@ -91,4 +95,4 @@ useViewStack(() => showRightCtrlPanoStack.push(showRightCtrl))
     }
   }
 }
-</style>
+</style>

+ 12 - 34
src/views/tagging/hot/sign.vue

@@ -1,8 +1,8 @@
 <template>
   <ui-group-option
     class="sign-tagging"
-    :class="{ active: selected, edit }"
-    @click="edit && getTaggingIsShow(tagging) && emit('select', true)"
+    :class="{ active: selected, edit, search }"
+    @click="(search || edit) && getTaggingIsShow(tagging) && emit('select', true)"
   >
     <div class="info">
       <img :src="getResource(getFileUrl(findImage))" v-if="findImage" />
@@ -11,7 +11,7 @@
         <span>放置:{{ positions.length }}</span>
       </div>
     </div>
-    <div class="actions" @click.stop>
+    <div class="actions" @click.stop v-if="!search">
       <ui-icon
         v-if="!edit"
         type="pin"
@@ -45,11 +45,16 @@ import {
 } from "@/store";
 
 import type { Tagging } from "@/store";
-import { flyTaggingPosition } from "@/hook/use-fly";
+import { flyTagging, flyTaggingPosition } from "@/hook/use-fly";
 
 const props = withDefaults(
-  defineProps<{ tagging: Tagging; selected?: boolean; edit?: boolean }>(),
-  { edit: true }
+  defineProps<{
+    tagging: Tagging;
+    selected?: boolean;
+    edit?: boolean;
+    search?: boolean;
+  }>(),
+  { edit: true, search: false }
 );
 const style = computed(() => getTaggingStyle(props.tagging.styleId));
 const positions = computed(() => getTaggingPositions(props.tagging));
@@ -81,37 +86,10 @@ const actions = {
   delete: () => emit("delete"),
 };
 
-const flyTaggingPositions = (tagging: Tagging, callback?: () => void) => {
-  const positions = getTaggingPositions(tagging);
-
-  let isStop = false;
-  const flyIndex = (i: number) => {
-    if (isStop || i >= positions.length) {
-      callback && nextTick(callback);
-      return;
-    }
-    const position = positions[i];
-    const model = getFuseModel(position.modelId);
-    if (!model || !getFuseModelShowVariable(model).value) {
-      flyIndex(i + 1);
-      return;
-    }
-
-    const pop = showTaggingPositionsStack.push(ref(new WeakSet([position])));
-    flyTaggingPosition(position)
-
-    setTimeout(() => {
-      pop();
-      flyIndex(i + 1);
-    }, 2000);
-  };
-  flyIndex(0);
-  return () => (isStop = true);
-};
 watchEffect((onCleanup) => {
   if (props.selected) {
     const success = () => emit("select", false);
-    const stop = flyTaggingPositions(props.tagging, success);
+    const stop = flyTagging(props.tagging, success);
     const keyupHandler = (ev: KeyboardEvent) => ev.code === "Escape" && success();
 
     document.documentElement.addEventListener("keyup", keyupHandler, false);

+ 6 - 0
src/views/tagging/hot/style.scss

@@ -7,6 +7,12 @@
   border-bottom: 1px solid var(--colors-border-color);
   position: relative;
 
+  &.search {
+    padding: 0;
+    border-bottom: none;
+    margin-bottom: 5px;
+  }
+
   &.edit{
     cursor: pointer;
 

+ 18 - 5
src/views/tagging/monitor/sign.vue

@@ -1,7 +1,7 @@
 <template>
   <ui-group-option
     class="sign-tagging"
-    :class="{ active: selected, edit }"
+    :class="{ active: selected, edit, search }"
     @click="emit('select', true)"
   >
     <div class="info">
@@ -17,7 +17,7 @@
         height="28px"
       />
     </div>
-    <div class="actions" @click.stop>
+    <div class="actions" @click.stop v-if="edit">
       <ui-more
         :options="menus"
         style="margin-left: 20px"
@@ -32,9 +32,17 @@ import type { Monitor } from "@/store";
 import useFocus from "bill/hook/useFocus";
 import { computed, ref } from "vue";
 
-withDefaults(defineProps<{ monitor: Monitor; selected?: boolean; edit?: boolean }>(), {
-  edit: true,
-});
+withDefaults(
+  defineProps<{
+    monitor: Monitor;
+    selected?: boolean;
+    edit?: boolean;
+    search?: boolean;
+  }>(),
+  {
+    edit: true,
+  }
+);
 
 const emit = defineEmits<{
   (e: "delete"): void;
@@ -64,6 +72,11 @@ const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root));
   margin: 0;
   border-bottom: 1px solid var(--colors-border-color);
   position: relative;
+  &.search {
+    padding: 0;
+    border: none !important;
+    margin-bottom: 5px;
+  }
 
   &.edit {
     cursor: pointer;

+ 51 - 51
src/views/view/sign.vue

@@ -1,93 +1,93 @@
 <template>
-  <ui-group-option class="sign" :class="{active}">
+  <ui-group-option class="sign" :class="{ active, search }">
     <div class="content">
       <span class="cover" @click="fly">
-        <img :src="getResource(getFileUrl(view.cover))" alt="">
+        <img :src="getResource(getFileUrl(view.cover))" alt="" />
       </span>
-      <ui-input 
+      <ui-input
         class="view-title-input"
-        type="text" 
-        :modelValue="view.title" 
+        type="text"
+        :modelValue="view.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 class="title" v-show="!isEditTitle" @click="fly">
         <p>{{ view.title }}</p>
-        <span>  {{ getModelDesc(modelType as ModelType) }}</span>
+        <span> {{ getModelDesc(modelType as ModelType) }}</span>
       </div>
     </div>
     <div class="action" v-if="edit">
       <ui-icon type="order" ctrl />
-      <ui-more 
-        :options="menus" 
-        style="margin-left: 20px" 
-        @click="(action: keyof typeof actions) => actions[action]()" 
+      <ui-more
+        :options="menus"
+        style="margin-left: 20px"
+        @click="(action: keyof typeof actions) => actions[action]()"
       />
     </div>
   </ui-group-option>
 </template>
 
 <script lang="ts" setup>
-import { ref, computed, watchEffect } from 'vue'
-import { useFocus } from 'bill/hook/useFocus'
-import { custom, getResource } from '@/env'
-import { deepIsRevise, getFileUrl } from '@/utils'
-import { loadModel, getModelDesc, ModelType, currentModel } from '@/model'
-import { viewToModelType } from '@/store'
+import { ref, computed, watchEffect } from "vue";
+import { useFocus } from "bill/hook/useFocus";
+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'
+import type { View } from "@/store";
+import { Message } from "bill/expose-common";
 
 const props = withDefaults(
-  defineProps<{ view: View, edit?: boolean }>(),
+  defineProps<{ view: View; edit?: boolean; search?: boolean }>(),
   { edit: true }
-)
+);
 const emit = defineEmits<{
-    (e: 'updateCover', cover: string): void,
-    (e: 'updateTitle', title: string): void,
-    (e: 'delete'): void,
-}>()
+  (e: "updateCover", cover: string): void;
+  (e: "updateTitle", title: string): void;
+  (e: "delete"): void;
+}>();
 
 const menus = [
-  { label: '重命名', value: 'rename' },
-  { label: '删除', value: 'delete' },
-]
+  { label: "重命名", value: "rename" },
+  { label: "删除", value: "delete" },
+];
 
-const inputRef = ref()
-const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root))
+const inputRef = ref();
+const isEditTitle = useFocus(computed(() => inputRef.value?.vmRef.root));
 
 watchEffect(() => {
   if (!isEditTitle.value && !props.view.title.length) {
-    isEditTitle.value = true
-    Message.warning('视图名称不可为空')
+    isEditTitle.value = true;
+    Message.warning("视图名称不可为空");
   }
-})
+});
 
 const actions = {
-  delete: () => emit('delete'),
-  rename: () => isEditTitle.value = true
-}
-const modelType = viewToModelType(props.view)
+  delete: () => emit("delete"),
+  rename: () => (isEditTitle.value = true),
+};
+const modelType = viewToModelType(props.view);
 const fly = async () => {
-  const sdk = await loadModel(modelType)
-  custom.currentView = props.view
-  sdk.setView(props.view.flyData)
-}
+  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)
-})
-
+  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>
+.view-title-input.ui-input .text.suffix input {
+  padding-right: 50px;
+}
+</style>

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

@@ -37,11 +37,16 @@
   margin-bottom: 0 !important;
   position: relative;
 
+  &.search {
+    padding: 0;
+    border: none !important;
+    margin-bottom: 5px;
+  }
   &:last-child {
     border-bottom: 1px solid rgba(255,255,255,0.1600);
   }
 
-  &.active::after {
+  &.active:not(.search)::after {
     content: '';
     position: absolute;
     pointer-events: none;