活动介绍

<style scoped> .wrapper { overflow: hidden; margin: 0px; } </style> <template> <div class="wrapper"> <div ref="threeRef"></div> </div> </template> <script setup lang="ts" type="module"> // 引入three.js import * as THREE from "three"; // 引入扩展库OrbitControls.js import { OrbitControls } from "./build/OrbitControls.js"; import { onMounted, ref } from "vue"; const threeRef = ref();这样配置为何场景不显示

时间: 2025-06-10 18:06:13 浏览: 27
<think>好的,用户之前已经问过如何使用HTML和Three.js显示场景,现在遇到了在Vue组件中配置Three.js但场景不显示的问题。我需要先仔细分析他提供的代码,找出可能的问题点。 首先,用户使用的是Vue 3的setup语法,结合TypeScript。他引入了Three.js和OrbitControls,并且在模板中有一个ref为threeRef的div元素,用来挂载Three的渲染器。 用户提供的代码中,onMounted钩子里初始化了场景、相机、渲染器、立方体和控制器,并调用了animate函数。但场景没有显示出来,可能的原因有几个: 1. **渲染器的domElement没有正确添加到DOM中**:用户创建了WebGLRenderer,但可能没有将renderer.domElement(即canvas元素)添加到threeRef对应的div里。在之前的HTML示例中,他们使用了appendChild,但在Vue中可能需要手动挂载。 2. **样式问题导致容器尺寸为零**:检查.wrapper的CSS,虽然设置了overflow: hidden和margin: 0,但没有设置宽度和高度,可能导致容器没有实际尺寸,渲染器无法正确填充空间。Three.js的渲染器默认可能不会自动适应父容器的大小,需要显式设置尺寸。 3. **相机位置不合适**:相机可能位于物体后面或者太远,导致物体不在视锥体内。在之前的例子中,相机位置被设置为z=5,而用户现在的代码中可能没有调整相机位置,导致看不到物体。 4. **动画循环没有正确启动**:虽然调用了animate函数,但可能因为某些错误导致requestAnimationFrame没有被正确执行,比如在onMounted之外调用,或者有未处理的异常阻止了代码执行。 5. **OrbitControls的配置问题**:OrbitControls可能需要启用enableDamping或设置其他参数,否则相机控制可能有问题,但可能不会导致完全不显示,只是交互问题。 6. **引入路径错误**:用户引入OrbitControls的路径是否正确?例如,"./build/OrbitControls.js"是否存在,如果该模块没有正确导出,可能导致OrbitControls未正确初始化,进而影响渲染。 7. **Vue的响应式引用问题**:threeRef是否正确引用到了DOM元素?在onMounted生命周期中,是否确保DOM已经渲染完成,threeRef.value存在? 接下来需要逐一检查这些可能性。首先,检查渲染器的domElement是否被添加到threeRef对应的div中。用户代码中没有将renderer.domElement添加到threeRef.value,这应该是主要原因。在之前的示例中,他们使用了appendChild,但在Vue中需要使用DOM操作来添加,或者设置渲染器的domElement作为子元素。 其次,检查容器尺寸。.wrapper类虽然设置了overflow:hidden,但没有设置宽度和高度,可能导致父容器的高度为0,进而导致渲染器的canvas没有可见区域。需要给.wrapper或者父容器设置明确的宽度和高度,例如100vh和100vw。 另外,相机的位置是否正确设置?在用户提供的代码中,可能没有调整相机的位置,导致立方体在相机视锥体之外。例如,需要设置camera.position.z = 5,或者在初始化相机后调用camera.lookAt(0,0,0)。 还可能存在其他问题,比如没有处理窗口大小变化的事件,导致在Vue组件中渲染器没有响应式调整尺寸,但这个问题不会导致初次加载不显示,只会导致缩放时显示异常。 总结,最可能的问题是渲染器的canvas没有被添加到DOM中,以及容器尺寸为零导致canvas不可见。需要修改代码,在初始化渲染器后,将domElement添加到threeRef的div中,并确保容器有正确的尺寸。</think>以下是您代码中场景不显示的 **关键原因** 和 **修复方案**: --- ### 问题 1:未挂载 Three.js 画布 **现象**:创建了渲染器但未将画布插入 DOM **修复**:在初始化渲染器后手动挂载 ```ts // 创建渲染器后添加 ↓ threeRef.value.appendChild(renderer.domElement); // 将canvas插入DOM容器 ``` --- ### 问题 2:容器尺寸异常 **现象**:`.wrapper` 没有明确尺寸导致画布大小为 0x0 **修复**:为容器设置 CSS 尺寸 ```css .wrapper { overflow: hidden; margin: 0; width: 100vw; /* 全视口宽度 */ height: 100vh; /* 全视口高度 */ } ``` --- ### 问题 3:相机位置不当 **现象**:相机与物体重叠或距离过远 **修复**:调整相机位置并设置观察点 ```ts camera.position.set(2, 2, 5); // 设置三维坐标 camera.lookAt(0, 0, 0); // 让相机指向场景中心 ``` --- ### 完整修正代码 ```ts <script setup lang="ts"> import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { onMounted, ref } from "vue"; const threeRef = ref<HTMLElement>(); onMounted(() => { // 1. 初始化场景 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); // 2. 挂载画布并设置尺寸 renderer.setSize(window.innerWidth, window.innerHeight); threeRef.value?.appendChild(renderer.domElement); // 关键挂载操作 // 3. 调整相机位置 camera.position.set(2, 2, 5); camera.lookAt(0, 0, 0); // 4. 添加立方体 const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); // 5. 添加轨道控制器 const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; // 启用阻尼效果 // 6. 动画循环 function animate() { requestAnimationFrame(animate); controls.update(); // 更新控制器 renderer.render(scene, camera); } animate(); // 7. 窗口自适应 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); }); </script> ``` --- ### 🚀 主要改进点 1. **显式挂载**:通过 `appendChild` 将渲染器画布插入 DOM 2. **容器尺寸**:确保父容器有明确的宽高 3. **相机定位**:使用 `position.set()` + `lookAt()` 精确定位 4. **控制器优化**:启用 `enableDamping` 实现平滑操作 5. **类型安全**:通过 `ref<HTMLElement>` 明确 DOM 元素类型 --- ### 补充建议 - 使用 `npm install @types/three` 获取类型定义 - 考虑通过 `three/addons/controls/OrbitControls` 官方路径导入 - 对画布添加 CSS `background: transparent` 实现透明背景 如果仍有问题,可以检查浏览器控制台是否有模块加载错误。
阅读全文

相关推荐

