活动介绍
file-type

深入解析Vue2 Proxy实现的双向数据绑定

ZIP文件

下载需积分: 30 | 9KB | 更新于2025-03-08 | 66 浏览量 | 0 下载量 举报 收藏
download 立即下载
### Vue2双向绑定原理解析 Vue.js 是一个流行的前端JavaScript框架,以数据驱动和组件化的思想设计。Vue.js的核心特性之一是实现了数据和视图的双向绑定,也就是当数据变化时,视图自动更新,同时视图上的操作也能更新数据。这种机制大大简化了前端的开发流程,特别是对于表单处理等场景。 #### 1. Vue.js中双向绑定的实现方式 在Vue.js中,双向绑定主要通过Object.defineProperty()方法实现,它允许我们精确地添加或修改对象的属性。Vue.js利用这个方法,定义了一个Observer对象,它能够对所有数据进行监听,一旦数据发生变化,就会通知依赖收集器(Dep)。 在视图层,Vue.js使用了虚拟DOM(Virtual DOM)和模板编译器,将模板转换为渲染函数,当数据变化时,虚拟DOM会重新渲染并更新真实DOM。 #### 2. Vue2的双向绑定原理 Vue2的双向绑定涉及几个核心概念:响应式(Reactive)、依赖收集(Dep)、观察者(Watcher)和虚拟DOM(Virtual DOM)。 - **响应式(Reactive)**:指的是Vue实例初始化时,对data选项中的数据进行遍历,使用Object.defineProperty()将其转换为getter/setter的形式。当数据被访问和修改时,能够触发getter/setter函数,从而收集依赖和更新数据。 - **依赖收集(Dep)**:每个属性都有一个对应的Dep实例,它用来存放依赖这个属性的Watcher实例。当数据被读取时,会触发getter函数,Dep开始执行依赖收集操作,将对应的Watcher添加到其内部的依赖列表中。 - **观察者(Watcher)**:当视图模板中使用到了某个数据时,对应的Watcher实例会被创建,并在数据变化时得到通知,从而执行更新视图的操作。 - **虚拟DOM(Virtual DOM)**:Vue使用虚拟DOM来描述实际的DOM结构,通过diff算法比较前后两次的虚拟DOM结构,计算出需要更新的最小差异,并应用这些差异到实际的DOM节点上。 #### 3. Vue2中的Proxy与Object.defineProperty()的对比 Vue2的双向绑定主要依赖Object.defineProperty(),但这种实现方式有一些局限性。例如,它无法检测到对象属性的添加或删除,对于数组操作的监听也不够完善。在Vue3中,Vue的作者尤雨溪引入了Proxy来解决这些问题。 - **Proxy的优势**: - 可以直接监听整个对象,而无需遍历,也不需要使用递归的方式去遍历属性。 - 可以监听数组的变化,包括数组项的增加和删除。 - 可以直接实现对对象方法的拦截和访问控制。 - **Proxy的局限**: - 浏览器兼容性问题,Proxy是ES6的特性,旧版浏览器可能不支持。 - 性能开销较Object.defineProperty()可能略高。 #### 4. Vue2的双向绑定的使用场景 在Vue.js中,双向绑定最常用于表单元素,使得表单数据的绑定和更新变得非常方便。开发者只需要在模板中使用v-model指令,就可以创建一个双向绑定的输入框,从而避免了手动同步输入值和数据的繁琐工作。 ```html <input v-model="message"> ``` 以上代码会创建一个双向绑定的输入框,用户输入的内容会实时更新到Vue实例的message属性中。 #### 5. Vue2中双向绑定的注意事项 尽管双向绑定极大地提高了开发效率,但它也并非没有缺点,开发者在使用时需要注意以下几点: - 双向绑定可能会让开发者对数据流向的理解变得模糊,特别是在大型项目中,这可能会导致调试和维护上的困难。 - 由于双向绑定的存在,使用第三方库时需要更加谨慎,避免数据流向出现混乱。 - 避免将复杂的逻辑处理放在getter/setter中,因为这会使得数据变更的原因变得难以追踪。 ### 结语 Vue2中的双向绑定是其强大功能的一个方面,它使得数据与视图的同步变得更加高效和自然。虽然在Vue3中已经切换到Proxy作为默认的数据监听手段,但了解Vue2中的实现原理对于理解Vue框架的基本概念以及提升开发技能仍然具有重要意义。

相关推荐

filetype

