Browse Source

截图功能

master
ctw 2 months ago
parent
commit
8e8a37bfd1
  1. 6
      ruoyi-ui/src/views/cesiumMap/index.vue
  2. 149
      ruoyi-ui/src/views/childRoom/index.vue

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

@ -1568,7 +1568,11 @@ export default {
mapProjection: new Cesium.WebMercatorProjection(),
imageryProvider: false,
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
baseLayer: false
baseLayer: false,
// canvas toDataURL
contextOptions: {
preserveDrawingBuffer: true
}
})
this.viewer.cesiumWidget.creditContainer.style.display = "none"
this.loadOfflineMap()

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

@ -1,6 +1,6 @@
<template>
<!-- 以地图为绝对定位背景所有组件浮动其上 -->
<div class="mission-planning-container">
<div class="mission-planning-container" :class="{ 'screenshot-mode': screenshotMode }">
<!-- 地图背景支持从右侧平台列表拖拽图标到地图 -->
<div
id="gis-map-background"
@ -19,18 +19,19 @@
@scale-click="handleScaleClick"
@platform-icon-updated="onPlatformIconUpdated"
@platform-icon-removed="onPlatformIconRemoved" />
<div class="map-overlay-text">
<div v-show="!screenshotMode" class="map-overlay-text">
<i class="el-icon-location-outline text-3xl mb-2 block"></i>
<p>二维GIS地图区域</p>
<p class="text-sm mt-1">支持标绘/航线/空域/实时态势</p>
</div>
<div v-if="missionDrawingActive && missionDrawingPointsCount >= 2" class="mission-drawing-actions" style="position:absolute; bottom:16px; left:50%; transform:translateX(-50%); z-index:10; display:flex; gap:8px; align-items:center;">
<div v-if="missionDrawingActive && missionDrawingPointsCount >= 2 && !screenshotMode" class="mission-drawing-actions" style="position:absolute; bottom:16px; left:50%; transform:translateX(-50%); z-index:10; display:flex; gap:8px; align-items:center;">
<span class="text-white text-sm"> {{ missionDrawingPointsCount }} 个航点右键结束</span>
<el-button type="primary" size="small" @click="openAddHoldDuringDrawing">插入盘旋</el-button>
</div>
<!-- 地图中间的浮动红点触发左侧菜单 -->
<div
v-show="!screenshotMode"
class="floating-red-dot left-red-dot"
:class="{ hidden: !isMenuHidden }"
@click="showMenu"
@ -73,6 +74,7 @@
</div>
<!-- 顶部导航栏 -->
<top-header
v-show="!screenshotMode"
:room-code="roomCode"
:online-count="onlineCount"
:combat-time="combatTime"
@ -130,6 +132,7 @@
/>
<!-- 左侧折叠菜单栏 - 蓝色主题 -->
<left-menu
v-show="!screenshotMode"
:is-hidden="isMenuHidden"
:menu-items="menuItems"
:active-menu="activeMenu"
@ -149,6 +152,7 @@
/>
<!-- 右侧实体列表浮动- 蓝色主题 -->
<right-panel
v-show="!screenshotMode"
ref="rightPanel"
:is-hidden="isRightPanelHidden"
:active-tab="activeRightTab"
@ -185,9 +189,10 @@
@open-import-dialog="showImportDialog = true"
/>
<!-- 左下角工具面板 -->
<bottom-left-panel />
<bottom-left-panel v-show="!screenshotMode" />
<!-- 底部时间轴最初版本的样式- 蓝色主题 -->
<div
v-show="!screenshotMode"
class="floating-timeline blue-theme"
:class="{ 'show': showKTimePopup }"
>
@ -364,6 +369,31 @@
<el-button type="primary" @click="confirmCreatePlan"> </el-button>
</div>
</el-dialog>
<!-- 截图保存弹窗选择文件名后保存到本地浏览器会弹出保存位置对话框 -->
<el-dialog
title="保存地图截图"
:visible.sync="showScreenshotDialog"
width="520px"
append-to-body
class="screenshot-save-dialog"
>
<div class="screenshot-preview" v-if="screenshotDataUrl">
<img :src="screenshotDataUrl" alt="截图预览" />
</div>
<el-form label-width="90px">
<el-form-item label="文件名">
<el-input v-model="screenshotFileName" placeholder="例如:地图截图.png" />
</el-form-item>
<p class="screenshot-tip">点击保存浏览器将弹出保存对话框您可选择保存路径</p>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showScreenshotDialog = false"> </el-button>
<el-button type="primary" @click="confirmSaveScreenshot">
<i class="el-icon-download"></i>
</el-button>
</div>
</el-dialog>
</div>
</template>
@ -441,6 +471,12 @@ export default {
//
showImportDialog: false,
//
screenshotMode: false,
showScreenshotDialog: false,
screenshotDataUrl: '',
screenshotFileName: '',
//
roomCode: 'JTF-7-ALPHA',
onlineCount: 30,
@ -463,7 +499,7 @@ export default {
{ id: 'pattern', name: '空域', icon: 'ky' },
{ id: 'deduction', name: '推演', icon: 'el-icon-video-play' },
{ id: 'modify', name: '测距', icon: 'cj' },
{ id: 'refresh', name: '刷新', icon: 'el-icon-refresh' },
{ id: 'refresh', name: '截图', icon: 'screenshot', action: 'refresh' },
{ id: 'basemap', name: '底图', icon: 'dt' },
{ id: 'save', name: '保存', icon: 'el-icon-document-checked' },
{ id: 'import', name: '导入', icon: 'el-icon-upload2' },
@ -1435,7 +1471,8 @@ export default {
'toggleLandmark': () => this.toggleLandmark(),
'toggleRoute': () => this.toggleRoute(),
'layerFavorites': () => this.layerFavorites(),
'routeFavorites': () => this.routeFavorites()
'routeFavorites': () => this.routeFavorites(),
'refresh': () => this.captureMapScreenshot()
}
if (actionMap[actionId]) {
@ -1443,6 +1480,76 @@ export default {
}
},
/** 截图:隐藏上下左右菜单只保留地图,用 postRender + readPixels 避免 WebGL 缓冲被清空导致黑屏 */
async captureMapScreenshot() {
const cm = this.$refs.cesiumMap
if (!cm || !cm.viewer || !cm.viewer.scene || !cm.viewer.scene.canvas) {
this.$message.warning('地图未就绪,请稍后再试')
return
}
this.screenshotMode = true
await this.$nextTick()
await new Promise(r => setTimeout(r, 350))
const viewer = cm.viewer
const canvas = viewer.scene.canvas
const self = this
const removeListener = viewer.scene.postRender.addEventListener(function captureFrame() {
removeListener()
self.screenshotMode = false
try {
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl')
if (!gl) {
self.$message.error('无法获取 WebGL 上下文')
return
}
const width = canvas.width
const height = canvas.height
if (width === 0 || height === 0) {
self.$message.error('画布尺寸为 0')
return
}
const pixels = new Uint8Array(width * height * 4)
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
const offscreen = document.createElement('canvas')
offscreen.width = width
offscreen.height = height
const ctx = offscreen.getContext('2d')
const imageData = ctx.createImageData(width, height)
for (let y = 0; y < height; y++) {
const srcRow = (height - 1 - y) * width * 4
const dstRow = y * width * 4
for (let i = 0; i < width * 4; i++) imageData.data[dstRow + i] = pixels[srcRow + i]
}
ctx.putImageData(imageData, 0, 0)
const dataUrl = offscreen.toDataURL('image/png')
const now = new Date()
const timeStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + '_' + String(now.getHours()).padStart(2, '0') + String(now.getMinutes()).padStart(2, '0') + String(now.getSeconds()).padStart(2, '0')
self.screenshotFileName = `地图截图_${timeStr}.png`
self.screenshotDataUrl = dataUrl
self.showScreenshotDialog = true
} catch (e) {
self.$message.error('截图失败:' + (e && e.message ? e.message : '未知错误'))
}
})
viewer.scene.requestRender()
},
/** 确认保存截图:触发浏览器下载(用户可在保存对话框中选择路径) */
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 a = document.createElement('a')
a.href = this.screenshotDataUrl
a.download = name
a.click()
this.showScreenshotDialog = false
this.screenshotDataUrl = ''
this.$message.success('已触发下载,请在浏览器保存对话框中选择保存位置')
},
handleDeleteMenuItem(deletedItem) {
const index = this.menuItems.findIndex(item => item.id === deletedItem.id)
if (index > -1) {
@ -1478,7 +1585,12 @@ export default {
arr = typeof data.menuItems === 'string' ? JSON.parse(data.menuItems) : data.menuItems
} catch (e) { /* 解析失败保留默认 */ }
if (Array.isArray(arr) && arr.length > 0) {
this.menuItems = arr
const defaultMap = (this.defaultMenuItems || []).reduce((m, it) => { m[it.id] = it; return m }, {})
this.menuItems = arr.map(item => {
const def = defaultMap[item.id]
if (def) return { ...item, name: def.name, icon: def.icon, action: def.action }
return item
})
}
}
if (data.position && ['left', 'top', 'bottom'].includes(data.position)) {
@ -2646,6 +2758,29 @@ export default {
z-index: 1;
}
/* 截图保存弹窗 */
.screenshot-save-dialog .screenshot-preview {
max-height: 320px;
overflow: hidden;
border-radius: 4px;
background: #f5f5f5;
margin-bottom: 12px;
}
.screenshot-save-dialog .screenshot-preview img {
display: block;
max-width: 100%;
width: auto;
height: auto;
max-height: 320px;
object-fit: contain;
}
.screenshot-save-dialog .screenshot-tip {
font-size: 12px;
color: #909399;
margin-top: 8px;
margin-bottom: 0;
}
/* ...其余样式省略,保持不变... */
.map-overlay-text {
position: absolute;

Loading…
Cancel
Save