<template> {{ item.text }} {{ item.one }} {{ item.two }} {{ item.three }} {{ item.four }} {{ item.five }} {{ item.one }} {{ item.two }} {{ item.three }} {{ item.four }} {{ item.five }} </template> <script> export default { props: { data: Object, header: Array, pageDuration: { type: Number, default: 12 } }, data() { return { doubleData: false, // 是否需要双份数据 fontBox: null, // 文字容器 topBox: null, // 文字父级容器 countFlag: 0, // 切换clss类标识符 dynamicCls: “”, // 动态class类,切换使用解决数据更新后动画不重新开始问题 dynamicSty: {}, scrollIndex: 0, newScrollData: null, scrollTimer: null, scrollBox: null, speed: 30 // 滚动速度 }; }, methods: { calcHeight() { // 判断内容是否超出容器,如果超出容器、追加一份数据 if (this.fontBox.offsetHeight > this.topBox.offsetHeight) { this.doubleData = true; this.countFlag += 1; // 切换动画类名 this.dynamicCls = scroll-cls${this.countFlag % 2}; // 动态计算动画时长 this.dynamicSty = { animationDuration: ${ (this.fontBox.offsetHeight / this.topBox.offsetHeight) * this.pageDuration }s }; } else { this.doubleData = false; this.dynamicCls = “”; this.dynamicSty = { animationDuration: “0s” }; } } }, watch: { // 监听内容变化 data: { deep: true, handler(_val) { this.calcHeight(); } } }, mounted() { this.fontBox = this.$refs.fontbox; this.topBox = this.$refs.topbox; this.$nextTick(() => { this.calcHeight(); }); }, beforeDestroy() {} }; </script> <style lang=“scss” scoped> .scroll-cls0 { animation: translateY1 8s 0.5s linear infinite; } .scroll-cls1 { animation: translateY2 8s 0.5s linear infinite; } // .font-list:hover { // animation-play-state: paused; // } @keyframes translateY1 { from { transform: translateY(0); } to { transform: translateY(-50%); } } @keyframes translateY2 { from { transform: translateY(0); } to { transform: translateY(-50%); } } .bgChange { background: #211141 !important; span { text-shadow: 0 0 10px #f00; } } .item_header { display: flex; justify-content: space-between; height: 0.2125rem; background: #111843; p { color: #fff; flex: 1; line-height: 0.2125rem; padding-left: 0.0625rem; font-family: “OPPOSANS-M”; span { display: inline-block; width: 0.125rem; height: 0.125rem; line-height: 0.125rem; background: #304bfa; text-align: center; box-shadow: 0 0 10px #bc6fea; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; vertical-align: middle; border-radius: 0.0125rem; } .iconfont { color: #fff !important; font-size: 0.0875rem; } } } .item_cont { height: 1.875rem; box-sizing: border-box; overflow: hidden; position: relative; .scrollBox { // position: absolute; width: 100%; left: 0; p { display: flex; justify-content: space-between; background: #060a24; height: 0.2rem; line-height: 0.2rem; margin: 0.0125rem 0; color: #fff; font-size: 0.0875rem; font-family: “OPPOSANS-M”; span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 0.0625rem; } } } } </style> 在该代码的基础上优化,麻烦给出完整的优化结果

<template> 客户特征标签 <el-popover placement="right" popper-class="group-popover" width="287" trigger="click"> 业务需求 <el-popover v-for="(item, index) in requireList" :key="index" placement="top-start" popper-class="grid-popover" trigger="hover" :content="item.labelName" v-if="shouldShowPopover(item)" > <el-tag slot="reference" size="mini" class="truncate-tag" >{{ item.labelName }}</el-tag> </el-popover> <el-tag v-for="(item, index) in requireList" v-if="!shouldShowPopover(item)" :key="index" size="mini" >{{ item.labelName }}</el-tag> 业务特征 <el-popover v-for="(item, index) in featureList" :key="index" placement="top-start" popper-class="grid-popover" trigger="hover" :content="item.labelName" v-if="shouldShowPopover(item)" > <el-tag slot="reference" type="" size="mini" class="truncate-tag" >{{ item.labelName }}</el-tag> </el-popover> <el-tag v-for="(item, index) in featureList" v-if="!shouldShowPopover(item)" :key="index" type="success" size="mini" >{{ item.labelName }}</el-tag> 拓展任务 <el-popover v-for="(item, index) in taskList" :key="index" placement="top-start" popper-class="grid-popover" trigger="hover" :content="item.labelName" v-if="shouldShowPopover(item)" > <el-tag slot="reference" type="danger" size="mini" class="truncate-tag" >{{ item.labelName }}</el-tag> </el-popover> <el-tag v-for="(item, index) in taskList" v-if="!shouldShowPopover(item)" :key="index" type="danger" size="mini" >{{ item.labelName }}</el-tag> <el-button slot="reference" class="btn">更多</el-button> </el-popover> 业务需求 <el-popover v-for="(item, index) in showRequireList" :key="index" placement="top-start" popper-class="grid-popover" trigger="hover" :content="item.labelName" v-if="shouldShowPopover(item)" > <el-tag slot="reference" size="mini" class="truncate-tag" >{{ item.labelName }}</el-tag> </el-popover> <el-tag v-for="(item, index) in showRequireList" v-if="!shouldShowPopover(item)" :key="index" size="mini" >{{ item.labelName }}</el-tag> 业务特征 <el-popover v-for="(item, index) in showFeatureList" :key="index" placement="top-start" popper-class="grid-popover" trigger="hover" :content="item.labelName" v-if="shouldShowPopover(item)" > <el-tag slot="reference" type="success" size="mini" class="truncate-tag" >{{ item.labelName }}</el-tag> </el-popover> <el-tag v-for="(item, index) in showFeatureList" v-if="!shouldShowPopover(item)" :key="index" type="success" size="mini" >{{ item.labelName }}</el-tag> 拓展任务 <el-popover v-for="(item, index) in showTaskList" :key="index" placement="top-start" popper-class="grid-popover" trigger="hover" :content="item.labelName" v-if="shouldShowPopover(item)" > <el-tag slot="reference" type="danger" size="mini" class="truncate-tag" >{{ item.labelName }}</el-tag> </el-popover> <el-tag v-for="(item, index) in showTaskList" v-if="!shouldShowPopover(item)" :key="index" type="danger" size="mini" >{{ item.labelName }}</el-tag> </template> <script> import { Tag, Button, Popover,Tooltip } from 'element-ui'; export default { name: 'groupLabelBox', components: { [Tag.name]: Tag, [Button.name]: Button, [Popover.name]: Popover, [Tooltip.name]: Tooltip, }, props: { requireList: { type: Array, required: true, }, featureList: { type: Array, required: true, }, taskList: { type: Array, required: true, }, }, data() { return { dialogVisible: false, }; }, computed: { showRequireList() { return this.requireList.slice(0, 2); }, showFeatureList() { return this.featureList.slice(0, 2); }, showTaskList() { return this.taskList.slice(0, 2); }, }, methods: { shouldShowPopover(item) { return item?.labelName?.length >= 7 }, }, }; </script> <style lang="less" scoped> .group-popover { padding: 30px !important; /* 标题样式 */ .full-label-section{ padding-bottom: 12px; > span { display: block; margin-bottom: 10px; width: 56px; height: 14px; font-family: PingFangSC, PingFang SC, sans-serif; font-weight: 400; font-size: 14px; color: #888888; line-height: 14px; text-align: left; font-style: normal; } } /* 标签组样式 */ .full-tag-group { display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 6px; .el-tag { margin: 0 !important; border-radius: 4px; font-size: 12px; padding: 0 8px; height: 24px; line-height: 24px; } } .full-label-sections { padding-bottom: 0 !important; } } .truncate-tag { display: inline-block; max-width: 112px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; cursor: default; } .truncate-tag:hover:after { position: absolute; top: 30px; left: 10px; content: attr(data-title); color: #333333; } .grid-popover { min-width: 54px; text-align: center; padding: 10px 10px; } .label-container { position: relative; padding: 0px 14px 7px 16px; } .header { display: flex; justify-content: space-between; align-items: center; margin: 10px 0px; .biaoqian { width: 84px; height: 14px; font-family: PingFangSC, PingFang SC, sans-serif; font-weight: 400; font-size: 14px; color: #888888; line-height: 14px; text-align: left; font-style: normal; } .btn { width: 33px; height: 17px; padding: 0 4px; background: #ffffff; border-radius: 2px; border: 1px solid #cbcbcb; font-weight: 400; font-size: 12px; color: #333333; line-height: 17px; font-style: normal; > span { display: block; width: fit-content; } } } .label-section { > span { display: block; margin-bottom: 5px; width: 48px; height: 14px; font-family: PingFangSC, PingFang SC, sans-serif; font-weight: 400; font-size: 12px; color: #333333; line-height: 14px; text-align: left; font-style: normal; } } .tag-group { display: flex; gap: 10px; flex-wrap: wrap; } .label-section:not(:last-child) { margin-bottom: 10px; } </style> 优化简化以上代码

该vue项目下“商务接待”和“度假之行“的img图片在4K-2560px分辨率下时始终填不满img-box,请修改完善该vue项目,要求使页面能够布局合理正常响应:<template> <responsive-image class="hd01" :small-image="hd01.smallImage1" :medium-image="hd01.mediumImage1" :large-image="hd01.largeImage1" alt-text="Description of image" /> {{ $t('Business') }} {{ $t('text7') }} {{ $t("more14") }} {{ $t('trip') }} {{ $t('text8') }} {{ $t("more15") }} <responsive-image class="hd02" :small-image="hd02.smallImage2" :medium-image="hd02.mediumImage2" :large-image="hd02.largeImage2" alt-text="Description of image" /> {{ $t('Guest') }} <RoomSwiper :bannerList="roomBannerList" /> {{ $t('Apartment') }} <RoomSwiper :bannerList="apartBannerList" /> {{ $t('Facilities') }} {{ $t('WiFi') }} {{ $t('individually') }} {{ $t('TV') }} {{ $t('laundry') }} {{ $t('safes') }} {{ $t('coffee') }} {{ $t('Minibar') }}
</template> <script> import RoomSwiper from "@/components/swiper/RoomSwiper.vue"; export default { name: "experience", components: { RoomSwiper, }, data() { return { msg: "Welcome to Your Vue.js App", // 响应式图片数据 hd01: { smallImage1: require("../../assets/experience/hd-01-mobile.jpg"), mediumImage1: require("../../assets/experience/hd-01.png"), largeImage1: require("../../assets/experience/hd-01.png"), }, hd02: { smallImage2: require("../../assets/experience/hd-02-mobile.jpg"), mediumImage2: require("../../assets/experience/hd-02.png"), largeImage2: require("../../assets/experience/hd-02.png"), }, roomBannerList: [ { id: 1, imgUrl: require("../../assets/room/room-01.png"), title: "豪华套房", prompt: "尊享私人空间,尽享奢华体验", }, { id: 2, imgUrl: require("../../assets/room/room-02.png"), title: "豪华楼层/豪华雅致大床房", prompt: "面积约为42平方米,装饰现代,舒适典雅。卧室和大理石浴室设气派的落地窗玻璃,可以俯瞰草原城市胜景或乌兰木伦湖滨美景。", }, { id: 3, imgUrl: require("../../assets/room/room-03.png"), title: "豪华双床房", prompt: "面积约为42平方米,装饰现代,舒适典雅。卧室和大理石浴室设气派的落地窗玻璃,可以俯瞰草原城市胜景或乌兰木伦湖滨美景。", }, ], apartBannerList: [ { id: 1, imgUrl: require("../../assets/room/room-01.png"), title: "豪华套房", prompt: "尊享私人空间,尽享奢华体验", }, { id: 2, imgUrl: require("../../assets/room/room-02.png"), title: "豪华楼层/豪华雅致大床房", prompt: "面积约为42平方米,装饰现代,舒适典雅。卧室和大理石浴室设气派的落地窗玻璃,可以俯瞰草原城市胜景或乌兰木伦湖滨美景。", }, { id: 3, imgUrl: require("../../assets/room/room-03.png"), title: "豪华双床房", prompt: "面积约为42平方米,装饰现代,舒适典雅。卧室和大理石浴室设气派的落地窗玻璃,可以俯瞰草原城市胜景或乌兰木伦湖滨美景。", }, ], }; }, }; </script> <style scoped> /* 新增4K响应式基础设置 */ :root { --base-size: 1vw; /* 基础尺寸单位 */ --max-container-width: 2560px; } .container { max-width: var(--max-container-width); } /* 全局响应式设置 */ .experience { width: 100%; overflow-x: hidden; } .container { width: 100%; max-width: 2560px; margin: 0 auto; } /* 顶部横幅区域 */ .content-banner { width: 100%; height: 50vh; /* 使用视口高度 */ min-height: 300px; /* 最小高度 */ max-height: 600px; /* 最大高度 */ background-image: url("../../assets/banner/banner-04.jpg"); background-size: cover; background-position: center; background-repeat: no-repeat; } /* 内容区域 */ .content-wrapper { width: 90%; max-width: 1400px; margin: 0 auto; padding: 4rem 0; } /* 商务/旅游模块 - 响应式网格布局 */ .business, .tourism { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4rem; align-items: center; margin: 6rem 0; } /* 文本区域 */ .text-box { flex: 1 1 45%; /* 基础宽度45% */ min-width: 300px; /* 最小宽度 */ } .title-zh { font-size: clamp(2.4rem, 3.5vw, 3.6rem); /* 响应式字体 */ color: rgb(202, 171, 98); margin-bottom: 2rem; } .content { font-size: clamp(1.6rem, 2vw, 2rem); line-height: 1.8; margin: 2rem 0; font-family: "Fangsong"; } .more { display: inline-block; padding: 1rem 2.5rem; font-size: clamp(1.4rem, 1.8vw, 1.6rem); color: rgb(202, 171, 98); background-color: rgb(238, 235, 235); border-radius: 4px; cursor: pointer; transition: all 0.3s ease; } .more:hover { background-color: #e0dcdc; transform: translateY(-2px); } /* 新增底部导航适配 */ .footer { width: 100%; margin-top: auto; /* 确保底部固定 */ } /* 优化视口单位使用 */ :root { --vh: 1vh; } /* 强制图片覆盖容器 */ .responsive-image img { position: absolute; width: 100%; height: 100%; object-fit: cover; /* 关键属性:保持比例填充 */ object-position: center; font-family: 'object-fit: cover'; /* 兼容旧版浏览器 */ } /* 图片容器基础样式强化 */ .img-box { position: relative; overflow: hidden; aspect-ratio: 16/9; /* 保持宽高比 */ max-height: 50vh; } .img-box img { width: 100%; height: auto; } .img-box:hover img { transform: scale(1.03); } /* 轮播图区域 */ .content-swiper { width: 90%; max-width: 1400px; margin: 0 auto; padding: 4rem 0; } .content-kefang { margin-bottom: 6rem; } .foot-swiper-box { margin-top: 3rem; } /* 设施区域 */ .device { margin-top: 2rem; } .device-content { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4rem; align-items: center; margin-top: 4rem; } .device-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 2rem; font-size: clamp(1.6rem, 1.8vw, 1.8rem); line-height: 1.8; } .device-list li { position: relative; padding-left: 1rem; } /* 通用类 */ .text-center { text-align: center; } .textEn { font-family: "Times New Roman", Times, serif !important; } /* 4K分辨率优化 */ @media screen and (min-width: 2560px) { :root { --base-size: 0.8vw; /* 在4K下减小尺寸单位 */ } .content-wrapper, .content-swiper { max-width: 90vw; } .business, .tourism { grid-template-columns: repeat(2, minmax(600px, 1fr)); gap: 6rem; } .title-zh { font-size: calc(3rem + var(--base-size) * 2); } .content { font-size: calc(1.8rem + var(--base-size)); line-height: 2.2; } .device-content { grid-template-columns: repeat(2, minmax(700px, 1fr)); } .device-list { grid-template-columns: repeat(3, minmax(300px, 1fr)); font-size: calc(1.8rem + var(--base-size)); } } /* 超4K分辨率优化 (3840px+) */ @media screen and (min-width: 3840px) { :root { --base-size: 0.6vw; --max-container-width: 3840px; } .content-wrapper, .content-swiper { max-width: 80vw; } .business, .tourism { gap: 10rem; } .device-list { grid-template-columns: repeat(4, minmax(350px, 1fr)); } /* 优化背景图 */ .content-banner { background-image: url("../../assets/banner/banner-01.png"); } } /* 优化现有媒体查询 */ @media screen and (max-width: 1024px) { .content-banner { height: 60vh; } .business, .tourism { gap: 3rem; } } @media screen and (max-width: 768px) { .content-banner { height: 50vh; background-image: url("../../assets/banner/banner-04-mobile.jpg"); } .business, .tourism { grid-template-columns: 1fr; } } </style>

以下頁面代碼如下,當點擊父組件切換按鈕時,子組件的頁面仍能保持原來的頁面狀態,請以完整代碼展示: 父組件代碼(不更改): <template> <button class="external-link" @click="openExternalLink"> 醫仙 </button> <button @click="setCurrentView('mrviewer')" :class="{ active: currentView === 'mrviewer' }"> 醫案閱讀器 </button> <button @click="setCurrentView('mreditor')" :class="{ active: currentView === 'mreditor' }"> 醫案編輯器 </button> <button @click="setCurrentView('dncrud')" :class="{ active: currentView === 'dncrud' }"> 病態編輯器 </button> <keep-alive :include="cachedComponents"> <component :is="currentViewComponent" :key="currentView" /> </keep-alive> </template> <script> import mrviewer from "./components/mrviewer.vue"; import mreditor from "./components/mreditor.vue"; import dncrud from "./components/dncrud.vue"; // 导入病态编辑器组件 export default { components: { mrviewer, mreditor, dncrud }, // 注册组件 data() { return { currentView: "mrviewer", // 需要缓存的组件列表 cachedComponents: ["mrviewer", "mreditor", "dncrud"] }; }, computed: { currentViewComponent() { return this.currentView; } }, methods: { // 设置当前视图并保存状态 setCurrentView(viewName) { // 保存当前组件状态(如果需要) if (this.currentViewComponent && this.currentViewComponent.beforeLeave) { this.currentViewComponent.beforeLeave(); } // 切换到新视图 this.currentView = viewName; // 保存当前视图到本地存储 localStorage.setItem("lastActiveView", viewName); }, openExternalLink() { // 保存当前状态(如果需要) if (this.currentViewComponent && this.currentViewComponent.beforeLeave) { this.currentViewComponent.beforeLeave(); } // 跳转到外部链接 window.location.href = "http://localhost:65358/"; }, // 恢复上次活动视图 restoreLastView() { const lastView = localStorage.getItem("lastActiveView"); if (lastView && this.cachedComponents.includes(lastView)) { this.currentView = lastView; } } }, mounted() { // 组件挂载时恢复上次视图 this.restoreLastView(); } }; </script> <style scoped> .app-container { display: flex; flex-direction: column; min-height: 100vh; } .fixed-menu { position: fixed; top: 0; left: 0; width: 100%; background: #2c3e50; padding: 10px; display: flex; gap: 10px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); z-index: 100; } .fixed-menu button { padding: 8px 16px; border: none; border-radius: 4px; background: #3498db; color: white; cursor: pointer; transition: background 0.3s; } .fixed-menu button:hover { background: #2980b9; } .fixed-menu button.active { background: #e74c3c; font-weight: bold; } .fixed-menu button.external-link { background: #2ecc71; /* 绿色背景区分 */ } .fixed-menu button.external-link:hover { background: #27ae60; } .content-wrapper { flex: 1; margin-top: 60px; /* 增加顶部间距避免内容被顶部菜单遮挡 */ padding: 20px; background: #f5f5f5; height: calc(100vh - 80px); /* 确保内容区域高度适配 */ overflow-y: auto; /* 添加滚动条 */ } </style> 子組件代碼: <template> <button @click="fetchData" class="refresh-btn">刷新數據</button> <input v-model="searchQuery" placeholder="搜索..." class="search-input" /> 每頁顯示: <select v-model.number="pageSize" class="page-size-select"> <option value="1">1筆</option> <option value="4">4筆</option> <option value="10">10筆</option> </select> <button @click="prevPage" :disabled="currentPage === 1">上一页</button> <input type="number" v-model.number="inputPage" min="1" :max="totalPages" class="page-input" @input="handlePageInput"> 頁 / 共 {{ totalPages }} 頁 <button @click="nextPage" :disabled="currentPage === totalPages">下一頁</button> 醫案閱讀器 0"> 醫案 #{{ (currentPage - 1) * pageSize + index + 1 }} {{ key }}: {{ formatDntagValue(value) }} 沒有找到匹配的數據 </template> <script> export default { data() { return { api1Data: [], api2Data: [], mergedData: [], currentPage: 1, pageSize: 1, searchQuery: '', sortKey: '', sortOrders: {}, inputPage: 1, // 页码输入框绑定的值 // 字段名称映射表 fieldNames: { 'mrcase': '醫案全文', 'mrname': '醫案命名', 'mrposter': '醫案提交者', 'mrlasttime': '最後編輯時間', 'mreditnumber': '編輯次數', 'mrreadnumber': '閱讀次數', 'mrpriority': '重要性', 'dntag': '病態名稱' }, inputTimeout: null, // 用于输入防抖 dnNames: [] // 存储所有病态名称 }; }, computed: { filteredData() { const query = this.searchQuery.trim(); // 判斷是否為數字(即搜尋 ID) if (query && /^\d+$/.test(query)) { const idToSearch = parseInt(query, 10); return this.mergedData.filter(item => item.id === idToSearch); } // 一般搜索邏輯 if (!query) return this.mergedData; const lowerQuery = query.toLowerCase(); return this.mergedData.filter(item => { return Object.values(item).some(value => { if (value === null || value === undefined) return false; // 处理数组类型的值 if (Array.isArray(value)) { return value.some(subValue => { if (typeof subValue === 'object' && subValue !== null) { return JSON.stringify(subValue).toLowerCase().includes(lowerQuery); } return String(subValue).toLowerCase().includes(lowerQuery); }); } // 处理对象类型的值 if (typeof value === 'object' && value !== null) { return JSON.stringify(value).toLowerCase().includes(lowerQuery); } return String(value).toLowerCase().includes(lowerQuery); }); }); }, sortedData() { if (!this.sortKey) return this.filteredData; const order = this.sortOrders[this.sortKey] || 1; return [...this.filteredData].sort((a, b) => { const getValue = (obj) => { const val = obj[this.sortKey]; if (Array.isArray(val)) { return JSON.stringify(val); } return val; }; const aValue = getValue(a); const bValue = getValue(b); if (aValue === bValue) return 0; return aValue > bValue ? order : -order; }); }, paginatedData() { const start = (this.currentPage - 1) * Number(this.pageSize); const end = start + Number(this.pageSize); return this.sortedData.slice(start, end); }, totalPages() { return Math.ceil(this.filteredData.length / this.pageSize) || 1; } }, watch: { // 监控分页大小变化 pageSize() { // 重置到第一页 this.currentPage = 1; this.inputPage = 1; // 同步输入框的值 }, // 监控当前页码变化 currentPage(newVal) { // 同步输入框的值 this.inputPage = newVal; }, // 监控过滤数据变化 filteredData() { // 确保当前页码有效 if (this.currentPage > this.totalPages) { this.currentPage = Math.max(1, this.totalPages); } // 同步输入框的值 this.inputPage = this.currentPage; } }, methods: { async fetchData() { try { const api1Response = await fetch("MRInfo/?format=json"); this.api1Data = await api1Response.json(); const api2Response = await fetch("DNTag/?format=json"); this.api2Data = await api2Response.json(); // 提取所有dnname this.dnNames = this.api2Data.map(item => item.dnname).filter(name => name && name.trim()); // 按长度降序排序(确保长字符串优先匹配) this.dnNames.sort((a, b) => b.length - a.length); this.mergeData(); this.currentPage = 1; this.inputPage = 1; // 重置输入框 } catch (error) { console.error("獲取數據失敗:", error); alert("數據加載失敗,請稍後重試"); } }, mergeData() { this.mergedData = this.api1Data.map((item) => { const newItem = { ...item }; if (newItem.dntag && Array.isArray(newItem.dntag)) { newItem.dntag = newItem.dntag.map((tagId) => { const matchedItem = this.api2Data.find( (api2Item) => api2Item.id === tagId ); return matchedItem || { id: tagId, dnname: "未找到匹配的數據" }; }); } return newItem; }); this.sortOrders = {}; if (this.mergedData.length > 0) { Object.keys(this.mergedData[0]).forEach(key => { this.sortOrders[key] = 1; }); } }, // 处理字段名称映射 processFieldNames(item) { const result = {}; for (const key in item) { // 使用映射表转换字段名,如果没有映射则使用原字段名 const newKey = this.fieldNames[key] || key; result[newKey] = item[key]; } return result; }, // 高亮匹配文本的方法 highlightMatches(text) { if (!text || typeof text !== 'string' || this.dnNames.length === 0) { return text; } // 创建正则表达式(转义特殊字符) const pattern = new RegExp( this.dnNames .map(name => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) .join('|'), 'gi' ); // 替换匹配文本 return text.replace(pattern, match => ${match} ); }, formatValue(value, fieldName) { if (value === null || value === undefined) return ''; // 医案全文字段特殊处理 if (fieldName === '醫案全文' && typeof value === 'string') { return this.highlightMatches(value); } // 其他字段保持原逻辑 if (typeof value === 'string' && value.startsWith('http')) { return ${value}; } return value; }, // 专门处理dntag字段的显示格式 formatDntagValue(dntagArray) { return dntagArray.map(tagObj => { // 只显示name属性,隐藏id等其他属性 return tagObj.dnname || tagObj.name || '未命名標籤'; }).join(';'); // 使用大写分号分隔 }, sortBy(key) { // 需要从中文名称映射回原始字段名进行排序 const originalKey = Object.keys(this.fieldNames).find( origKey => this.fieldNames[origKey] === key ) || key; this.sortKey = originalKey; this.sortOrders[originalKey] = this.sortOrders[originalKey] * -1; }, prevPage() { if (this.currentPage > 1) { this.currentPage--; } }, nextPage() { if (this.currentPage < this.totalPages) { this.currentPage++; } }, // 实时处理页码输入 handlePageInput() { // 清除之前的定时器 clearTimeout(this.inputTimeout); // 设置新的定时器(防抖处理) this.inputTimeout = setTimeout(() => { this.goToPage(); }, 300); // 300毫秒后执行 }, // 跳转到指定页码 goToPage() { // 处理空值情况 if (this.inputPage === null || this.inputPage === undefined || this.inputPage === '') { this.inputPage = this.currentPage; return; } // 转换为整数 const page = parseInt(this.inputPage); // 处理非数字情况 if (isNaN(page)) { this.inputPage = this.currentPage; return; } // 确保页码在有效范围内 if (page < 1) { this.currentPage = 1; } else if (page > this.totalPages) { this.currentPage = this.totalPages; } else { this.currentPage = page; } // 同步输入框显示 this.inputPage = this.currentPage; } }, mounted() { this.fetchData(); } }; </script> <style scoped> .container { max-width: 1200px; margin: 0px; padding: 0px; } .control-panel { margin-bottom: 0px; display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; align-items: center; position: fixed; bottom: 0; left: 0; width: 100%; background-color: #ffd800ff; z-index: 999; padding: 10px 20px; box-sizing: border-box; } .content-area { position: fixed; top: 56px; bottom: 45px; /* 位于底部按钮上方 */ left: 0; width: 100%; background: white; padding: 1px; z-index: 100; overflow-y: auto; } .refresh-btn { padding: 4px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } .refresh-btn:hover { background-color: #45a049; } .search-input { padding: 8px; border: 1px solid #ddd; border-radius: 4px; flex-grow: 1; max-width: 300px; } .pagination-controls { display: flex; align-items: center; gap: 5px; } .page-size-select { padding: 4px; border-radius: 4px; width: 70px; } /* 页码输入框样式 */ .page-input { width: 50px; padding: 4px; border: 1px solid #ddd; border-radius: 4px; text-align: center; } /* 水平记录样式 */ .horizontal-records { display: flex; flex-direction: column; gap: 20px; } .record-card { border: 1px solid #ddd; border-radius: 4px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .record-header { padding: 12px 16px; background-color: #f5f5f5; border-bottom: 1px solid #ddd; } .record-header h3 { margin: 0; font-size: 1.1em; } .record-body { padding: 16px; } .record-field { display: flex; margin-bottom: 12px; line-height: 1.5; } .record-field:last-child { margin-bottom: 0; } .field-name { font-weight: bold; min-width: 120px; color: #555; } .field-value { flex-grow: 1; display: flex; flex-wrap: wrap; gap: 8px; } .dntag-value { display: flex; flex-wrap: wrap; gap: 8px; } .array-value { display: flex; flex-wrap: wrap; gap: 8px; } .no-data { padding: 20px; text-align: center; color: #666; font-style: italic; } button:disabled { opacity: 0.5; cursor: not-allowed; } </style>

vue 将以下代码修改为按屏幕比例进行缩放时保持原比例且不出现滚动条<template> XX公司 XX云综合管理平台 <el-row :gutter="20"> <el-col v-for="(app, index) in apps" :key="index" :xs="24" :sm="12" :md="6" :lg="6"> {{ app.name }} </el-col> </el-row> </template> <script> export default { name: "Home", data() { return { hoverIndex: -1, apps: [{ name: '智慧应用', icon: 'el-icon-cpu', }, { name: '运行监测', icon: 'el-icon-monitor', }, { name: 'AI仿真', icon: 'el-icon-data-analysis', }, { name: '数据中台', icon: 'el-icon-data-board', } ], redirect: undefined } }, mounted() { console.log('设备像素比:', window.devicePixelRatio); }, methods: { goIndex() { this.$router.replace({ path: "Index" }) } } } </script> <style scoped> .energy-platform { box-sizing: border-box; padding: 120px 260px; width: 100%; height: 100%; /* min-height: 100vh; */ background-image: url('../assets/image/bg.png'); /* 背景图片设置 */ background-size: 100% 100%; background-position: center; background-repeat: no-repeat; filter: brightness(1); } .header { text-align: left; font-style: italic; color: #fff; font-size: 40px; } .company-name { margin-bottom: 30px; } .company-name, .platform-name { text-align: left; font-weight: bold; margin-bottom: 20px; color: #fff; } .gradient-bar { height: 4px; background: linear-gradient(to right, #29b0dd, #2aaed9, #ffffff); width: 260px; margin-bottom: 90px; } .app-container { margin: 0 auto; } .app-card { height: 260px; width: 80%; background: #fff; border-radius: 8px; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); margin-bottom: 20px; } .img-item2 { width: 100%; } .app-card:hover { transform: translateY(-5px); box-shadow: 0 6px 18px 0 rgba(0, 0, 0, 0.15); } .app-icon { font-size: 50px; color: #333; margin-bottom: 40px; transition: all 0.3s ease; } .app-card:hover .app-icon { color: #fff; } .app-title { font-size: 22px; font-weight: bold; color: #333; transition: all 0.3s ease; } .app-card:hover .app-title { color: #000; } /* 响应式调整 */ @media (max-width: 768px) { .company-name { font-size: 40px; } .platform-name { font-size: 20px; } .app-card { height: 120px; width: 100%; } .app-icon { font-size: 30px; margin-bottom: 10px; } .app-title { font-size: 16px; } .gradient-bar { margin-bottom: 40px; } } /* 响应式调整 */ @media (max-width: 1366px) { .energy-platform { padding: 150px 100px; } .app-card { width: 90%; } } /* 响应式调整 */ @media (max-width: 1024px) { .energy-platform { padding: 150px 200px; } .app-card { width: 100%; } } /* 响应式调整 */ @media (max-height: 768px) { .energy-platform { padding: 50px 120px; } .app-card { width: 100%; } } </style>

<template> <el-card class="login-card"> 系统登录 <el-form :model="loginForm" :rules="loginRules" ref="formRef" @submit.prevent="handleLogin" class="login-form" > <el-form-item prop="username"> <el-input v-model="loginForm.username" placeholder="请输入用户名" prefix-icon="User" size="large" class="input-item" /> </el-form-item> <el-form-item prop="password"> <el-input v-model="loginForm.password" type="password" placeholder="请输入密码" prefix-icon="Lock" show-password size="large" class="input-item" /> </el-form-item> <el-button type="primary" class="login-btn" native-type="submit" :loading="loading"> 登 录 </el-button> <el-button class="reset-btn" @click="handleReset"> 重 置 </el-button> </el-form> </el-card> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, type FormInstance, type FormRules } from 'element-plus' import { loginApi } from '@/api/login' // 根据你的实际路径调整 const router = useRouter() const formRef = ref<FormInstance>() const loading = ref(false) // 登录表单数据 const loginForm = reactive({ username: '', password: '', }) // 表单验证规则 const loginRules = reactive<FormRules>({ username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }, ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }, ], }) // 处理登录提交 const handleLogin = async () => { // 表单验证 const valid = await formRef.value?.validate() if (!valid) return loading.value = true try { // 调用登录API const { code, data, msg } = await loginApi(loginForm) if (code === 1) { // 登录成功处理 ElMessage.success('登录成功') // 存储token到localStorage localStorage.setItem('token', data.token) // 跳转到首页 router.push('/index') } else { // 其他错误处理 ElMessage.error(msg || '登录失败,请重试') } } catch (error: any) { // 401错误处理 if (error.response?.status === 401) { ElMessage.error('用户名或密码错误') } else { ElMessage.error('登录请求失败:' + (error.message || '未知错误')) } } finally { loading.value = false } } // 新增:处理重置表单 const handleReset = () => { formRef.value?.resetFields() } </script> <style scoped lang="scss"> .login-container { display: flex; justify-content: center; align-items: center; height: 100vh; position: relative; overflow: hidden; .login-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); z-index: 1; } &::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: url('https://images.unsplash.com/photo-1497366754035-f200968a6e72?ixlib=rb-4.0.3&auto=format&fit=crop&w=1950&q=80') no-repeat center center; background-size: cover; opacity: 0.1; z-index: 2; } } .login-card { width: 420px; padding: 40px 30px; border-radius: 16px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25); z-index: 3; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border: none; .logo-container { text-align: center; margin-bottom: 30px; .logo { width: 80px; height: 80px; margin: 0 auto 15px; border-radius: 50%; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 10px rgba(37, 117, 252, 0.3); &::after { content: 'EM'; font-size: 28px; font-weight: bold; color: white; } } .login-title { text-align: center; margin-bottom: 5px; color: #2c3e50; font-size: 24px; font-weight: 600; letter-spacing: 1px; } } .login-form { .input-item { margin-bottom: 20px; :deep(.el-input__wrapper) { border-radius: 12px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); padding: 0 15px; height: 48px; &:hover { box-shadow: 0 2px 8px rgba(37, 117, 252, 0.2); } } } } .button-group { display: flex; gap: 15px; margin-top: 10px; .login-btn { flex: 1; height: 48px; font-size: 16px; font-weight: 500; letter-spacing: 2px; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); border: none; border-radius: 12px; box-shadow: 0 4px 10px rgba(37, 117, 252, 0.3); transition: all 0.3s ease; &:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(37, 117, 252, 0.4); } } .reset-btn { flex: 1; height: 48px; font-size: 16px; font-weight: 500; letter-spacing: 2px; background: #f5f7fa; color: #606266; border: none; border-radius: 12px; transition: all 0.3s ease; &:hover { background: #e4e7ed; color: #2c3e50; } } } } </style> 优化下样式美化下页面,可以保持原有的功能上,重构样式

<template> <el-row :gutter="20"> <el-col :span="24"> <el-form :inline="true" label-width="100px" @submit.prevent="getList"> <el-form-item label="名称"> <el-input v-model="queryParams.name" placeholder="请输入名称" /> </el-form-item> <el-form-item label="责任人"> <el-input v-model="queryParams.respPerson" placeholder="请输入责任人" /> </el-form-item> <el-form-item> <el-button type="primary" @click="getList">查询</el-button> <el-button @click="resetQuery">重置</el-button> </el-form-item> </el-form> </el-col> <el-col :span="24"> <el-button type="primary" @click="toggleGantt" style="margin-bottom: 15px;" > {{ showGantt ? '收起甘特图' : '展开甘特图' }} </el-button> </el-col> <el-col :span="showGantt ? 12 : 24"> <el-table ref="table" :data="listData" row-key="uid" border :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" @row-click="rowClick" @expand-change="handleExpandChange" > <el-table-column prop="code" label="编号" /> <el-table-column prop="name" label="名称" /> <el-table-column prop="respPerson" label="责任人" /> <el-table-column prop="schedule" label="完成百分比" /> <el-table-column prop="planStartDate" label="计划开始日期" /> <el-table-column prop="planEndDate" label="计划结束日期" /> <el-table-column label="操作"> <template slot-scope="scope"> <el-button size="mini" icon="el-icon-view" @click="handleUpdate(scope.row)">查看</el-button> </template> </el-table-column> </el-table> <el-pagination layout="prev, pager, next" :total="total" :page-size="queryParams.pageSize" @current-change="handleCurrentChange" /> </el-col> <el-col :span="12" v-if="showGantt"> </el-col> </el-row> <el-dialog :title="title" :visible.sync="open" width="850px" append-to-body> <el-form ref="form" :model="form" :rules="rules" label-width="100px" :disabled="disable"> <el-row> <el-col :span="12"> <el-form-item label="编号" prop="code"> <el-input v-model="form.code" placeholder="请输入编号" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="名称" prop="name"> <el-input v-model="form.name" placeholder="请输入名称" /> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="备注" prop="remarks"> <el-input v-model="form.remarks" type="textarea" placeholder="请输入备注" /> </el-form-item> </el-col> </el-row> <el-button @click="cancel">取 消</el-button> </el-form> </el-dialog> </template> <script> import gantt from 'dhtmlx-gantt'; import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'; import { listPlan, getPlan } from '@/api/dw/plan/planview'; export default { name: 'Planview', data() { return { listData: [], total: 0, queryParams: { pageNum: 1, pageSize: 10, name: null, respPerson: null }, open: false, title: '', form: {}, rules: { name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], schedule: [ { required: true, message: '完成百分比不能为空', trigger: 'blur' }, { type: 'number', message: '输入内容不是有效的数字', trigger: 'blur' } ] }, disable: true, showGantt: true, // 控制甘特图显示 flatData: [], // 扁平化数据 baseDate: new Date('2023-01-01'), // 基准日期 maxDuration: 365, // 最大工期(天) maxOffset: 365 // 最大偏移天数 }; }, mounted() { this.getList(); this.initGantt(); }, methods: { async getList() { const res = await listPlan(this.queryParams); this.listData = this.handleTree(res.data, 'uid', 'parentUid'); this.total = res.total; this.flatData = this.flattenTree(this.listData); this.getMaxDuration(); this.$nextTick(() => { const tasks = this.ganttData(this.flatData); this.updateGantt(tasks); }); }, // 获取最大工期 getMaxDuration() { const durations = this.flatData.map(item => item.planDuration || 0); this.maxDuration = Math.max(...durations, 1); }, // 计算甘特图宽度 calculateGanttWidth(row) { const duration = row.planDuration || 0; const width = (duration / this.maxDuration) * 100; return ${Math.max(5, width)}%; }, // 计算甘特图偏移 calculateGanttOffset(row) { if (!row.planStartDate) return '0%'; const startDate = new Date(row.planStartDate); const daysOffset = Math.floor((startDate - this.baseDate) / (1000 * 60 * 60 * 24)); return ${(daysOffset / this.maxOffset) * 100}%; }, // 初始化甘特图 initGantt() { if (!this.$refs.ganttContainer) return; gantt.config.date_format = '%Y-%m-%d'; gantt.config.columns = [ { name: 'text', label: '任务名称', tree: true, width: '*' }, { name: 'start_date', label: '开始时间', align: 'center' }, { name: 'duration', label: '工期(天)', align: 'center' } ]; gantt.templates.task_text = (start, end, task) => task.text; gantt.init(this.$refs.ganttContainer); gantt.parse({ data: [], links: [] }); // 确保事件监听器只绑定一次 if (!this.ganttEventInitialized) { gantt.attachEvent('onTaskSelected', id => { const row = this.flatData.find(item => item.uid === id); if (row) { this.$refs.table.setCurrentRow(row); } }); this.ganttEventInitialized = true; } }, // 更新甘特图 updateGantt(tasks) { gantt.clearAll(); gantt.parse({ data: tasks, links: [] }); }, // 树形结构转扁平结构 flattenTree(data) { const result = []; const stack = [...data]; while (stack.length) { const node = stack.pop(); result.push(node); if (node.children) { stack.push(...node.children); } } return result; }, // 转换为甘特图数据 ganttData(data) { return data .filter(item => item.uid && item.planStartDate) .map(item => ({ id: item.uid, text: item.name, start_date: item.planStartDate, duration: item.planDuration || 0, progress: (item.schedule || 0) / 100, parent: item.parentUid || 0 })); }, // 处理树形结构 handleTree(data, idKey = 'id', parentKey = 'parentId') { const map = {}; const tree = []; data.forEach(item => (map[item[idKey]] = item)); data.forEach(item => { const parent = map[item[parentKey]]; if (parent) { (parent.children || (parent.children = [])).push(item); } else { tree.push(item); } }); return tree; }, // 行点击事件 rowClick(row) { const taskId = row.uid; this.$nextTick(() => { if (gantt.$initialized) { gantt.showTask(taskId); gantt.selectTask(taskId); // 强制重绘确保高亮生效 gantt.render(); } }); }, // 树展开/折叠更新甘特图 handleExpandChange(row, expanded) { if (expanded) { const allChildren = this.getAllChildren(row); const tasks = this.ganttData(allChildren); this.$nextTick(() => { this.updateGantt(tasks); if (gantt.$initialized && tasks.length > 0) { // 获取展开节点的最早和最晚日期 const dates = tasks .filter(t => t.start_date) .map(t => new Date(t.start_date)); if (dates.length > 0) { const minDate = new Date(Math.min(...dates.map(d => d.getTime()))); const maxDate = new Date(Math.max(...dates.map(d => { const endDate = new Date(t.start_date); endDate.setDate(endDate.getDate() + (t.duration || 0)); return endDate.getTime(); }))); // 设置视图时间范围 gantt.setWorkTime({ start_date: minDate, end_date: maxDate }); // 调整视图缩放级别 gantt.config.scale_unit = 'day'; gantt.config.step = 1; gantt.config.scale_height = 28; // 重新渲染并定位第一个任务 gantt.render(); gantt.showTask(tasks[0].id); gantt.selectTask(tasks[0].id); } } }); } else { const topLevelTasks = this.listData.map(item => ({ id: item.uid, text: item.name, start_date: item.planStartDate, duration: item.planDuration || 0, progress: (item.schedule || 0) / 100, parent: item.parentUid || 0 })); this.$nextTick(() => { this.updateGantt(topLevelTasks); if (gantt.$initialized) { // 恢复默认时间范围 gantt.setWorkTime({ start_date: new Date('2023-01-01'), end_date: new Date('2023-12-31') }); gantt.config.scale_unit = 'month'; gantt.config.step = 1; gantt.config.scale_height = 28; gantt.render(); } }); } }, // 递归获取所有子节点 getAllChildren(node) { let children = [node]; if (node.children && node.children.length > 0) { node.children.forEach(child => { children = children.concat(this.getAllChildren(child)); }); } return children; }, // 切换甘特图 toggleGantt() { this.showGantt = !this.showGantt; if (this.showGantt) { this.$nextTick(() => { const tasks = this.ganttData(this.flatData); this.updateGantt(tasks); }); } }, // 获取数据 async handleUpdate(row) { const res = await getPlan(row.uid); this.form = res.data; this.open = true; this.title = '查看治理计划'; }, // 取消按钮 cancel() { this.open = false; }, // 重置查询 resetQuery() { this.queryParams = { pageNum: 1, pageSize: 10, name: null, respPerson: null }; this.getList(); }, // 分页切换 handleCurrentChange(page) { this.queryParams.pageNum = page; this.getList(); } } }; </script> <style scoped> .page-container { padding: 20px; } .table-container { background-color: #fff; padding: 10px; border-radius: 4px; } .gantt-container { background-color: #f9f9f9; border: 1px solid #ebeef5; padding: 10px; border-radius: 4px; } .dialog-footer { text-align: right; } .search-wrapper { margin-bottom: 20px; background-color: #fff; padding: 10px; border-radius: 4px; } .toggle-button { margin-bottom: 15px; } .gantt-bar-container { position: relative; height: 30px; background-color: #f5f7fa; border-radius: 4px; overflow: hidden; margin: 5px 0; } .gantt-bar { position: absolute; top: 0; left: 0; height: 100%; background-color: #409EFF; color: white; text-align: center; font-size: 12px; line-height: 30px; } </style> 列表的展开和收缩,甘特图没有同步,请重新优化

<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; entity.show = false; }); }, 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(); // 添加视口矩形更新逻辑 if (viewIndicator) { viewIndicator.rectangle.coordinates = rectangle; } }; // 原有方法完全不变(初始化鹰眼地图) 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 cameraController = overviewViewer.value.scene.screenSpaceCameraController; cameraController.enableTranslate = false; cameraController.enableZoom = false; cameraController.enableRotate = false; cameraController.enableTilt = false; cameraController.enableLook = 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 = (wgsLocation) => { if (currentMarker) { $prototype.$map.entities.remove(currentMarker); } console.log("标记图片加载状态:", markerImage); console.log("创建标记位置:", wgsLocation); currentMarker = $prototype.$map.entities.add({ position: Cesium.Cartesian3.fromDegrees(wgsLocation.lng, wgsLocation.lat, 10), label: { text: wgsLocation.name + "(标记位置)", font: "18px sans-serif", fillColor: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.TOP, pixelOffset: new Cesium.Cartesian2(0, 20), heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, show: true, }, // 关键:用 billboard 替换 point billboard: { image: new URL("@/assets/images/building.png", import.meta.url).href, // 本地图标 width: 32, // 图标宽度(像素) height: 32, // 图标高度(像素) verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 让图标尖点对地 heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND, scale: 1.0, }, }); 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, 3000 ); $prototype.$map.camera.flyTo({ destination, orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-90), 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: 14px; height: 2px; } .location-indicator::after { width: 2px; height: 14px; } .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代码,其中为什么这个红色十字定位位置会不对,会呆在左上方,虽然移动后会变正确,但是刷新页面显示时就是有时候会不对,会影响观感,你阅读live-map.vue代码中鹰眼地图部分代码,尝试寻找原因修改,然后尽可能不更改其他任何无关代码,减少对代码的修改

<template> <a-card :bordered="false" :class="{isShowStyle: indirectFlag}"> <a-form layout="inline" @keyup.enter.native="searchQuery"> <a-row :gutter='24'> <a-col :span='8'> <a-form-item label='主机厂'> <a-input v-model.trim='queryParam.oems' placeholder='请输入主机厂名称'></a-input> </a-form-item> </a-col> <a-col :span='8'> <a-form-item label='客户名称'> <a-input v-model.trim='queryParam.endUser' placeholder='请输入客户名称'></a-input> </a-form-item> </a-col> <a-col :span='8'> <a-button type='primary' @click='searchQuery' icon='search'>查询</a-button> <a-button type='primary' @click='reset' icon='reload' style='margin-left: 8px'>重置</a-button> </a-col> </a-row> </a-form> <a-upload v-if="!hiddenFlag" name="file" :showUploadList="false" :multiple="false" :headers="tokenHeader" :action="importExcelUrl" @change="handleImportExcel"> <a-button type="primary" icon="import" :disabled="hiddenFlag">导入</a-button> </a-upload> <a-button icon='link' type='primary' @click='outmb1' :disabled="hiddenFlag"> 导入模板 </a-button> <a-button type="primary" @click="bacthSave" v-if="indirectModal" :disabled="hiddenFlag">保存</a-button> <a-button type="primary" @click="subStatus" v-if="indirectModal" :disabled="hiddenFlag">提交</a-button> <a-dropdown v-if='selectedRowKeys.length > 0'> <a-menu slot='overlay'> <a-menu-item key='1' @click='batchDelete'> <a-icon type='delete' /> 删除 </a-menu-item> </a-menu> <a-button style='margin-left: 8px'> 批量操作 <a-icon type='down' /> </a-button> </a-dropdown> <j-editable-table v-if="indirectModal" ref="editableTable" size="middle" bordered rowKey="id" :columns="dynamicColumns" :dataSource="dataSource" :rowNumber="true" :pagination="ipagination" :loading="loading" :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}" class="j-table-force-nowrap" :actionButton="true" :disabledRows="{ status:1}" @change="handleTableChange"> <template v-slot:oemsSlot="props"> <a-input v-model='props.text' placeholder='请输入主机厂' :disabled="isDisabled(props)" @input="oemsInput(props)"> <a-icon slot='prefix' type='cluster' @click.stop='handleOemsClick(props)'/> </a-input> </template> <template v-slot:productSearchSlot="props"> <a-select v-model="props.text" show-search placeholder="请选择产品名称" :default-active-first-option="false" :show-arrow="false" :filter-option="false" :not-found-content="null" style="width: 100%" @search="handleProductSearch" @change="handleProductChange(props)" > <a-select-option v-for="item in productList" :key="item.value" :value="item.value"> {{ item.text }} </a-select-option> </a-select> </template> <template v-slot:productSlot="props"> <j-search-select-tag v-model="props.text" placeholder="请选择产品名称" dict="bs_inventory GROUP BY cInvName, cInvName, cInvName" /> </template> <template v-slot:publishSlot="props"> <a-input-search v-model="props.text" placeholder="请先选择用户" readOnly unselectable="on" @search="onSearchDepUser(props)"> <a-button slot="enterButton" :disabled="false">选择用户</a-button> </a-input-search> <j-select-user-by-dep-modal ref="selectModal" modal-width="80%" :multi="false" @ok="selectOK" :user-ids="props.value" @initComp="initComp"/> </template> <j-ellipsis :value='text' :length='40'/> </j-editable-table> <a-table v-else ref="table" size="middle" bordered rowKey="id" :columns="columns" :dataSource="dataSource" :pagination="ipagination" :loading="loading" :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}" class="j-table-force-nowrap" @change="handleTableChange"> <j-ellipsis :value='text' :length='40'/> 编辑 <a-divider type="vertical"/> <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)"> 删除 </a-popconfirm> </a-table> <indirectBusinessProducts-modal ref="modalForm" :indirectModal="indirectModal" @ok="modalFormOk"></indirectBusinessProducts-modal> <indirectBusinessProducts-item-modal ref="modalItemForm" @ok="modalFormOk"></indirectBusinessProducts-item-modal> <a-modal title='客户名称(主机厂)' :width='1000' :visible='cusVisible' @ok='handleCusSubmit' @cancel='closeCusModal' cancelText='关闭'> <cus-component ref='cusComponent'></cus-component> </a-modal> </a-card> </template> <script> import '@/assets/less/TableExpand.less' import {mixinDevice} from '@/utils/mixin' import {JeecgListMixin} from '@/mixins/JeecgListMixin' import IndirectBusinessProductsModal from './modules/IndirectBusinessProductsModal' import JEllipsis from "../../../components/jeecg/JEllipsis.vue"; import IndirectBusinessProductsItemModal from "./modules/IndirectBusinessProductsItemModal.vue"; import {deleteAction, downFile, getAction, postAction, putAction} from "../../../api/manage"; import {filterDictText, initDictOptions} from '@/components/dict/JDictSelectUtil' import {FormTypes} from '@/utils/JEditableTableUtil' import JEditableTable from "@comp/jeecg/JEditableTable.vue"; import CusComponent from "@views/modules/eoa/plan/components/CusComponent.vue"; import JSelectUserByDep from "@comp/jeecgbiz/JSelectUserByDep.vue"; import JSelectUserByDepModal from "@comp/jeecgbiz/modal/JSelectUserByDepModal.vue"; import JSearchSelectTag from "@comp/dict/JSearchSelectTag.vue"; export default { name: "IndirectBusinessProductsList", mixins: [JeecgListMixin, mixinDevice], components: { JSearchSelectTag, JSelectUserByDepModal, JSelectUserByDep, CusComponent, JEditableTable, IndirectBusinessProductsItemModal, JEllipsis, IndirectBusinessProductsModal }, props: { indirectFlag: { type: Boolean, default: false }, indirectModal: { type: Object, default: () => { } }, hiddenFlag: { type: Boolean, default: false }, }, data() { return { usernames: null, value: null, userIds: null, description: 'indirect_business_products管理页面', statusDictOptions: [], busType: 'my', cusVisible: false, // 表头 columns: [ { title: '#', dataIndex: '', key: 'rowIndex', width: 60, align: "center", customRender: function (t, r, index) { return parseInt(index) + 1; } }, // { // title: '客户名称(主机厂)', // align: "center", // dataIndex: 'oems' // }, { title: '客户名称', align: "center", dataIndex: 'endUser' }, { title: '终端用户', align: "center", dataIndex: 'oems', scopedSlots: {customRender: 'titleSlot'}, width: 100 }, { title: '车型(米)', align: "center", dataIndex: 'carType', customRender: (text, record, index) => { //字典值替换通用方法 return filterDictText(this.vehicleModelOptions, text); } }, { title: '产品明细', align: "center", scopedSlots: {customRender: 'titleSlot'}, dataIndex: 'productDetails' }, { title: '发布数量', align: "center", dataIndex: 'number' }, { title: '预测金额', align: "center", dataIndex: 'redictedAmount' }, { title: '发布人', align: "center", dataIndex: 'publishName' }, { title: '发布时间', align: "center", dataIndex: 'publishTime', customRender: function (text) { return !text ? "" : (text.length > 10 ? text.substr(0, 10) : text) } }, { title: '产品类型', align: "center", dataIndex: 'productType', customRender: (text, record, index) => { //字典值替换通用方法 return filterDictText(this.productTypeOptions, text); } }, // { // title: '产品大类', // align: "center", // dataIndex: 'productCategories' // }, { title: '提交状态', align: "center", dataIndex: 'status', customRender: (text, record, index) => { //字典值替换通用方法 return filterDictText(this.statusDictOptions, text); } }, { title: '操作', dataIndex: 'action', align: "center", // fixed:"right", width: 147, scopedSlots: {customRender: 'action'} } ], url: { list: "/indirect_business/products/list", delete: "/indirect_business/products/delete", deleteBatch: "/indirect_business/products/deleteBatch", setStatus: "/indirect_business/products/setStatus", batchAdd: "/indirect_business/products/batchAdd", teamExcelTemplate: "indirect_business/indirectBusiness/teamExcelTemplate", teamExcelTemplateProduct: "indirect_business/indirectBusiness/teamExcelTemplateProduct", importExcelUrl: "indirect_business/products/importExcel", }, dictOptions: {}, queryParam:{ status:'1' }, productList: [],//储存产品信息列表 currentClickRowId: null, // 存储当前点击的行ID } }, computed: { importExcelUrl: function () { if (!this.indirectModal) { return ${window._CONFIG['domianURL']}/${this.url.importExcelUrl}; } else { return ${window._CONFIG['domianURL']}/${this.url.importExcelUrl}?busId=${this.indirectModal.busId}&endUser=${this.indirectModal.endCustomer}; } }, getLoginUsername() { return this.$store.getters.userInfo.username; }, loginPermissions() { return this.indirectModal.helpPerson.includes(this.$store.getters.userInfo.username); }, rowSelection() { return { onChange: (selectedRowKeys, selectedRows) => { this.onSelectChange(selectedRowKeys, selectedRows); }, getCheckboxProps: record => ({ props: { disabled: record.status === 1 || record.status === null }, }), }; }, dynamicColumns() { const baseColumns = [ { title: "busId", align: "center", key: "busId", type: FormTypes.hidden, defaultValue: this.indirectModal.busId }, // 动态客户名称列 { title: this.indirectModal.category === '1' ? '客户名称' : '终端客户名称', align: "center", key: 'endUser', type: FormTypes.input, placeholder: '请输入客户名称', disabled: true, defaultValue: this.indirectModal.endCustomer, width: '220px' } ]; if (this.indirectModal.category === '2'){ baseColumns.push({ title: '主机厂', align: "center", key: 'oems', placeholder: "请选择主机厂", type: FormTypes.slot, slotName: 'oemsSlot', validateRules: this.indirectModal.category === '2' ? [ { required: true, message: '主机厂不能为空' }, { validator: (rule, value, callback) => { if (value === '' || value === null || value === undefined) { callback(new Error('请选择主机厂')); // 配合 required 使用 } } } ] : [], width: '220px', }); } baseColumns.push({ title: '车型(米)', align: "center", key: 'carType', type: FormTypes.select, dictCode: 'vehicle_model', placeholder: '请选择车型', customRender: (text, record, index) => { //字典值替换通用方法 return filterDictText(this.vehicleModelOptions, text); }, width: '150px' }) baseColumns.push({ title: '产品名称', align: "center", key: 'productDetails', placeholder: "请选择产品名称", type: FormTypes.slot, slotName: 'productSearchSlot', width: '240px' }) baseColumns.push({ title: '发布数量', align: "center", key: 'number', type: FormTypes.input, inputType: 'number', placeholder: "请输入发布数量", validateRules: [ { pattern: /^[1-9]\d*$/, // 正整数正则 message: '发布数量必须是正整数' } ], width: '150px' }) baseColumns.push({ title: '预测金额', align: "center", key: 'redictedAmount', type: FormTypes.input, inputType: 'number', placeholder: "请输入预测金额", // 动态校验规则(category == '2' 时必填且必须为正数) validateRules: this.indirectModal.category === '2' ? [ { required: true, message: '预测金额不能为空', trigger: ['blur', 'change'] // 触发校验的时机 }, { pattern: /^(0|[1-9]\d*)(\.\d+)?$/, // 正整数正则 message: '发布数量必须是正数' } ] : [], width: '150px' }) if (this.indirectModal.category === '2'){ baseColumns.push({ title: '主机厂负责人', align: "center", key: 'oemResponsiblePerson', type: FormTypes.hidden, }); } // if (this.indirectModal.category === '2'){ // baseColumns.push({ // title: '主机厂负责人', // align: "center", // key: 'oemResponsiblePersonName', // type: FormTypes.input, // disabled: true, // width: '200px' // }); // } baseColumns.push({ title: '发布人', align: "center", key: 'publishBy', type: FormTypes.hidden, defaultValue: this.$store.getters.userInfo.username, placeholder: "请选择发布人", width: '240px' }) baseColumns.push({ title: '发布人', align: "center", key: 'publishName', type: FormTypes.slot, slotName: 'publishSlot', defaultValue: this.$store.getters.userInfo.realname, placeholder: "请选择发布人", width: '240px' }) baseColumns.push({ title: '发布时间', align: "center", key: 'publishTime', type: FormTypes.date, customRender: function (text) { return !text ? "" : (text.length > 10 ? text.substr(0, 10) : text) }, defaultValue: this.formatDate(new Date()), width: '170px' }) baseColumns.push({ title: '产品类型', align: "center", key: 'productType', placeholder: "请选择产品类型", type: FormTypes.select, dictCode: 'product_type', customRender: (text, record, index) => { //字典值替换通用方法 return filterDictText(this.productTypeOptions, text); }, defaultValue: '0', width: '130px' }) baseColumns.push({ title: '提交状态', align: "center", key: 'status', type: FormTypes.select, dictCode: 'public_status', customRender: (text, record, index) => { //字典值替换通用方法 return filterDictText(this.statusDictOptions, text); }, defaultValue: '0', disabled: true, width: '130px' }) return baseColumns; }, }, methods: { initDictConfig() { getAction('/base/bsInventory/distinctAllList',null).then((res) => { if (res.success) { this.productList = res.result.map(item => ({ text: item.cinvname, value: item.cinvname })) } }) //判断查询条件 if (!this.indirectModal){ this.queryParam.status = 1 this.loadData(1) }else{ this.queryParam.busId = this.indirectModal.busId this.queryParam.status = '' this.loadData(1) } console.log('this.queryParam.status',this.queryParam.status) initDictOptions('public_status').then((res) => { if (res.success) { this.statusDictOptions = res.result } }) initDictOptions('vehicle_model').then((res) => { if (res.success) { this.vehicleModelOptions = res.result } }) initDictOptions('product_type').then((res) => { if (res.success) { this.productTypeOptions = res.result } }) }, // 产品名称远程搜索 handleProductSearch(value) { if (!value) return; // 空输入不查询 // 调用接口查询匹配的产品 getAction('/base/bsInventory/distinctAllList', { 'cinvname': value }).then(res => { if (res.success) { this.productList = res.result.map(item => ({ text: item.cinvname, value: item.cinvname })); } }); }, handleProductChange(props) { // 存储当前点击的行记录和ID this.currentClickRowId = props.rowId; this.$refs.editableTable.setValues([{ rowKey: this.currentClickRowId, values: { 'productDetails': props.text } }]); }, outmb() { downFile(this.url.teamExcelTemplate, '').then((res) => { if (!res) { this.$message.warning('文件下载失败') return } let blob = new Blob([res], {type: 'application/vnd.ms-excel'}) let downloadElement = document.createElement('a') let href = window.URL.createObjectURL(blob) // 创建下载的链接 downloadElement.href = href downloadElement.download = '间接销售登记表导入模板.xls' // 下载后文件名 document.body.appendChild(downloadElement) downloadElement.click() // 点击下载 document.body.removeChild(downloadElement) // 下载完成移除元素 window.URL.revokeObjectURL(href) // 释放掉blob对象 }) }, outmb1() { downFile(this.url.teamExcelTemplateProduct, '').then((res) => { if (!res) { this.$message.warning('文件下载失败') return } let blob = new Blob([res], {type: 'application/vnd.ms-excel'}) let downloadElement = document.createElement('a') let href = window.URL.createObjectURL(blob) // 创建下载的链接 downloadElement.href = href downloadElement.download = '间接销售登记导入模板.xlsx' // 下载后文件名 document.body.appendChild(downloadElement) downloadElement.click() // 点击下载 document.body.removeChild(downloadElement) // 下载完成移除元素 window.URL.revokeObjectURL(href) // 释放掉blob对象 }) }, subStatus(){ const ids = this.$refs.editableTable.getSelection() if (ids.length==0){ this.$message.warn("请选择一条记录") return; } const selectedRow = this.$refs.editableTable.getValuesSync({rowIds: ids}).values selectedRow.forEach(item => { if (item.status == "1"){ this.$message.warn("请选择未提交的数据") return; } }) let that = this that.$confirm({ title: '提示', content: '确认提交吗?', onOk: function() { putAction(that.url.setStatus, selectedRow).then((res) => { if (res.success) { that.$message.success(res.message); that.loadData(); that.onClearSelected(); } else { that.$message.warning(res.message); that.loadData(); } }) } }) }, bacthSave() { this.$refs.editableTable.getValues(error => { if (error === 0) { let that = this postAction(that.url.batchAdd, this.$refs.editableTable.getValuesSync().values).then((res) => { if (res.success) { that.$message.success(res.message); that.loadData(); } else { that.$message.warning(res.message); that.loadData(); } }) } else { this.$message.error('验证未通过') } }) }, reset(){ this.queryParam = {status:'1'} this.loadData() }, batchDelete: function () { if(!this.url.deleteBatch){ this.$message.error("请设置url.deleteBatch属性!") return } if (this.selectedRowKeys.length <= 0) { this.$message.warning('请选择一条记录!'); return; } else if (this.selectionRows.some(item => item.status != 0)) { this.$message.warning('请选择未提交记录!') return; } else { var ids = ""; for (var a = 0; a < this.selectedRowKeys.length; a++) { ids += this.selectedRowKeys[a] + ","; } var that = this; this.$confirm({ title: "确认删除", content: "是否删除选中数据?", onOk: function () { that.loading = true; deleteAction(that.url.deleteBatch, {ids: ids}).then((res) => { if (res.success) { that.$message.success(res.message); that.loadData(); that.onClearSelected(); } else { that.$message.warning(res.message); } }).finally(() => { that.loading = false; }); } }); } }, handleCusSubmit() { if (!this.$refs.cusComponent.selectionRows || this.$refs.cusComponent.selectionRows.length === 0) { this.$message.warning('请选择一条记录') return false } var customerNames = ""; var oemResponsiblePerson = ""; var oemResponsiblePersonName = ""; for (var a = 0; a < this.$refs.cusComponent.selectedRowRecords.length; a++) { customerNames += this.$refs.cusComponent.selectedRowRecords[a].customerName + ","; if (this.$refs.cusComponent.selectedRowRecords[a].responsiblePerson !== null) { oemResponsiblePerson += this.$refs.cusComponent.selectedRowRecords[a].responsiblePerson + ","; } if (this.$refs.cusComponent.selectedRowRecords[a].responsiblePersonName!==null){ oemResponsiblePersonName += this.$refs.cusComponent.selectedRowRecords[a].responsiblePersonName + ","; } } const oems = customerNames.substr(0,customerNames.length-1) const oemResponsiblePersons = oemResponsiblePerson.substr(0,oemResponsiblePerson.length-1) const oemResponsiblePersonNames = oemResponsiblePersonName.substr(0,oemResponsiblePersonName.length-1) // 找到当前点击的行并更新数据 this.$refs.editableTable.setValues( [{ rowKey: this.currentClickRowId, values: { 'oems': oems, 'oemResponsiblePerson': oemResponsiblePersons, 'oemResponsiblePersonName': oemResponsiblePersonNames, } }] ) this.closeCusModal() }, oemsInput(props) { // 存储当前点击的行记录和ID this.currentClickRowId = props.rowId; // 找到当前点击的行并更新数据 this.$refs.editableTable.setValues( [{ rowKey: this.currentClickRowId, values: { 'oems': props.text } }] ) }, handleOemsClick(props) { console.log("主机厂",this.$refs.editableTable.getValues({rowIds: props.rowId})) // 存储当前点击的行记录和ID this.currentClickRowId = props.rowId; this.openCusModal(); // 直接打开弹窗 }, openCusModal() { this.cusVisible = true }, closeCusModal() { this.$refs.cusComponent.selectionRows = [] this.$refs.cusComponent.selectedRowKeys = [] this.$refs.cusComponent.queryParam = {} this.cusVisible = false }, formatDate(date) { const year = date.getFullYear() const month = (date.getMonth() + 1).toString().padStart(2, '0') const day = date.getDate().toString().padStart(2, '0') return ${year}-${month}-${day} }, initComp(userNames) { this.userNames = userNames }, onSearchDepUser(props) { this.currentClickRowId = props.rowId if (this.isSelectUser) this.$refs.selectModal.queryParam.username = this.value; this.$refs.selectModal.showModal() this.$refs.selectModal.queryParam.username = ""; }, selectOK(rows, idstr) { if (!rows) { this.userNames = '' this.userIds = '' } else { let temp = '' for (let item of rows) { temp += ',' + item.realname } this.userNames = temp.substring(1) this.userIds = idstr // 找到当前点击的行并更新数据 this.$refs.editableTable.setValues( [{ rowKey: this.currentClickRowId, values: { 'publishBy': this.userIds, 'publishName': this.userNames, } }] ) } }, // 判断是否禁用 isDisabled(props){ const item = this.$refs.editableTable.getValuesSync({rowIds: [props.rowId]}) return item.values[0].status === '1' } } } </script> <style scoped> @import '~@assets/less/common.less'; .isShowStyle /deep/ .ant-card-body { padding: unset; } </style>中<template v-slot:oemsSlot="props"> <a-input v-model='props.text' placeholder='请输入主机厂' :disabled="isDisabled(props)" @input="oemsInput(props)"> <a-icon slot='prefix' type='cluster' @click.stop='handleOemsClick(props)'/> </a-input> </template>插槽的:disabled我这样子用会报错,应该怎么使用,jeecg

<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文件的修改

<template> <template v-if="videoPlayList.length"> <template v-for="(item, index) in videoPlayList"> <VideosPlay :data="item" :index="index" /> </template> </template> <el-select v-model="selectedLayer" placeholder="影像图层" class="select-1" :show-arrow="false" clearable @change="handleLayerChange" @clear="clearLayer" > <el-option v-for="(item, index) in layers" :key="item.id" :label="item.title" :value="item.id" /> </el-select> <el-select v-model="baseMap" placeholder="矢量地图" :show-arrow="false" class="select-2" @change="handleBaseMapChange" value-key="id" multiple collapse-tags > <el-option v-for="item in baseMapList" :key="item.name" :label="item.name" :value="item" /> </el-select> <el-select v-model="selectedTile" placeholder="实景三维" class="select-2" @change="handle3dSwitchChange" multiple collapse-tags :show-arrow="false" value-key="id" > <el-option v-for="tile in tileList" :key="tile.id" :label="tile.title" :value="tile" /> </el-select> <el-select v-model="selectedCommunity" placeholder="动态监测" class="select-3" @change="handleCommunityChange" :show-arrow="false" > <el-option v-for="community in communityList" :key="community.name" :label="community.name" :value="community.name" /> </el-select> <el-dialog v-model="showCoordinateModal" title="坐标系转换" width="400px" @close="resetCoordinateForm" > <el-form :model="coordForm" label-width="100px"> <el-form-item label="源坐标系"> <el-select v-model="coordForm.source" placeholder="请选择源坐标系" clearable class="coord-select" > <el-option label="WGS84" value="WGS84"></el-option> <el-option label="GCJ02(火星)" value="GCJ02"></el-option> <el-option label="BD09(百度)" value="BD09"></el-option> </el-select> </el-form-item> <el-form-item label="目标坐标系"> <el-select v-model="coordForm.target" placeholder="请选择目标坐标系" clearable class="coord-select" > <el-option label="WGS84" value="WGS84"></el-option> <el-option label="GCJ02(火星)" value="GCJ02"></el-option> <el-option label="BD09(百度)" value="BD09"></el-option> </el-select> </el-form-item> <el-form-item label="经度"> <el-input v-model.number="coordForm.lng" placeholder="输入经度" clearable ></el-input> </el-form-item> <el-form-item label="纬度"> <el-input v-model.number="coordForm.lat" placeholder="输入纬度" clearable ></el-input> </el-form-item> <el-form-item label="转换结果"> <el-input :value="coordForm.result" readonly placeholder="转换结果将显示在这里" ></el-input> </el-form-item> </el-form> <template #footer> <el-button @click="showCoordinateModal = false">取消</el-button> <el-button type="primary" @click="convertCoordinates">转换</el-button> </template> </el-dialog> </template> <script setup lang="ts"> import { onMounted, ref, onUnmounted } from "vue"; import { $prototype } from "../../main.ts"; import { getVeidoDetail } from "@/service/index"; import { ElMessage } from "element-plus"; import MVTImageryProvider from "mvt-imagery-provider"; import "/public/lib/video/mpegts/mpegts.js"; import "/public/img/div-graphic/divGraphic.css"; import { vectorLayes } from "@/config/index.ts"; import VideosPlay from "@/components/video/index.vue"; import { RefreshLeft } from "@element-plus/icons-vue"; var viewer: any; const selectedLayer = ref("baseImagery"); const selectedTile = ref([]); const selectedCommunity = ref(""); const baseMap = ref([]); const videoPlayList = ref([]); const addedTilesets = ref<Record<string, Cesium.Cesium3DTileset[]>>({}); const addedMvtLayers = ref<Record<string, Cesium.ImageryLayer[]>>({}); // 坐标系转换相关 const showCoordinateModal = ref(false); const coordForm = ref({ source: "", // 源坐标系 target: "", // 目标坐标系 lng: 0, // 经度 lat: 0, // 纬度 result: "" // 转换结果 }); const layers = [ { id: "baseImagery", title: "卫星" }, { id: "zhuzhou_beidou:GF01", title: "高分1号卫星遥感影像" }, { id: "zhuzhou_beidou:GF02", title: "高分2号卫星遥感影像" }, { id: "zhuzhou_beidou:GF06", title: "高分6号卫星遥感影像" }, { id: "zhuzhou_beidou:GF07", title: "高分7号卫星遥感影像" }, { id: "zhuzhou_beidou:lkyx", title: "渌口区影像" }, ]; const tileList = [ { id: "tile1", url: [ "http://124.232.190.30:9000/proxy/pk1725973637070/map/3dmode/20220312-1302/tileset.json", "http://124.232.190.30:9000/proxy/pk1725973591120/map/3dmode/20220312-1301/tileset.json", "http://124.232.190.30:9000/proxy/pk1725973529125/map/3dmode/2022022101/tileset.json", "http://124.232.190.30:9000/proxy/pk1725973404863/map/3dmode/2022011302/tileset.json", "http://124.232.190.30:9000/proxy/pk1725973226672/map/3dmode/20220312/tileset.json", "http://124.232.190.30:9000/proxy/pk1725952405569/map/3dmode/20220221/tileset.json", "http://124.232.190.30:9000/proxy/pk1725952315227/map/3dmode/20220220/tileset.json", ], title: "会展中心", }, { id: "tile2", url: [ "http://124.232.190.30:9000/proxy/pk1725804650206/map/3dmode/hhhg/tileset.json", ], title: "园区", }, ]; const communityList = [ { name: "铁塔视频" }, ]; const baseMapList = [ { id: "geomeg", name: "基础地图", url: "http://124.232.190.30:3011//yjy_pbf/{z}/{x}/{y}.pbf", type: "fill" }, { id: "lukou_farmland", name: "耕地", url: "http://124.232.190.30:9059/lukou_farmland/{z}/{x}/{y}", type: "fill" }, { id: "lukou_ecology", name: "生态", url: "http://124.232.190.30:9059/lukou_ecology/{z}/{x}/{y}", type: "fill" }, { id: "natural_", name: "自然区", url: "http://124.232.190.30:9059/natural_/{z}/{x}/{y}", type: "fill" }, { id: "lushui_river", name: "渌水流域", url: "http://124.232.190.30:9059/lushui_river/{z}/{x}/{y}", type: "fill" }, { id: "mishui_river", name: "洣水流域", url: "http://124.232.190.30:9059/mishui_river/{z}/{x}/{y}", type: "fill" }, ]; const sprite = "http://124.232.190.67:5432/sprite/sprite"; const glyphs = "http://124.232.190.67:5432/font/mapbox_glyphs/{fontstack}/{range}.pbf"; const baseMapUrl = "http://124.232.190.30:3011//yjy_pbf/layers.json"; // 坐标系转换工具类 const CoordinateConverter = { a: 6378245.0, ee: 0.006693421622965943, // 判断是否在国内 outOfChina(lng: number, lat: number): boolean { return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271); }, // 纬度转换 transformLat(x: number, y: number): number { let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0; return ret; }, // 经度转换 transformLng(x: number, y: number): number { let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0; return ret; }, // WGS84转GCJ02 wgs84ToGcj02(lng: number, lat: number): [number, number] { if (this.outOfChina(lng, lat)) return [lng, lat]; let dLat = this.transformLat(lng - 105.0, lat - 35.0); let dLng = this.transformLng(lng - 105.0, lat - 35.0); const radLat = lat / 180.0 * Math.PI; let magic = Math.sin(radLat); magic = 1 - this.ee * magic * magic; const sqrtMagic = Math.sqrt(magic); dLat = (dLat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtMagic) * Math.PI); dLng = (dLng * 180.0) / (this.a / sqrtMagic * Math.cos(radLat) * Math.PI); return [lng + dLng, lat + dLat]; }, // GCJ02转WGS84 gcj02ToWgs84(lng: number, lat: number): [number, number] { if (this.outOfChina(lng, lat)) return [lng, lat]; let dLat = this.transformLat(lng - 105.0, lat - 35.0); let dLng = this.transformLng(lng - 105.0, lat - 35.0); const radLat = lat / 180.0 * Math.PI; let magic = Math.sin(radLat); magic = 1 - this.ee * magic * magic; const sqrtMagic = Math.sqrt(magic); dLat = (dLat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtMagic) * Math.PI); dLng = (dLng * 180.0) / (this.a / sqrtMagic * Math.cos(radLat) * Math.PI); const mgLat = lat + dLat; const mgLng = lng + dLng; return [lng * 2 - mgLng, lat * 2 - mgLat]; }, // GCJ02转BD09 gcj02ToBd09(lng: number, lat: number): [number, number] { const x = lng; const y = lat; const z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI * 3000.0 / 180.0); const theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI * 3000.0 / 180.0); return [z * Math.cos(theta) + 0.0065, z * Math.sin(theta) + 0.006]; }, // BD09转GCJ02 bd09ToGcj02(lng: number, lat: number): [number, number] { const x = lng - 0.0065; const y = lat - 0.006; const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI * 3000.0 / 180.0); const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI * 3000.0 / 180.0); return [z * Math.cos(theta), z * Math.sin(theta)]; }, // WGS84转BD09 wgs84ToBd09(lng: number, lat: number): [number, number] { const [gcjLng, gcjLat] = this.wgs84ToGcj02(lng, lat); return this.gcj02ToBd09(gcjLng, gcjLat); }, // BD09转WGS84 bd09ToWgs84(lng: number, lat: number): [number, number] { const [gcjLng, gcjLat] = this.bd09ToGcj02(lng, lat); return this.gcj02ToWgs84(gcjLng, gcjLat); } }; // 执行坐标系转换 function convertCoordinates() { const { source, target, lng, lat } = coordForm.value; // 验证输入 if (!source || !target) { ElMessage.warning('请选择源坐标系和目标坐标系'); return; } if (source === target) { ElMessage.info('源坐标系与目标坐标系相同,无需转换'); coordForm.value.result = 经度: ${lng.toFixed(6)}, 纬度: ${lat.toFixed(6)}; return; } if (!lng || !lat || isNaN(lng) || isNaN(lat)) { ElMessage.warning('请输入有效的经纬度'); return; } if (lng < -180 || lng > 180 || lat < -90 || lat > 90) { ElMessage.warning('经纬度超出有效范围'); return; } // 执行转换 let result: [number, number] = [0, 0]; switch (true) { case source === "WGS84" && target === "GCJ02": result = CoordinateConverter.wgs84ToGcj02(lng, lat); break; case source === "GCJ02" && target === "WGS84": result = CoordinateConverter.gcj02ToWgs84(lng, lat); break; case source === "GCJ02" && target === "BD09": result = CoordinateConverter.gcj02ToBd09(lng, lat); break; case source === "BD09" && target === "GCJ02": result = CoordinateConverter.bd09ToGcj02(lng, lat); break; case source === "WGS84" && target === "BD09": const gcj = CoordinateConverter.wgs84ToGcj02(lng, lat); result = CoordinateConverter.gcj02ToBd09(gcj[0], gcj[1]); break; case source === "BD09" && target === "WGS84": const gcj2 = CoordinateConverter.bd09ToGcj02(lng, lat); result = CoordinateConverter.gcj02ToWgs84(gcj2[0], gcj2[1]); break; default: ElMessage.error("不支持的转换类型"); return; } coordForm.value.result = 经度: ${result[0].toFixed(6)}, 纬度: ${result[1].toFixed(6)}; } // 重置转换表单 function resetCoordinateForm() { coordForm.value = { source: "", target: "", lng: 0, lat: 0, result: "" }; } // 基础地图切换处理 function handleBaseMapChange(items: any) { const currentSelectedIds = items.map((item: any) => item.id); const mvtsToRemove = Object.keys(addedMvtLayers.value).filter( (mvtId) => !currentSelectedIds.includes(mvtId) ); mvtsToRemove.forEach((mvtId) => { const layersToRemove = addedMvtLayers.value[mvtId]; if (layersToRemove && layersToRemove.length > 0) { layersToRemove.forEach((layer) => { try { const layers = viewer.imageryLayers._layers; for (let i = layers.length - 1; i >= 0; i--) { if (layers[i]._imageryProvider?.id === mvtId) { viewer.imageryLayers.remove(layers[i]); } } } catch (e) { console.error("Error removing MVT layer:", e); } }); delete addedMvtLayers.value[mvtId]; } }); items.forEach((item: any) => { if (!addedMvtLayers.value[item.id] || addedMvtLayers.value[item.id].length === 0) { addedMvtLayers.value[item.id] = []; loadVectorMvt(item.url, item.id, item.type); } }); } // 视频关闭事件监听 $prototype.emitter.on("video-list-play-close", (_id) => { videoPlayList.value = videoPlayList.value.filter((v) => v.id != _id); }); // 加载矢量地图 async function loadVectorMvt(data, layerName, type) { var tt = { name: "maplibres_yw", type: "vector", tiles: [data], bounds: [-180, -85.051129, 180, 85.051129], scheme: "xyz", }; var layersMapbox = []; const sourcesMapbox: any = {}; if (layerName == "geomeg") { await fetch(baseMapUrl) .then((response) => response.json()) .then((data) => { layersMapbox = JSON.parse(JSON.stringify(data.layers)); }); } else if (vectorLayes[layerName] == undefined) { const currentLayers = JSON.parse(JSON.stringify(vectorLayes[layerName])); currentLayers["id"] = currentLayers["id"] + layerName; currentLayers["source-layer"] = layerName; currentLayers["source"] = layerName; layersMapbox.push(currentLayers); } else { layersMapbox = JSON.parse(JSON.stringify(vectorLayes[layerName])); } sourcesMapbox[layerName] = tt; var exampleStyle = { id: data.id, name: "MapLibre", zoom: 10, layers: layersMapbox, bearing: 0, sources: sourcesMapbox, version: 8, metadata: { "maptiler:copyright": "cxwl" }, }; exampleStyle.glyphs = glyphs; exampleStyle.sprite = sprite; let provider = new MVTImageryProvider({ style: exampleStyle }); provider.id = layerName; viewer.imageryLayers.addImageryProvider(provider); addedMvtLayers.value[layerName].push(provider); if (layerName === "lukou_farmland" || layerName === "lukou_ecology") { viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(113.159047, 27.721148, 3270), duration: 3, }); } else { viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(113.490927, 27.048641, 406000), duration: 3, }); } } // 动态监测处理 var videoData = [113.182301, 27.971201]; async function handleCommunityChange(item: any) { if (item == "铁塔视频") { var data = await getVeidoDetail(); if (data.data != null) { addRandomGraphicByCount(videoData, data.data); } else { ElMessage.error(data.message); } } } // 添加铁塔标记 function addRandomGraphicByCount(position, url) { viewer.entities.add({ id: "tietaId", beof: "铁塔视频", name: "铁塔视频001", position: Cesium.Cartesian3.fromDegrees(position[0], position[1]), billboard: { image: "img/marker/mark-spjk.png", width: 30, height: 115, scale: 1.0, color: Cesium.Color.WHITE, eyeOffset: new Cesium.Cartesian3(0, 0, -10), }, label: { text: "铁塔视频", font: "14px sans-serif", fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, style: Cesium.LabelStyle.FILL_AND_OUTLINE, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -30), }, properties: { capacityResourceName: "铁塔视频", url: url, name: "铁塔视频", id: "tieta_001", }, }); viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(position[0], position[1], 10000), duration: 3, }); } // 3D模型切换处理 function handle3dSwitchChange(items: any) { const currentSelectedIds = items.map((item: any) => item.id); const tilesToRemove = Object.keys(addedTilesets.value).filter( (tileId) => !currentSelectedIds.includes(tileId) ); tilesToRemove.forEach((tileId) => { addedTilesets.value[tileId].forEach((tileset) => { try { if (tileset && !tileset.isDestroyed()) { tileset.destroy(); } viewer.scene.primitives.remove(tileset); } catch (e) { console.error("Error removing tileset:", e); } }); delete addedTilesets.value[tileId]; }); items.forEach((item: any) => { if (!addedTilesets.value[item.id]) { addedTilesets.value[item.id] = []; item.url.forEach((url: string) => { const tileset = new Cesium.Cesium3DTileset({ url: url, skipLevelOfDetail: true, skipScreenSpaceErrorFactor: 16, baseScreenSpaceError: 1024, maximumScreenSpaceError: 10, immediatelyLoadDesiredLevelOfDetail: false, loadSiblings: false, cullWithChildrenBounds: true, cullRequestsWhileMoving: true, cullRequestsWhileMovingMultiplier: 10, preloadWhenHidden: true, preferLeaves: true, maximumMemoryUsage: 2048, progressiveResolutionHeightFraction: 0.5, dynamicScreenSpaceErrorDensity: 0.5, dynamicScreenSpaceErrorFactor: 1, dynamicScreenSpaceError: false, }); var heightOffset = 0; if (url.indexOf("20220220") >= 0) heightOffset = -32; else if (url.indexOf("2022022101") >= 0) heightOffset = -50.4; tileset.readyPromise .then((palaceTileset) => { viewer.scene.primitives.add(palaceTileset); addedTilesets.value[item.id].push(palaceTileset); const boundingSphere = palaceTileset.boundingSphere; const cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center); const surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0); const offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset); const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3()); palaceTileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, heightOffset + 10000 ), duration: 3, }); }) .catch((error) => { console.error("Error loading tileset:", error); }); }); } }); } // 影像图层切换处理 var currentImageryLayer: Cesium.ImageryLayer | null = null; var src; function handleLayerChange(val: any) { if (currentImageryLayer) { viewer.imageryLayers.remove(currentImageryLayer); currentImageryLayer = null; } if (!val) return; if (val === "baseImagery") { addImage(val); } if (val === "zhuzhou_beidou:GF07" || val === "zhuzhou_beidou:lkyx") { src = "EPSG:4326"; } else if (val !== "baseImagery") { src = "EPSG:4326x2"; } var imageryProvider = new Cesium.WebMapTileServiceImageryProvider({ url: "http://124.232.190.14:3013/geoserver/gwc/service/wmts", layer: val, format: "image/png", style: "", tileMatrixSetID: src, maximumLevel: 18, tilingScheme: new Cesium.GeographicTilingScheme({ numberOfLevelZeroTilesX: 2, numberOfLevelZeroTilesY: 1, }), tileMatrixLabels: [...Array(18).keys()].map((level) => (src + ":" + level).toString() ), }); currentImageryLayer = viewer.imageryLayers.addImageryProvider(imageryProvider); currentImageryLayer.id = val; } // 添加基础影像 async function addImage(val: any) { var rightImageProvider = new Cesium.UrlTemplateImageryProvider({ name: "影像图", type: "xyz", layer: val, url: "http://124.232.190.30:9000/proxy/pk1725866655224/map/zzzsyx_18/{z}/{x}/{y}.png", minimumLevel: 1, maximumLevel: 17, crs: "EPSG:3857", }); currentImageryLayer = viewer.imageryLayers.addImageryProvider(rightImageProvider); currentImageryLayer.id = val; rightImageProvider.splitDirection = Cesium.SplitDirection.right; } // 清除影像图层 function clearLayer() { if (currentImageryLayer) { viewer.imageryLayers.remove(currentImageryLayer); currentImageryLayer = null; } selectedLayer.value = ""; } // 组件挂载 onMounted(async () => { viewer = $prototype.$map; await addImage("baseImagery"); const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); const scene = $prototype.$map.scene; const ellipsoid = scene.globe.ellipsoid; handler.setInputAction(function (click) { const pick = $prototype.$map.scene.pick(click.position); const cartesians = $prototype.$map.camera.pickEllipsoid(click.position, ellipsoid); var data = null; if (cartesians) { const cartographic = ellipsoid.cartesianToCartographic(cartesians); const longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(6); const latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(6); data = { longitude: longitude, latitude: latitude }; console.log(longitude + "," + latitude); } if (pick && pick.primitive._imageId != undefined && pick.primitive._imageId.indexOf("mark-spjk") >= 0) { var obj = pick.id._properties; videoPlayList.value.push({ name: obj.name, url: obj.url, id: obj.id }); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); }); // 组件卸载 onUnmounted(() => { Object.values(addedTilesets.value).forEach((tilesets) => { tilesets.forEach((tileset) => { viewer.scene.primitives.remove(tileset); }); }); addedTilesets.value = {}; }); </script> <style lang="scss" scoped> .box { position: absolute; top: 15%; right: 80px; overflow: hidden; } .top-select { display: flex; align-items: center; justify-content: center; gap: 0px; } .select-1, .select-2, .select-3 { background: linear-gradient(180deg, #00f0f87f 10%, #173c6a 100%); border-radius: 2px; border: 1px solid #00f0f87f; } .select-1 { width: 110px; border-radius: 5px 0 0 5px; } .select-2 { flex: 1; width: 90px; } .select-3 { flex: 1; width: 180px; } /* 坐标系转换按钮样式 */ .coordinate-btn { background: linear-gradient(180deg, #00f0f87f 10%, #173c6a 100%); border: 1px solid #00f0f87f; color: #b4fdff; border-radius: 0 5px 5px 0; padding: 0 15px; display: flex; align-items: center; gap: 5px; &:hover { background: linear-gradient(180deg, #00f0f8 10%, #173c6a 100%); color: #fff; box-shadow: 0 0 10px rgba(0, 240, 248, 0.5); } } /* 自定义Element组件样式 */ :deep(.el-select__wrapper) { background: transparent !important; box-shadow: none !important; } :deep(.el-select__caret) { color: #3cffff; } :deep(.el-select__placeholder), :deep(.el-select__selected-item), :deep(.el-select-dropdown__item) { color: #b4fdff !important; } :deep(.el-tag.el-tag--info) { --el-tag-bg-color: #3cffff96 !important; } /* 确保下拉菜单正常显示 */ :deep(.el-select-dropdown) { display: block !important; background: rgba(1, 28, 82, 0.8) !important; border: 1px solid #00f0f87f !important; z-index: 9999 !important; } :deep(.el-select-dropdown__item) { color: #b4fdff !important; &:hover { background: rgba(0, 240, 248, 0.2) !important; } &.selected { background: rgba(0, 240, 248, 0.3) !important; } } </style> <style lang="scss"> /* 全局样式保持不变 */ .el-select-dropdown__item { color: #b4fdff !important; } .el-select__popper.el-popper { border: 1px solid #3f3f41 !important; background: rgba(1, 28, 82, 0.8) !important; } .el-select-dropdown { border: none !important; background: rgba(1, 28, 82, 0.8) !important; &::-webkit-scrollbar { width: 6px; height: 6px; } &::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); border-radius: 3px; } &::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 3px; &:hover { background: rgba(255, 255, 255, 0.4); } } } .el-select-dropdown__item.is-hovering { border: none !important; background: rgba(1, 28, 82, 0.8) !important; } .is-focus .el-select__wrapper { box-shadow: none !important; } /* ================= 坐标系转换弹窗样式优化 ================= */ .coordinate-dialog { .el-dialog__headerbtn .el-dialog__close { color: #b4fdff !important; &:hover { color: #fff !important; } } /* 内容区域样式 */ .el-dialog__body { padding: 20px; background: transparent; } /* 表单样式 */ .coord-form { .el-form-item { margin-bottom: 20px; } .el-form-item__label { color: #b4fdff; font-weight: bold; } /* 输入框样式 */ .el-input { .el-input__wrapper { background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(0, 240, 248, 0.5); box-shadow: 0 0 5px rgba(0, 240, 248, 0.2) inset; color: #b4fdff; border-radius: 4px; transition: all 0.3s; &:hover { border-color: #00f0f8; box-shadow: 0 0 8px rgba(0, 240, 248, 0.4) inset; } &.is-focus { border-color: #00f0f8; box-shadow: 0 0 12px rgba(0, 240, 248, 0.6) inset; } } .el-input__inner { color: #b4fdff; &::placeholder { color: rgba(180, 253, 255, 0.6); } } } /* 结果输入框特殊样式 */ .el-form-item:last-child .el-input__wrapper { background: rgba(0, 240, 248, 0.1) !important; border: 1px solid rgba(0, 240, 248, 0.8) !important; } } /* 底部按钮区域 */ .el-dialog__footer { padding: 15px 20px; background: linear-gradient(90deg, rgba(23, 60, 106, 0.5), rgba(0, 240, 248, 0.2)); border-top: 1px solid rgba(0, 240, 248, 0.3); border-radius: 0 0 8px 8px; } /* 按钮样式 */ .cancel-btn { background: transparent; border: 1px solid rgba(0, 240, 248, 0.5); color: #b4fdff; &:hover { background: rgba(0, 240, 248, 0.2); border-color: #00f0f8; color: #fff; } } .convert-btn { background: linear-gradient(90deg, rgba(0, 240, 248, 0.7), rgba(23, 60, 106, 0.9)); border: none; color: #fff; font-weight: bold; box-shadow: 0 0 10px rgba(0, 240, 248, 0.5); &:hover { background: linear-gradient(90deg, rgba(0, 240, 248, 0.9), rgba(23, 60, 106, 1)); box-shadow: 0 0 15px rgba(0, 240, 248, 0.7); transform: translateY(-1px); } &:active { transform: translateY(1px); } } } </style>如上所示代码,其中我想知道卫星,矢量地图这些是使用了element的哪些组件布局,

最新推荐

recommend-type

讯图GD460电脑DSP调音软件下载

讯图GD460电脑DSP调音软件下载
recommend-type

港股历史逐笔十档订单簿分钟日数据下载

csv格式港股历史高频数据集(GB2312编码) ─────────────────────────────────── 【数据种类】逐笔成交|订单簿快照|分钟K线|多周期聚合数据 【时间跨度】2005年至今完整历史数据 【数据精度】 - Level2逐笔数据:包含十档订单簿及逐笔成交明细(≥1TB) - 多频段K线:1/5/15/30/60分钟+日/周/月线(≥100GB) 【技术特性】 ● 存储方式:网盘加密压缩包 ● 数据规范:经时间戳对齐及异常值清洗 ● 格式兼容:原生csv适配Python(pandas.read_csv)及量化平台 【应用场景】 ◆ 高频策略验证:ARIMA/LSTM时序预测、做市商策略模拟、冲击成本测算 ◆ 风险建模:市场操纵模式识别、流动性风险压力测试 ◆ AI训练:深度强化学习(DRL)策略生成、OrderBook特征工程 ◆ 学术研究:市场微观结构分析、波动率聚类效应验证 【质量保障】 √ 订单簿重构误差<0.1% √ Tick数据时延校准至交易所时钟 √ 提供完整的symbol-mapping表及除权处理说明 本数据集通过PCAOB审计标准校验,适用于《金融研究》等核心期刊论文的实证分析,满足资管机构合规性回测要求。 【版权说明】 数据源:银禾金融数据库,解释权归该数据库所有。
recommend-type

虚拟同步机离网模型与30KW PCS储能逆变技术研究:前端BUCK-BOOST电路与多功能的并网控制

内容概要:本文详细介绍了30KW PCS储能逆变器的虚拟同步机离网模型及其核心技术。首先阐述了离网与并网功能的融合,强调了逆变器在不同状态下的稳定性和可靠性。接着深入探讨了前端BUCK-BOOST电路的作用,解释了其对电压升降调节的关键意义。随后重点讲解了后端三电平逆变器的工作原理,特别是下垂控制加虚拟同步机控制策略的应用,实现了多台PCS的并联运行。此外,还讨论了C语言编程和S-function在DSP28XX系列芯片上的实现,提供了详细的代码和理论计算支持。最后,总结了该逆变器的速度与效率优势,展示了其在电力系统中的广泛应用前景。 适合人群:从事电力电子领域的工程师、研究人员及高校相关专业师生。 使用场景及目标:①理解和掌握30KW PCS储能逆变器的离网并网功能;②学习前端BUCK-BOOST电路和后端三电平逆变器的设计原理;③熟悉虚拟同步机控制技术和C语言编程在DSP芯片上的应用。 其他说明:本文基于大厂内部资料,提供了详尽的理论计算和实际问题解决方案,有助于提高产品研发和应用能力。
recommend-type

永磁同步电机直接转矩控制仿真的MATLAB 2010a版

内容概要:本文详细介绍了如何使用MATLAB 2010a进行永磁同步电机(PMSM)的直接转矩控制(DTC)仿真。首先,文章提供了PMSM的关键参数配置,强调了电感参数单位的选择。接着,逐步讲解了磁链观测器、滞环比较器以及电压矢量选择表的设计方法,并给出了具体的MATLAB代码实现。此外,还分享了一些实用的调参技巧,如调整磁链观测器、滞环宽度等,以优化仿真效果。最后展示了仿真结果图,包括电磁转矩跟踪、转速曲线和磁链轨迹,帮助读者更好地理解和验证DTC的工作原理。 适合人群:具有一定MATLAB基础并希望深入了解电机控制领域的工程师和技术爱好者。 使用场景及目标:适用于需要研究或应用永磁同步电机直接转矩控制技术的研究人员、学生或工程师。主要目标是掌握DTC的基本原理及其在MATLAB环境下的具体实现方法,同时学会如何根据实际情况调整参数以获得更好的性能。 阅读建议:由于文中涉及较多的技术细节和代码片段,在阅读过程中建议读者跟随作者提供的步骤亲自尝试编写代码,并结合理论知识深入理解每个模块的功能。对于遇到的问题可以通过查阅相关文献资料或者在线社区寻求解决方案。
recommend-type

langchain4j-embeddings-bge-small-en-1.1.0-beta7.jar中文文档.zip

1、压缩文件中包含: 中文文档、jar包下载地址、Maven依赖、Gradle依赖、源代码下载地址。 2、使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 3、特殊说明: (1)本文档为人性化翻译,精心制作,请放心使用; (2)只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; (3)不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 4、温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件。 5、本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册。
recommend-type

掌握XFireSpring整合技术:HELLOworld原代码使用教程

标题:“xfirespring整合使用原代码”中提到的“xfirespring”是指将XFire和Spring框架进行整合使用。XFire是一个基于SOAP的Web服务框架,而Spring是一个轻量级的Java/Java EE全功能栈的应用程序框架。在Web服务开发中,将XFire与Spring整合能够发挥两者的优势,例如Spring的依赖注入、事务管理等特性,与XFire的简洁的Web服务开发模型相结合。 描述:“xfirespring整合使用HELLOworld原代码”说明了在这个整合过程中实现了一个非常基本的Web服务示例,即“HELLOworld”。这通常意味着创建了一个能够返回"HELLO world"字符串作为响应的Web服务方法。这个简单的例子用来展示如何设置环境、编写服务类、定义Web服务接口以及部署和测试整合后的应用程序。 标签:“xfirespring”表明文档、代码示例或者讨论集中于XFire和Spring的整合技术。 文件列表中的“index.jsp”通常是一个Web应用程序的入口点,它可能用于提供一个用户界面,通过这个界面调用Web服务或者展示Web服务的调用结果。“WEB-INF”是Java Web应用中的一个特殊目录,它存放了应用服务器加载的Servlet类文件和相关的配置文件,例如web.xml。web.xml文件中定义了Web应用程序的配置信息,如Servlet映射、初始化参数、安全约束等。“META-INF”目录包含了元数据信息,这些信息通常由部署工具使用,用于描述应用的元数据,如manifest文件,它记录了归档文件中的包信息以及相关的依赖关系。 整合XFire和Spring框架,具体知识点可以分为以下几个部分: 1. XFire框架概述 XFire是一个开源的Web服务框架,它是基于SOAP协议的,提供了一种简化的方式来创建、部署和调用Web服务。XFire支持多种数据绑定,包括XML、JSON和Java数据对象等。开发人员可以使用注解或者基于XML的配置来定义服务接口和服务实现。 2. Spring框架概述 Spring是一个全面的企业应用开发框架,它提供了丰富的功能,包括但不限于依赖注入、面向切面编程(AOP)、数据访问/集成、消息传递、事务管理等。Spring的核心特性是依赖注入,通过依赖注入能够将应用程序的组件解耦合,从而提高应用程序的灵活性和可测试性。 3. XFire和Spring整合的目的 整合这两个框架的目的是为了利用各自的优势。XFire可以用来创建Web服务,而Spring可以管理这些Web服务的生命周期,提供企业级服务,如事务管理、安全性、数据访问等。整合后,开发者可以享受Spring的依赖注入、事务管理等企业级功能,同时利用XFire的简洁的Web服务开发模型。 4. XFire与Spring整合的基本步骤 整合的基本步骤可能包括添加必要的依赖到项目中,配置Spring的applicationContext.xml,以包括XFire特定的bean配置。比如,需要配置XFire的ServiceExporter和ServicePublisher beans,使得Spring可以管理XFire的Web服务。同时,需要定义服务接口以及服务实现类,并通过注解或者XML配置将其关联起来。 5. Web服务实现示例:“HELLOworld” 实现一个Web服务通常涉及到定义服务接口和服务实现类。服务接口定义了服务的方法,而服务实现类则提供了这些方法的具体实现。在XFire和Spring整合的上下文中,“HELLOworld”示例可能包含一个接口定义,比如`HelloWorldService`,和一个实现类`HelloWorldServiceImpl`,该类有一个`sayHello`方法返回"HELLO world"字符串。 6. 部署和测试 部署Web服务时,需要将应用程序打包成WAR文件,并部署到支持Servlet 2.3及以上版本的Web应用服务器上。部署后,可以通过客户端或浏览器测试Web服务的功能,例如通过访问XFire提供的服务描述页面(WSDL)来了解如何调用服务。 7. JSP与Web服务交互 如果在应用程序中使用了JSP页面,那么JSP可以用来作为用户与Web服务交互的界面。例如,JSP可以包含JavaScript代码来发送异步的AJAX请求到Web服务,并展示返回的结果给用户。在这个过程中,JSP页面可能使用XMLHttpRequest对象或者现代的Fetch API与Web服务进行通信。 8. 项目配置文件说明 项目配置文件如web.xml和applicationContext.xml分别在Web应用和服务配置中扮演关键角色。web.xml负责定义Web组件,比如Servlet、过滤器和监听器,而applicationContext.xml则负责定义Spring容器中的bean,包括数据源、事务管理器、业务逻辑组件和服务访问器等。 总之,通过上述整合使用原代码的知识点,可以深入理解XFire与Spring框架的结合使用,以及如何开发和部署基本的Web服务。这些技术知识有助于进行更高层次的Web服务开发,以及在复杂的IT环境中灵活运用各种框架和工具。
recommend-type

【Unity2018汉化大揭秘】:一步到位优化中文用户体验

# 摘要 本论文详细介绍了Unity2018汉化项目的实施过程,从理论准备到实践步骤,再到后期的测试与优化,最终分享了汉化经验和心得。首先,强调了汉化项目对于用户体验提升和产品国际化的重要性,并分析了汉化资源的分类、识别与管理方法。接着,详细阐述了汉化工具的选择、脚本编写、资源手动调整等实践步骤,以及汉化质量测试、体验优化和项目维护更新的重要性。论文最后通过案例研究,分析了汉化中的挑战和应对策略,并预测了汉化技术未来的发展方向。整个汉化过程不仅涉及技术层面的操作,还包含了深入的理论研究和实践心得分享,对提升汉化项目的效率和质量具有指导意义。 # 关键字 Unity汉化;用户体验;国际化;资源
recommend-type

iPhone

<think>我们注意到用户意图不明确,但提到了“照片提取”和“其他功能帮助”。因此,我们需要通过搜索来获取关于iPhone照片提取的常见方法以及其他可能的功能帮助。由于用户问题比较宽泛,我们将重点放在照片提取上,因为这是明确提到的关键词。同时,我们也会考虑一些其他常用功能的帮助。首先,针对照片提取,可能涉及从iPhone导出照片、从备份中提取照片、或者从损坏的设备中恢复照片等。我们将搜索这些方面的信息。其次,关于其他功能帮助,我们可以提供一些常见问题的快速指南,如电池优化、屏幕时间管理等。根据要求,我们需要将答案组织为多个方法或步骤,并在每个步骤间换行。同时,避免使用第一人称和步骤词汇。由于
recommend-type

驾校一点通软件:提升驾驶证考试通过率

标题“驾校一点通”指向的是一款专门为学员考取驾驶证提供帮助的软件,该软件强调其辅助性质,旨在为学员提供便捷的学习方式和复习资料。从描述中可以推断出,“驾校一点通”是一个与驾驶考试相关的应用软件,这类软件一般包含驾驶理论学习、模拟考试、交通法规解释等内容。 文件标题中的“2007”这个年份标签很可能意味着软件的最初发布时间或版本更新年份,这说明了软件具有一定的历史背景和可能经过了多次更新,以适应不断变化的驾驶考试要求。 压缩包子文件的文件名称列表中,有以下几个文件类型值得关注: 1. images.dat:这个文件名表明,这是一个包含图像数据的文件,很可能包含了用于软件界面展示的图片,如各种标志、道路场景等图形。在驾照学习软件中,这类图片通常用于帮助用户认识和记忆不同交通标志、信号灯以及驾驶过程中需要注意的各种道路情况。 2. library.dat:这个文件名暗示它是一个包含了大量信息的库文件,可能包含了法规、驾驶知识、考试题库等数据。这类文件是提供给用户学习驾驶理论知识和准备科目一理论考试的重要资源。 3. 驾校一点通小型汽车专用.exe:这是一个可执行文件,是软件的主要安装程序。根据标题推测,这款软件主要是针对小型汽车驾照考试的学员设计的。通常,小型汽车(C1类驾照)需要学习包括车辆构造、基础驾驶技能、安全行车常识、交通法规等内容。 4. 使用说明.html:这个文件是软件使用说明的文档,通常以网页格式存在,用户可以通过浏览器阅读。使用说明应该会详细介绍软件的安装流程、功能介绍、如何使用软件的各种模块以及如何通过软件来帮助自己更好地准备考试。 综合以上信息,我们可以挖掘出以下几个相关知识点: - 软件类型:辅助学习软件,专门针对驾驶考试设计。 - 应用领域:主要用于帮助驾考学员准备理论和实践考试。 - 文件类型:包括图片文件(images.dat)、库文件(library.dat)、可执行文件(.exe)和网页格式的说明文件(.html)。 - 功能内容:可能包含交通法规知识学习、交通标志识别、驾驶理论学习、模拟考试、考试题库练习等功能。 - 版本信息:软件很可能最早发布于2007年,后续可能有多个版本更新。 - 用户群体:主要面向小型汽车驾照考生,即C1类驾照学员。 - 使用方式:用户需要将.exe安装文件进行安装,然后根据.html格式的使用说明来熟悉软件操作,从而利用images.dat和library.dat中的资源来辅助学习。 以上知识点为从给定文件信息中提炼出来的重点,这些内容对于了解“驾校一点通”这款软件的功能、作用、使用方法以及它的发展历史都有重要的指导意义。
recommend-type

【DFLauncher自动化教程】:简化游戏启动流程,让游戏体验更流畅

# 摘要 DFLauncher是一个功能丰富的游戏启动和管理平台,本论文将介绍其安装、基础使用、高级设置、社区互动以及插件开发等方面。通过对配置文件的解析、界面定制、自动化功能的实现、高级配置选项、安全性和性能监控的详细讨论,本文阐述了DFLauncher如何帮助用户更高效地管理和优化游戏环境。此外,本文还探讨了DFLauncher社区的资源分享、教育教程和插件开发等内容,