Selaa lähdekoodia

fix: Merge branch 'v2.0.0-fire' of http://192.168.0.115:3000/bill/fuse-code into v2.0.0-fire

# Conflicts:
#	public/lib/potree/potree.js
#	public/lib/potree/potree.js.map
xzw 15 tuntia sitten
vanhempi
commit
56578273e8

+ 2 - 2
.env

@@ -1,5 +1,5 @@
 VITE_LASER_HOST=https://test-mix3d.4dkankan.com
-VITE_LASER_OSS=/laser-data
+VITE_LASER_OSS=https://laser-oss.4dkankan.com
 VITE_OSS=
-VITE_PANO_OSS=/oss
+VITE_PANO_OSS=https://4dkk.4dage.com
 VITE_MAP_PLATFORM=jm

+ 2 - 2
.env.development

@@ -1,5 +1,5 @@
 VITE_LASER_HOST=https://test-mix3d.4dkankan.com
-VITE_LASER_OSS=/laser-data
+VITE_LASER_OSS=https://laser-oss.4dkankan.com
 VITE_OSS=
-VITE_PANO_OSS=/oss
+VITE_PANO_OSS=https://4dkk.4dage.com
 VITE_MAP_PLATFORM=gaode

+ 44 - 0
index.html

@@ -12,5 +12,49 @@
   <body>
     <div id="app"></div>
     <script type="module" src="/src/main.ts"></script>
+    <script>
+      // 获取URL参数中的app值
+      function getAppParam() {
+        const urlParams = new URLSearchParams(window.location.search);
+        return {fromRoute: urlParams.get('fromRoute')};
+      }
+  
+      // 根据app参数设置favicon
+      function setFavicon() {
+        const appParam = getAppParam();
+        // 获取基础路径
+        const basePath = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
+        let faviconPath = basePath + 'favicon.ico'; // 默认图标
+        console.log(appParam, basePath, 6666)
+        if (appParam.fromRoute == 'fire') {
+          faviconPath = basePath + 'fire.ico';
+        } else if (appParam.fromRoute == 'crimical') {
+          faviconPath = basePath + 'police.ico';
+        } else if (appParam.fromRoute == 'xmfire') {
+          faviconPath = basePath + 'jmlogo.png';
+        } else if (appParam.fromRoute == 'cjzfire') {
+          faviconPath = basePath + 'logo_big.ico';
+        }
+        
+        // 查找现有的favicon链接元素
+        let link = document.querySelector("link[rel~='icon']");
+        
+        // 如果没有找到,创建一个新的
+        if (!link) {
+          link = document.createElement('link');
+          link.rel = 'icon';
+          document.head.appendChild(link);
+        }
+        
+        // 设置favicon路径
+        link.href = faviconPath;
+      }
+  
+      // 页面加载时设置favicon
+      window.addEventListener('load', setFavicon);
+      
+      // 当URL通过History API变化时也更新favicon
+      window.addEventListener('popstate', setFavicon);
+    </script>
   </body>
 </html>

BIN
public/fire.ico


BIN
public/jmlogo.png


BIN
public/logo_big.ico


BIN
public/oldfavicon.ico


BIN
public/police.ico


+ 11 - 8
src/api/constant.ts

@@ -34,7 +34,7 @@ export const FUSE_INSERT_MODEL = `${namespace}/caseFusion/add`
 export const FUSE_UPDATE_MODEL = `${namespace}/caseFusion/update`
 export const FUSE_DELETE_MODEL = `${namespace}/caseFusion/delete`
 // 场景列表
-export const SCENE_LIST_ALL = `/service/manage/scene/list`
+export const SCENE_LIST_ALL = `${namespace}/scene/list`
 export const MODEL_LIST = `${namespace}/case/sceneList`
 export const MODEL_SIGN = `${namespace}/model/getInfo`
 export const SYNC_INFO = `${namespace}/caseLive/getTakeLookRoom`;
@@ -125,12 +125,12 @@ export const FLODER_LIST = `${namespace}/caseFiles/allList`
 export const UPLOAD_FILE = `${namespace}/upload/file`
 
 // 素材库分页
-export const MATERIAL_PAG = `/service/manage/dictFile/pageList/media-library`
-export const ADD_MATERIAL = `/service/manage/common/upload/fileNew`
-export const DEL_MATERIAL = `/service/manage/dictFile/del/media-library`
-export const MATERIAL_GROUP_LIST = `/service/manage/dict/getByKey/media-library`
-export const MATERIAL_TA_GROUP_LIST = `/service/manage/dict/getByUseType/trace_evidence`
-export const SYNC_MATERIAL = `/service/manage/caseFusion/refreshTraceEvidenceInfoList/`
+export const MATERIAL_PAG = `${namespace}/dictFile/pageList/media-library`
+export const ADD_MATERIAL = `${namespace}/common/upload/fileNew`
+export const DEL_MATERIAL = `${namespace}/dictFile/del/media-library`
+export const MATERIAL_GROUP_LIST = `${namespace}/dict/getByKey/media-library`
+export const MATERIAL_TA_GROUP_LIST = `${namespace}/dict/getByUseType/trace_evidence`
+export const SYNC_MATERIAL = `${namespace}/caseFusion/refreshTraceEvidenceInfoList/`
 
 
 // 动画模块
@@ -140,4 +140,7 @@ export const UPDATE_AM_MODEL = `${namespace}/caseAnimation/addOrUpdate`
 export const DELETE_AM_MODEL = `${namespace}/caseAnimation/delete`
 
 
-export const MAP_TILE_LIST = `${namespace}/notAuth/getMapConfig`
+export const MAP_TILE_LIST = `${namespace}/notAuth/getMapConfig`
+
+// 获取验证码
+export const getCode = `${namespace}/notAuth/getLoginAuthCode`

+ 1 - 1
src/api/instance.ts

