diff --git a/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue index 9acf607..c23a904 100644 --- a/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue +++ b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue @@ -40,6 +40,10 @@ export default { { id: 'polygon', name: '面', icon: 'el-icon-s-grid' }, { id: 'rectangle', name: '矩形', icon: 'el-icon-s-data' }, { id: 'circle', name: '圆形', icon: 'el-icon-circle-plus-outline' }, + { id: 'sector', name: '扇形', icon: 'el-icon-s-operation' }, + { id: 'arrow', name: '箭头', icon: 'el-icon-right' }, + { id: 'text', name: '文本', icon: 'el-icon-document' }, + { id: 'image', name: '图片', icon: 'el-icon-picture-outline' }, { id: 'locate', name: '定位', icon: 'el-icon-aim' }, { id: 'clear', name: '清除', icon: 'el-icon-delete' }, { id: 'import', name: '导入', icon: 'el-icon-upload' }, diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index e17cf32..d0a99ec 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -26,6 +26,9 @@ import * as Cesium from 'cesium' import 'cesium/Build/Cesium/Widgets/widgets.css' import DrawingToolbar from './DrawingToolbar.vue' import MeasurementPanel from './MeasurementPanel.vue' +import axios from 'axios' +import request from '@/utils/request' +import { getToken } from '@/utils/auth' export default { name: 'CesiumMap', props: { @@ -81,7 +84,11 @@ export default { line: { color: '#00FF00', width: 3 }, polygon: { color: '#0000FF', opacity: 0.5, width: 2 }, rectangle: { color: '#FFA500', opacity: 0.3, width: 2 }, - circle: { color: '#800080', opacity: 0.4, width: 2 } + circle: { color: '#800080', opacity: 0.4, width: 2 }, + sector: { color: '#FF6347', opacity: 0.5, width: 2 }, + arrow: { color: '#FF0000', width: 6 }, + text: { color: '#000000', font: '32px Microsoft YaHei, PingFang SC, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.8)' }, + image: { width: 100, height: 100 } } } }, @@ -304,6 +311,18 @@ export default { case 'circle': this.startCircleDrawing() break + case 'sector': + this.startSectorDrawing() + break + case 'arrow': + this.startArrowDrawing() + break + case 'text': + this.startTextDrawing() + break + case 'image': + this.startImageDrawing() + break } this.viewer.scene.canvas.style.cursor = 'crosshair' @@ -812,6 +831,602 @@ export default { this.stopDrawing(); }, + // 绘制扇形 + startSectorDrawing() { + this.drawingPoints = []; // 存储圆心、半径端点、角度端点 + let activeCursorPosition = null; // 实时鼠标位置 + let centerPoint = null; // 圆心坐标 + let radiusPoint = null; // 半径端点 + let radius = 0; // 半径长度 + + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity); + this.tempEntity = null; + this.tempPreviewEntity = null; + + // 2. 鼠标移动事件 + this.drawingHandler.setInputAction((movement) => { + const newPosition = this.getClickPosition(movement.endPosition); + if (newPosition) { + activeCursorPosition = newPosition; + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + // 3. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + // --- 情况A:第一次点击(确定圆心) --- + if (!centerPoint) { + centerPoint = position; + this.drawingPoints.push(centerPoint); + activeCursorPosition = centerPoint; + + // 创建动态预览半径线 + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(() => { + if (centerPoint && activeCursorPosition) { + return [centerPoint, activeCursorPosition]; + } + return []; + }, false), + width: this.defaultStyles.sector.width, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + dashLength: 16 + }), + clampToGround: true + } + }); + } + // --- 情况B:第二次点击(确定半径) --- + else if (!radiusPoint) { + radiusPoint = position; + this.drawingPoints.push(radiusPoint); + radius = Cesium.Cartesian3.distance(centerPoint, radiusPoint); + + // 移除半径预览线 + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; + } + + // 创建动态预览扇形 + this.tempEntity = this.viewer.entities.add({ + polygon: { + hierarchy: new Cesium.CallbackProperty(() => { + if (centerPoint && activeCursorPosition) { + const currentRadius = Cesium.Cartesian3.distance(centerPoint, activeCursorPosition); + const startAngle = this.calculatePointAngle(centerPoint, radiusPoint); + const endAngle = this.calculatePointAngle(centerPoint, activeCursorPosition); + const positions = this.generateSectorPositions(centerPoint, currentRadius, startAngle, endAngle); + return new Cesium.PolygonHierarchy(positions); + } + return new Cesium.PolygonHierarchy([]); + }, false), + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(0.5), + outline: true, + outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + outlineWidth: this.defaultStyles.sector.width + } + }); + } + // --- 情况C:第三次点击(确定角度) --- + else { + const anglePoint = position; + this.drawingPoints.push(anglePoint); + activeCursorPosition = null; // 停止动态更新 + + // 传递角度点位置去结束绘制 + this.finishSectorDrawing(centerPoint, radiusPoint, anglePoint); + } + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 4. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + // 完成扇形绘制 + finishSectorDrawing(centerPoint, radiusPoint, anglePoint) { + const radius = Cesium.Cartesian3.distance(centerPoint, anglePoint); + const startAngle = this.calculatePointAngle(centerPoint, radiusPoint); + const endAngle = this.calculatePointAngle(centerPoint, anglePoint); + + // 1. 移除动态预览实体 + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } + + // 2. 生成扇形顶点 + const positions = this.generateSectorPositions(centerPoint, radius, startAngle, endAngle); + + // 3. 创建最终显示的静态实体 + const finalEntity = this.viewer.entities.add({ + id: 'sector-' + new Date().getTime(), + name: `扇形 ${this.entityCounter}`, + polygon: { + hierarchy: new Cesium.PolygonHierarchy(positions), + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(0.5), + outline: true, + outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + outlineWidth: this.defaultStyles.sector.width, + perPositionHeight: false + } + }); + + // 4. 记录实体 + this.entityCounter++; + const entityData = { + id: `sector_${this.entityCounter}`, + type: 'sector', + center: this.cartesianToLatLng(centerPoint), + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + positions: positions, + entity: finalEntity, + color: this.defaultStyles.sector.color, + opacity: 0.5, + width: this.defaultStyles.sector.width, + label: `扇形 ${this.entityCounter}` + }; + this.allEntities.push(entityData); + + // 5. 结束绘制状态 + this.stopDrawing(); + }, + + // 计算角度 + calculateAngle(center, start, end) { + const startLL = Cesium.Cartographic.fromCartesian(start); + const endLL = Cesium.Cartographic.fromCartesian(end); + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 计算两点相对于圆心的角度 + const startAngle = Math.atan2(startLL.longitude - centerLL.longitude, startLL.latitude - centerLL.latitude); + const endAngle = Math.atan2(endLL.longitude - centerLL.longitude, endLL.latitude - centerLL.latitude); + + // 返回角度差 + return endAngle - startAngle; + }, + + // 计算角度差 + calculateAngleDiff(center, start, end) { + const startLL = Cesium.Cartographic.fromCartesian(start); + const endLL = Cesium.Cartographic.fromCartesian(end); + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 计算两点相对于圆心的角度 + const startAngle = Math.atan2(startLL.longitude - centerLL.longitude, startLL.latitude - centerLL.latitude); + const endAngle = Math.atan2(endLL.longitude - centerLL.longitude, endLL.latitude - centerLL.latitude); + + // 计算角度差(确保为正值) + let angleDiff = endAngle - startAngle; + if (angleDiff < 0) { + angleDiff += 2 * Math.PI; + } + + // 确保角度差在合理范围内 + return Math.max(0.1, Math.min(Math.PI * 2, angleDiff)); + }, + + // 计算点相对于圆心的角度 + calculatePointAngle(center, point) { + const pointLL = Cesium.Cartographic.fromCartesian(point); + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 计算点相对于圆心的角度 + const angle = Math.atan2(pointLL.longitude - centerLL.longitude, pointLL.latitude - centerLL.latitude); + return angle; + }, + + // 生成扇形顶点位置 + generateSectorPositions(center, radius, startAngle, endAngle) { + const positions = []; + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 添加圆心 + positions.push(center); + + // 计算角度差 + let angleDiff = endAngle - startAngle; + if (angleDiff < 0) { + angleDiff += 2 * Math.PI; + } + + // 确保角度差不为零 + angleDiff = Math.max(0.01, angleDiff); + + // 计算扇形的顶点数(根据角度差确定,确保平滑) + const numPoints = Math.max(5, Math.ceil(angleDiff * 180 / Math.PI / 10)); + const angleStep = angleDiff / (numPoints - 1); + + // 生成扇形的顶点 + for (let i = 0; i < numPoints; i++) { + const currentAngle = startAngle + i * angleStep; + const distance = radius / 6378137; // 转换为弧度 + + const lat = centerLL.latitude + Math.cos(currentAngle) * distance; + const lng = centerLL.longitude + Math.sin(currentAngle) * distance / Math.cos(centerLL.latitude); + + const position = Cesium.Cartesian3.fromRadians(lng, lat); + positions.push(position); + } + + // 闭合扇形 + positions.push(center); + + return positions; + }, + + // 计算两点之间的距离(米) + calculateDistance(point1, point2) { + return Cesium.Cartesian3.distance(point1, point2); + }, + + // 绘制箭头 + startArrowDrawing() { + this.drawingPoints = []; // 存储起点和终点 + let activeCursorPosition = null; // 实时鼠标位置 + + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity); + this.tempEntity = null; + this.tempPreviewEntity = null; + + // 2. 鼠标移动事件 + this.drawingHandler.setInputAction((movement) => { + const newPosition = this.getClickPosition(movement.endPosition); + if (newPosition) { + activeCursorPosition = newPosition; + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + // 3. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + this.drawingPoints.push(position); + + // --- 情况A:第一次点击(确定起点) --- + if (this.drawingPoints.length === 1) { + activeCursorPosition = position; // 初始化鼠标位置 + + // 创建动态预览箭头 + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + // 使用 CallbackProperty 动态获取位置 + positions: new Cesium.CallbackProperty(() => { + // 只有当有点且鼠标位置存在时才渲染 + if (this.drawingPoints.length > 0 && activeCursorPosition) { + // 获取最后一个已确认的点 + const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; + // 返回 [最后一个点, 当前鼠标位置] + return [lastPoint, activeCursorPosition]; + } + return []; + }, false), + width: 8, // 增加宽度以获得更大的箭头头部 + // 使用箭头材质 + material: new Cesium.PolylineArrowMaterialProperty( + Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) + ), + clampToGround: true, // 贴地 + widthInMeters: false // 使用像素宽度模式 + } + }); + } + // --- 情况B:第二次点击(确定终点) --- + else { + // 停止监听鼠标移动,因为形状已确定 + activeCursorPosition = null; + + // 调用完成逻辑 + this.finishArrowDrawing(); + } + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 4. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + // 完成箭头绘制 + finishArrowDrawing() { + // 将预览箭头转换为最终箭头 + if (this.drawingPoints.length > 1) { + // 移除预览箭头 + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; + } + + // 创建最终的箭头实体 + const entity = this.addArrowEntity([...this.drawingPoints]); + + this.stopDrawing(); + return entity; + } else { + this.cancelDrawing(); + return null; + } + }, + + // 添加箭头实体 + addArrowEntity(positions) { + this.entityCounter++ + const id = `arrow_${this.entityCounter}` + + // 创建箭头实体,使用固定宽度以确保等比例放大 + const entity = this.viewer.entities.add({ + id: id, + name: `箭头 ${this.entityCounter}`, + polyline: { + positions: positions, + width: 8, // 增加宽度以获得更大的箭头头部 + material: new Cesium.PolylineArrowMaterialProperty( + Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) + ), + clampToGround: true, + // 使用像素宽度模式,确保箭头在缩放时保持比例 + widthInMeters: false + } + }) + + const entityData = { + id, + type: 'arrow', + points: positions.map(p => this.cartesianToLatLng(p)), + positions: positions, + entity: entity, + color: this.defaultStyles.arrow.color, + width: this.defaultStyles.arrow.width, + label: `箭头 ${this.entityCounter}` + } + + this.allEntities.push(entityData) + + entity.clickHandler = (e) => { + this.selectEntity(entityData) + e.stopPropagation() + } + + return entityData + }, + + // 绘制文本框 + startTextDrawing() { + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + + // 2. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + // 弹出输入框,让用户输入文本内容 + const text = prompt('请输入文本内容:', '文本'); + if (text) { + // 创建文本实体 + this.addTextEntity(position, text); + } + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 3. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + // 添加文本实体 + addTextEntity(position, text) { + this.entityCounter++ + const id = `text_${this.entityCounter}` + + // 获取经纬度坐标 + const { lat, lng } = this.cartesianToLatLng(position) + + const entity = this.viewer.entities.add({ + id: id, + name: `文本 ${this.entityCounter}`, + position: position, + label: { + text: text, + font: this.defaultStyles.text.font, + fillColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.color), + outlineColor: Cesium.Color.BLACK, + outlineWidth: 2, + style: Cesium.LabelStyle.FILL_AND_OUTLINE, + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + backgroundColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.backgroundColor), + backgroundPadding: new Cesium.Cartesian2(10, 5), + pixelOffset: new Cesium.Cartesian2(0, 0), + // 随地图缩放调整大小 + scaleByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的缩放比例 + 1000000, // 远距离(米) + 0.0 // 远距离时的缩放比例 + ) + } + }) + + const entityData = { + id, + type: 'text', + lat, + lng, + text: text, + position: position, + entity: entity, + color: this.defaultStyles.text.color, + font: this.defaultStyles.text.font, + backgroundColor: this.defaultStyles.text.backgroundColor, + label: `文本 ${this.entityCounter}` + } + + this.allEntities.push(entityData) + + entity.clickHandler = (e) => { + this.selectEntity(entityData) + e.stopPropagation() + } + + return entityData + }, + + // 绘制图片 + startImageDrawing() { + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + + // 2. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + // 创建一个隐藏的文件输入元素 + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*'; + + // 监听文件选择事件 + fileInput.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + // 创建 FormData 对象用于上传 + const formData = new FormData(); + formData.append('file', file); + + // 上传文件到服务器 + this.uploadImage(formData, position); + } + }; + + // 触发文件选择对话框 + fileInput.click(); + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 3. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + // 上传图片 + uploadImage(formData, position) { + // 显示加载状态 + this.$modal.loading('图片上传中,请稍候...'); + + try { + // 创建请求配置 + const config = { + headers: { + 'Content-Type': 'multipart/form-data' + } + }; + + // 发送请求 - 使用项目封装的 request 实例 + request.post('/common/upload', formData, config) + .then((response) => { + this.$modal.closeLoading(); + + if (response.code === 200) { + // 上传成功,获取图片 URL + let imageUrl = response.url; + // 创建图片实体 + this.addImageEntity(position, imageUrl); + } else { + this.$modal.msgError('图片上传失败:' + (response.msg || '未知错误')); + } + }) + .catch((error) => { + this.$modal.closeLoading(); + + if (error.response) { + // 服务器返回错误 + if (error.response.data && error.response.data.msg) { + this.$modal.msgError('图片上传失败:' + error.response.data.msg); + } else { + this.$modal.msgError('图片上传失败:服务器返回错误 ' + error.response.status); + } + } else if (error.request) { + // 请求发送但没有收到响应 + this.$modal.msgError('图片上传失败:无法连接到服务器,请检查网络'); + } else { + // 请求配置错误 + this.$modal.msgError('图片上传失败:' + error.message); + } + }); + } catch (error) { + this.$modal.closeLoading(); + this.$modal.msgError('图片上传失败:' + error.message); + } + }, + + // 添加图片实体 + addImageEntity(position, imageUrl) { + this.entityCounter++ + const id = `image_${this.entityCounter}` + + // 获取经纬度坐标 + const { lat, lng } = this.cartesianToLatLng(position) + + const entity = this.viewer.entities.add({ + id: id, + name: `图片 ${this.entityCounter}`, + position: position, + billboard: { + image: imageUrl, + width: this.defaultStyles.image.width, + height: this.defaultStyles.image.height, + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + // 随地图缩放调整大小 + scaleByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的缩放比例 + 1000000, // 远距离(米) + 0.0 // 远距离时的缩放比例 + ) + } + }) + + const entityData = { + id, + type: 'image', + lat, + lng, + imageUrl: imageUrl, + position: position, + entity: entity, + width: this.defaultStyles.image.width, + height: this.defaultStyles.image.height, + label: `图片 ${this.entityCounter}` + } + + this.allEntities.push(entityData) + + entity.clickHandler = (e) => { + this.selectEntity(entityData) + e.stopPropagation() + } + + return entityData + }, + // ================== 实体创建方法 ================== addPointEntity(lat, lng) { @@ -1060,19 +1675,7 @@ export default { return area }, - calculateRectangleArea(coordinates) { - const rect = coordinates.getValue ? coordinates.getValue() : coordinates - const width = Cesium.Cartesian3.distance( - Cesium.Cartesian3.fromRadians(rect.west, rect.north), - Cesium.Cartesian3.fromRadians(rect.east, rect.north) - ) - const height = Cesium.Cartesian3.distance( - Cesium.Cartesian3.fromRadians(rect.west, rect.north), - Cesium.Cartesian3.fromRadians(rect.west, rect.south) - ) - return width * height - }, // ================== 实体管理 ================== @@ -1219,7 +1822,11 @@ export default { line: '线', polygon: '面', rectangle: '矩形', - circle: '圆形' + circle: '圆形', + sector: '扇形', + arrow: '箭头', + text: '文本', + image: '图片' } return names[type] || type }, diff --git a/ruoyi-ui/src/views/childRoom/TopHeader.vue b/ruoyi-ui/src/views/childRoom/TopHeader.vue index 6398f74..6192637 100644 --- a/ruoyi-ui/src/views/childRoom/TopHeader.vue +++ b/ruoyi-ui/src/views/childRoom/TopHeader.vue @@ -2,8 +2,14 @@
- - 联合任务筹划系统 + + 系统logo + 网络化任务规划系统
@@ -28,6 +34,9 @@ > + + 新建计划 + 打开 保存 @@ -337,6 +346,14 @@ export default { this.$emit('select-nav', item) }, + // 文件下拉菜单新增方法 + newPlan() { + this.$emit('new-plan') + }, + openPlan() { + this.$emit('open-plan') + }, + // 文件下拉菜单方法 savePlan() { this.$emit('save-plan') @@ -544,9 +561,10 @@ export default { justify-content: space-between; z-index: 100; backdrop-filter: blur(15px); - background: rgba(255, 255, 255, 0.85); - border-bottom: 1px solid rgba(0, 138, 255, 0.2); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); + /* 调整背景为更透明的白色 */ + background: rgba(255, 255, 255, 0.3); + border-bottom: 1px solid rgba(0, 138, 255, 0.1); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); } .header-left { @@ -564,6 +582,13 @@ export default { min-width: 180px; } +/* 新增logo图片样式,保证显示效果 */ +.logo-icon { + width: 24px; + height: 24px; + object-fit: contain; +} + .system-title i { font-size: 24px; color: #008aff; @@ -673,8 +698,9 @@ export default { border-radius: 6px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); backdrop-filter: blur(15px); - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(0, 138, 255, 0.2); + /* 下拉菜单也同步调整为更透明的白色 */ + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(0, 138, 255, 0.1); padding: 0; min-width: auto; width: fit-content; @@ -729,8 +755,9 @@ export default { border-radius: 6px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); backdrop-filter: blur(15px); - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(0, 138, 255, 0.2); + /* 子菜单同步调整透明度 */ + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(0, 138, 255, 0.1); padding: 0; min-width: auto; width: fit-content; @@ -775,17 +802,18 @@ export default { align-items: center; gap: 8px; padding: 8px 12px; - background: rgba(255, 255, 255, 0.6); + /* 信息框也调整为更透明的白色 */ + background: rgba(255, 255, 255, 0.5); border-radius: 8px; cursor: pointer; transition: all 0.3s; - border: 1px solid rgba(0, 138, 255, 0.1); + border: 1px solid rgba(0, 138, 255, 0.05); } .info-box:hover { background: rgba(0, 138, 255, 0.1); transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 138, 255, 0.2); + box-shadow: 0 4px 12px rgba(0, 138, 255, 0.1); } .combat-info-group .info-box:nth-child(3) .info-value { @@ -832,6 +860,6 @@ export default { .user-avatar:hover { transform: scale(1.1); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } - + \ No newline at end of file diff --git a/ruoyi-ui/src/views/childRoom/logo.png b/ruoyi-ui/src/views/childRoom/logo.png new file mode 100644 index 0000000..5168989 Binary files /dev/null and b/ruoyi-ui/src/views/childRoom/logo.png differ diff --git a/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue b/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue index a465dae..06e0c97 100644 --- a/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue +++ b/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue @@ -118,12 +118,49 @@
+ + + +
+ +
+

群聊 - 在线成员交流

+ {{ onlineMembers.length }} 人在线 +
+ + +
+
+
+ {{ message.sender.charAt(0) }} +
+
+
{{ message.sender }}
+
{{ message.content }}
+
{{ message.time }}
+
+
+
+ + +
+ + 发送 +
+
+
- - + @@ -205,6 +242,29 @@ export default { time: 'K+00:32:08', type: 'success' } + ], + + // 新增:聊天室相关数据 + newMessage: '', + chatMessages: [ + { + sender: '张三', + content: '各位注意,J-20参数已更新,速度调整为850km/h', + time: 'K+00:45:30', + avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png' + }, + { + sender: '李四', + content: '收到,Alpha进场航线已选中,等待进一步指令', + time: 'K+00:42:20', + avatar: 'https://cube.elemecdn.com/1/88/03b0d39583f48206768a7534e55bcpng.png' + }, + { + sender: '王五', + content: 'Beta巡逻航线已添加WP5航点,坐标确认完毕', + time: 'K+00:38:50', + avatar: 'https://cube.elemecdn.com/2/88/03b0d39583f48206768a7534e55bcpng.png' + } ] }; }, @@ -221,6 +281,81 @@ export default { this.showRollbackDialog = false; this.$message.success('操作回滚成功'); // 这里可以添加实际的回滚逻辑 + }, + + // 新增:发送聊天消息 + sendMessage() { + if (!this.newMessage.trim()) { + this.$message.warning('请输入消息内容'); + return; + } + + // 模拟生成当前时间(K+格式) + const now = new Date(); + const hours = now.getHours().toString().padStart(2, '0'); + const minutes = now.getMinutes().toString().padStart(2, '0'); + const seconds = now.getSeconds().toString().padStart(2, '0'); + const currentTime = `K+${hours}:${minutes}:${seconds}`; + + // 添加新消息到聊天记录 + this.chatMessages.push({ + sender: '我', + content: this.newMessage.trim(), + time: currentTime, + avatar: 'https://cube.elemecdn.com/5/88/03b0d39583f48206768a7534e55bcpng.png' + }); + + // 清空输入框 + this.newMessage = ''; + + // 滚动到聊天底部 + this.$nextTick(() => { + const chatContent = this.$refs.chatContent; + chatContent.scrollTop = chatContent.scrollHeight; + }); + + // 模拟其他人回复(可选) + setTimeout(() => { + const randomMember = this.onlineMembers[Math.floor(Math.random() * this.onlineMembers.length)]; + const replies = [ + `收到你的消息,${randomMember.role}已确认`, + `已处理:${randomMember.name}正在执行相关操作`, + `明白,${randomMember.role}这边已准备就绪` + ]; + const randomReply = replies[Math.floor(Math.random() * replies.length)]; + + const now2 = new Date(); + const hours2 = now2.getHours().toString().padStart(2, '0'); + const minutes2 = now2.getMinutes().toString().padStart(2, '0'); + const seconds2 = now2.getSeconds().toString().padStart(2, '0'); + const replyTime = `K+${hours2}:${minutes2}:${seconds2}`; + + this.chatMessages.push({ + sender: randomMember.name, + content: randomReply, + time: replyTime, + avatar: randomMember.avatar + }); + + // 再次滚动到底部 + this.$nextTick(() => { + const chatContent = this.$refs.chatContent; + chatContent.scrollTop = chatContent.scrollHeight; + }); + }, 1000); + } + }, + // 新增:进入聊天室时自动滚动到底部 + watch: { + activeTab(newVal) { + if (newVal === 'chat') { + this.$nextTick(() => { + const chatContent = this.$refs.chatContent; + if (chatContent) { + chatContent.scrollTop = chatContent.scrollHeight; + } + }); + } } } }; @@ -247,6 +382,7 @@ export default { bottom: 0; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(2px); + cursor: pointer; } .dialog-content { @@ -292,23 +428,22 @@ export default { color: #999; cursor: pointer; transition: color 0.3s; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; } .close-btn:hover { color: #666; + background: #f5f5f5; + border-radius: 50%; } .dialog-body { padding: 20px; -} - -.dialog-footer { - display: flex; - align-items: center; - justify-content: flex-end; - padding: 16px 20px; - border-top: 1px solid #e8e8e8; - gap: 10px; + /* 调整内边距 让内容和顶部更协调 */ } /* 在线成员样式 */ @@ -483,4 +618,102 @@ export default { .text-warning { color: #fa8c16; } + +/* 新增:聊天室样式 */ +.chat-room { + display: flex; + flex-direction: column; + height: 400px; +} + +.chat-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.chat-header h4 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: #333; +} + +.online-count { + font-size: 12px; + color: #008aff; +} + +.chat-content { + flex: 1; + overflow-y: auto; + padding: 12px; + background: rgba(240, 242, 245, 0.5); + border-radius: 8px; + margin-bottom: 12px; +} + +.chat-message { + display: flex; + margin-bottom: 16px; + max-width: 80%; +} + +.self-message { + flex-direction: row-reverse; + margin-left: auto; +} + +.other-message { + margin-right: auto; +} + +.message-avatar { + margin: 0 8px; +} + +.message-content { + background: white; + padding: 8px 12px; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.self-message .message-content { + background: #e6f7ff; +} + +.message-sender { + font-size: 12px; + font-weight: 600; + color: #333; + margin-bottom: 4px; +} + +.message-text { + font-size: 13px; + color: #333; + line-height: 1.4; + margin-bottom: 4px; +} + +.message-time { + font-size: 11px; + color: #999; + text-align: right; +} + +.chat-input { + display: flex; + gap: 8px; +} + +.chat-input .el-input { + flex: 1; +} + +.send-btn { + width: 80px; +} \ No newline at end of file