{ "name": "iview-admin", "version": "2.0.0", "author": "Lison<[email protected]>", "private": false, "scripts": { "dev": "vue-cli-service serve --open --mode development", "dev:prod": "vue-cli-service serve --open --mode production", "build": "vue-cli-service build", "build:dev": "vue-cli-service build --mode development", "dev-to-oss": "node deploy.js dat", "build:dev-to-oss": "vue-cli-service build --mode development && node deploy.js dat", "build:prod": "vue-cli-service build --mode production", "prod-to-oss": "node deploy.js prod", "build:prod-to-oss": "vue-cli-service build --mode production && node deploy.js prod", "oss:all": "yarn build:dev-to-oss && yarn build:prod-to-oss", "lint": "vue-cli-service lint", "test:unit": "vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e" }, "dependencies": { "ali-oss": "^6.17.1", "axios": "^0.18.0", "clipboard": "^2.0.0", "codemirror": "^5.38.0", "countup": "^1.8.2", "cropperjs": "^1.2.2", "dayjs": "^1.7.7", "echarts": "^4.0.4", "html2canvas": "^1.0.0-alpha.12", "iview": "^3.2.2", "iview-area": "^1.5.17", "js-cookie": "^2.2.0", "simplemde": "^1.11.2", "sortablejs": "^1.7.0", "tree-table-vue": "^1.1.0", "v-org-tree": "^1.0.6", "vue": "^2.5.10", "vue-i18n": "^7.8.0", "vue-router": "^3.0.1", "vuedraggable": "^2.16.0", "vuex": "^3.0.1", "wangeditor": "^4.7.15", "xlsx": "^0.13.3" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.0.1", "@vue/cli-plugin-eslint": "^3.0.1", "@vue/cli-plugin-unit-mocha": "^3.0.1", "@vue/cli-service": "^3.0.1", "@vue/eslint-config-standard": "^3.0.0-beta.10", "@vue/test-utils": "^1.0.0-beta.10", "chai": "^4.1.2", "eslint-plugin-cypress": "^2.0.1", "less": "^2.7.3", "less-loader": "^4.0.5", "lint-staged": "^6.0.0", "mockjs": "^1.0.1-beta3", "qs": "^6.6.0", "vue-template-c

filetype

如下述代码1,是一个弹窗的代码页面(如图1所示),调整一下左侧的"SQL查询",当分辨率高变形的有点厉害了,请参照代码2中的样式所示,请优化"SQL查询"的样式,并完整的写出修改后的代码(不改变代码1中的功能,即"sql查询"框中能够输入sql语句不变) ### 代码1:src\views\handle\dataset\add\AddView.vue ```vue <template>
<el-container class="layout-container"> <el-aside width="40%" class="config-aside">

<el-icon><setting /></el-icon> SQL数据集配置

<el-form :model="form" :rules="rules" label-position="top" class="config-form" ref="formRef" > <el-form-item v-if="props.id===''" label="数据集名称" prop="name" class="form-item-card inline-form-item"> <el-input v-model="form.name" placeholder="例如: 用户行为分析数据集" size="large" :prefix-icon="Document" /> </el-form-item> <el-form-item label="SQL查询" prop="sql" class="form-item-card sql-editor-item">
<VAceEditor v-model:value="form.sql" lang="sql" theme="github" style="height: 300px; width: 100%" :options="{ enableBasicAutocompletion: true, enableLiveAutocompletion: true, highlightActiveLine: true, showLineNumbers: true, tabSize: 2, }" />
<el-tag type="info" size="small"> <el-icon><info-filled /></el-icon> 提示: 请确保SQL语法正确且符合数据源规范 </el-tag>
</el-form-item> </el-form>
</el-aside> <el-main class="preview-main">

<el-icon><data-line /></el-icon> 数据预览

<el-button type="warning" plain size="small" @click="emit('cancel')" :icon="Close">取消</el-button> <el-button type="success" plain size="small" :disabled="!form.sql" @click="fetchPreviewData" :icon="Refresh">执行SQL预览</el-button> <el-button type="primary" plain size="small" @click="handleSave" :icon="Check">保存</el-button>
<zr-table :tableModule="tableModule" />
</el-main> </el-container>
</template> <script setup> import {ref, reactive, onMounted, getCurrentInstance} from 'vue' import { VAceEditor } from 'vue3-ace-editor' import '@/components/CodeEdit/ace-config.js' import { Setting, DataLine, Document, Refresh, Check, Close, InfoFilled, Edit, Download } from '@element-plus/icons-vue' import ZrTable from "@/components/ZrTable/index.vue"; import {buildFilterSos} from "@/components/ZrTable/table.js"; import { handleDataSetCreate, handleDataSetGet, handleDataSetPreviewData, handleDataSetReconstruct } from "@/api/handle/dataset.js"; const { proxy } = getCurrentInstance() const props = defineProps({ sceneId:{ type: String, default: '', }, id:{ type: String, default: '', } }) const emit = defineEmits(['saved', 'cancel']) const form = ref({ id:props.id, name: '', sceneId:props.sceneId, type:'view', sql: '', info:'', }) //数据预览 const columnsData= ref([]) const queryData = ref([]) const state = reactive({ columns: columnsData, // 表格配置 query: queryData, // 查询条件配置 queryForm: {}, // 查询form表单 loading: false, // 加载状态 dataList: [], // 列表数据 pages:false, }) const { loading, dataList, columns, pages, query, queryForm } = toRefs(state) const formRef = ref(null) // 预览数据 // 传给子组件的 const tableModule = ref({ callback: fetchPreviewData, // 回调,子组件中可以看到很多调用callback的,这里对应的是获取列表数据的方法 // 以下不说了,上面都给解释了 queryForm, columns, dataList, loading, pages, query, }) const rules = reactive({ name: [ { required: true, message: '请输入数据集名称', trigger: 'blur' }, { max: 50, message: '名称长度不能超过50个字符', trigger: 'blur' } ], sql: [ { required: true, message: '请输入SQL查询语句', trigger: 'blur' }, { validator: (rule, value, callback) => { if (!value || !value.trim()) { callback(new Error('SQL不能为空')) } else if (!value.toLowerCase().includes('select')) { callback(new Error('SQL必须是SELECT查询语句')) } else { callback() } }, trigger: 'blur' } ] }) async function fetchPreviewData() { state.loading = true const valid = await formRef.value.validate() if (!valid) { proxy.$modal.msgError("校验错误,请修改好再次执行") return } // 掉自己的接口,切勿复制粘贴 handleDataSetPreviewData({ sql:form.value.sql }).then((res) => { if (res.success) { if(res.data.pageData.length>0){ for (let key in res.data.pageData[0]) { columnsData.value.push({prop: key, label:key, align: 'center',show:1}) } } state.dataList = res.data.pageData proxy.$modal.msgSuccess('SQL执行成功') } else { proxy.$modal.msgError(res.message) } }) state.loading = false } const handleSave = async () => { const valid = await formRef.value.validate() if (!valid) return form.value.info=JSON.stringify({ sql:form.value.sql }) const method=form.value.id===""?handleDataSetCreate:handleDataSetReconstruct method(form.value).then((res) => { if (res.success) { emit('saved',{id: res.data }) } else { proxy.$modal.msgError(res.message) } }) } watch(() => props.id, (newid, oldid) => { if (newid !== oldid && newid!=="") { // 引用变化时触发 handleDataSetGet(newid).then((res) => { if(res.success){ form.value.sql=JSON.parse(res.data.info)?.sql; } }) } }, { immediate: true } // 注意:不要用 deep: true ); </script> <style scoped lang="scss"> .sql-dataset-container { height: calc(90vh - 60px); padding: 20px; background-color: #f5f7fa; } .layout-container { height: 100%; background: #fff; border-radius: 12px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); overflow: hidden; display: flex; } .config-aside { background: #f9fafc; border-right: 1px solid var(--el-border-color-light); padding: 24px; display: flex; flex-direction: column; } .preview-main { padding: 24px; background: #fff; display: flex; flex-direction: column; } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; h2 { margin: 0; color: var(--el-text-color-primary); font-weight: 600; font-size: 18px; display: flex; align-items: center; gap: 8px; } .header-left, .header-right { display: flex; align-items: center; } } .preview-content { flex: 1; border: 1px solid var(--el-border-color-light); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; } .config-form { flex: 1; display: flex; flex-direction: column; :deep(.el-form-item) { margin-bottom: 20px; &.inline-form-item { :deep(.el-form-item__label) { display: inline-flex; align-items: center; width: auto; margin-right: 12px; padding-bottom: 0; } :deep(.el-form-item__content) { display: inline-flex; flex: 1; } } .el-form-item__label { font-weight: 500; padding-bottom: 8px; color: var(--el-text-color-regular); font-size: 14px; } } } .form-item-card { background: #fff; padding: 16px; border-radius: 8px; border-left: 3px solid var(--el-color-primary); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); } .sql-editor-item { flex: 1; display: flex; flex-direction: column; :deep(.el-form-item__content) { flex: 1; display: flex; flex-direction: column; } } .sql-editor-container { flex: 1; display: flex; flex-direction: column; border: 1px solid var(--el-border-color-light); border-radius: 4px; overflow: hidden; } .sql-tips { padding: 8px 12px; background: var(--el-color-info-light-9); border-top: 1px solid var(--el-border-color-light); .el-tag { width: 100%; justify-content: flex-start; .el-icon { margin-right: 6px; } } } .form-actions { margin-top: 24px; padding-top: 16px; display: flex; justify-content: flex-end; gap: 16px; border-top: 1px dashed var(--el-border-color); } .refresh-btn { :deep(.el-icon) { margin-right: 6px; } } @media (max-width: 992px) { .layout-container { flex-direction: column; } .config-aside { width: 100% !important; border-right: none; border-bottom: 1px solid var(--el-border-color-light); } } </style> ``` ### 代码2:src\views\handle\dataset\add\AddView.vue ```vue <template>
<el-container class="layout-container"> <el-aside class="config-aside">

<el-icon><setting /></el-icon> SQL数据集配置

<el-form :model="form" :rules="rules" label-position="top" class="config-form" ref="formRef" > <el-form-item v-if="props.id === ''" label="数据集名称" prop="name" class="form-item-card inline-form-item"> <el-input v-model="form.name" placeholder="例如: 用户行为分析数据集" size="large" :prefix-icon="Document" /> </el-form-item> <el-form-item label="SQL查询" prop="sql" class="form-item-card sql-editor-item">
<VAceEditor v-model:value="form.sql" lang="sql" theme="github" style="height: 100%; width: 100%" :options="{ enableBasicAutocompletion: true, enableLiveAutocompletion: true, highlightActiveLine: true, showLineNumbers: true, tabSize: 2, }" />
<el-tag type="info" size="small"> <el-icon><info-filled /></el-icon> 提示: 请确保SQL语法正确且符合数据源规范 </el-tag>
</el-form-item> </el-form>
</el-aside> <el-main class="preview-main">

<el-icon><data-line /></el-icon> 数据预览

<el-button type="warning" plain size="small" @click="emit('cancel')" :icon="Close">取消</el-button> <el-button type="success" plain size="small" :disabled="!form.sql" @click="fetchPreviewData" :icon="Refresh">执行SQL预览</el-button> <el-button type="primary" plain size="small" @click="handleSave" :icon="Check">保存</el-button>
<zr-table :tableModule="tableModule" />
</el-main> </el-container>
</template> <script setup> import {ref, reactive, onMounted, getCurrentInstance, watch} from 'vue' import { VAceEditor } from 'vue3-ace-editor' import '@/components/CodeEdit/ace-config.js' import { Setting, DataLine, Document, Refresh, Check, Close, InfoFilled, Edit, Download } from '@element-plus/icons-vue' import ZrTable from "@/components/ZrTable/index.vue"; import {buildFilterSos} from "@/components/ZrTable/table.js"; import { handleDataSetCreate, handleDataSetGet, handleDataSetPreviewData, handleDataSetReconstruct } from "@/api/handle/dataset.js"; const { proxy } = getCurrentInstance() const props = defineProps({ sceneId:{ type: String, default: '', }, id:{ type: String, default: '', } }) const emit = defineEmits(['saved', 'cancel']) const form = ref({ id:props.id, name: '', sceneId:props.sceneId, type:'view', sql: '', info:'', }) //数据预览 const columnsData= ref([]) const queryData = ref([]) const state = reactive({ columns: columnsData, // 表格配置 query: queryData, // 查询条件配置 queryForm: {}, // 查询form表单 loading: false, // 加载状态 dataList: [], // 列表数据 pages:false, }) const { loading, dataList, columns, pages, query, queryForm } = toRefs(state) const formRef = ref(null) // 预览数据 // 传给子组件的 const tableModule = ref({ callback: fetchPreviewData, // 回调,子组件中可以看到很多调用callback的,这里对应的是获取列表数据的方法 // 以下不说了,上面都给解释了 queryForm, columns, dataList, loading, pages, query, }) const rules = reactive({ name: [ { required: true, message: '请输入数据集名称', trigger: 'blur' }, { max: 50, message: '名称长度不能超过50个字符', trigger: 'blur' } ], sql: [ { required: true, message: '请输入SQL查询语句', trigger: 'blur' }, { validator: (rule, value, callback) => { if (!value || !value.trim()) { callback(new Error('SQL不能为空')) } else if (!value.toLowerCase().includes('select')) { callback(new Error('SQL必须是SELECT查询语句')) } else { callback() } }, trigger: 'blur' } ] }) async function fetchPreviewData() { state.loading = true const valid = await formRef.value.validate() if (!valid) { proxy.$modal.msgError("校验错误,请修改好再次执行") return } // 掉自己的接口,切勿复制粘贴 handleDataSetPreviewData({ sql:form.value.sql }).then((res) => { if (res.success) { columnsData.value = []; // 重置列数据 if(res.data.pageData.length>0){ for (let key in res.data.pageData[0]) { columnsData.value.push({prop: key, label:key, align: 'center',show:1}) } } state.dataList = res.data.pageData proxy.$modal.msgSuccess('SQL执行成功') } else { proxy.$modal.msgError(res.message) } }) state.loading = false } const handleSave = async () => { const valid = await formRef.value.validate() if (!valid) return form.value.info=JSON.stringify({ sql:form.value.sql }) const method=form.value.id===""?handleDataSetCreate:handleDataSetReconstruct method(form.value).then((res) => { if (res.success) { emit('saved',{id: res.data }) } else { proxy.$modal.msgError(res.message) } }) } watch(() => props.id, (newid, oldid) => { if (newid !== oldid && newid!=="") { // 引用变化时触发 handleDataSetGet(newid).then((res) => { if(res.success){ form.value.sql=JSON.parse(res.data.info)?.sql; } }) } }, { immediate: true } // 注意:不要用 deep: true ); </script> <style scoped lang="scss"> .sql-dataset-container { height: calc(90vh - 60px); padding: 16px; background-color: #f5f7fa; } .layout-container { height: 100%; background: #fff; border-radius: 8px; box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05); overflow: hidden; display: flex; /* 使用flex横向布局 */ flex-direction: row; /* 横向排列 */ } .config-aside { background: #f9fafc; border-right: 1px solid var(--el-border-color-light); /* 右侧边框分隔 */ padding: 18px; display: flex; flex-direction: column; width: 50%; /* 左侧占50%宽度 */ min-width: 300px; /* 最小宽度限制 */ } .preview-main { padding: 18px; background: #fff; display: flex; flex-direction: column; flex: 1; /* 右侧占剩余宽度 */ min-width: 300px; /* 最小宽度限制 */ } .section-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; margin-bottom: 16px; gap: 12px; h2 { margin: 0; color: var(--el-text-color-primary); font-weight: 600; font-size: 16px; display: flex; align-items: center; gap: 6px; white-space: nowrap; } } .preview-content { flex: 1; border: 1px solid var(--el-border-color-light); border-radius: 6px; overflow: hidden; display: flex; flex-direction: column; min-height: 250px; } .config-form { flex: 1; display: flex; flex-direction: column; :deep(.el-form-item) { margin-bottom: 16px; } } .form-item-card { background: #fff; padding: 12px; border-radius: 6px; border-left: 2px solid var(--el-color-primary); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); } .sql-editor-container { flex: 1; display: flex; flex-direction: column; border: 1px solid var(--el-border-color-light); border-radius: 4px; overflow: hidden; min-height: 220px; max-height: 400px; } .sql-tips { padding: 6px 10px; background: var(--el-color-info-light-9); border-top: 1px solid var(--el-border-color-light); .el-tag { width: 100%; justify-content: flex-start; font-size: 11px; padding: 4px 8px; } } /* 响应式调整 */ @media (max-width: 992px) { .layout-container { flex-direction: column; /* 小屏幕下改为纵向布局 */ } .config-aside, .preview-main { width: 100%; /* 占满宽度 */ min-width: auto; /* 取消最小宽度限制 */ } .config-aside { border-right: none; border-bottom: 1px solid var(--el-border-color-light); /* 底部边框分隔 */ max-height: 50%; /* 限制最大高度 */ } .sql-dataset-container { padding: 12px; height: auto; min-height: 100vh; } .section-header { flex-direction: column; align-items: stretch; .header-left, .header-right { width: 100%; } .header-right { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px; } } .sql-editor-container { max-height: 250px; min-height: 180px; } } @media (max-width: 768px) { .preview-content { min-height: 200px; } .section-header { gap: 8px; h2 { font-size: 15px; } } .header-right { grid-template-columns: repeat(2, 1fr) !important; .el-button { width: 100%; margin: 0 !important; } .el-button:last-child { grid-column: span 2; } } } @media (max-width: 480px) { .sql-dataset-container { padding: 8px; } .config-aside, .preview-main { padding: 12px; } .sql-editor-container { min-height: 150px; max-height: 200px; } .form-item-card { padding: 8px; } .el-input, .el-button { font-size: 13px !important; } } </style> ```

filetype

<template>
坐标系: <el-radio-group v-model="coordinateSystem" size="small"> <el-radio-button label="gcj02">高德/火星坐标</el-radio-button> <el-radio-button label="bd09">百度坐标</el-radio-button> </el-radio-group>
<Search @location-selected="handleLocationSelected" /> <LocationBar v-if="loaded" :update-interval="100" :use-dms-format="useDmsFormat" /> </template> <style> /* 原有样式完全不变 */ </style> <script setup lang="ts"> // 原有导入 + 新增坐标转换工具类导入 import { computed, onUnmounted, onMounted, reactive, ref } from "vue"; import LocationBar from "./location-bar.vue"; import Search from "./search.vue"; import initMap from "./init"; import { throttle } from "lodash"; import { loadRipplePoints, createMultipleRippleCircles } from "./circle.js"; import { $ prototype } from "../../main.ts"; import markerImage from "@/assets/images/building.png"; import { imageDark } from "naive-ui/es/image/styles"; // 新增:导入坐标转换工具类 import { CoordinateTransformer } from "./coordinate-transformer"; // 修复类型定义 type Rectangle = any; // 原有状态 + 新增坐标系选择状态 const miniMapContainer = ref<HTMLElement>(); let viewIndicator: Rectangle; const currentPosition = reactive({ longitude: 113.361538, latitude: 27.339318, }); const ZHUZHOU_EXTENT = { west: 112.5, east: 114.5, south: 26.0, north: 28.0, }; const rippleEntities = ref<any[]>([]); const heightThreshold = 80000; const indicatorStyle = ref({ left: "50%", top: "50%", display: "block", }); const loaded = ref(false); const useDmsFormat = ref(false); const overviewViewer = ref(null); let currentMarker: any = null; // 新增:坐标系选择状态(默认高德GCJ-02) const coordinateSystem = ref("gcj02"); // 'gcj02' | 'bd09' // 原有方法完全不变(波纹可见性更新) const updateRippleVisibility = throttle(() => { if (!$ prototype.$ map || rippleEntities.value.length === 0) return; let shouldShow = false; const cartographic = $ prototype.$ map.camera.positionCartographic; if (cartographic) { const cameraHeight = cartographic.height; shouldShow = cameraHeight > heightThreshold; } rippleEntities.value.forEach((entity) => { entity.show = shouldShow; }); }, 200); // 原有方法完全不变(指示器位置更新) const updateIndicatorPosition = () => { if (!$ prototype.$ map) return; const camera = $ prototype.$ map.camera; const rect = camera.computeViewRectangle(); if (!rect) return; const center = Cesium.Rectangle.center(rect); const lon = Cesium.Math.toDegrees(center.longitude); const lat = Cesium.Math.toDegrees(center.latitude); const constrainedLon = Math.max( ZHUZHOU_EXTENT.west, Math.min(ZHUZHOU_EXTENT.east, lon) ); const constrainedLat = Math.max( ZHUZHOU_EXTENT.south, Math.min(ZHUZHOU_EXTENT.north, lat) ); const lonPercent = ((constrainedLon - ZHUZHOU_EXTENT.west) / (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west)) * 100; const latPercent = 100 - ((constrainedLat - ZHUZHOU_EXTENT.south) / (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south)) * 100; indicatorStyle.value = { left: `${lonPercent}%`, top: `${latPercent}%`, display: "block", }; }; // 原有方法完全不变(鹰眼地图更新) const updateOverview = () => { if (!$ prototype.$ map || !overviewViewer.value) return; const rectangle = $ prototype.$ map.camera.computeViewRectangle(); if (!rectangle) return; updateIndicatorPosition(); }; // 原有方法完全不变(初始化鹰眼地图) const initMiniMap = () => { Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxMDhlNDdmYy03NzFhLTQ1ZTQtOWQ3NS1lZDAzNDc3YjE4NDYiLCJpZCI6MzAxNzQyLCJpYXQiOjE3NDcwNTMyMDN9.eaez8rQxVbPv2LKEU0sMDclPWyHKhh1tR27Vg-_rQSM"; if (!miniMapContainer.value) return; const worldProvider = new Cesium.UrlTemplateImageryProvider({ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", minimumLevel: 0, maximumLevel: 19, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); const zhuzhouProvider = new Cesium.UrlTemplateImageryProvider({ url: "http://124.232.190.30:9000/proxy/pk1725866655224/map/zzzsyx_18/{z}/{x}/{y}.png", minimumLevel: 1, maximumLevel: 17, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); overviewViewer.value = new Cesium.Viewer(miniMapContainer.value, { sceneMode: Cesium.SceneMode.SCENE2D, baseLayerPicker: false, homeButton: false, timeline: false, navigationHelpButton: false, animation: false, scene3DOnly: true, selectionIndicator: false, infoBox: false, imageryProvider: worldProvider, terrainProvider: undefined, mapProjection: new Cesium.WebMercatorProjection(), skyBox: false, skyAtmosphere: false, }); const zhuzhouLayer = overviewViewer.value.imageryLayers.addImageryProvider( zhuzhouProvider ); overviewViewer.value.camera.setView({ destination: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), }); const toolbar = overviewViewer.value.container.getElementsByClassName( "cesium-viewer-toolbar" )[0]; if (toolbar) toolbar.style.display = "none"; overviewViewer.value.cesiumWidget.creditContainer.style.display = "none"; initRectangle(); }; // 原有方法完全不变(初始化视图指示器) function initRectangle() { viewIndicator = overviewViewer.value.entities.add({ rectangle: { coordinates: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), material: Cesium.Color.RED.withAlpha(0.3), outline: true, outlineColor: Cesium.Color.RED, outlineWidth: 2, }, }); } // 原有方法完全不变(添加演示图形) function addDemoGraphics() { const chinaBoundary = $ prototype.$ map.dataSources.add( Cesium.GeoJsonDataSource.load("/shp_zz.geojson", { stroke: Cesium.Color.WHITE, fill: false, clampToGround: true, describe: null, }) ); chinaBoundary.then((dataSource) => { const entities = dataSource.entities.values; for (let entity of entities) { if (entity.polyline) { entity.polyline.fill = false; } } }); } // 原有方法完全不变(飞行到默认位置) function flyToDes() { const center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0); $ prototype.$ map.camera.flyTo({ destination: new Cesium.Cartesian3( -2432812.6687511606, 5559483.804371395, 2832009.419525571 ), orientation: { heading: 6.283185307179421, pitch: -1.0472145569408116, roll: 6.2831853071795205, }, complete: function () {}, }); } // 原有方法完全不变(监听相机变化) const setupCameraListener = () => { $ prototype.$ map.camera.changed.addEventListener(updateOverview); }; // 原有方法完全不变(鹰眼地图点击处理) const handleMiniMapClick = (event: MouseEvent) => { if (!miniMapContainer.value || !overviewViewer.value) return; const rect = miniMapContainer.value.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const xPercent = (x / rect.width) * 100; const yPercent = (y / rect.height) * 100; const lon = ZHUZHOU_EXTENT.west + (xPercent / 100) * (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west); const lat = ZHUZHOU_EXTENT.north - (yPercent / 100) * (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south); $ prototype.$ map.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1000000), }); }; // 原有生命周期方法完全不变 onMounted(() => { initMap(); loaded.value = true; addDemoGraphics(); flyToDes(); setTimeout(() => { initMiniMap(); setupCameraListener(); updateOverview(); }, 1000); (async () => { try { const ripplePoints = await loadRipplePoints(); rippleEntities.value = createMultipleRippleCircles($ prototype.$ map, ripplePoints); $ prototype.$ map.camera.changed.addEventListener(updateRippleVisibility); updateRippleVisibility(); } catch (error) { console.error("加载波纹圆失败:", error); } })(); }); // 原有方法完全不变(创建标记) const createMarker = (location: { lng: number; lat: number; name: string }) => { if (currentMarker) { $ prototype.$ map.entities.remove(currentMarker); } console.log("标记图片加载状态:", markerImage); console.log("创建标记位置:", location); currentMarker = $ prototype.$ map.entities.add({ position: Cesium.Cartesian3.fromDegrees(location.lng, location.lat, 10), label: { text: location.name + "(标记位置)", font: "18px sans-serif", fillColor: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.TOP, pixelOffset: new Cesium.Cartesian2(0, 40), heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, show: true, }, point: { pixelSize: 15, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 3, heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, show: true, }, }); console.log("标记实体已添加:", currentMarker); $ prototype.$ map.scene.requestRender(); return currentMarker; }; // 核心修改:使用工具类实现动态坐标系转换 const handleLocationSelected = (location: { lng: number; lat: number; name: string }) => { if (!$ prototype.$ map) return; // 根据选择的坐标系进行转换 let wgsLocation; if (coordinateSystem.value === "gcj02") { // 高德/火星坐标转WGS84 wgsLocation = CoordinateTransformer.gcj02ToWgs84(location.lng, location.lat); } else { // 百度坐标转WGS84 wgsLocation = CoordinateTransformer.bd09ToWgs84(location.lng, location.lat); } console.log(`转换前(${coordinateSystem.value})坐标:`, location.lng, location.lat); console.log("转换后(WGS84)坐标:", wgsLocation.lng, wgsLocation.lat); // 使用转换后的坐标执行原有逻辑 const destination = Cesium.Cartesian3.fromDegrees( wgsLocation.lng, wgsLocation.lat, 2000 ); $ prototype.$ map.camera.flyTo({ destination, orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-45), roll: 0, }, duration: 2, complete: () => { createMarker({ ...location, ...wgsLocation }); }, }); }; // 原有方法完全不变(组件销毁清理) onUnmounted(() => { if ($ prototype.$ map) { $ prototype.$ map.destroy(); $ prototype.$ map = null; } if ($ prototype.$ map) { $ prototype.$ map.camera.changed.removeEventListener(updateRippleVisibility); } console.log("组件销毁"); }); const emit = defineEmits(["onload", "onclick"]); const initMars3d = async (option: any) => { emit("onclick", true); emit("onload", $ prototype.$ map); }; </script> <style lang="less"> /**cesium 工具按钮栏*/ .cesium-viewer-toolbar { top: auto !important; bottom: 35px !important; left: 12px !important; right: auto !important; } .cesium-toolbar-button img { height: 100%; } .cesium-viewer-toolbar > .cesium-toolbar-button, .cesium-navigationHelpButton-wrapper, .cesium-viewer-geocoderContainer { margin-bottom: 5px; float: left; clear: both; text-align: center; } .cesium-button { background-color: rgba(23, 49, 71, 0.8); color: #e6e6e6; fill: #e6e6e6; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); line-height: 32px; } .cesium-button:hover { background: #3ea6ff; } /**cesium 底图切换面板*/ .cesium-baseLayerPicker-dropDown { bottom: 0; left: 40px; max-height: 700px; margin-bottom: 5px; background-color: rgba(23, 49, 71, 0.8); } /**cesium 帮助面板*/ .cesium-navigation-help { top: auto; bottom: 0; left: 40px; transform-origin: left bottom; background: none; background-color: rgba(23, 49, 71, 0.8); .cesium-navigation-help-instructions { background: none; } .cesium-navigation-button { background: none; } .cesium-navigation-button-selected, .cesium-navigation-button-unselected:hover { background: rgba(0, 138, 255, 0.2); } } /**cesium 二维三维切换*/ .cesium-sceneModePicker-wrapper { width: auto; } .cesium-sceneModePicker-wrapper .cesium-sceneModePicker-dropDown-icon { float: right; margin: 0 3px; } /**cesium POI查询输入框*/ .cesium-viewer-geocoderContainer .search-results { left: 0; right: 40px; width: auto; z-index: 9999; } .cesium-geocoder-searchButton { background-color: rgba(23, 49, 71, 0.8); } .cesium-viewer-geocoderContainer .cesium-geocoder-input { background-color: rgba(63, 72, 84, 0.7); } .cesium-viewer-geocoderContainer .cesium-geocoder-input:focus { background-color: rgba(63, 72, 84, 0.9); } .cesium-viewer-geocoderContainer .search-results { background-color: rgba(23, 49, 71, 0.8); } /**cesium info信息框*/ .cesium-infoBox { top: 50px; background-color: rgba(23, 49, 71, 0.8); } .cesium-infoBox-title { background-color: rgba(23, 49, 71, 0.8); } /**cesium 任务栏的FPS信息*/ .cesium-performanceDisplay-defaultContainer { top: auto; bottom: 35px; right: 50px; } .cesium-performanceDisplay-ms, .cesium-performanceDisplay-fps { color: #fff; } /**cesium tileset调试信息面板*/ .cesium-viewer-cesiumInspectorContainer { top: 10px; left: 10px; right: auto; } .cesium-cesiumInspector { background-color: rgba(23, 49, 71, 0.8); } /**覆盖mars3d内部控件的颜色等样式*/ .mars3d-compass .mars3d-compass-outer { fill: rgba(23, 49, 71, 0.8); } .mars3d-contextmenu-ul, .mars3d-sub-menu { background-color: rgba(23, 49, 71, 0.8); > li > a:hover, > li > a:focus, > li > .active { background-color: #3ea6ff; } > .active > a, > .active > a:hover, > .active > a:focus { background-color: #3ea6ff; } } /* Popup样式*/ .mars3d-popup-color { color: #ffffff; } .mars3d-popup-background { background: rgba(23, 49, 71, 0.8); } .mars3d-popup-content { margin: 15px; } .mars3d-template-content label { padding-right: 6px; } .mars3d-template-titile { border-bottom: 1px solid #3ea6ff; } .mars3d-template-titile a { font-size: 16px; } .mars3d-tooltip { background: rgba(23, 49, 71, 0.8); border: 1px solid rgba(23, 49, 71, 0.8); } .mars3d-popup-btn-custom { padding: 3px 10px; border: 1px solid #209ffd; background: #209ffd1c; } .mars-dialog .mars-dialog__content { height: 100%; width: 100%; overflow: auto; padding: 5px; } .image { border: solid 2px #fff; } .content { height: 90%; padding-top: 10px; overflow-x: auto; overflow-y: auto; } .content-text { padding: 0 10px; text-indent: 30px; font-size: 17px; } .details-video { width: 100%; height: 760px; background-color: #000; } :where(.css-lt97qq9).ant-space { display: inline-flex; } :where(.css-lt97qq9).ant-space-align-center { align-items: center; } :where(.css-lt97qq9).ant-image .ant-image-mask { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; background: rgba(0, 0, 0, 0.5); cursor: pointer; opacity: 0; transition: opacity 0.3s; } :where(.css-lt97qq9).ant-image .ant-image-mask .ant-image-mask-info { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 4px; } :where(.css-1t97qq9)[class^="ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class^="ant-image"] [class*=" ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class*=" ant-image"] { box-sizing: border-box; } :where(.css-lt97qq9).ant-image .ant-image-img { width: 100%; height: auto; vertical-align: middle; } </style> <style scoped> .mini-map-container { position: relative; width: 100%; height: 100%; } .main-viewer { width: 100%; height: 100%; } .mini-map { position: absolute; right: 3vw; bottom: 6vh; width: 12vw; height: 17vh; border: 2px solid #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); z-index: 999; cursor: pointer; overflow: hidden; } .location-indicator { position: absolute; width: 19px; height: 19px; transform: translate(-50%, -50%); z-index: 100; border: 2px solid red; border-radius: 2px; } /* 原十字准星伪元素保持不变 */ .location-indicator::before, .location-indicator::after { content: ''; position: absolute; background-color: red; top: 50%; left: 50%; transform: translate(-50%, -50%); } .location-indicator::before { width: 12px; height: 2px; } .location-indicator::after { width: 2px; height: 12px; } /* 新增四条准星延伸线 */ .ext-line { position: absolute; background-color: red; } /* 顶部延伸线 - 从矩形边框向上延伸 */ .ext-line-top { top: -5px; left: 50%; transform: translateX(-50%); width: 2px; height: 5px; } /* 右侧延伸线 - 从矩形边框向右延伸 */ .ext-line-right { top: 50%; right: -5px; transform: translateY(-50%); width: 5px; height: 2px; } /* 底部延伸线 - 从矩形边框向下延伸 */ .ext-line-bottom { bottom: -5px; left: 50%; transform: translateX(-50%); width: 2px; height: 5px; } /* 左侧延伸线 - 从矩形边框向左延伸 */ .ext-line-left { top: 50%; left: -5px; transform: translateY(-50%); width: 5px; height: 2px; } .view-rectangle { position: absolute; border: 2px solid #00f2fe; z-index: 99; pointer-events: none; box-shadow: 0 0 20px rgba(0, 242, 254, 0.7); } /* 相机聚焦样式 - 四个角折角 */ .corner { position: absolute; width: 25px; height: 25px; z-index: 100; pointer-events: none; } .corner::before, .corner::after { content: ""; position: absolute; background: #00f2fe; box-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .corner-top-left { top: -2px; left: -2px; } .corner-top-left::before { top: 0; left: 0; width: 15px; height: 3px; } .corner-top-left::after { top: 0; left: 0; width: 3px; height: 15px; } .corner-top-right { top: -2px; right: -2px; } .corner-top-right::before { top: 0; right: 0; width: 15px; height: 3px; } .corner-top-right::after { top: 0; right: 0; width: 3px; height: 15px; } .corner-bottom-left { bottom: -2px; left: -2px; } .corner-bottom-left::before { bottom: 0; left: 0; width: 15px; height: 3px; } .corner-bottom-left::after { bottom: 0; left: 0; width: 3px; height: 15px; } .corner-bottom-right { bottom: -2px; right: -2px; } .corner-bottom-right::before { bottom: 0; right: 0; width: 15px; height: 3px; } .corner-bottom-right::after { bottom: 0; right: 0; width: 3px; height: 15px; } .camera-icon { position: absolute; top: 10px; right: 10px; color: #00f2fe; font-size: 24px; z-index: 100; text-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .focus-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 98; border: 2px solid rgba(0, 242, 254, 0.2); border-radius: 5px; box-shadow: inset 0 0 30px rgba(0, 242, 254, 0.1); } .pulse { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 10px; height: 10px; border-radius: 50%; background: rgba(0, 242, 254, 0.7); box-shadow: 0 0 0 0 rgba(0, 242, 254, 0.7); animation: pulse 2s infinite; } ::v-deep.cesium-viewer-toolbar { display: none; } </style> 检查live-map.vue代码,其中的鹰眼地图部分代码好像存在问题如下:鹰眼联动有问题,鹰眼图不用操作,基于此你帮我检查修改一下,尽可能不更改其他任何无关代码,减少对vue文件的修改

filetype

<template>
坐标系: <el-radio-group v-model="coordinateSystem" size="small"> <el-radio-button label="gcj02">高德/火星坐标</el-radio-button> <el-radio-button label="bd09">百度坐标</el-radio-button> </el-radio-group>
<Search @location-selected="handleLocationSelected" /> <LocationBar v-if="loaded" :update-interval="100" :use-dms-format="useDmsFormat" /> </template> <style> /* 原有样式完全不变 */ </style> <script setup lang="ts"> // 原有导入 + 新增坐标转换工具类导入 import { computed, onUnmounted, onMounted, reactive, ref } from "vue"; import LocationBar from "./location-bar.vue"; import Search from "./search.vue"; import initMap from "./init"; import { throttle } from "lodash"; import { loadRipplePoints, createMultipleRippleCircles } from "./circle.js"; import { $prototype } from "../../main.ts"; import markerImage from "@/assets/images/building.png"; import { imageDark } from "naive-ui/es/image/styles"; // 新增:导入坐标转换工具类 import { CoordinateTransformer } from "./coordinate-transformer"; // 修复类型定义 type Rectangle = any; // 原有状态 + 新增坐标系选择状态 const miniMapContainer = ref<HTMLElement>(); let viewIndicator: Rectangle; const currentPosition = reactive({ longitude: 113.361538, latitude: 27.339318, }); const ZHUZHOU_EXTENT = { west: 112.5, east: 114.5, south: 26.0, north: 28.0, }; const rippleEntities = ref<any[]>([]); const heightThreshold = 80000; const indicatorStyle = ref({ left: "50%", top: "50%", display: "block", }); const loaded = ref(false); const useDmsFormat = ref(false); const overviewViewer = ref(null); let currentMarker: any = null; // 新增:坐标系选择状态(默认高德GCJ-02) const coordinateSystem = ref("gcj02"); // 'gcj02' | 'bd09' // 原有方法完全不变(波纹可见性更新) const updateRippleVisibility = throttle(() => { if (!$prototype.$map || rippleEntities.value.length === 0) return; let shouldShow = false; const cartographic = $prototype.$map.camera.positionCartographic; if (cartographic) { const cameraHeight = cartographic.height; shouldShow = cameraHeight > heightThreshold; } rippleEntities.value.forEach((entity) => { entity.show = shouldShow; }); }, 200); // 原有方法完全不变(指示器位置更新) const updateIndicatorPosition = () => { if (!$prototype.$map) return; const camera = $prototype.$map.camera; const rect = camera.computeViewRectangle(); if (!rect) return; const center = Cesium.Rectangle.center(rect); const lon = Cesium.Math.toDegrees(center.longitude); const lat = Cesium.Math.toDegrees(center.latitude); const constrainedLon = Math.max( ZHUZHOU_EXTENT.west, Math.min(ZHUZHOU_EXTENT.east, lon) ); const constrainedLat = Math.max( ZHUZHOU_EXTENT.south, Math.min(ZHUZHOU_EXTENT.north, lat) ); const lonPercent = ((constrainedLon - ZHUZHOU_EXTENT.west) / (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west)) * 100; const latPercent = 100 - ((constrainedLat - ZHUZHOU_EXTENT.south) / (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south)) * 100; indicatorStyle.value = { left: `${lonPercent}%`, top: `${latPercent}%`, display: "block", }; }; // 原有方法完全不变(鹰眼地图更新) const updateOverview = () => { if (!$prototype.$map || !overviewViewer.value) return; const rectangle = $prototype.$map.camera.computeViewRectangle(); if (!rectangle) return; updateIndicatorPosition(); }; // 原有方法完全不变(初始化鹰眼地图) const initMiniMap = () => { Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxMDhlNDdmYy03NzFhLTQ1ZTQtOWQ3NS1lZDAzNDc3YjE4NDYiLCJpZCI6MzAxNzQyLCJpYXQiOjE3NDcwNTMyMDN9.eaez8rQxVbPv2LKEU0sMDclPWyHKhh1tR27Vg-_rQSM"; if (!miniMapContainer.value) return; const worldProvider = new Cesium.UrlTemplateImageryProvider({ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", minimumLevel: 0, maximumLevel: 19, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); const zhuzhouProvider = new Cesium.UrlTemplateImageryProvider({ url: "http://124.232.190.30:9000/proxy/pk1725866655224/map/zzzsyx_18/{z}/{x}/{y}.png", minimumLevel: 1, maximumLevel: 17, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); overviewViewer.value = new Cesium.Viewer(miniMapContainer.value, { sceneMode: Cesium.SceneMode.SCENE2D, baseLayerPicker: false, homeButton: false, timeline: false, navigationHelpButton: false, animation: false, scene3DOnly: true, selectionIndicator: false, infoBox: false, imageryProvider: worldProvider, terrainProvider: undefined, mapProjection: new Cesium.WebMercatorProjection(), skyBox: false, skyAtmosphere: false, }); const zhuzhouLayer = overviewViewer.value.imageryLayers.addImageryProvider( zhuzhouProvider ); overviewViewer.value.camera.setView({ destination: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), }); const toolbar = overviewViewer.value.container.getElementsByClassName( "cesium-viewer-toolbar" )[0]; if (toolbar) toolbar.style.display = "none"; overviewViewer.value.cesiumWidget.creditContainer.style.display = "none"; initRectangle(); }; // 原有方法完全不变(初始化视图指示器) function initRectangle() { viewIndicator = overviewViewer.value.entities.add({ rectangle: { coordinates: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), material: Cesium.Color.RED.withAlpha(0.3), outline: true, outlineColor: Cesium.Color.RED, outlineWidth: 2, }, }); } // 原有方法完全不变(添加演示图形) function addDemoGraphics() { const chinaBoundary = $prototype.$map.dataSources.add( Cesium.GeoJsonDataSource.load("/shp_zz.geojson", { stroke: Cesium.Color.WHITE, fill: false, clampToGround: true, describe: null, }) ); chinaBoundary.then((dataSource) => { const entities = dataSource.entities.values; for (let entity of entities) { if (entity.polyline) { entity.polyline.fill = false; } } }); } // 原有方法完全不变(飞行到默认位置) function flyToDes() { const center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0); $prototype.$map.camera.flyTo({ destination: new Cesium.Cartesian3( -2432812.6687511606, 5559483.804371395, 2832009.419525571 ), orientation: { heading: 6.283185307179421, pitch: -1.0472145569408116, roll: 6.2831853071795205, }, complete: function () {}, }); } // 原有方法完全不变(监听相机变化) const setupCameraListener = () => { $prototype.$map.camera.changed.addEventListener(updateOverview); }; // 原有方法完全不变(鹰眼地图点击处理) const handleMiniMapClick = (event: MouseEvent) => { if (!miniMapContainer.value || !overviewViewer.value) return; const rect = miniMapContainer.value.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const xPercent = (x / rect.width) * 100; const yPercent = (y / rect.height) * 100; const lon = ZHUZHOU_EXTENT.west + (xPercent / 100) * (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west); const lat = ZHUZHOU_EXTENT.north - (yPercent / 100) * (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south); $prototype.$map.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1000000), }); }; // 原有生命周期方法完全不变 onMounted(() => { initMap(); loaded.value = true; addDemoGraphics(); flyToDes(); setTimeout(() => { initMiniMap(); setupCameraListener(); updateOverview(); }, 1000); (async () => { try { const ripplePoints = await loadRipplePoints(); rippleEntities.value = createMultipleRippleCircles($prototype.$map, ripplePoints); $prototype.$map.camera.changed.addEventListener(updateRippleVisibility); updateRippleVisibility(); } catch (error) { console.error("加载波纹圆失败:", error); } })(); }); // 原有方法完全不变(创建标记) const createMarker = (location: { lng: number; lat: number; name: string }) => { if (currentMarker) { $prototype.$map.entities.remove(currentMarker); } console.log("标记图片加载状态:", markerImage); console.log("创建标记位置:", location); currentMarker = $prototype.$map.entities.add({ position: Cesium.Cartesian3.fromDegrees(location.lng, location.lat, 10), label: { text: location.name + "(标记位置)", font: "18px sans-serif", fillColor: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.TOP, pixelOffset: new Cesium.Cartesian2(0, 40), heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, show: true, }, point: { pixelSize: 15, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 3, heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, show: true, }, }); console.log("标记实体已添加:", currentMarker); $prototype.$map.scene.requestRender(); return currentMarker; }; // 核心修改:使用工具类实现动态坐标系转换 const handleLocationSelected = (location: { lng: number; lat: number; name: string }) => { if (!$prototype.$map) return; // 根据选择的坐标系进行转换 let wgsLocation; if (coordinateSystem.value === "gcj02") { // 高德/火星坐标转WGS84 wgsLocation = CoordinateTransformer.gcj02ToWgs84(location.lng, location.lat); } else { // 百度坐标转WGS84 wgsLocation = CoordinateTransformer.bd09ToWgs84(location.lng, location.lat); } console.log(`转换前(${coordinateSystem.value})坐标:`, location.lng, location.lat); console.log("转换后(WGS84)坐标:", wgsLocation.lng, wgsLocation.lat); // 使用转换后的坐标执行原有逻辑 const destination = Cesium.Cartesian3.fromDegrees( wgsLocation.lng, wgsLocation.lat, 2000 ); $prototype.$map.camera.flyTo({ destination, orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-45), roll: 0, }, duration: 2, complete: () => { createMarker({ ...location, ...wgsLocation }); }, }); }; // 原有方法完全不变(组件销毁清理) onUnmounted(() => { if ($prototype.$map) { $prototype.$map.destroy(); $prototype.$map = null; } if ($prototype.$map) { $prototype.$map.camera.changed.removeEventListener(updateRippleVisibility); } console.log("组件销毁"); }); const emit = defineEmits(["onload", "onclick"]); const initMars3d = async (option: any) => { emit("onclick", true); emit("onload", $prototype.$map); }; </script> <style lang="less"> /**cesium 工具按钮栏*/ .cesium-viewer-toolbar { top: auto !important; bottom: 35px !important; left: 12px !important; right: auto !important; } .cesium-toolbar-button img { height: 100%; } .cesium-viewer-toolbar > .cesium-toolbar-button, .cesium-navigationHelpButton-wrapper, .cesium-viewer-geocoderContainer { margin-bottom: 5px; float: left; clear: both; text-align: center; } .cesium-button { background-color: rgba(23, 49, 71, 0.8); color: #e6e6e6; fill: #e6e6e6; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); line-height: 32px; } .cesium-button:hover { background: #3ea6ff; } /**cesium 底图切换面板*/ .cesium-baseLayerPicker-dropDown { bottom: 0; left: 40px; max-height: 700px; margin-bottom: 5px; background-color: rgba(23, 49, 71, 0.8); } /**cesium 帮助面板*/ .cesium-navigation-help { top: auto; bottom: 0; left: 40px; transform-origin: left bottom; background: none; background-color: rgba(23, 49, 71, 0.8); .cesium-navigation-help-instructions { background: none; } .cesium-navigation-button { background: none; } .cesium-navigation-button-selected, .cesium-navigation-button-unselected:hover { background: rgba(0, 138, 255, 0.2); } } /**cesium 二维三维切换*/ .cesium-sceneModePicker-wrapper { width: auto; } .cesium-sceneModePicker-wrapper .cesium-sceneModePicker-dropDown-icon { float: right; margin: 0 3px; } /**cesium POI查询输入框*/ .cesium-viewer-geocoderContainer .search-results { left: 0; right: 40px; width: auto; z-index: 9999; } .cesium-geocoder-searchButton { background-color: rgba(23, 49, 71, 0.8); } .cesium-viewer-geocoderContainer .cesium-geocoder-input { background-color: rgba(63, 72, 84, 0.7); } .cesium-viewer-geocoderContainer .cesium-geocoder-input:focus { background-color: rgba(63, 72, 84, 0.9); } .cesium-viewer-geocoderContainer .search-results { background-color: rgba(23, 49, 71, 0.8); } /**cesium info信息框*/ .cesium-infoBox { top: 50px; background-color: rgba(23, 49, 71, 0.8); } .cesium-infoBox-title { background-color: rgba(23, 49, 71, 0.8); } /**cesium 任务栏的FPS信息*/ .cesium-performanceDisplay-defaultContainer { top: auto; bottom: 35px; right: 50px; } .cesium-performanceDisplay-ms, .cesium-performanceDisplay-fps { color: #fff; } /**cesium tileset调试信息面板*/ .cesium-viewer-cesiumInspectorContainer { top: 10px; left: 10px; right: auto; } .cesium-cesiumInspector { background-color: rgba(23, 49, 71, 0.8); } /**覆盖mars3d内部控件的颜色等样式*/ .mars3d-compass .mars3d-compass-outer { fill: rgba(23, 49, 71, 0.8); } .mars3d-contextmenu-ul, .mars3d-sub-menu { background-color: rgba(23, 49, 71, 0.8); > li > a:hover, > li > a:focus, > li > .active { background-color: #3ea6ff; } > .active > a, > .active > a:hover, > .active > a:focus { background-color: #3ea6ff; } } /* Popup样式*/ .mars3d-popup-color { color: #ffffff; } .mars3d-popup-background { background: rgba(23, 49, 71, 0.8); } .mars3d-popup-content { margin: 15px; } .mars3d-template-content label { padding-right: 6px; } .mars3d-template-titile { border-bottom: 1px solid #3ea6ff; } .mars3d-template-titile a { font-size: 16px; } .mars3d-tooltip { background: rgba(23, 49, 71, 0.8); border: 1px solid rgba(23, 49, 71, 0.8); } .mars3d-popup-btn-custom { padding: 3px 10px; border: 1px solid #209ffd; background: #209ffd1c; } .mars-dialog .mars-dialog__content { height: 100%; width: 100%; overflow: auto; padding: 5px; } .image { border: solid 2px #fff; } .content { height: 90%; padding-top: 10px; overflow-x: auto; overflow-y: auto; } .content-text { padding: 0 10px; text-indent: 30px; font-size: 17px; } .details-video { width: 100%; height: 760px; background-color: #000; } :where(.css-lt97qq9).ant-space { display: inline-flex; } :where(.css-lt97qq9).ant-space-align-center { align-items: center; } :where(.css-lt97qq9).ant-image .ant-image-mask { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #fff; background: rgba(0, 0, 0, 0.5); cursor: pointer; opacity: 0; transition: opacity 0.3s; } :where(.css-lt97qq9).ant-image .ant-image-mask .ant-image-mask-info { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 4px; } :where(.css-1t97qq9)[class^="ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class^="ant-image"], :where(.css-1t97qq9)[class^="ant-image"] [class*=" ant-image"], :where(.css-1t97qq9)[class*=" ant-image"] [class*=" ant-image"] { box-sizing: border-box; } :where(.css-lt97qq9).ant-image .ant-image-img { width: 100%; height: auto; vertical-align: middle; } </style> <style scoped> .mini-map-container { position: relative; width: 100%; height: 100%; } .main-viewer { width: 100%; height: 100%; } .mini-map { position: absolute; right: 3vw; bottom: 6vh; width: 12vw; height: 17vh; border: 2px solid #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); z-index: 999; cursor: pointer; overflow: hidden; } .location-indicator { position: absolute; width: 14px; height: 14px; background: #ff3e3e; border: 2px solid white; border-radius: 50%; transform: translate(-50%, -50%); box-shadow: 0 0 15px rgba(255, 62, 62, 1); z-index: 100; } .view-rectangle { position: absolute; border: 2px solid #00f2fe; z-index: 99; pointer-events: none; box-shadow: 0 0 20px rgba(0, 242, 254, 0.7); } /* 相机聚焦样式 - 四个角折角 */ .corner { position: absolute; width: 25px; height: 25px; z-index: 100; pointer-events: none; } .corner::before, .corner::after { content: ""; position: absolute; background: #00f2fe; box-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .corner-top-left { top: -2px; left: -2px; } .corner-top-left::before { top: 0; left: 0; width: 15px; height: 3px; } .corner-top-left::after { top: 0; left: 0; width: 3px; height: 15px; } .corner-top-right { top: -2px; right: -2px; } .corner-top-right::before { top: 0; right: 0; width: 15px; height: 3px; } .corner-top-right::after { top: 0; right: 0; width: 3px; height: 15px; } .corner-bottom-left { bottom: -2px; left: -2px; } .corner-bottom-left::before { bottom: 0; left: 0; width: 15px; height: 3px; } .corner-bottom-left::after { bottom: 0; left: 0; width: 3px; height: 15px; } .corner-bottom-right { bottom: -2px; right: -2px; } .corner-bottom-right::before { bottom: 0; right: 0; width: 15px; height: 3px; } .corner-bottom-right::after { bottom: 0; right: 0; width: 3px; height: 15px; } .camera-icon { position: absolute; top: 10px; right: 10px; color: #00f2fe; font-size: 24px; z-index: 100; text-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .focus-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 98; border: 2px solid rgba(0, 242, 254, 0.2); border-radius: 5px; box-shadow: inset 0 0 30px rgba(0, 242, 254, 0.1); } .pulse { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 10px; height: 10px; border-radius: 50%; background: rgba(0, 242, 254, 0.7); box-shadow: 0 0 0 0 rgba(0, 242, 254, 0.7); animation: pulse 2s infinite; } ::v-deep.cesium-viewer-toolbar { display: none; } </style> 如图所示map.vue代码,现在有一个小的变动,就是这个鹰眼地图这个红点感觉不太美观,我想要做成如图所示的样式,具体为:一个红色边框的正方形(代表视口范围)中间有一个红色十字准星(代表中心点),你修改一下代码,然后不要改动其他任何无关的代码,尽可能减少对原有代码的修改,基于此修改发我

太远有一点点
  • 粉丝: 48
上传资源 快速赚钱