From 825452a560a34f0dd015ddb43595729bf1d608ee Mon Sep 17 00:00:00 2001 From: sd <1504629600@qq.com> Date: Tue, 3 Feb 2026 16:38:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=82=B9=E5=8F=8A?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E6=8F=92=E5=85=A5bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/public/stepEditor.html | 24 +++++++++++++++++++++--- ruoyi-ui/src/views/childRoom/BottomTimeline.vue | 17 +++++++++-------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/ruoyi-ui/public/stepEditor.html b/ruoyi-ui/public/stepEditor.html index 8c2e600..eb9f60f 100644 --- a/ruoyi-ui/public/stepEditor.html +++ b/ruoyi-ui/public/stepEditor.html @@ -515,25 +515,43 @@ insertText(text) { const editor = this.$refs.editor const selection = window.getSelection() + if (selection.rangeCount > 0) { const range = selection.getRangeAt(0) const textNode = document.createTextNode(text) range.insertNode(textNode) } else { - editor.innerHTML += `

${text}

` + if (editor.innerHTML.trim() === '') { + editor.innerHTML = `

${text}

` + } else { + editor.innerHTML += `

${text}

` + } } + + this.$nextTick(() => { + this.updateWordCount() + }) }, insertHTML(html) { const editor = this.$refs.editor const selection = window.getSelection() + if (selection.rangeCount > 0) { const range = selection.getRangeAt(0) const div = document.createElement('div') div.innerHTML = html range.insertNode(div) } else { - editor.innerHTML += html + if (editor.innerHTML.trim() === '') { + editor.innerHTML = `

${html}

` + } else { + editor.innerHTML += `

${html}

` + } } + + this.$nextTick(() => { + this.updateWordCount() + }) }, onInput() { this.content = this.$refs.editor.innerHTML @@ -566,7 +584,7 @@ }, onFocus() { const editor = this.$refs.editor - if (editor.innerText.trim() === '' || editor.innerText === this.placeholder) { + if (editor.innerText.trim() === '' && editor.innerHTML.trim() === '') { editor.innerHTML = '' } }, diff --git a/ruoyi-ui/src/views/childRoom/BottomTimeline.vue b/ruoyi-ui/src/views/childRoom/BottomTimeline.vue index d4ecdba..8b8b636 100644 --- a/ruoyi-ui/src/views/childRoom/BottomTimeline.vue +++ b/ruoyi-ui/src/views/childRoom/BottomTimeline.vue @@ -471,18 +471,19 @@ export default { }) this.scaleLabels = [] - const labelCount = 5 - for (let i = 0; i <= labelCount; i++) { - const percentage = (i / labelCount) * 100 - const currentSeconds = startSeconds + (totalSeconds * percentage / 100) - const hours = Math.floor(currentSeconds / 3600) - const minutes = Math.floor((currentSeconds % 3600) / 60) + const sortedSegments = [...this.timelineSegments].sort((a, b) => { + return this.timeToSeconds(a.time) - this.timeToSeconds(b.time) + }) + sortedSegments.forEach(segment => { + const segmentSeconds = this.timeToSeconds(segment.time) + const hours = Math.floor(segmentSeconds / 3600) + const minutes = Math.floor((segmentSeconds % 3600) / 60) const timeStr = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}` this.scaleLabels.push({ - position: percentage, + position: segment.position, time: timeStr }) - } + }) }, editSegment(segment) { From 28d36ca0a15126bc04a6cf386104f2bdc95314fc Mon Sep 17 00:00:00 2001 From: sd <1504629600@qq.com> Date: Thu, 5 Feb 2026 13:24:23 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=AF=94=E4=BE=8B=E5=B0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/index.vue | 190 +++++++++++++++++++++++++---- ruoyi-ui/src/views/childRoom/TopHeader.vue | 18 +++ ruoyi-ui/src/views/childRoom/index.vue | 43 ++++++- ruoyi-ui/src/views/dialogs/ScaleDialog.vue | 41 +++---- 4 files changed, 248 insertions(+), 44 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index a84b523..c5f5a32 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -46,7 +46,7 @@
-
+
{{ scaleBarText }}
@@ -89,6 +89,15 @@ export default { type: String, default: 'airspace' // 'airspace' or 'ranging' }, + scaleConfig: { + type: Object, + default: () => ({ + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'km', + position: 'bottom-right' + }) + } }, watch: { drawDomClick: { @@ -99,6 +108,14 @@ export default { // this.initMap() } } + }, + scaleConfig: { + deep: true, + handler(newVal) { + if (newVal) { + this.updateScaleFromConfig(newVal) + } + } } }, data() { @@ -149,6 +166,10 @@ export default { // 比例尺(高德风格) scaleBarText: '--', scaleBarWidthPx: 80, + useCustomScale: false, + customScaleText: '', + currentScaleUnit: 'm', + isApplyingScale: false, // 定位相关 locateDialogVisible: false } @@ -174,6 +195,102 @@ export default { this.destroyViewer() }, methods: { + updateScaleFromConfig(config) { + if (!config || !this.viewer) return + + const { scaleNumerator, scaleDenominator, unit } = config + if (!scaleDenominator || scaleDenominator <= 0) return + + let displayValue = '' + let metersPerPixel = 0 + + const standardDPI = 96 + const pixelsPerCm = standardDPI * 0.393701 + + const scaleFactor = scaleDenominator / scaleNumerator + + switch (unit) { + case 'm': + displayValue = `1:${scaleDenominator}` + metersPerPixel = scaleFactor / pixelsPerCm + break + case 'km': + displayValue = `1:${scaleDenominator}` + metersPerPixel = (scaleFactor * 1000) / pixelsPerCm + break + default: + displayValue = `1:${scaleDenominator}` + metersPerPixel = scaleFactor / pixelsPerCm + } + + console.log('比例尺设置:', displayValue, '每像素米数:', metersPerPixel.toFixed(4)) + + this.isApplyingScale = true + this.useCustomScale = true + this.customScaleText = displayValue + this.currentScaleUnit = unit + + this.applyScaleToCamera(metersPerPixel) + this.updateScaleBar() + this.$forceUpdate() + + setTimeout(() => { + this.isApplyingScale = false + }, 1000) + }, + + applyScaleToCamera(metersPerPixel) { + if (!this.viewer || !this.viewer.camera) return + + const canvas = this.viewer.scene.canvas + const width = canvas.clientWidth + const height = canvas.clientHeight + + if (width <= 0 || height <= 0) return + + const camera = this.viewer.camera + const scene = this.viewer.scene + + if (scene.mode === Cesium.SceneMode.SCENE2D) { + const frustumWidth = width * metersPerPixel + camera.frustum.right = frustumWidth / 2 + camera.frustum.left = -frustumWidth / 2 + const frustumHeight = height * metersPerPixel + camera.frustum.top = frustumHeight / 2 + camera.frustum.bottom = -frustumHeight / 2 + } else { + const cameraPosition = camera.positionCartographic + const groundWidth = width * metersPerPixel + + const fov = camera.frustum.fov || Cesium.Math.PI_OVER_THREE + const aspectRatio = width / height + const verticalFov = fov / aspectRatio + + const targetHeight = (groundWidth / 2) / Math.tan(fov / 2) + + const destination = Cesium.Cartesian3.fromRadians( + cameraPosition.longitude, + cameraPosition.latitude, + targetHeight + ) + + camera.flyTo({ + destination: destination, + duration: 0.5, + orientation: { + heading: camera.heading, + pitch: camera.pitch, + roll: camera.roll + } + }) + } + }, + handleScaleClick() { + this.$emit('scale-click', { + useCustomScale: this.useCustomScale, + customScaleText: this.customScaleText + }) + }, preventContextMenu(e) { e.preventDefault(); }, @@ -2766,6 +2883,10 @@ export default { initScaleBar() { const that = this const update = () => { + if (!that.isApplyingScale) { + that.useCustomScale = false + that.customScaleText = '' + } that.updateScaleBar() } update() @@ -2806,10 +2927,12 @@ export default { if (leftCartesian && rightCartesian) { const rawMeters = Cesium.Cartesian3.distance(leftCartesian, rightCartesian) if (rawMeters > 0) { - const niceMeters = this.niceScaleValue(rawMeters) - const widthPx = Math.round((niceMeters / rawMeters) * barPx) - const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` - return { text, widthPx, niceMeters } + const metersPerPx = rawMeters / barPx + const scaleRatio = this.calculateScaleRatio(metersPerPx) + const pixelsPerCm = this.getPixelsPerCm() + const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) + const text = `1:${scaleRatio}${this.currentScaleUnit}` + return { text, widthPx, niceMeters: rawMeters } } } // 2D/WebMercator 备用:用整屏宽度对应的地理范围计算(四角 pick 得到视口矩形) @@ -2819,11 +2942,11 @@ export default { const widthMeters = Cesium.Cartesian3.distance(leftBottom, rightBottom) if (widthMeters > 0) { const metersPerPx = widthMeters / w - const rawMeters = metersPerPx * barPx - const niceMeters = this.niceScaleValue(rawMeters) - const widthPx = Math.round(niceMeters / metersPerPx) - const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` - return { text, widthPx: Math.min(120, Math.max(40, widthPx)), niceMeters } + const scaleRatio = this.calculateScaleRatio(metersPerPx) + const pixelsPerCm = this.getPixelsPerCm() + const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) + const text = `1:${scaleRatio}${this.currentScaleUnit}` + return { text, widthPx, niceMeters: widthMeters } } } // 最后备用:从 2D 相机视锥估算(正交宽度 -> 米) @@ -2831,15 +2954,34 @@ export default { const frustumWidth = camera.frustum.right - camera.frustum.left if (frustumWidth > 0) { const metersPerPx = frustumWidth / w - const rawMeters = metersPerPx * barPx - const niceMeters = this.niceScaleValue(rawMeters) - const widthPx = Math.round(niceMeters / metersPerPx) - const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` - return { text, widthPx: Math.min(120, Math.max(40, widthPx)), niceMeters } + const scaleRatio = this.calculateScaleRatio(metersPerPx) + const pixelsPerCm = this.getPixelsPerCm() + const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) + const text = `1:${scaleRatio}${this.currentScaleUnit}` + return { text, widthPx, niceMeters: frustumWidth } } } return null }, + /** 计算比例尺比例 */ + calculateScaleRatio(metersPerPx) { + const standardDPI = 96 + const pixelsPerCm = standardDPI * 0.393701 + const metersPerCm = metersPerPx * pixelsPerCm + let scaleRatio = 0 + if (this.currentScaleUnit === 'km') { + scaleRatio = Math.round(metersPerCm / 1000) + } else { + scaleRatio = Math.round(metersPerCm) + } + return scaleRatio + }, + /** 获取1厘米对应的像素数 */ + getPixelsPerCm() { + const standardDPI = 96 + const pixelsPerCm = standardDPI * 0.393701 + return pixelsPerCm + }, /** 将实际距离圆整为易读的刻度值(米) */ niceScaleValue(meters) { const candidates = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000] @@ -2850,13 +2992,19 @@ export default { return best }, updateScaleBar() { - const info = this.getScaleBarInfo() - if (info) { - this.scaleBarText = info.text - this.scaleBarWidthPx = Math.min(120, Math.max(40, info.widthPx)) + if (this.useCustomScale && this.customScaleText) { + this.scaleBarText = `${this.customScaleText}${this.currentScaleUnit}` + const pixelsPerCm = this.getPixelsPerCm() + this.scaleBarWidthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) } else { - this.scaleBarText = '--' - this.scaleBarWidthPx = 80 + const info = this.getScaleBarInfo() + if (info) { + this.scaleBarText = info.text + this.scaleBarWidthPx = Math.min(120, Math.max(40, info.widthPx)) + } else { + this.scaleBarText = '--' + this.scaleBarWidthPx = 80 + } } this.$nextTick() }, diff --git a/ruoyi-ui/src/views/childRoom/TopHeader.vue b/ruoyi-ui/src/views/childRoom/TopHeader.vue index b3e3b34..2d21f3e 100644 --- a/ruoyi-ui/src/views/childRoom/TopHeader.vue +++ b/ruoyi-ui/src/views/childRoom/TopHeader.vue @@ -319,6 +319,14 @@ export default { isIconEditMode: { type: Boolean, default: false + }, + currentScaleConfig: { + type: Object, + default: () => ({ + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'm' + }) } }, data() { @@ -351,6 +359,16 @@ export default { ] } }, + watch: { + currentScaleConfig: { + deep: true, + handler(newVal) { + if (newVal) { + this.currentScale = { ...newVal } + } + } + } + }, methods: { selectTopNav(item) { this.$emit('select-nav', item) diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index b3956c1..331e55b 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -6,8 +6,10 @@ + @open-waypoint-dialog="handleOpenWaypointEdit" + @scale-click="handleScaleClick" />