@@ -28,7 +28,7 @@ export const {
 
 export const gotoLogin = () => {
   if (import.meta.env.DEV) {
-    GAxios.post("/service/manage/login", {
+    GAxios.post("/fusion/login", {
       password: "MRinIEn3ExMjM0NTY=Q5Lm39urQWzN7k4oCG",
       userName: "super-admin",
       username: "super-admin",

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

@@ -125,8 +125,8 @@ export const fetchTaggingStyles = async () => {
   const tree: any = { children: [] };
   genTree(treeData, tree);
   styleTypes.length = 0
-  // styleTypes.push(...tree.children)
-  // Object.assign(defStyleType, tree.children[tree.children.length - 1])
+  styleTypes.push(...tree.children)
+  Object.assign(defStyleType, tree.children[tree.children.length - 1])
   
   console.error('StyleT', styleTypes)
 

+ 2 - 2
src/app.vue

@@ -44,11 +44,11 @@
   </ConfigProvider>
   <Login v-else />
 
-  <p class="botton-marker">
+  <!-- <p class="botton-marker">
     <img src="/images/logo_police.png" /><span class="s1">&</span>
     <img src="/images/logo_4dage.png" /><span class="s2">|</span
     >公安部科技强警基础工作计划(2022JC13)
-  </p>
+  </p> -->
 </template>
 
 <script lang="ts" setup>

+ 7 - 1
src/components/bill-ui/components/input/index.vue

@@ -26,6 +26,7 @@ import search from './search.vue'
 import richtext from './richtext.vue'
 import color from './color.vue'
 import multiple from './multiple.vue'
+import richinput from './richInput.vue'
 import {
     inputPropsDesc,
     textPropsDesc,
@@ -42,7 +43,8 @@ import {
     colorPropsDesc,
     inputEmitDesc,
     textEmitsDesc,
-    multiplePropsDesc
+    multiplePropsDesc,
+    richinputPropsDesc
 } from './state'
 
 const types = {
@@ -97,6 +99,10 @@ const types = {
     multiple: {
         component: multiple,
         propsDesc: multiplePropsDesc
+    },
+    richinput: {
+        component: richinput,
+        propsDesc: richinputPropsDesc,
     }
 }
 

+ 766 - 0
src/components/bill-ui/components/input/richInput.vue

@@ -0,0 +1,766 @@
+<template>
+    <div
+        class="input textarea"
+        :class="{ suffix: showBar, disabled, right, 'has-toolbar': enableToolbar }"
+        ref="textRef"
+    >
+        <div
+            :contenteditable="canEdit"
+            class="ui-text input-div"
+            @input="inputHandler"
+            :placeholder="props.placeholder"
+            @click="emit('click')"
+            @focus="focusHandler"
+            @blur="blurHandler"
+            @paste="pasteHandler"
+            @compositionstart="compositionstartHandler"
+            @compositionend="compositionendHandler"
+            @mouseup="selectionUpdate"
+            @keyup="selectionUpdate"
+            ref="inputRef"
+            v-bind="other"
+        />
+        <span class="replace"></span>
+        <span v-if="showBar" class="retouch">
+            <div v-if="enableToolbar" class="toolbar" @mousedown.prevent>
+                <div class="toolbar-item dropdown font-size" :class="{ open: showFontSizeMenu }" @mousedown.prevent="toggleFontSizeMenu">
+                    <span class="dropdown-value">{{ currentFontSize }}</span>
+                    <span class="dropdown-arrow">▾</span>
+                    <div v-if="showFontSizeMenu" class="dropdown-menu" @mousedown.prevent>
+                        <div
+                            v-for="size in fontSizeOptions"
+                            :key="size"
+                            class="dropdown-option"
+                            :class="{ active: size === currentFontSize }"
+                            @mousedown.prevent="applyFontSize(size)"
+                        >
+                            {{ size }}
+                        </div>
+                    </div>
+                </div>
+
+                <button
+                    class="toolbar-item btn"
+                    type="button"
+                    :class="{ active: isBold }"
+                    :disabled="!canEdit"
+                    @mousedown.prevent="toggleBold"
+                >
+                    B
+                </button>
+
+                <button
+                    class="toolbar-item btn"
+                    type="button"
+                    :disabled="!canEdit"
+                    @mousedown.prevent="triggerTextColor"
+                >
+                    <span class="a-letter" :style="{ '--a-color': textColor }">A</span>
+                </button>
+                <input ref="textColorInputRef" class="color-input" type="color" :value="textColor" @input="applyTextColor" />
+
+                <button
+                    class="toolbar-item btn"
+                    type="button"
+                    :disabled="!canEdit"
+                    @mousedown.prevent="triggerHighlightColor"
+                >
+                    <span class="hl-icon" :style="{ '--hl-color': highlightColor }"></span>
+                </button>
+                <input ref="highlightColorInputRef" class="color-input" type="color" :value="highlightColor" @input="applyHighlightColor" />
+
+                <div class="toolbar-item dropdown align-menu" :class="{ open: showAlignMenu }" @mousedown.prevent="toggleAlignMenu">
+                    <span class="align-icon">{{ alignLabel }}</span>
+                    <span class="dropdown-arrow">▾</span>
+                    <div v-if="showAlignMenu" class="dropdown-menu" @mousedown.prevent>
+                        <div class="dropdown-option" :class="{ active: align === 'left' }" @mousedown.prevent="applyAlign('left')">左对齐</div>
+                        <div class="dropdown-option" :class="{ active: align === 'center' }" @mousedown.prevent="applyAlign('center')">居中</div>
+                        <div class="dropdown-option" :class="{ active: align === 'right' }" @mousedown.prevent="applyAlign('right')">右对齐</div>
+                    </div>
+                </div>
+
+                <div class="toolbar-item link">
+                    <button class="btn link-btn-trigger" type="button" :disabled="!canEdit" @mousedown.prevent="openLinkInput">
+                        链接
+                    </button>
+                    <div v-if="showLinkInput" class="link-pop" @mousedown.prevent>
+                        <input class="link-input" v-model="linkValue" placeholder="请输入链接" />
+                        <button class="link-btn" type="button" @mousedown.prevent="confirmLink">确认</button>
+                    </div>
+                </div>
+            </div>
+            <slot v-else name="icon" />
+            <span v-if="props.maxlength" class="len">
+                <span>{{ length }}</span> / {{ maxlength }}
+            </span>
+        </span>
+    </div>
+</template>
+
+<script setup>
+import { richtextPropsDesc } from './state'
+import { computed, nextTick, onMounted, onUnmounted, ref, useSlots, watchEffect } from 'vue'
+const props = defineProps({
+    ...richtextPropsDesc,
+})
+
+const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click', 'updatePos'])
+const slots = useSlots()
+const textRef = ref(null)
+const inputRef = ref(null)
+const length = ref(0)
+const textColor = ref('#ffffff')
+const highlightColor = ref('#ffe58f')
+const textColorInputRef = ref(null)
+const highlightColorInputRef = ref(null)
+const showFontSizeMenu = ref(false)
+const showAlignMenu = ref(false)
+const showLinkInput = ref(false)
+const linkValue = ref('')
+const savedRange = ref(null)
+const isBold = ref(false)
+const align = ref('left')
+const currentFontSize = ref(12)
+
+const enableToolbar = computed(() => !!props.rich)
+const showBar = computed(() => enableToolbar.value || !!props.maxlength || !!slots.icon)
+const canEdit = computed(() => !props.disabled && !props.readonly)
+
+const fontSizeOptions = computed(() => Array.from({ length: 37 }, (_, idx) => idx + 12))
+const alignLabel = computed(() => (align.value === 'center' ? '居中' : align.value === 'right' ? '右' : '左'))
+
+const getTextLen = (text = '') => text.replace(/[\u200B\uFEFF]/g, '').length
+const normalizeUrl = raw => {
+    const val = String(raw || '').trim()
+    if (!val) return ''
+    const low = val.toLowerCase()
+    if (low.startsWith('javascript:') || low.startsWith('data:')) return ''
+    if (/^(https?:\/\/|mailto:|tel:)/i.test(val)) return val
+    return 'https://' + val
+}
+
+const syncFromDom = () => {
+    if (!inputRef.value) return
+    length.value = getTextLen(inputRef.value.textContent || '')
+    emit('update:modelValue', inputRef.value.innerHTML)
+}
+
+const saveSelection = () => {
+    const sel = window.getSelection?.()
+    if (!sel || sel.rangeCount === 0) return
+    const range = sel.getRangeAt(0)
+    if (!inputRef.value || !inputRef.value.contains(range.commonAncestorContainer)) return
+    savedRange.value = range.cloneRange()
+}
+
+const restoreSelection = () => {
+    if (!savedRange.value || !window.getSelection || !inputRef.value) return false
+    const range = savedRange.value
+    if (range?.startContainer?.isConnected === false || range?.endContainer?.isConnected === false) {
+        savedRange.value = null
+        return false
+    }
+    if (!inputRef.value.contains(range.commonAncestorContainer)) return false
+    const sel = window.getSelection()
+    if (!sel) return false
+    try {
+        sel.removeAllRanges()
+        sel.addRange(range)
+        return true
+    } catch (e) {
+        savedRange.value = null
+        return false
+    }
+}
+
+const getAlignFromRange = range => {
+    if (!inputRef.value || !window.getComputedStyle) return null
+    let node = range?.startContainer
+    if (!node) return null
+    if (node.nodeType === Node.TEXT_NODE) node = node.parentElement
+    if (!(node instanceof Element)) return null
+
+    let cur = node
+    while (cur && cur !== inputRef.value) {
+        const style = window.getComputedStyle(cur)
+        const display = style.display
+        if (display === 'block' || display === 'list-item') {
+            const v = style.textAlign
+            if (v === 'center') return 'center'
+            if (v === 'right' || v === 'end') return 'right'
+            if (v === 'left' || v === 'start' || v === 'justify') return 'left'
+        }
+        cur = cur.parentElement
+    }
+
+    const v = window.getComputedStyle(inputRef.value).textAlign
+    if (v === 'center') return 'center'
+    if (v === 'right' || v === 'end') return 'right'
+    if (v === 'left' || v === 'start' || v === 'justify') return 'left'
+    return null
+}
+
+const selectionUpdate = () => {
+    const sel = window.getSelection?.()
+    if (!sel || sel.rangeCount === 0) return
+    const range = sel.getRangeAt(0)
+    if (!inputRef.value || !inputRef.value.contains(range.commonAncestorContainer)) return
+    savedRange.value = range.cloneRange()
+    if (!document.queryCommandState) return
+    try {
+        isBold.value = document.queryCommandState('bold')
+        if (document.queryCommandState('justifyCenter')) align.value = 'center'
+        else if (document.queryCommandState('justifyRight')) align.value = 'right'
+        else if (document.queryCommandState('justifyLeft')) align.value = 'left'
+    } catch (e) {}
+    const nextAlign = getAlignFromRange(range)
+    if (nextAlign) align.value = nextAlign
+}
+
+const focusEditor = () => {
+    if (!inputRef.value) return
+    inputRef.value.focus()
+}
+
+const ensureEditorSelection = () => {
+    if (!inputRef.value) return false
+    focusEditor()
+    const sel = window.getSelection?.()
+    if (!sel) return false
+    if (sel.rangeCount > 0) {
+        const range = sel.getRangeAt(0)
+        if (inputRef.value.contains(range.commonAncestorContainer)) {
+            savedRange.value = range.cloneRange()
+            return true
+        }
+    }
+    if (restoreSelection()) return true
+    const range = document.createRange()
+    range.selectNodeContents(inputRef.value)
+    range.collapse(false)
+    try {
+        sel.removeAllRanges()
+        sel.addRange(range)
+        savedRange.value = range.cloneRange()
+        return true
+    } catch (e) {
+        return false
+    }
+}
+
+const wrapSelectionWithStyle = style => {
+    if (!canEdit.value) return
+    ensureEditorSelection()
+    const sel = window.getSelection?.()
+    if (!sel || sel.rangeCount === 0) return
+    const range = sel.getRangeAt(0)
+    if (!inputRef.value || !inputRef.value.contains(range.commonAncestorContainer)) return
+
+    const span = document.createElement('span')
+    Object.keys(style).forEach(key => {
+        span.style[key] = style[key]
+    })
+
+    if (range.collapsed) {
+        span.appendChild(document.createTextNode('\u200B'))
+        range.insertNode(span)
+        const nextRange = document.createRange()
+        nextRange.setStart(span.firstChild, 1)
+        nextRange.collapse(true)
+        sel.removeAllRanges()
+        sel.addRange(nextRange)
+        savedRange.value = nextRange.cloneRange()
+    } else {
+        const content = range.extractContents()
+        span.appendChild(content)
+        range.insertNode(span)
+        const nextRange = document.createRange()
+        nextRange.selectNodeContents(span)
+        nextRange.collapse(false)
+        sel.removeAllRanges()
+        sel.addRange(nextRange)
+        savedRange.value = nextRange.cloneRange()
+    }
+
+    syncFromDom()
+    selectionUpdate()
+}
+
+const toggleFontSizeMenu = () => {
+    if (!enableToolbar.value) return
+    saveSelection()
+    showFontSizeMenu.value = !showFontSizeMenu.value
+    showAlignMenu.value = false
+    showLinkInput.value = false
+}
+const applyFontSize = size => {
+    currentFontSize.value = Number(size)
+    showFontSizeMenu.value = false
+    wrapSelectionWithStyle({ fontSize: currentFontSize.value + 'px' })
+}
+
+const toggleBold = () => {
+    if (!canEdit.value) return
+    ensureEditorSelection()
+    document.execCommand?.('bold')
+    nextTick(() => {
+        syncFromDom()
+        selectionUpdate()
+    })
+}
+
+const triggerTextColor = () => {
+    if (!canEdit.value) return
+    saveSelection()
+    textColorInputRef.value?.click?.()
+}
+const applyTextColor = ev => {
+    if (!canEdit.value) return
+    const val = ev?.target?.value
+    if (!val) return
+    textColor.value = val
+    ensureEditorSelection()
+    document.execCommand?.('styleWithCSS', false, true)
+    document.execCommand?.('foreColor', false, val)
+    nextTick(() => {
+        syncFromDom()
+        selectionUpdate()
+    })
+}
+
+const triggerHighlightColor = () => {
+    if (!canEdit.value) return
+    saveSelection()
+    highlightColorInputRef.value?.click?.()
+}
+const applyHighlightColor = ev => {
+    if (!canEdit.value) return
+    const val = ev?.target?.value
+    if (!val) return
+    highlightColor.value = val
+    ensureEditorSelection()
+    document.execCommand?.('styleWithCSS', false, true)
+    document.execCommand?.('hiliteColor', false, val) || document.execCommand?.('backColor', false, val)
+    nextTick(() => {
+        syncFromDom()
+        selectionUpdate()
+    })
+}
+
+const toggleAlignMenu = () => {
+    if (!enableToolbar.value) return
+    saveSelection()
+    showAlignMenu.value = !showAlignMenu.value
+    showFontSizeMenu.value = false
+    showLinkInput.value = false
+}
+const applyAlign = mode => {
+    if (!canEdit.value) return
+    align.value = mode
+    showAlignMenu.value = false
+    ensureEditorSelection()
+    const cmd = mode === 'center' ? 'justifyCenter' : mode === 'right' ? 'justifyRight' : 'justifyLeft'
+    document.execCommand?.(cmd)
+    nextTick(() => {
+        syncFromDom()
+        selectionUpdate()
+    })
+}
+
+const openLinkInput = () => {
+    if (!canEdit.value) return
+    saveSelection()
+    showLinkInput.value = true
+    showFontSizeMenu.value = false
+    showAlignMenu.value = false
+    nextTick(() => {
+        const el = textRef.value?.querySelector?.('.link-input')
+        el?.focus?.()
+    })
+}
+const confirmLink = () => {
+    if (!canEdit.value) return
+    const url = normalizeUrl(linkValue.value)
+    showLinkInput.value = false
+    linkValue.value = ''
+    if (!url) return
+    ensureEditorSelection()
+    const sel = window.getSelection?.()
+    if (!sel || sel.rangeCount === 0) return
+    const range = sel.getRangeAt(0)
+    if (!inputRef.value || !inputRef.value.contains(range.commonAncestorContainer)) return
+
+    if (range.collapsed) {
+        const a = document.createElement('a')
+        a.href = url
+        a.target = '_blank'
+        a.rel = 'noopener noreferrer'
+        a.textContent = url
+        range.insertNode(a)
+        const nextRange = document.createRange()
+        nextRange.setStartAfter(a)
+        nextRange.collapse(true)
+        sel.removeAllRanges()
+        sel.addRange(nextRange)
+        savedRange.value = nextRange.cloneRange()
+        syncFromDom()
+        selectionUpdate()
+        return
+    }
+
+    try {
+        document.execCommand?.('createLink', false, url)
+        inputRef.value.querySelectorAll('a').forEach(a => {
+            a.target = '_blank'
+            a.rel = 'noopener noreferrer'
+        })
+        nextTick(() => {
+            syncFromDom()
+            selectionUpdate()
+        })
+    } catch (e) {}
+}
+
+const updateContent = html => {
+    if (!inputRef.value) return
+    inputRef.value.innerHTML = html || ''
+    length.value = getTextLen(inputRef.value.textContent || '')
+}
+
+watchEffect(() => {
+    if (inputRef.value && props.modelValue !== inputRef.value.innerHTML) {
+        updateContent(props.modelValue)
+    }
+})
+
+let inComposition = false
+const compositionstartHandler = () => {
+    inComposition = true
+}
+const compositionendHandler = ev => {
+    inComposition = false
+    inputHandler(ev)
+}
+
+const inputHandler = ev => {
+    if (inComposition) return
+    const nextLen = getTextLen(ev.target.textContent || '')
+    if (!props.maxlength || nextLen <= Number(props.maxlength)) {
+        length.value = nextLen
+        emit('update:modelValue', ev.target.innerHTML || '')
+    } else {
+        nextTick(() => {
+            if (ev.target.innerHTML !== props.modelValue.toString()) {
+                updateContent(props.modelValue.toString())
+                inputFocus()
+            }
+        })
+    }
+    nextTick(selectionUpdate)
+}
+//获取当前光标位置
+const getCursortPosition = function (element = inputRef.value) {
+    var caretOffset = 0
+    var doc = element.ownerDocument || element.document
+    var win = doc.defaultView || doc.parentWindow
+    var sel
+    if (typeof win.getSelection != 'undefined') {
+        //谷歌、火狐
+        sel = win.getSelection()
+        if (sel.rangeCount > 0) {
+            //选中的区域
+            var range = win.getSelection().getRangeAt(0)
+            var preCaretRange = range.cloneRange() //克隆一个选中区域
+            preCaretRange.selectNodeContents(element) //设置选中区域的节点内容为当前节点
+            preCaretRange.setEnd(range.endContainer, range.endOffset) //重置选中区域的结束位置
+            caretOffset = preCaretRange.toString().length
+        }
+    } else if ((sel = doc.selection) && sel.type != 'Control') {
+        //IE
+        var textRange = sel.createRange()
+        var preCaretTextRange = doc.body.createTextRange()
+        preCaretTextRange.moveToElementText(element)
+        preCaretTextRange.setEndPoint('EndToEnd', textRange)
+        caretOffset = preCaretTextRange.text.length
+    }
+    return caretOffset
+}
+
+let interval
+const focusHandler = ev => {
+    clearInterval(interval)
+    interval = setInterval(() => {
+        emit('updatePos', getCursortPosition())
+    }, 100)
+    emit('focus')
+    nextTick(selectionUpdate)
+}
+const blurHandler = () => {
+    clearInterval(interval)
+    emit('blur')
+}
+
+const inputFocus = () => {
+    inputRef.value.focus()
+    const range = window.getSelection()
+    range.selectAllChildren(inputRef.value)
+    range.collapseToEnd()
+}
+
+const getPasteText = text => {
+    if (!props.maxlength) {
+        return text
+    }
+
+    const $el = document.createElement('div')
+    $el.innerHTML = text
+    const allowLen = Number(props.maxlength) - length.value
+    const rawText = $el.textContent || ''
+    if (getTextLen(rawText) > allowLen) {
+        return rawText.replace(/[\u200B\uFEFF]/g, '').substring(0, allowLen)
+    } else {
+        return text
+    }
+}
+
+const pasteHandler = event => {
+    event.preventDefault()
+    var text
+    var clp = (event.originalEvent || event).clipboardData
+    // 兼容针对于opera ie等浏览器
+    if (clp === undefined || clp === null) {
+        text = window.clipboardData.getData('text') || ''
+        if (text !== '') {
+            if (window.getSelection) {
+                // 针对于ie11 10 9 safari
+                var newNode = document.createElement('span')
+                newNode.innerHTML = getPasteText(text)
+                window.getSelection().getRangeAt(0).insertNode(newNode)
+            } else {
+                document.selection.createRange().pasteHTML(text)
+            }
+        }
+    } else {
+        // 兼容chorme或hotfire
+        text = clp.getData('text/plain') || ''
+        if (text !== '') {
+            document.execCommand('insertText', false, getPasteText(text))
+        }
+    }
+}
+
+const onDocMousedown = ev => {
+    const root = textRef.value
+    if (!root) return
+    const target = ev.target
+    if (showFontSizeMenu.value) {
+        const el = root.querySelector('.font-size')
+        if (!el || !el.contains(target)) showFontSizeMenu.value = false
+    }
+    if (showAlignMenu.value) {
+        const el = root.querySelector('.align-menu')
+        if (!el || !el.contains(target)) showAlignMenu.value = false
+    }
+    if (showLinkInput.value) {
+        const pop = root.querySelector('.link-pop')
+        const btn = root.querySelector('.link-btn-trigger')
+        if ((!pop || !pop.contains(target)) && (!btn || !btn.contains(target))) showLinkInput.value = false
+    }
+
+    if (!root.contains(target)) {
+        savedRange.value = null
+        window.getSelection?.()?.removeAllRanges?.()
+    }
+}
+
+onMounted(() => {
+    document.addEventListener('mousedown', onDocMousedown)
+})
+onUnmounted(() => {
+    document.removeEventListener('mousedown', onDocMousedown)
+})
+
+defineExpose({
+    root: textRef,
+    input: inputRef,
+    getCursortPosition: getCursortPosition,
+})
+</script>
+
+<style scoped>
+.toolbar {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    min-width: 0;
+}
+
+.btn {
+    border: none;
+    background: transparent;
+    color: rgba(255, 255, 255, 0.8);
+    padding: 0 6px;
+    height: 22px;
+    line-height: 22px;
+    border-radius: 4px;
+    cursor: pointer;
+    font-weight: 600;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    user-select: none;
+}
+
+.btn:disabled {
+    opacity: 0.4;
+    cursor: not-allowed;
+}
+
+.btn.active {
+    color: var(--colors-primary-base);
+    background-color: rgba(var(--colors-primary-fill), 0.18);
+}
+
+.dropdown {
+    position: relative;
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    padding: 0 6px;
+    height: 22px;
+    border-radius: 4px;
+    cursor: pointer;
+    color: rgba(255, 255, 255, 0.8);
+    background: rgba(255, 255, 255, 0.06);
+    border: 1px solid rgba(255, 255, 255, 0.08);
+    user-select: none;
+}
+
+.dropdown-value {
+    font-size: 12px;
+    width: 24px;
+    text-align: center;
+}
+
+.dropdown-arrow {
+    font-size: 10px;
+    opacity: 0.7;
+}
+
+.dropdown-menu {
+    position: absolute;
+    bottom: calc(100% + 6px);
+    left: 0;
+    min-width: 92px;
+    max-height: 220px;
+    overflow: auto;
+    background: rgba(27, 27, 28, 0.95);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 6px;
+    padding: 6px 0;
+    z-index: 2;
+    backdrop-filter: blur(6px);
+}
+
+.dropdown-option {
+    height: 28px;
+    line-height: 28px;
+    padding: 0 10px;
+    font-size: 12px;
+    color: rgba(255, 255, 255, 0.85);
+    cursor: pointer;
+}
+
+.dropdown-option:hover {
+    background: rgba(255, 255, 255, 0.06);
+}
+
+.dropdown-option.active {
+    color: var(--colors-primary-base);
+}
+
+.a-letter {
+    position: relative;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 14px;
+    height: 14px;
+    color: rgba(255, 255, 255, 0.95);
+}
+.a-letter::after {
+    content: '';
+    position: absolute;
+    left: 1px;
+    right: 1px;
+    bottom: -3px;
+    height: 2px;
+    border-radius: 2px;
+    background: var(--a-color, #ffffff);
+}
+
+.hl-icon {
+    width: 14px;
+    height: 14px;
+    border-radius: 3px;
+    background: var(--hl-color, #ffe58f);
+    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18) inset;
+}
+
+.color-input {
+    position: absolute;
+    width: 1px;
+    height: 1px;
+    opacity: 0;
+    pointer-events: none;
+}
+
+.link {
+    position: relative;
+    display: inline-flex;
+    align-items: center;
+}
+
+.link-pop {
+    position: absolute;
+    bottom: calc(100% + 6px);
+    left: 0;
+    padding: 10px;
+    width: 320px;
+    height: 40px;
+    display: flex;
+    gap: 8px;
+    background: rgba(27, 27, 28, 0.95);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 6px;
+    z-index: 3;
+    backdrop-filter: blur(6px);
+}
+
+.link-input {
+    flex: 1;
+    height: 28px;
+    border-radius: 4px;
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    background: rgba(255, 255, 255, 0.06);
+    outline: none;
+    color: rgba(255, 255, 255, 0.9);
+    padding: 0 8px;
+    font-size: 12px;
+}
+
+.link-btn {
+    height: 28px;
+    padding: 0 10px;
+    border-radius: 4px;
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    background: rgba(var(--colors-primary-fill), 0.2);
+    color: rgba(255, 255, 255, 0.9);
+    cursor: pointer;
+    font-size: 12px;
+}
+
+.has-toolbar > .retouch {
+    justify-content: space-between !important;
+}
+</style>

+ 8 - 0
src/components/bill-ui/components/input/state.js

@@ -123,6 +123,13 @@ export const richtextPropsDesc = {
     onUpdatePos: Function,
 }
 
+export const richinputPropsDesc = {
+    ...textareaPropsDesc,
+    onUpdatePos: Function,
+}
+
+
+
 export const selectPropsDesc = {
     ...textPropsDesc,
     isTransform: {
@@ -212,6 +219,7 @@ const summary = {
     ...filePropsDesc,
     ...searchPropsDesc,
     ...richtextPropsDesc,
+    ...richinputPropsDesc,
     ...colorPropsDesc,
 }
 for (let key in summary) {

+ 34 - 17
src/components/tagging/sign-new.vue

@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="show && posStyle"
+    v-if="task3D && show && posStyle"
     class="hot-item pc"
     :style="posStyle"
     @mouseenter="isHover = true"
@@ -21,7 +21,7 @@
           <ui-audio
             v-if="tagging.audio"
             class="audio"
-            :src="getResource(getFileUrl(tagging.audio))"
+            :src="getResources(getFileUrl(tagging.audio))"
             ref="audio"
           />
         </h2>
@@ -72,12 +72,13 @@ import Preview from "../static-preview/index.vue";
 import { getTaggingStyle } from "@/store";
 import { getFileUrl } from "@/utils";
 import { sdk, TaggingPositionNode } from "@/sdk";
-import { custom, getResource } from "@/env";
+import { custom, getResource as getResources } from "@/env";
 
 import type { Tagging, TaggingPosition } from "@/store";
 import { useCameraChange, usePixel } from "@/hook/use-pixel";
 import { inRevise } from "bill/utils";
 import { defStyleType } from "@/api";
+import mitt from "mitt";
 
 export type SignProps = { tagging: Tagging; scenePos: TaggingPosition; show?: boolean };
 
@@ -94,13 +95,11 @@ const emit = defineEmits<{
 const desc = ref<HTMLDivElement>();
 const showDesc = computed(() => {
   const a = desc.value?.innerText.trim();
-  console.error("--aaa->", desc.value, a);
   return a;
 });
 
 const audio = ref();
 watchEffect(() => {
-  audio.value && console.error("准备好了!,");
   if (props.show && audio.value) {
     audio.value.play();
   }
@@ -109,20 +108,38 @@ const [posStyle, pos, pause, recovery] = usePixel(() => undefined);
 
 const queryItems = computed(() =>
   props.tagging.images.map((image) => ({
-    url: getResource(getFileUrl(image)),
+    url: getResources(getFileUrl(image)),
   }))
 );
-console.log(props.tagging.styleId);
 const taggingStyle = computed(() => getTaggingStyle(props.tagging.styleId));
-const tag = markRaw(
-  sdk.createTagging({
-    ...props.scenePos,
-    title: props.tagging.title,
-    position: props.scenePos.localPos,
-    canMove: false,
-    image: getFileUrl(taggingStyle.value!.icon),
-  })
-) as TaggingPositionNode;
+let task3D = sdk.createTagging({
+  ...props.scenePos,
+  title: props.tagging.title,
+  position: props.scenePos.localPos,
+  canMove: false,
+  image: getFileUrl(taggingStyle.value!.icon),
+});
+if (!task3D) {
+  emit("delete");
+}
+const emptyfn = () => {};
+const tag = (task3D
+  ? markRaw(task3D)
+  : {
+      changeCanMove: emptyfn,
+      changeMat: emptyfn,
+      changeFontSize: emptyfn,
+      changeTitle: emptyfn,
+      visibilityTitle: emptyfn,
+      changeType: emptyfn,
+      changeImage: emptyfn,
+      changePosition: emptyfn,
+      changeLineHeight: emptyfn,
+      visibility: emptyfn,
+      getImageCenter: emptyfn,
+      bus: mitt(),
+      destroy: emptyfn,
+    }) as TaggingPositionNode;
 const showDelete = ref(false);
 tag.showDelete = (show) => {
   showDelete.value = show;
@@ -184,7 +201,6 @@ tag.bus.on("changeLineHeight", (lineHeight) => {
 watch(getPosition, (p) => {
   changeTimeout = setTimeout(() => {
     if (inRevise(p, currentPosition)) {
-      console.log("更改当前位置");
       tag.changePosition(p);
       currentPosition = p;
     }
@@ -311,6 +327,7 @@ defineExpose(tag);
       p {
         margin-bottom: 10px;
         display: flex;
+
         span {
           flex: 0 0 auto;
         }

+ 1 - 0
src/env/index.ts

@@ -89,6 +89,7 @@ export type Params = {
   share?: boolean;
   single?: boolean;
   token?: string;
+  fromRoute?: string;
 };
 
 export const baseURL = params.baseURL ? params.baseURL : "";

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

@@ -47,6 +47,7 @@ export const flyTagging = (tagging: Tagging, callback?: () => void) => {
   };
   flyIndex(0);
   stopFly = () => {
+    console.log('stopstop')
     clearTimeout(timeout)
     isStop = true;
     stopFly = null;
@@ -146,4 +147,4 @@ export const flyModel = (model: FuseModel, mode: "pano" | "fuse", f = false) =>
   if (currentModel.value !== fuseModel) {
     loadModel(fuseModel);
   }
-};
+};

+ 18 - 10
src/layout/edit/scene-select.vue

@@ -22,7 +22,7 @@
           style="width: 244px"
         />
       </div>
-      <div class="table-layout" v-if="origin.length">
+      <div class="table-layout">
         <Tabs v-model:activeKey="type">
           <TabPane v-for="current in types" :key="current" :tab="current">
             <Table
@@ -44,13 +44,13 @@
           </TabPane>
         </Tabs>
       </div>
-      <div style="padding: 1px" v-else>
+      <!-- <div style="padding: 1px" v-else>
         <Empty
           :description="keyword.length ? '暂无搜索结果' : '暂无结果'"
           :image="Empty.PRESENTED_IMAGE_SIMPLE"
           className="ant-empty ant-empty-normal"
         />
-      </div>
+      </div> -->
     </div>
   </Modal>
 
@@ -157,7 +157,6 @@ const typeFilterScenes = computed(() => {
   const typeScenes: any = {};
   for (const type of types.value) {
     typeScenes[type] = origin.value
-    // && item.name.includes(keyword.value)
       .filter((item) => item.name && item.num )
       .filter((item) => item.type === type);
   }
@@ -178,12 +177,21 @@ const rowSelection: any = ref({
 
   hideSelectAll: true,
   onChange: (ids: string[]) => {
-    console.error(ids)
+    const key =  typeFilterScenes.value[type.value].map((item: any) => item.num).join('')
     ids = ids.filter(id => !selectIds.value.includes(id))
-    cache[type.value] = ids
+    if (!cache[type.value]) {
+      cache[type.value] = {}
+    }
+    cache[type.value][key] = ids
+
+
+
+
     const curIds = [...selectIds.value]
-    for (const key in cache) {
-      curIds.push(...cache[key])
+    for (const keyp in cache) {
+      for (const key in cache[keyp]) {
+        curIds.push(...cache[keyp][key])
+      }
     }
     selects.value = curIds
   },
@@ -218,7 +226,7 @@ const addModelHandler = createLoadPack(async (attachs: any[]) => {
       attach
     }
   });
-  const addPromises = items.map(item => addFuseModel(item.data, item.attach));
+  const addPromises = items.map(item => addFuseModel(item.data, item.attach).then(data => item.data = data));
 
   const addModels = await Promise.all(addPromises);
   await new Promise<void>((resolve) => {
@@ -237,7 +245,7 @@ const addModelHandler = createLoadPack(async (attachs: any[]) => {
   addModels.forEach(item => {
     if (getSceneModel(item)) {
       item.rotation = getSceneModel(item)!.getDefaultRotation();
-      getSceneModel(item)!.putInFrontOfCam()
+      // getSceneModel(item)!.putInFrontOfCam()
     }
 
   });

+ 64 - 48
src/sdk/association/fuseMode.ts

@@ -1,6 +1,6 @@
 import { SDK, SceneModel, ModelAttrRange } from "../sdk";
 import { toRaw, watch, reactive, ref, watchEffect } from "vue";
-import { custom, getResource } from "@/env";
+import { custom, getResource as getResources } from "@/env";
 import {
   diffArrayChange,
   shallowWatchArray,
@@ -25,7 +25,7 @@ import { currentLayout, RoutesName } from "@/router";
 import { unsetFactory } from "@/utils/unset";
 import { getTaggingPosNode, taggingGroup } from "./tagging";
 
-const us = unsetFactory()
+export const us = unsetFactory();
 
 // -----------------模型关联--------------------
 
@@ -62,7 +62,7 @@ const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
     const itemRaw = toRaw(item);
     let sceneModel: SceneModel;
     try {
-      
+      console.error("addMode", itemRaw);
       sceneModel = sdk.addModel({
         ...itemRaw,
         ...modelRange,
@@ -75,7 +75,7 @@ const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
           : item.modelType,
         url: [SceneType.SWSS, SceneType.SWYDSS].includes(item.type)
           ? item.url
-          : item.url && item.url.map(getResource),
+          : item.url && item.url.map(getResources),
         fromType: item.type,
       });
     } catch (e) {
@@ -86,6 +86,12 @@ const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
 
     sceneModelMap.set(itemRaw, sceneModel);
 
+    let stop = true;
+    sceneModel.bus.on("loadDone", () => {
+      setTimeout(() => {
+        stop = false;
+      }, 3000);
+    });
     let changeId: NodeJS.Timeout;
     sceneModel.bus.on("transformChanged", (transform) => {
       clearTimeout(changeId);
@@ -112,9 +118,9 @@ const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
         if (transform.scale) {
           transform.scale = round(transform.scale, 2);
 
-          taggingPositions.value.forEach(position => {
-            getTaggingPosNode(position)?.bus.emit('scaleChanged', true)
-          })
+          taggingPositions.value.forEach((position) => {
+            getTaggingPosNode(position)?.bus.emit("scaleChanged", true);
+          });
         }
 
         const updateKeys = Object.keys(transform);
@@ -124,8 +130,12 @@ const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
         }
 
         if (deepIsRevise(update, transform)) {
-          console.error('change', item)
-          us.unSet(() => Object.assign(item, transform));
+          console.error("change", item);
+          if (stop) {
+            us.unSet(() => Object.assign(item, transform));
+          } else {
+            Object.assign(item, transform);
+          }
         }
       }, 16);
     });
@@ -133,11 +143,11 @@ const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
     sceneModel.bus.on("changeSelect", (select) => {
       us.unSet(() => {
         // if (custom.showMode === "fuse") {
-          if (custom.currentModel === item && !select) {
-            custom.currentModel = null;
-          } else if (custom.currentModel !== item && select) {
-            custom.currentModel = item;
-          }
+        if (custom.currentModel === item && !select) {
+          custom.currentModel = null;
+        } else if (custom.currentModel !== item && select) {
+          custom.currentModel = item;
+        }
         // }
       });
     });
@@ -165,7 +175,7 @@ const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
 export const activeModel = (status: {
   showMode: "fuse" | "pano";
   active?: FuseModel;
-  fore?: boolean
+  fore?: boolean;
 }) => {
   const oldStatus = {
     showMode: custom.showMode,
@@ -188,30 +198,31 @@ export const activeModel = (status: {
   if (model && status.active === oldStatus.active) {
     if (status.showMode === "pano") {
       if (model) {
-        console.error('--->', model)
-        model.changeSelect(false)
+        console.error("--->", model);
+        model.changeSelect(false);
         model.flyInPano();
       }
     } else {
       if (model) {
         model.flyOutPano();
-        custom.currentModel === status.active && model.changeSelect(true)
+        custom.currentModel === status.active && model.changeSelect(true);
       }
     }
   } else {
     if (oldStatus.showMode !== status.showMode) {
       if (oldStatus.showMode === "pano") {
         if (oldModel) {
-          console.error('---> a', oldModel)
+          console.error("---> a", oldModel);
           oldModel.flyOutPano();
-          custom.currentModel === oldStatus.active && oldModel.changeSelect(true)
+          custom.currentModel === oldStatus.active &&
+            oldModel.changeSelect(true);
         }
       }
     }
     if (status.showMode === "pano") {
       if (model) {
-        console.error('---> b', oldModel)
-        model.changeSelect(false)
+        console.error("---> b", oldModel);
+        model.changeSelect(false);
         model.flyInPano();
       }
     } else {
@@ -221,7 +232,7 @@ export const activeModel = (status: {
 
   setTimeout(() => {
     if (status.showMode !== "pano" && model) {
-      if (oldStatus.showMode !== 'pano' || status.fore) {
+      if (oldStatus.showMode !== "pano" || status.fore) {
         model && model.changeSelect(true);
       }
     }
@@ -234,19 +245,22 @@ export const activeModel = (status: {
 export const associationModels = (sdk: SDK) => {
   sdk.sceneBus.on("modeChange", (data) => {
     if (data.active || data.model) {
-      activeModel({ active: getFuseModel(data.active || data.model)!, showMode: data.mode })
+      activeModel({
+        active: getFuseModel(data.active || data.model)!,
+        showMode: data.mode,
+      });
     }
     custom.showMode = data.mode;
 
-    console.error('modeChange', data, (data.active || data.model))
-    if ((data.active || data.model) && data.mode === 'pano') {
-      (data.active || data.model).changeSelect(false)
+    console.error("modeChange", data, data.active || data.model);
+    if ((data.active || data.model) && data.mode === "pano") {
+      (data.active || data.model).changeSelect(false);
     }
   });
   sdk.sceneBus.on("panoModelChange", (data) => {
     custom.showMode = "pano";
     custom.currentModel = getFuseModel(data)!;
-    data.changeSelect(false)
+    data.changeSelect(false);
     // activeModel({ active: getFuseModel(data)!, showMode: 'pano' })
   });
 
@@ -278,15 +292,19 @@ export const associationModels = (sdk: SDK) => {
           watch(
             () => item.scale,
             () => {
-              us.isUnSet || getSceneModel(item)?.changeScale(item.scale)
+              us.isUnSet || getSceneModel(item)?.changeScale(item.scale);
             }
             // { immediate: true }
-          ); 
+          );
           watch(
             () => item.position,
             () => {
               if (!us.isUnSet) {
-                console.log('position', item.raw.modelTitle, toRaw(item.position))
+                console.log(
+                  "position",
+                  item.raw.modelTitle,
+                  toRaw(item.position)
+                );
                 getSceneModel(item)?.changePosition(item.position);
               }
             }
@@ -296,7 +314,6 @@ export const associationModels = (sdk: SDK) => {
             () => item.rotation,
             () => {
               if (!us.isUnSet) {
-                console.log('rotation', item.raw.modelTitle,  toRaw(item.rotation))
                 getSceneModel(item)?.changeRotation(toRaw(item.rotation));
               }
             }
@@ -322,22 +339,21 @@ export const associationModels = (sdk: SDK) => {
   });
 };
 
-
 export const getSupportPano = (model: FuseModel) => {
-const supportPano = ref(false);
-const show = getFuseModelShowVariable(model);
-watchEffect(() => {
-  if (!show.value) {
-    supportPano.value = false;
-    return;
-  }
-  const sceneModel = getSceneModel(model);
-  const support = sceneModel?.supportPano();
-  supportPano.value = !!support;
+  const supportPano = ref(false);
+  const show = getFuseModelShowVariable(model);
+  watchEffect(() => {
+    if (!show.value) {
+      supportPano.value = false;
+      return;
+    }
+    const sceneModel = getSceneModel(model);
+    const support = sceneModel?.supportPano();
+    supportPano.value = !!support;
 
-  sceneModel?.bus.on("loadDone", () => {
-    supportPano.value = sceneModel?.supportPano();
+    sceneModel?.bus.on("loadDone", () => {
+      supportPano.value = sceneModel?.supportPano();
+    });
   });
-});
-return supportPano
-}
+  return supportPano;
+};

+ 10 - 3
src/sdk/association/measure.ts

@@ -37,9 +37,17 @@ export const getSceneMeasureDesc = (smMeasure: SceneMeasure, measure: Measure) =
 
 export const associationMessaure = <T extends MeasureType>(smMeasure: SceneMeasure<T>, measure: Measure<T>) => {
   smMeasure.bus.on('update', ([points, modelIds]) => {
-    us.unSet(() => measure.positions = points.map((point, i) => ({ point, modelId: modelIds[i] })))
+    // us.unSet(() => 
+      measure.positions = points.map((point, i) => ({ point, modelId: modelIds[i] }))
+  // )
+  })
+  let timeout: any
+  smMeasure.bus.on('highlight', selected => {
+    clearTimeout(timeout)
+    timeout = setTimeout(() => {
+      us.unSet(() => measure.selected = selected)
+    })
   })
-  smMeasure.bus.on('highlight', selected => us.unSet(() => measure.selected = selected))
 }
 
 export const associationMessaures = (sdk: SDK) => {
@@ -93,4 +101,3 @@ export const associationMessaures = (sdk: SDK) => {
     )
   })
 }
-

+ 3 - 3
src/sdk/cover/index.js

@@ -1094,7 +1094,7 @@ export const enter = ({ dom, mapDom, isLocal, lonlat, scenes, laserRoot, laserOS
                     }
 
                     msg = Potree.Common.CloneObject(msg) 
-                    //console.log(model.name, msg)
+                    console.log(model.name, msg)
                     bus.emit('transformChanged', msg)
                 }
                 model.addEventListener('transformChanged', (e) => {
@@ -1192,7 +1192,7 @@ export const enter = ({ dom, mapDom, isLocal, lonlat, scenes, laserRoot, laserOS
                 },
 
                 putInFrontOfCam(){//首次加载放在面前,高度和相机一致。(但不保证会不会被遮挡)
-         
+                    if(model.hasLonLat)return 
                     let size = Math.max(1, new THREE.Vector2().copy(model.boundSize).length() * 0.7 ) 
                     let vec = viewer.mainViewport.view.direction.clone().setZ(0).multiplyScalar(size)
                     let pos = new THREE.Vector3().addVectors(viewer.mainViewport.view.position, vec)
@@ -1265,7 +1265,7 @@ export const enter = ({ dom, mapDom, isLocal, lonlat, scenes, laserRoot, laserOS
                     }else{ */
                         model && model.rotation.setFromVector3(rot)
                     //} 
-                    model.dispatchEvent({ type: 'rotation_changed' , by2d:true/* , byControl:true */})
+                    model.dispatchEvent({ type: 'rotation_changed' /*, by2d:true , byControl:true */})
                 },
                 getModelPose(){
                     return {

+ 6 - 5
src/store/sys.ts

@@ -5,6 +5,7 @@ import { useViewStack } from "@/hook";
 
 import type { UnwrapRef } from "vue";
 import { currentMeta } from "@/router";
+import { stackVar } from "@/components/drawing/hook";
 
 const Flags = {
   EDIT: 0b10,
@@ -99,11 +100,11 @@ export type AutoSetModeSetting<T> = {
   recovery?: () => void;
 };
 
-let isUnset = false;
+export let isUnset = stackVar(false);
 export const unSetModelUpdate = (run: () => void) => {
-  isUnset = true;
+  const pop = isUnset.push(true)
   run();
-  nextTick(() => (isUnset = false));
+  nextTick(pop);
 };
 export const autoSetModeCallback = <T extends object>(
   current: T,
@@ -136,7 +137,7 @@ export const autoSetModeCallback = <T extends object>(
   };
 
   const handler = (newv: UnwrapRef<T>, oldv?: UnwrapRef<T>) => {
-    if (isSave || isUnset || isLeaveIng) return;
+    if (isSave || isUnset.value || isLeaveIng) return;
     if (!setting.isUpdate || setting.isUpdate(newv, oldv)) {
       isEdit.value || enterEdit();
       isOld.value || enterOld();
@@ -158,4 +159,4 @@ export const useAutoSetMode = <T extends object>(
 ) => {
   const startWatch = autoSetModeCallback(current, setting, last);
   useViewStack(startWatch);
-};
+};

+ 6 - 4
src/utils/unset.ts

@@ -1,18 +1,20 @@
+import { isUnset as isStoreUnset } from "@/store";
 import { nextTick } from "vue";
 
 export const unsetFactory = () => {
   let isUnSet = false;
+  
   const unSet = (fn: () => void) => {
-    console.error("unset");
+    const pop = isStoreUnset.push(true)
     isUnSet = true;
     fn();
-    nextTick(() => (isUnSet = false));
+    nextTick(pop)
   };
 
   return {
     get isUnSet() {
-      return isUnSet
+      return isStoreUnset.value
     },
     unSet
   }
-};
+};

+ 73 - 11
src/views/login.vue

@@ -2,7 +2,7 @@
   <div class="login-layout">
     <div class="login-content">
       <div class="header">
-        <img src="/favicon.ico" />
+        <!-- <img src="/favicon.ico" /> -->
         <p>登录多元融合</p>
       </div>
 
@@ -30,6 +30,16 @@
           </template>
         </ui-input>
         <br />
+        <div class="code-row">
+          <ui-input
+            class="code-input"
+            type="text"
+            placeholder="请输入验证码"
+            v-model="code"
+          />
+          <img :src="codeImg" class="code-img" @click="refer" />
+        </div>
+        <br />
         <ui-input
           type="checkbox"
           @click.stop
@@ -40,33 +50,64 @@
         />
       </div>
       <div class="bottom">
-        <ui-button type="submit" @click="login(username, password)">登录</ui-button>
+        <ui-button type="submit" @click="login(username, password, code)">登录</ui-button>
       </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref } from "vue";
+import { ref, computed } from "vue";
 import { encodePwd } from "@/utils";
 import GAxios from "axios";
-import { fetchSetting, setToken } from "@/api";
+import { setToken } from "@/api";
 import { Message } from "bill/expose-common";
 import { params } from "@/env";
-import { GET_SETTING, UPDATE_SETTING } from "@/api/constant";
+import { GET_SETTING, getCode } from "@/api/constant";
 import { currentLayout, RoutesName } from "@/router";
 
 const username = ref(localStorage.getItem("fuse-username") || "");
 const password = ref(localStorage.getItem("fuse-password") || "");
+const code = ref("");
 const mark = ref(!!localStorage.getItem("fuse-mark"));
 const showPwd = ref(false);
-const login = (username: string, password: string) => {
+const guid = () => {
+  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
+    let r = (Math.random() * 16) | 0,
+      v = c == "x" ? r : (r & 0x3) | 0x8;
+    return v.toString(16);
+  });
+};
+// 图片验证码
+let baseURL = "https://test-mix3d.4dkankan.com";
+const imgKey = ref(guid());
+const refer = () => (imgKey.value = guid());
+if (window.location.href.indexOf("localhost") !== -1) {
+  baseURL = "https://test-mix3d.4dkankan.com";
+} else if (window.location.href.indexOf("test") !== -1) {
+  baseURL = "https://test-mix3d.4dkankan.com";
+} else {
+  baseURL = "https://mix3d.4dkankan.com";
+}
+const codeImg = computed(() => baseURL + getCode + "?key=" + imgKey.value);
+const getDeptId = () => {
+  const fromRoute = params.fromRoute;
+  if (fromRoute === "fire") return "1";
+  if (fromRoute === "cjzfire") return "DEP000011734134901809483776";
+  if (fromRoute === "xmfire") return "3";
+  if (fromRoute === "criminal") return "2";
+  return "";
+};
+const login = (username: string, password: string, code: string) => {
   if (!username) {
     return Message.error("账号不能为空");
   }
   if (!password) {
     return Message.error("密码不能为空");
   }
+  if (!code && !import.meta.env.DEV) {
+    return Message.error("验证码不能为空");
+  }
 
   const isView = [RoutesName.show, RoutesName.signModel, RoutesName.error].includes(
     currentLayout.value!
@@ -75,11 +116,13 @@ const login = (username: string, password: string) => {
   const headers = { fusionId: params.caseId, "page-type": type };
 
   GAxios.post(
-    "/service/manage/login",
+    "/fusion/fdLogin",
     {
       password: encodePwd(password),
       userName: username,
-      username: username,
+      phoneNum: username,
+      code: code,
+      deptId: getDeptId(),
     },
     { headers }
   ).then(async (res) => {
@@ -114,9 +157,9 @@ const login = (username: string, password: string) => {
   });
 };
 
-if (import.meta.env.DEV) {
-  login("super-admin", "Aa123456");
-}
+// if (import.meta.env.DEV) {
+//   login("super-admin", "Aa123456", "");
+// }
 </script>
 
 <style lang="scss" scoped>
@@ -183,5 +226,24 @@ if (import.meta.env.DEV) {
     border: 1px solid var(--color-main-normal) !important;
     background: var(--color-main-normal) !important;
   }
+
+  .code-row {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    margin-top: 20px;
+  }
+
+  .code-input {
+    flex: 1;
+  }
+
+  .code-img {
+    width: 120px;
+    height: 40px;
+    cursor: pointer;
+    user-select: none;
+    flex: none;
+  }
 }
 </style>

+ 32 - 9
src/views/setting/select-back.vue

@@ -20,6 +20,7 @@
             >
               <MenuItem
                 v-for="item in back.children"
+                :key="item.value"
                 @click="
                   value[1] !== item.value && $emit('update:value', [null, item.value])
                 "
@@ -46,6 +47,24 @@
         @click="value[0] !== back.value && $emit('update:value', [back.value, null])"
       />
     </template>
+    <!-- 上传入口 -->
+      <!-- <ui-input
+        class="img-input"
+        type="file"
+        width="88px"
+        height="88px"
+        preview
+        :accept="'.png'"
+        :disable="true"
+        :multiple="false"
+        :maxSize="audioSize"
+      >
+        <template v-slot:replace>
+          <p class="audio-tip">
+            <ui-icon type="add" />
+          </p>
+        </template>
+    </ui-input> -->
   </div>
 </template>
 
@@ -68,33 +87,33 @@ const backs = computed(() => [
   {
     label: "地图",
     type: "map",
-    image: "/oss/fusion/default/images/map.png",
+    image: "https://4dkk.4dage.com/fusion/default/images/map.png",
     value: "dt",
     children: sysTiles.value.map((t) => ({ label: t.name, value: t.id })),
   },
   {
     label: "蓝天白云",
     type: "img",
-    image: "/oss/fusion/default/images/pic_ltby@2x.png",
-    value: "/oss/fusion/default/images/蓝天白云.jpg",
+    image: "https://4dkk.4dage.com/fusion/default/images/pic_ltby@2x.png",
+    value: "https://4dkk.4dage.com/fusion/default/images/蓝天白云.jpg",
   },
   {
     label: "乌云密布",
     type: "img",
-    image: "/oss/fusion/default/images/pic_wymb@2x.png",
-    value: "/oss/fusion/default/images/乌云密布.jpg",
+    image: "https://4dkk.4dage.com/fusion/default/images/pic_wymb@2x.png",
+    value: "https://4dkk.4dage.com/fusion/default/images/乌云密布.jpg",
   },
   {
     label: "夜空",
     type: "img",
-    image: "/oss/fusion/default/images/pic_yk@2x.png",
-    value: "/oss/fusion/default/images/夜空.jpg",
+    image: "https://4dkk.4dage.com/fusion/default/images/pic_yk@2x.png",
+    value: "https://4dkk.4dage.com/fusion/default/images/夜空.jpg",
   },
   {
     label: "傍晚",
     type: "img",
-    image: "/oss/fusion/default/images/pic_bw@2x.png",
-    value: "/oss/fusion/default/images/傍晚.jpg",
+    image: "https://4dkk.4dage.com/fusion/default/images/pic_bw@2x.png",
+    value: "https://4dkk.4dage.com/fusion/default/images/傍晚.jpg",
   },
 ]);
 
@@ -117,6 +136,10 @@ const activeParent = computed(() => {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
   gap: 20px;
+  .img-input{
+    border: 1px solid #F5F5F5;
+    border-radius: 6px;
+  }
 }
 .child-layout-parent {
   position: relative;

+ 1 - 0
src/views/tagging/hot/edit.vue

@@ -47,6 +47,7 @@
         height="158px"
         :placeholder="defStyleType.id === type ? '描述:' : '特征描述:'"
         type="richtext"
+        :rich="true"
         v-model="tagging.desc"
         :maxlength="200"
       />

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

@@ -84,7 +84,7 @@ const quiskAdd = async (key: string) => {
 
 const exposeTagging = async () => {
   const list = await selectMaterials({
-    useType: "trace_evidence",
+    // useType: "trace_evidence",
     readonly: true,
     format: ["jpg", "png", "mp4", "mp3", "wav", "jpeg"],
   });
@@ -114,7 +114,7 @@ const exposeTagging = async () => {
       tagging.audioName = item.name;
     }
 
-    console.log(item);
+    console.log('item11111111', item);
     if (item.dictId && item.useType === "trace_evidence") {
       const typeId = getStyleTypeId(item.dictId);
       const styles = taggingStyles.value.filter((item) => item.typeId === typeId);
@@ -125,9 +125,10 @@ const exposeTagging = async () => {
       const styles = taggingStyles.value.filter(
         (item) => item.typeId === defStyleType.id
       );
-      console.log(defStyleType);
       if (styles.length) {
-        tagging.styleId = styles[0].id;
+        // 火调这里特殊,默认图片是11
+        // tagging.styleId = styles[0].id;
+        tagging.styleId = styles[11].id;
       }
     }
     return tagging;

+ 5 - 4
vite.config.ts

@@ -7,10 +7,9 @@ import { resolve } from 'path'
 
 // const oss = `https://phx.4dkankan.com/`
 // const ip = `https://phx.4dkankan.com/`
-// const oss = `http://192.168.0.125:1804/`
-// const ip = `http://192.168.0.125:1804/`
 const oss = `https://4dkk.4dage.com/`
 const ip = `https://test-mix3d.4dkankan.com/`
+
 const proxy = {
   '/offlineData': {
     target: 'http://192.168.0.13:8080/',
@@ -83,7 +82,9 @@ export default defineConfig({
       input
     },
   },
-  plugins: [vue(), mkcert() ],
+  plugins: [vue(), 
+    // mkcert() 
+  ],
   css: {
     preprocessorOptions: {
       less: {
@@ -111,7 +112,7 @@ export default defineConfig({
     port: 7173,
     // open: true,
     proxy: proxy,
-    https: true
+    // https: true
   },
   preview: {
     proxy