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/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 53ed7b2..a709b16 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(); }, @@ -2920,6 +3037,10 @@ export default { initScaleBar() { const that = this const update = () => { + if (!that.isApplyingScale) { + that.useCustomScale = false + that.customScaleText = '' + } that.updateScaleBar() } update() @@ -2960,9 +3081,10 @@ export default { if (leftCartesian && rightCartesian) { const rawMeters = Cesium.Cartesian3.distance(leftCartesian, rightCartesian) if (rawMeters > 0) { + const metersPerPx = rawMeters / barPx const niceMeters = this.niceScaleValue(rawMeters) const widthPx = Math.round((niceMeters / rawMeters) * barPx) - const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` + const text = this.formatScaleText(niceMeters) return { text, widthPx, niceMeters } } } @@ -2976,8 +3098,8 @@ export default { 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 text = this.formatScaleText(niceMeters) + return { text, widthPx, niceMeters } } } // 最后备用:从 2D 相机视锥估算(正交宽度 -> 米) @@ -2988,12 +3110,31 @@ export default { 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 text = this.formatScaleText(niceMeters) + return { text, widthPx, niceMeters } } } 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] @@ -3003,14 +3144,29 @@ export default { } return best }, + /** 格式化比例尺文本 */ + formatScaleText(meters) { + if (meters >= 1000) { + const kmValue = (meters / 1000).toFixed(0) + return `${kmValue}km` + } else { + return `${meters}m` + } + }, 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/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) { diff --git a/ruoyi-ui/src/views/childRoom/TopHeader.vue b/ruoyi-ui/src/views/childRoom/TopHeader.vue index b04d5d2..2b027a7 100644 --- a/ruoyi-ui/src/views/childRoom/TopHeader.vue +++ b/ruoyi-ui/src/views/childRoom/TopHeader.vue @@ -334,6 +334,14 @@ export default { isIconEditMode: { type: Boolean, default: false + }, + currentScaleConfig: { + type: Object, + default: () => ({ + scaleNumerator: 1, + scaleDenominator: 1000, + unit: 'm' + }) } }, data() { @@ -366,6 +374,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 42fa47e..0d42696 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -6,8 +6,10 @@ @open-route-dialog="handleOpenRouteEdit" />
@@ -67,6 +69,7 @@ :can-set-k-time="canSetKTime" :user-avatar="userAvatar" :is-icon-edit-mode="isIconEditMode" + :current-scale-config="scaleConfig" @select-nav="selectTopNav" @set-k-time="openKTimeSetDialog" @save-plan="savePlan" @@ -269,6 +272,7 @@ v-model="showScaleDialog" :scale="currentScale" @save="saveScale" + @unit-change="handleScaleUnitChange" /> @@ -368,7 +372,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, @@ -1395,10 +1408,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..b6650ba 100644 --- a/ruoyi-ui/src/views/dialogs/ScaleDialog.vue +++ b/ruoyi-ui/src/views/dialogs/ScaleDialog.vue @@ -30,17 +30,6 @@ - - - - - - - - - - - @@ -71,9 +60,8 @@ export default { return { formData: { scaleNumerator: 1, - scaleDenominator: 1000, - unit: 'km', - position: 'bottom-right' + scaleDenominator: 20, + unit: 'km' }, rules: { scaleNumerator: [ @@ -84,9 +72,6 @@ export default { ], unit: [ { required: true, message: '请选择单位', trigger: 'change' } - ], - position: [ - { required: true, message: '请选择显示位置', trigger: 'change' } ] } }; @@ -101,15 +86,19 @@ export default { if (this.value && newVal) { this.initFormData(); } + }, + 'formData.unit'(newVal) { + if (this.value) { + this.$emit('unit-change', newVal); + } } }, methods: { initFormData() { this.formData = { scaleNumerator: this.scale.scaleNumerator || 1, - scaleDenominator: this.scale.scaleDenominator || 1000, - unit: this.scale.unit || 'km', - position: this.scale.position || 'bottom-right' + scaleDenominator: this.scale.scaleDenominator || 20, + unit: this.scale.unit || 'km' }; }, 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 {