二维GIS地图区域

@@ -44,6 +46,7 @@ :astro-time="astroTime" :user-avatar="userAvatar" :is-icon-edit-mode="isIconEditMode" + :current-scale-config="scaleConfig" @select-nav="selectTopNav" @save-plan="savePlan" @import-plan-file="importPlanFile" @@ -238,6 +241,7 @@ v-model="showScaleDialog" :scale="currentScale" @save="saveScale" + @unit-change="handleScaleUnitChange" /> @@ -335,7 +339,16 @@ export default { showPowerZoneDialog: false, currentPowerZone: {}, showScaleDialog: false, - currentScale: {}, + currentScale: { + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'm' + }, + scaleConfig: { + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'm' + }, showExternalParamsDialog: false, currentExternalParams: {}, showPageLayoutDialog: false, @@ -1111,10 +1124,36 @@ export default { saveScale(scale) { console.log('保存比例尺:', scale) + this.scaleConfig = { + scaleNumerator: scale.scaleNumerator, + scaleDenominator: scale.scaleDenominator, + unit: scale.unit + } + + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.updateScaleFromConfig(this.scaleConfig) + } + const scaleText = `${scale.scaleNumerator}:${scale.scaleDenominator}` this.$message.success(`比例尺 "${scaleText}" 保存成功`); }, + handleScaleClick(scaleInfo) { + this.currentScale = { + scaleNumerator: this.scaleConfig.scaleNumerator, + scaleDenominator: this.scaleConfig.scaleDenominator, + unit: this.scaleConfig.unit + } + this.showScaleDialog = true + }, + + handleScaleUnitChange(unit) { + this.scaleConfig.unit = unit + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.currentScaleUnit = unit + } + }, + // 地图下拉菜单方法 loadTerrain() { this.$message.success('加载/切换地形'); diff --git a/ruoyi-ui/src/views/dialogs/ScaleDialog.vue b/ruoyi-ui/src/views/dialogs/ScaleDialog.vue index 4015c8e..c4d64fb 100644 --- a/ruoyi-ui/src/views/dialogs/ScaleDialog.vue +++ b/ruoyi-ui/src/views/dialogs/ScaleDialog.vue @@ -30,17 +30,6 @@ - - - - - - - - - - - @@ -72,8 +61,7 @@ export default { formData: { scaleNumerator: 1, scaleDenominator: 1000, - unit: 'km', - position: 'bottom-right' + unit: 'm' }, rules: { scaleNumerator: [ @@ -84,9 +72,6 @@ export default { ], unit: [ { required: true, message: '请选择单位', trigger: 'change' } - ], - position: [ - { required: true, message: '请选择显示位置', trigger: 'change' } ] } }; @@ -101,6 +86,11 @@ export default { if (this.value && newVal) { this.initFormData(); } + }, + 'formData.unit'(newVal) { + if (this.value) { + this.$emit('unit-change', newVal); + } } }, methods: { @@ -108,8 +98,7 @@ export default { this.formData = { scaleNumerator: this.scale.scaleNumerator || 1, scaleDenominator: this.scale.scaleDenominator || 1000, - unit: this.scale.unit || 'km', - position: this.scale.position || 'bottom-right' + unit: this.scale.unit || 'm' }; }, closeDialog() { @@ -151,7 +140,7 @@ export default { border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); width: 90%; - max-width: 500px; + max-width: 400px; max-height: 90vh; overflow-y: auto; animation: dialog-fade-in 0.3s ease; @@ -173,7 +162,7 @@ export default { display: flex; align-items: center; justify-content: space-between; - padding: 16px 20px; + padding: 12px 16px; border-bottom: 1px solid #e8e8e8; } @@ -196,7 +185,17 @@ export default { } .dialog-body { - padding: 20px; + padding: 16px; +} + +.quick-scale-buttons { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.quick-scale-buttons .el-button { + margin: 0; } .scale-inputs { From 4e14d47edc387c506bf0a8cc9fcb9d7e262c5c3c Mon Sep 17 00:00:00 2001 From: sd <1504629600@qq.com> Date: Thu, 5 Feb 2026 15:29:30 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=AF=94=E4=BE=8B=E5=B0=BA=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/index.vue | 38 ++++++++++++++++++------------ ruoyi-ui/src/views/dialogs/ScaleDialog.vue | 8 +++---- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index c5f5a32..fd6a625 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -2928,11 +2928,10 @@ export default { const rawMeters = Cesium.Cartesian3.distance(leftCartesian, rightCartesian) if (rawMeters > 0) { const metersPerPx = rawMeters / barPx - const scaleRatio = this.calculateScaleRatio(metersPerPx) - const pixelsPerCm = this.getPixelsPerCm() - const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) - const text = `1:${scaleRatio}${this.currentScaleUnit}` - return { text, widthPx, niceMeters: rawMeters } + const niceMeters = this.niceScaleValue(rawMeters) + const widthPx = Math.round((niceMeters / rawMeters) * barPx) + const text = this.formatScaleText(niceMeters) + return { text, widthPx, niceMeters } } } // 2D/WebMercator 备用:用整屏宽度对应的地理范围计算(四角 pick 得到视口矩形) @@ -2942,11 +2941,11 @@ export default { const widthMeters = Cesium.Cartesian3.distance(leftBottom, rightBottom) if (widthMeters > 0) { const metersPerPx = widthMeters / w - const scaleRatio = this.calculateScaleRatio(metersPerPx) - const pixelsPerCm = this.getPixelsPerCm() - const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) - const text = `1:${scaleRatio}${this.currentScaleUnit}` - return { text, widthPx, niceMeters: widthMeters } + const rawMeters = metersPerPx * barPx + const niceMeters = this.niceScaleValue(rawMeters) + const widthPx = Math.round(niceMeters / metersPerPx) + const text = this.formatScaleText(niceMeters) + return { text, widthPx, niceMeters } } } // 最后备用:从 2D 相机视锥估算(正交宽度 -> 米) @@ -2954,11 +2953,11 @@ export default { const frustumWidth = camera.frustum.right - camera.frustum.left if (frustumWidth > 0) { const metersPerPx = frustumWidth / w - const scaleRatio = this.calculateScaleRatio(metersPerPx) - const pixelsPerCm = this.getPixelsPerCm() - const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) - const text = `1:${scaleRatio}${this.currentScaleUnit}` - return { text, widthPx, niceMeters: frustumWidth } + const rawMeters = metersPerPx * barPx + const niceMeters = this.niceScaleValue(rawMeters) + const widthPx = Math.round(niceMeters / metersPerPx) + const text = this.formatScaleText(niceMeters) + return { text, widthPx, niceMeters } } } return null @@ -2991,6 +2990,15 @@ export default { } return best }, + /** 格式化比例尺文本 */ + formatScaleText(meters) { + if (meters >= 1000) { + const kmValue = (meters / 1000).toFixed(0) + return `${kmValue}km` + } else { + return `${meters}m` + } + }, updateScaleBar() { if (this.useCustomScale && this.customScaleText) { this.scaleBarText = `${this.customScaleText}${this.currentScaleUnit}` diff --git a/ruoyi-ui/src/views/dialogs/ScaleDialog.vue b/ruoyi-ui/src/views/dialogs/ScaleDialog.vue index c4d64fb..b6650ba 100644 --- a/ruoyi-ui/src/views/dialogs/ScaleDialog.vue +++ b/ruoyi-ui/src/views/dialogs/ScaleDialog.vue @@ -60,8 +60,8 @@ export default { return { formData: { scaleNumerator: 1, - scaleDenominator: 1000, - unit: 'm' + scaleDenominator: 20, + unit: 'km' }, rules: { scaleNumerator: [ @@ -97,8 +97,8 @@ export default { initFormData() { this.formData = { scaleNumerator: this.scale.scaleNumerator || 1, - scaleDenominator: this.scale.scaleDenominator || 1000, - unit: this.scale.unit || 'm' + scaleDenominator: this.scale.scaleDenominator || 20, + unit: this.scale.unit || 'km' }; }, closeDialog() {