menghao 3 days ago
parent
commit
19772a72dc
  1. 27
      ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
  2. 1353
      ruoyi-ui/src/views/cesiumMap/index.vue
  3. 48
      ruoyi-ui/src/views/childRoom/ScreenshotGalleryPanel.vue
  4. 93
      ruoyi-ui/src/views/childRoom/index.vue

27
ruoyi-ui/src/views/cesiumMap/ContextMenu.vue

@ -1,14 +1,6 @@
<template>
<div>
<div class="context-menu" v-if="visible" :style="positionStyle">
<!-- 重叠/接近时切换选择其他图形 -->
<div class="menu-section" v-if="pickList && pickList.length > 1">
<div class="menu-item" @click="$emit('switch-pick')">
<MenuGlyph name="switch" />
<span class="menu-label">切换选择 ({{ (pickIndex || 0) + 1 }}/{{ pickList.length }})</span>
</div>
</div>
<!-- 框选多平台复制摆放 / 批量删除 -->
<div class="menu-section" v-if="entityData && entityData.type === 'platformBoxSelection'">
<div class="menu-title">框选平台{{ entityData.count }} </div>
@ -188,6 +180,10 @@
<MenuGlyph name="target" />
<span class="menu-label">威力区</span>
</div>
<div class="menu-item" @click="handleEditZoneBatch">
<MenuGlyph name="editDoc" />
<span class="menu-label">编辑模式</span>
</div>
<div class="menu-item menu-item-sub" @click="handleToggleDetectionZone">
<MenuGlyph :name="detectionZoneVisible ? 'eyeOff' : 'eye'" small />
<span class="menu-label">{{ detectionZoneVisible ? '隐藏探测区' : '显示探测区' }}</span>
@ -587,6 +583,10 @@
<MenuGlyph name="target" />
<span class="menu-label">威力区</span>
</div>
<div class="menu-item" @click="handleEditZoneBatch">
<MenuGlyph name="editDoc" />
<span class="menu-label">编辑模式</span>
</div>
<div class="menu-item menu-item-sub" @click="handleToggleDetectionZone">
<MenuGlyph :name="detectionZoneVisible ? 'eyeOff' : 'eye'" small />
<span class="menu-label">{{ detectionZoneVisible ? '隐藏探测区' : '显示探测区' }}</span>
@ -692,14 +692,6 @@ export default {
type: Object,
default: () => ({ x: 0, y: 0 })
},
pickList: {
type: Array,
default: null
},
pickIndex: {
type: Number,
default: 0
},
entityData: {
type: Object,
default: null
@ -1108,6 +1100,9 @@ export default {
handlePowerZone() {
this.$emit('power-zone')
},
handleEditZoneBatch() {
this.$emit('edit-zone-batch')
},
handleLaunchMissile() {
this.$emit('launch-missile')

1353
ruoyi-ui/src/views/cesiumMap/index.vue

File diff suppressed because it is too large

48
ruoyi-ui/src/views/childRoom/ScreenshotGalleryPanel.vue

@ -26,6 +26,7 @@
<div v-if="!hasImages" class="sg-empty">点击添加插入图片多图可使用两侧箭头翻页</div>
<div v-else class="sg-img-frame">
<img :key="currentIndex" :src="currentSrc" alt="" class="sg-img" draggable="false" />
<div class="sg-img-name" :title="currentName">{{ currentName }}</div>
</div>
</div>
@ -80,7 +81,16 @@ export default {
},
currentSrc() {
if (!this.hasImages) return ''
return this.images[this.currentIndex] || ''
const item = this.images[this.currentIndex]
if (!item) return ''
return typeof item === 'string' ? item : (item.src || '')
},
currentName() {
if (!this.hasImages) return ''
const item = this.images[this.currentIndex]
if (!item) return ''
if (typeof item === 'string') return '无文件名(历史数据)'
return item.name || '无文件名(历史数据)'
},
canPrev() {
return this.hasImages && this.currentIndex > 0
@ -137,7 +147,18 @@ export default {
}
}
const imgs = d.images
this.images = Array.isArray(imgs) ? imgs.filter(Boolean) : []
this.images = Array.isArray(imgs)
? imgs.map((it) => {
if (!it) return null
if (typeof it === 'string') {
return { src: it, name: '' }
}
if (typeof it === 'object' && it.src) {
return { src: it.src, name: it.name || '' }
}
return null
}).filter(Boolean)
: []
this.currentIndex = this.images.length > 0 ? Math.min(this.currentIndex, this.images.length - 1) : 0
if (d.panelSize) {
const w = Number(d.panelSize.width)
@ -188,13 +209,13 @@ export default {
this.$message.warning(`已达上限,本次仅添加 ${take.length}`)
}
const urls = []
const imageItems = []
for (const f of take) {
const u = await this.readFileAsDataURL(f)
if (u) urls.push(u)
if (u) imageItems.push({ src: u, name: f.name || '未命名图片.png' })
}
if (urls.length === 0) return
this.images = [...this.images, ...urls]
if (imageItems.length === 0) return
this.images = [...this.images, ...imageItems]
this.currentIndex = this.images.length - 1
if (this.roomId) this.saveData().catch(() => {})
},
@ -449,20 +470,33 @@ export default {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 0;
gap: 8px;
}
.sg-img {
max-width: 100%;
max-height: 100%;
max-height: calc(100% - 28px);
width: auto;
height: auto;
object-fit: contain;
vertical-align: middle;
}
.sg-img-name {
max-width: 100%;
font-size: 12px;
line-height: 18px;
color: #3f536e;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sg-resize {
position: absolute;
right: 0;

93
ruoyi-ui/src/views/childRoom/index.vue

@ -507,7 +507,7 @@
</div>
</el-dialog>
<!-- 截图保存弹窗选择文件名后保存到本地浏览器会弹出保存位置对话框 -->
<!-- 截图保存弹窗选择文件名后保存到本地 -->
<el-dialog
title="保存地图截图"
:visible.sync="showScreenshotDialog"
@ -522,7 +522,7 @@
<el-form-item label="文件名">
<el-input v-model="screenshotFileName" placeholder="例如:地图截图.png" />
</el-form-item>
<p class="screenshot-tip">点击保存浏览器将弹出保存对话框您可选择保存路径</p>
<p class="screenshot-tip">点击保存会优先弹出保存位置对话框若当前浏览器不支持则会下载到浏览器默认目录</p>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showScreenshotDialog = false"> </el-button>
@ -1332,6 +1332,38 @@ export default {
}
},
/** 插入航点命名:按插入位置给“锚点-子序号”,不改动既有航点名称 */
getInsertedWaypointName(waypoints = [], insertIndex = 0) {
const list = Array.isArray(waypoints) ? waypoints : []
const used = new Set(
list
.map(w => (w && w.name ? String(w.name).trim().toUpperCase() : ''))
.filter(Boolean)
)
const parseBase = (name) => {
const m = /^WP(\d+)(?:-(\d+))?$/i.exec(String(name || '').trim())
return m ? Number(m[1]) : NaN
}
const prev = insertIndex > 0 ? list[insertIndex - 1] : null
const next = insertIndex < list.length ? list[insertIndex] : null
const prevBase = prev && prev.name ? parseBase(prev.name) : NaN
const nextBase = next && next.name ? parseBase(next.name) : NaN
let anchor = Number.isFinite(prevBase) ? prevBase : NaN
if (!Number.isFinite(anchor)) {
if (Number.isFinite(nextBase)) anchor = Math.max(0, nextBase - 1)
else anchor = 0
}
let suffix = 1
let candidate = `WP${anchor}-${suffix}`
while (used.has(candidate.toUpperCase())) {
suffix += 1
candidate = `WP${anchor}-${suffix}`
}
return candidate
},
/** 右键航点“向前/向后增加航点”:进入放置模式,传入 waypoints 给地图预览 */
handleAddWaypointAt({ routeId, waypointIndex, mode, segmentMode, segmentTargetMinutes, segmentTargetSpeed }) {
if (this.isRouteLockedByOther(routeId)) {
@ -1386,7 +1418,7 @@ export default {
let segmentTargetSpeed = null;
let plannedPrevSpeed = null;
const count = waypoints.length + 1;
const newName = `WP${insertIndex + 1}`;
const newName = this.getInsertedWaypointName(waypoints, insertIndex);
const pos = { lat: position.lat, lng: position.lng, alt: position.alt != null ? position.alt : (refWp && refWp.alt) || 5000 };
if (segmentMode === 'fixed_time' && prevWp) {
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prevWp.startTime);
@ -1487,7 +1519,9 @@ export default {
const newSeq = i + 1;
const isNewlyInserted = wp.id === newWp.id;
const isPrevToNew = prevWp && wp.id === prevWp.id;
const nameToUse = isNewlyInserted ? (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`) : (wp.name || (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`));
const nameToUse = isNewlyInserted
? (wp.name || newName)
: (wp.name || (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`));
// 45°
const prevTurnAngle = (isPrevToNew && insertIndex > 1) ? 45 : wp.turnAngle;
const updatePayload = {
@ -3839,19 +3873,58 @@ export default {
viewer.scene.requestRender()
},
/** 确认保存截图:触发浏览器下载(用户可在保存对话框中选择路径) */
confirmSaveScreenshot() {
/** 确认保存截图:优先弹出系统保存位置选择框,不支持时回退浏览器下载 */
async confirmSaveScreenshot() {
if (!this.screenshotDataUrl) return
let name = (this.screenshotFileName || '地图截图.png').trim()
if (!name) name = '地图截图.png'
if (!/\.(png|jpg|jpeg)$/i.test(name)) name = name + '.png'
const blob = await fetch(this.screenshotDataUrl).then((res) => res.blob())
try {
if (window.showSaveFilePicker && window.isSecureContext) {
const fileHandle = await window.showSaveFilePicker({
suggestedName: name,
types: [
{
description: '图片文件',
accept: {
'image/png': ['.png'],
'image/jpeg': ['.jpg', '.jpeg']
}
}
]
})
const writable = await fileHandle.createWritable()
await writable.write(blob)
await writable.close()
this.$message.success('截图已保存')
} else {
this.downloadScreenshotFallback(blob, name)
this.$message.success('当前浏览器不支持选择保存路径,已下载到默认目录')
}
} catch (e) {
if (e && e.name === 'AbortError') {
this.$message.info('已取消保存')
return
}
console.warn('showSaveFilePicker 保存失败,已回退浏览器下载', e)
this.downloadScreenshotFallback(blob, name)
this.$message.success('保存位置选择失败,已回退为默认下载')
}
this.showScreenshotDialog = false
this.screenshotDataUrl = ''
},
/** 截图保存降级:使用浏览器默认下载流程 */
downloadScreenshotFallback(blob, name) {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = this.screenshotDataUrl
a.href = url
a.download = name
a.click()
this.showScreenshotDialog = false
this.screenshotDataUrl = ''
this.$message.success('已触发下载,请在浏览器保存对话框中选择保存位置')
setTimeout(() => URL.revokeObjectURL(url), 1000)
},
/** 白板:K+HH:MM:SS 时间块比较 */

Loading…
Cancel
Save