Browse Source

实现扇形,箭头绘制,插入文本图片功能并增加对应图标

master
ctw 2 months ago
parent
commit
33f4b0f047
  1. 4
      ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue
  2. 635
      ruoyi-ui/src/views/cesiumMap/index.vue
  3. 56
      ruoyi-ui/src/views/childRoom/TopHeader.vue
  4. BIN
      ruoyi-ui/src/views/childRoom/logo.png
  5. 259
      ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue

4
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' },

635
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
},

56
ruoyi-ui/src/views/childRoom/TopHeader.vue

@ -2,8 +2,14 @@
<div class="floating-header">
<div class="header-left">
<div class="system-title">
<i class="el-icon-s-promotion mr-2 logo-icon"></i>
<span class="title-text blue-title">联合任务筹划系统</span>
<!-- 按照实际路径引入logo.jpg -->
<img
src="@/views/childRoom/logo.png"
class="logo-icon mr-2"
alt="系统logo"
style="width:24px; height:24px; object-fit:contain;"
>
<span class="title-text blue-title">网络化任务规划系统</span>
</div>
<!-- 顶部导航菜单 -->
@ -28,6 +34,9 @@
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<!-- 新增新建计划和打开选项 -->
<el-dropdown-item @click.native="newPlan">新建计划</el-dropdown-item>
<el-dropdown-item @click.native="openPlan">打开</el-dropdown-item>
<el-dropdown-item @click.native="savePlan">保存</el-dropdown-item>
<!-- 导入二级菜单 -->
@ -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);
}
</style>
</style>

BIN
ruoyi-ui/src/views/childRoom/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

259
ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue

@ -118,12 +118,49 @@
</el-dialog>
</div>
</el-tab-pane>
<!-- 新增聊天室标签页 -->
<el-tab-pane label="聊天室" name="chat">
<div class="chat-room">
<!-- 聊天头部 -->
<div class="chat-header">
<h4>群聊 - 在线成员交流</h4>
<span class="online-count">{{ onlineMembers.length }} 人在线</span>
</div>
<!-- 聊天内容区域 -->
<div class="chat-content" ref="chatContent">
<div
v-for="(message, index) in chatMessages"
:key="index"
:class="['chat-message', message.sender === '我' ? 'self-message' : 'other-message']"
>
<div class="message-avatar">
<el-avatar :size="32" :src="message.avatar">{{ message.sender.charAt(0) }}</el-avatar>
</div>
<div class="message-content">
<div class="message-sender">{{ message.sender }}</div>
<div class="message-text">{{ message.content }}</div>
<div class="message-time">{{ message.time }}</div>
</div>
</div>
</div>
<!-- 聊天输入区域 -->
<div class="chat-input">
<el-input
v-model="newMessage"
placeholder="请输入消息..."
@keyup.enter="sendMessage"
clearable
></el-input>
<el-button type="primary" @click="sendMessage" class="send-btn">发送</el-button>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<div class="dialog-footer">
<el-button @click="closeDialog">关闭</el-button>
</div>
<!-- 移除了底部的dialog-footer关闭按钮 -->
</div>
</div>
</template>
@ -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;
}
</style>
Loading…
Cancel
Save