5 changed files with 1367 additions and 23 deletions
@ -0,0 +1,486 @@ |
|||||
|
<template> |
||||
|
<div v-if="value" class="online-members-dialog"> |
||||
|
<!-- 遮罩层 --> |
||||
|
<div class="dialog-overlay" @click="closeDialog"></div> |
||||
|
|
||||
|
<!-- 弹窗内容 --> |
||||
|
<div class="dialog-content"> |
||||
|
<div class="dialog-header"> |
||||
|
<h3>在线成员与操作日志</h3> |
||||
|
<div class="close-btn" @click="closeDialog">×</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-body"> |
||||
|
<el-tabs v-model="activeTab" type="card" size="small"> |
||||
|
<!-- 在线成员 --> |
||||
|
<el-tab-pane label="在线成员" name="members"> |
||||
|
<div class="members-list"> |
||||
|
<div |
||||
|
v-for="member in onlineMembers" |
||||
|
:key="member.id" |
||||
|
class="member-item" |
||||
|
:class="{ active: member.isEditing }" |
||||
|
> |
||||
|
<div class="member-avatar"> |
||||
|
<el-avatar :size="36" :src="member.avatar">{{ member.name.charAt(0) }}</el-avatar> |
||||
|
</div> |
||||
|
<div class="member-info"> |
||||
|
<div class="member-name">{{ member.name }}</div> |
||||
|
<div class="member-role">{{ member.role }}</div> |
||||
|
</div> |
||||
|
<div class="member-status"> |
||||
|
<span class="status-dot online"></span> |
||||
|
<span class="status-text">{{ member.status }}</span> |
||||
|
</div> |
||||
|
<div v-if="member.isEditing" class="editing-badge">编辑中</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-tab-pane> |
||||
|
|
||||
|
<!-- 编辑/选中状态 --> |
||||
|
<el-tab-pane label="当前操作" name="current"> |
||||
|
<div class="current-operation"> |
||||
|
<div class="operation-section"> |
||||
|
<h4>编辑状态</h4> |
||||
|
<div class="status-item"> |
||||
|
<span class="status-label">当前编辑者:</span> |
||||
|
<span class="status-value">{{ currentEditor || '无' }}</span> |
||||
|
</div> |
||||
|
<div class="status-item"> |
||||
|
<span class="status-label">编辑对象:</span> |
||||
|
<span class="status-value">{{ editingObject || '无' }}</span> |
||||
|
</div> |
||||
|
<div class="status-item"> |
||||
|
<span class="status-label">编辑时间:</span> |
||||
|
<span class="status-value">{{ editingTime || '无' }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="operation-section"> |
||||
|
<h4>选中状态</h4> |
||||
|
<div class="status-item"> |
||||
|
<span class="status-label">选中对象:</span> |
||||
|
<span class="status-value">{{ selectedObject || '无' }}</span> |
||||
|
</div> |
||||
|
<div class="status-item"> |
||||
|
<span class="status-label">选中数量:</span> |
||||
|
<span class="status-value">{{ selectedCount }} 个</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-tab-pane> |
||||
|
|
||||
|
<!-- 操作日志与回滚 --> |
||||
|
<el-tab-pane label="操作日志" name="logs"> |
||||
|
<div class="operation-logs"> |
||||
|
<div class="logs-header"> |
||||
|
<h4>对象级操作日志</h4> |
||||
|
<el-button |
||||
|
type="warning" |
||||
|
size="mini" |
||||
|
@click="showRollbackConfirm" |
||||
|
:disabled="operationLogs.length === 0" |
||||
|
> |
||||
|
<i class="el-icon-refresh-right"></i> 回滚操作 |
||||
|
</el-button> |
||||
|
</div> |
||||
|
<el-timeline> |
||||
|
<el-timeline-item |
||||
|
v-for="log in operationLogs" |
||||
|
:key="log.id" |
||||
|
:timestamp="log.time" |
||||
|
:type="log.type" |
||||
|
> |
||||
|
<div class="log-content"> |
||||
|
<div class="log-user">{{ log.user }}</div> |
||||
|
<div class="log-action">{{ log.action }}</div> |
||||
|
<div class="log-object">对象:{{ log.object }}</div> |
||||
|
<div class="log-detail">{{ log.detail }}</div> |
||||
|
</div> |
||||
|
</el-timeline-item> |
||||
|
</el-timeline> |
||||
|
|
||||
|
<!-- 回滚确认弹窗 --> |
||||
|
<el-dialog |
||||
|
title="操作回滚确认" |
||||
|
:visible.sync="showRollbackDialog" |
||||
|
width="400px" |
||||
|
center |
||||
|
> |
||||
|
<div class="rollback-confirm"> |
||||
|
<p>确定要回滚到所选操作吗?</p> |
||||
|
<p class="text-warning mt-2">此操作将撤销该操作及其后的所有更改,不可恢复!</p> |
||||
|
</div> |
||||
|
<span slot="footer" class="dialog-footer"> |
||||
|
<el-button @click="showRollbackDialog = false">取消</el-button> |
||||
|
<el-button type="primary" @click="rollbackOperation">确定回滚</el-button> |
||||
|
</span> |
||||
|
</el-dialog> |
||||
|
</div> |
||||
|
</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-footer"> |
||||
|
<el-button @click="closeDialog">关闭</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'OnlineMembersDialog', |
||||
|
props: { |
||||
|
value: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
activeTab: 'members', |
||||
|
showRollbackDialog: false, |
||||
|
|
||||
|
// 在线成员数据 |
||||
|
onlineMembers: [ |
||||
|
{ id: 1, name: '张三', role: '指挥官', status: '在线', isEditing: true, avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png' }, |
||||
|
{ id: 2, name: '李四', role: '参谋', status: '在线', isEditing: false, avatar: 'https://cube.elemecdn.com/1/88/03b0d39583f48206768a7534e55bcpng.png' }, |
||||
|
{ id: 3, name: '王五', role: '操作员', status: '在线', isEditing: false, avatar: 'https://cube.elemecdn.com/2/88/03b0d39583f48206768a7534e55bcpng.png' }, |
||||
|
{ id: 4, name: '赵六', role: '观察员', status: '在线', isEditing: false, avatar: 'https://cube.elemecdn.com/3/88/03b0d39583f48206768a7534e55bcpng.png' }, |
||||
|
{ id: 5, name: '孙七', role: '分析师', status: '在线', isEditing: false, avatar: 'https://cube.elemecdn.com/4/88/03b0d39583f48206768a7534e55bcpng.png' } |
||||
|
], |
||||
|
|
||||
|
// 当前操作状态 |
||||
|
currentEditor: '张三', |
||||
|
editingObject: 'J-20 歼击机', |
||||
|
editingTime: 'K+00:45:23', |
||||
|
selectedObject: 'Alpha进场航线', |
||||
|
selectedCount: 1, |
||||
|
|
||||
|
// 操作日志 |
||||
|
operationLogs: [ |
||||
|
{ |
||||
|
id: 1, |
||||
|
user: '张三', |
||||
|
action: '修改', |
||||
|
object: 'J-20 歼击机', |
||||
|
detail: '更新了速度参数从800km/h到850km/h', |
||||
|
time: 'K+00:45:23', |
||||
|
type: 'success' |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
user: '李四', |
||||
|
action: '选择', |
||||
|
object: 'Alpha进场航线', |
||||
|
detail: '选中了Alpha进场航线进行编辑', |
||||
|
time: 'K+00:42:15', |
||||
|
type: 'primary' |
||||
|
}, |
||||
|
{ |
||||
|
id: 3, |
||||
|
user: '王五', |
||||
|
action: '添加', |
||||
|
object: 'WP5', |
||||
|
detail: '在Beta巡逻航线上添加了新航点WP5', |
||||
|
time: 'K+00:38:47', |
||||
|
type: 'info' |
||||
|
}, |
||||
|
{ |
||||
|
id: 4, |
||||
|
user: '赵六', |
||||
|
action: '删除', |
||||
|
object: '旧侦察航线', |
||||
|
detail: '删除了过期的侦察航线', |
||||
|
time: 'K+00:35:12', |
||||
|
type: 'warning' |
||||
|
}, |
||||
|
{ |
||||
|
id: 5, |
||||
|
user: '孙七', |
||||
|
action: '修改', |
||||
|
object: 'HQ-9防空系统', |
||||
|
detail: '更新了射程参数从120km到150km', |
||||
|
time: 'K+00:32:08', |
||||
|
type: 'success' |
||||
|
} |
||||
|
] |
||||
|
}; |
||||
|
}, |
||||
|
methods: { |
||||
|
closeDialog() { |
||||
|
this.$emit('input', false); |
||||
|
}, |
||||
|
|
||||
|
showRollbackConfirm() { |
||||
|
this.showRollbackDialog = true; |
||||
|
}, |
||||
|
|
||||
|
rollbackOperation() { |
||||
|
this.showRollbackDialog = false; |
||||
|
this.$message.success('操作回滚成功'); |
||||
|
// 这里可以添加实际的回滚逻辑 |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.online-members-dialog { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 1000; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.dialog-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
backdrop-filter: blur(2px); |
||||
|
} |
||||
|
|
||||
|
.dialog-content { |
||||
|
position: relative; |
||||
|
background: white; |
||||
|
border-radius: 8px; |
||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
||||
|
width: 90%; |
||||
|
max-width: 700px; |
||||
|
max-height: 90vh; |
||||
|
overflow-y: auto; |
||||
|
animation: dialog-fade-in 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes dialog-fade-in { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(-20px); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dialog-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 16px 20px; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.dialog-header h3 { |
||||
|
margin: 0; |
||||
|
font-size: 16px; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.close-btn { |
||||
|
font-size: 20px; |
||||
|
color: #999; |
||||
|
cursor: pointer; |
||||
|
transition: color 0.3s; |
||||
|
} |
||||
|
|
||||
|
.close-btn:hover { |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
|
||||
|
/* 在线成员样式 */ |
||||
|
.members-list { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
.member-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 12px; |
||||
|
border-radius: 8px; |
||||
|
background: rgba(240, 242, 245, 0.8); |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.member-item:hover { |
||||
|
background: rgba(220, 233, 255, 0.8); |
||||
|
transform: translateY(-1px); |
||||
|
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); |
||||
|
} |
||||
|
|
||||
|
.member-item.active { |
||||
|
background: rgba(190, 220, 255, 0.8); |
||||
|
border: 1px solid rgba(0, 138, 255, 0.3); |
||||
|
box-shadow: 0 2px 10px rgba(0, 138, 255, 0.2); |
||||
|
} |
||||
|
|
||||
|
.member-avatar { |
||||
|
margin-right: 12px; |
||||
|
} |
||||
|
|
||||
|
.member-info { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.member-name { |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
|
||||
|
.member-role { |
||||
|
font-size: 12px; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.member-status { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 6px; |
||||
|
margin-right: 12px; |
||||
|
} |
||||
|
|
||||
|
.status-dot { |
||||
|
width: 8px; |
||||
|
height: 8px; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
.status-dot.online { |
||||
|
background: #52c41a; |
||||
|
box-shadow: 0 0 4px rgba(82, 196, 26, 0.8); |
||||
|
} |
||||
|
|
||||
|
.status-text { |
||||
|
font-size: 12px; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.editing-badge { |
||||
|
background: #ff7875; |
||||
|
color: white; |
||||
|
font-size: 11px; |
||||
|
padding: 2px 8px; |
||||
|
border-radius: 10px; |
||||
|
} |
||||
|
|
||||
|
/* 当前操作样式 */ |
||||
|
.current-operation { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 20px; |
||||
|
} |
||||
|
|
||||
|
.operation-section { |
||||
|
background: rgba(240, 242, 245, 0.8); |
||||
|
padding: 16px; |
||||
|
border-radius: 8px; |
||||
|
} |
||||
|
|
||||
|
.operation-section h4 { |
||||
|
margin: 0 0 12px 0; |
||||
|
font-size: 14px; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.status-item { |
||||
|
display: flex; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.status-label { |
||||
|
width: 100px; |
||||
|
font-size: 13px; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.status-value { |
||||
|
font-size: 13px; |
||||
|
color: #333; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 操作日志样式 */ |
||||
|
.operation-logs { |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.logs-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.logs-header h4 { |
||||
|
margin: 0; |
||||
|
font-size: 14px; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.log-content { |
||||
|
background: rgba(240, 242, 245, 0.8); |
||||
|
padding: 12px; |
||||
|
border-radius: 6px; |
||||
|
margin-left: 16px; |
||||
|
} |
||||
|
|
||||
|
.log-user { |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
|
||||
|
.log-action { |
||||
|
font-size: 13px; |
||||
|
color: #666; |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
|
||||
|
.log-object { |
||||
|
font-size: 12px; |
||||
|
color: #008aff; |
||||
|
margin-bottom: 2px; |
||||
|
} |
||||
|
|
||||
|
.log-detail { |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 回滚确认样式 */ |
||||
|
.rollback-confirm { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.text-warning { |
||||
|
color: #fa8c16; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,295 @@ |
|||||
|
<template> |
||||
|
<div v-if="value" class="platform-edit-dialog"> |
||||
|
<!-- 遮罩层 --> |
||||
|
<div class="dialog-overlay" @click="closeDialog"></div> |
||||
|
|
||||
|
<!-- 弹窗内容 --> |
||||
|
<div class="dialog-content"> |
||||
|
<div class="dialog-header"> |
||||
|
<h3>平台编辑</h3> |
||||
|
<div class="close-btn" @click="closeDialog">×</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-body"> |
||||
|
<el-form :model="formData" :rules="rules" ref="formRef" label-width="80px" size="small"> |
||||
|
<!-- 基本信息 --> |
||||
|
<el-form-item label="名称" prop="name"> |
||||
|
<el-input v-model="formData.name" placeholder="请输入平台名称"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="位置"> |
||||
|
<div class="location-inputs"> |
||||
|
<el-input v-model="formData.location.lat" placeholder="纬度" style="width: 120px;"></el-input> |
||||
|
<span class="location-separator">,</span> |
||||
|
<el-input v-model="formData.location.lng" placeholder="经度" style="width: 120px;"></el-input> |
||||
|
</div> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="速度" prop="speed"> |
||||
|
<el-input v-model="formData.speed" placeholder="请输入速度" suffix="km/h"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="油耗表" prop="fuelConsumption"> |
||||
|
<el-input-number |
||||
|
v-model="formData.fuelConsumption" |
||||
|
:min="0" |
||||
|
:precision="2" |
||||
|
placeholder="请输入油耗" |
||||
|
style="width: 100%;" |
||||
|
suffix="L/km" |
||||
|
></el-input-number> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="高度限制"> |
||||
|
<div class="altitude-inputs"> |
||||
|
<el-input-number |
||||
|
v-model="formData.altitude.min" |
||||
|
:min="0" |
||||
|
placeholder="最低高度" |
||||
|
style="width: 120px;" |
||||
|
suffix="m" |
||||
|
></el-input-number> |
||||
|
<span class="altitude-separator">~</span> |
||||
|
<el-input-number |
||||
|
v-model="formData.altitude.max" |
||||
|
:min="0" |
||||
|
placeholder="最高高度" |
||||
|
style="width: 120px;" |
||||
|
suffix="m" |
||||
|
></el-input-number> |
||||
|
</div> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="威力区/扇区"> |
||||
|
<div class="sector-inputs"> |
||||
|
<el-input-number |
||||
|
v-model="formData.sector.radius" |
||||
|
:min="0" |
||||
|
placeholder="半径" |
||||
|
style="width: 100px;" |
||||
|
suffix="km" |
||||
|
></el-input-number> |
||||
|
<el-input-number |
||||
|
v-model="formData.sector.angle" |
||||
|
:min="0" |
||||
|
:max="360" |
||||
|
placeholder="角度" |
||||
|
style="width: 100px;" |
||||
|
suffix="°" |
||||
|
></el-input-number> |
||||
|
</div> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-footer"> |
||||
|
<el-button @click="closeDialog">取消</el-button> |
||||
|
<el-button type="primary" @click="savePlatform">保存</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'PlatformEditDialog', |
||||
|
props: { |
||||
|
value: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
platform: { |
||||
|
type: Object, |
||||
|
default: () => ({}) |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
formData: { |
||||
|
name: '', |
||||
|
location: { |
||||
|
lat: '', |
||||
|
lng: '' |
||||
|
}, |
||||
|
speed: '', |
||||
|
fuelConsumption: 0, |
||||
|
altitude: { |
||||
|
min: 0, |
||||
|
max: 0 |
||||
|
}, |
||||
|
sector: { |
||||
|
radius: 0, |
||||
|
angle: 0 |
||||
|
} |
||||
|
}, |
||||
|
rules: { |
||||
|
name: [ |
||||
|
{ required: true, message: '请输入平台名称', trigger: 'blur' } |
||||
|
], |
||||
|
speed: [ |
||||
|
{ required: true, message: '请输入速度', trigger: 'blur' }, |
||||
|
{ type: 'number', message: '速度必须为数字', trigger: 'blur' } |
||||
|
], |
||||
|
fuelConsumption: [ |
||||
|
{ type: 'number', message: '油耗必须为数字', trigger: 'blur' } |
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
watch: { |
||||
|
value(newVal) { |
||||
|
if (newVal && this.platform) { |
||||
|
this.initFormData(); |
||||
|
} |
||||
|
}, |
||||
|
platform(newVal) { |
||||
|
if (this.value && newVal) { |
||||
|
this.initFormData(); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
initFormData() { |
||||
|
// 初始化表单数据,如果平台有相关属性则使用,否则使用默认值 |
||||
|
this.formData = { |
||||
|
name: this.platform.name || '', |
||||
|
location: { |
||||
|
lat: this.platform.lat || '', |
||||
|
lng: this.platform.lng || '' |
||||
|
}, |
||||
|
speed: this.platform.speed || '', |
||||
|
fuelConsumption: this.platform.fuelConsumption || 0, |
||||
|
altitude: { |
||||
|
min: this.platform.minAltitude || 0, |
||||
|
max: this.platform.maxAltitude || 0 |
||||
|
}, |
||||
|
sector: { |
||||
|
radius: this.platform.sectorRadius || 0, |
||||
|
angle: this.platform.sectorAngle || 0 |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
closeDialog() { |
||||
|
this.$emit('input', false); |
||||
|
}, |
||||
|
savePlatform() { |
||||
|
this.$refs.formRef.validate((valid) => { |
||||
|
if (valid) { |
||||
|
// 发送保存事件,将更新后的数据传递给父组件 |
||||
|
this.$emit('save', { |
||||
|
...this.platform, |
||||
|
...this.formData, |
||||
|
lat: this.formData.location.lat, |
||||
|
lng: this.formData.location.lng, |
||||
|
minAltitude: this.formData.altitude.min, |
||||
|
maxAltitude: this.formData.altitude.max, |
||||
|
sectorRadius: this.formData.sector.radius, |
||||
|
sectorAngle: this.formData.sector.angle |
||||
|
}); |
||||
|
this.closeDialog(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.platform-edit-dialog { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 1000; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.dialog-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
backdrop-filter: blur(2px); |
||||
|
} |
||||
|
|
||||
|
.dialog-content { |
||||
|
position: relative; |
||||
|
background: white; |
||||
|
border-radius: 8px; |
||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
||||
|
width: 90%; |
||||
|
max-width: 500px; |
||||
|
max-height: 90vh; |
||||
|
overflow-y: auto; |
||||
|
animation: dialog-fade-in 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes dialog-fade-in { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(-20px); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dialog-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 16px 20px; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.dialog-header h3 { |
||||
|
margin: 0; |
||||
|
font-size: 16px; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.close-btn { |
||||
|
font-size: 20px; |
||||
|
color: #999; |
||||
|
cursor: pointer; |
||||
|
transition: color 0.3s; |
||||
|
} |
||||
|
|
||||
|
.close-btn:hover { |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.dialog-body { |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.location-inputs, |
||||
|
.altitude-inputs, |
||||
|
.sector-inputs { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.location-separator, |
||||
|
.altitude-separator { |
||||
|
color: #999; |
||||
|
margin: 0 5px; |
||||
|
} |
||||
|
|
||||
|
.dialog-footer { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: flex-end; |
||||
|
padding: 16px 20px; |
||||
|
border-top: 1px solid #e8e8e8; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,229 @@ |
|||||
|
<template> |
||||
|
<div v-if="value" class="route-edit-dialog"> |
||||
|
<!-- 遮罩层 --> |
||||
|
<div class="dialog-overlay" @click="closeDialog"></div> |
||||
|
|
||||
|
<!-- 弹窗内容 --> |
||||
|
<div class="dialog-content"> |
||||
|
<div class="dialog-header"> |
||||
|
<h3>航线编辑</h3> |
||||
|
<div class="close-btn" @click="closeDialog">×</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-body"> |
||||
|
<el-form :model="formData" :rules="rules" ref="formRef" label-width="80px" size="small"> |
||||
|
<!-- 基本信息 --> |
||||
|
<el-form-item label="名称" prop="name"> |
||||
|
<el-input v-model="formData.name" placeholder="请输入航线名称"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="航向" prop="heading"> |
||||
|
<el-input-number |
||||
|
v-model="formData.heading" |
||||
|
:min="0" |
||||
|
:max="360" |
||||
|
placeholder="请输入航向" |
||||
|
style="width: 100%;" |
||||
|
suffix="°" |
||||
|
></el-input-number> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="时间" prop="time"> |
||||
|
<el-input v-model="formData.time" placeholder="请输入时间(如:K+00:40:00)"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="速度" prop="speed"> |
||||
|
<el-input v-model="formData.speed" placeholder="请输入速度" suffix="km/h"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="距离" prop="distance"> |
||||
|
<el-input-number |
||||
|
v-model="formData.distance" |
||||
|
:min="0" |
||||
|
:precision="2" |
||||
|
placeholder="请输入距离" |
||||
|
style="width: 100%;" |
||||
|
suffix="km" |
||||
|
></el-input-number> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-footer"> |
||||
|
<el-button @click="closeDialog">取消</el-button> |
||||
|
<el-button type="primary" @click="saveRoute">保存</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'RouteEditDialog', |
||||
|
props: { |
||||
|
value: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
route: { |
||||
|
type: Object, |
||||
|
default: () => ({}) |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
formData: { |
||||
|
name: '', |
||||
|
heading: 0, |
||||
|
time: '', |
||||
|
speed: '', |
||||
|
distance: 0 |
||||
|
}, |
||||
|
rules: { |
||||
|
name: [ |
||||
|
{ required: true, message: '请输入航线名称', trigger: 'blur' } |
||||
|
], |
||||
|
heading: [ |
||||
|
{ type: 'number', message: '航向必须为数字', trigger: 'blur' }, |
||||
|
{ min: 0, max: 360, message: '航向必须在0-360度之间', trigger: 'blur' } |
||||
|
], |
||||
|
time: [ |
||||
|
{ required: true, message: '请输入时间', trigger: 'blur' } |
||||
|
], |
||||
|
speed: [ |
||||
|
{ required: true, message: '请输入速度', trigger: 'blur' }, |
||||
|
{ type: 'number', message: '速度必须为数字', trigger: 'blur' } |
||||
|
], |
||||
|
distance: [ |
||||
|
{ type: 'number', message: '距离必须为数字', trigger: 'blur' } |
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
watch: { |
||||
|
value(newVal) { |
||||
|
if (newVal && this.route) { |
||||
|
this.initFormData(); |
||||
|
} |
||||
|
}, |
||||
|
route(newVal) { |
||||
|
if (this.value && newVal) { |
||||
|
this.initFormData(); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
initFormData() { |
||||
|
// 初始化表单数据,如果航线有相关属性则使用,否则使用默认值 |
||||
|
this.formData = { |
||||
|
name: this.route.name || '', |
||||
|
heading: this.route.heading || 0, |
||||
|
time: this.route.time || '', |
||||
|
speed: this.route.speed || '', |
||||
|
distance: this.route.distance || 0 |
||||
|
}; |
||||
|
}, |
||||
|
closeDialog() { |
||||
|
this.$emit('input', false); |
||||
|
}, |
||||
|
saveRoute() { |
||||
|
this.$refs.formRef.validate((valid) => { |
||||
|
if (valid) { |
||||
|
// 发送保存事件,将更新后的数据传递给父组件 |
||||
|
this.$emit('save', { |
||||
|
...this.route, |
||||
|
...this.formData |
||||
|
}); |
||||
|
this.closeDialog(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.route-edit-dialog { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 1000; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.dialog-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
backdrop-filter: blur(2px); |
||||
|
} |
||||
|
|
||||
|
.dialog-content { |
||||
|
position: relative; |
||||
|
background: white; |
||||
|
border-radius: 8px; |
||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
||||
|
width: 90%; |
||||
|
max-width: 500px; |
||||
|
max-height: 90vh; |
||||
|
overflow-y: auto; |
||||
|
animation: dialog-fade-in 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes dialog-fade-in { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(-20px); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dialog-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 16px 20px; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.dialog-header h3 { |
||||
|
margin: 0; |
||||
|
font-size: 16px; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.close-btn { |
||||
|
font-size: 20px; |
||||
|
color: #999; |
||||
|
cursor: pointer; |
||||
|
transition: color 0.3s; |
||||
|
} |
||||
|
|
||||
|
.close-btn:hover { |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,227 @@ |
|||||
|
<template> |
||||
|
<div v-if="value" class="waypoint-edit-dialog"> |
||||
|
<!-- 遮罩层 --> |
||||
|
<div class="dialog-overlay" @click="closeDialog"></div> |
||||
|
|
||||
|
<!-- 弹窗内容 --> |
||||
|
<div class="dialog-content"> |
||||
|
<div class="dialog-header"> |
||||
|
<h3>航点编辑</h3> |
||||
|
<div class="close-btn" @click="closeDialog">×</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-body"> |
||||
|
<el-form :model="formData" :rules="rules" ref="formRef" label-width="80px" size="small"> |
||||
|
<!-- 基本信息 --> |
||||
|
<el-form-item label="名称" prop="name"> |
||||
|
<el-input v-model="formData.name" placeholder="请输入航点名称"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="高度" prop="altitude"> |
||||
|
<el-input-number |
||||
|
v-model="formData.altitude" |
||||
|
:min="0" |
||||
|
placeholder="请输入高度" |
||||
|
style="width: 100%;" |
||||
|
suffix="m" |
||||
|
></el-input-number> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="速度" prop="speed"> |
||||
|
<el-input v-model="formData.speed" placeholder="请输入速度" suffix="km/h"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="时间" prop="eta"> |
||||
|
<el-input v-model="formData.eta" placeholder="请输入到达时间(如:K+00:40:00)"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="转弯半径" prop="turnRadius"> |
||||
|
<el-input-number |
||||
|
v-model="formData.turnRadius" |
||||
|
:min="0" |
||||
|
placeholder="请输入转弯半径" |
||||
|
style="width: 100%;" |
||||
|
suffix="m" |
||||
|
></el-input-number> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
|
||||
|
<div class="dialog-footer"> |
||||
|
<el-button @click="closeDialog">取消</el-button> |
||||
|
<el-button type="primary" @click="saveWaypoint">保存</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'WaypointEditDialog', |
||||
|
props: { |
||||
|
value: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
waypoint: { |
||||
|
type: Object, |
||||
|
default: () => ({}) |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
formData: { |
||||
|
name: '', |
||||
|
altitude: 0, |
||||
|
speed: '', |
||||
|
eta: '', |
||||
|
turnRadius: 0 |
||||
|
}, |
||||
|
rules: { |
||||
|
name: [ |
||||
|
{ required: true, message: '请输入航点名称', trigger: 'blur' } |
||||
|
], |
||||
|
altitude: [ |
||||
|
{ required: true, message: '请输入高度', trigger: 'blur' }, |
||||
|
{ type: 'number', message: '高度必须为数字', trigger: 'blur' } |
||||
|
], |
||||
|
speed: [ |
||||
|
{ required: true, message: '请输入速度', trigger: 'blur' }, |
||||
|
{ type: 'number', message: '速度必须为数字', trigger: 'blur' } |
||||
|
], |
||||
|
eta: [ |
||||
|
{ required: true, message: '请输入到达时间', trigger: 'blur' } |
||||
|
], |
||||
|
turnRadius: [ |
||||
|
{ type: 'number', message: '转弯半径必须为数字', trigger: 'blur' } |
||||
|
] |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
watch: { |
||||
|
value(newVal) { |
||||
|
if (newVal && this.waypoint) { |
||||
|
this.initFormData(); |
||||
|
} |
||||
|
}, |
||||
|
waypoint(newVal) { |
||||
|
if (this.value && newVal) { |
||||
|
this.initFormData(); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
initFormData() { |
||||
|
// 初始化表单数据,如果航点有相关属性则使用,否则使用默认值 |
||||
|
this.formData = { |
||||
|
name: this.waypoint.name || '', |
||||
|
altitude: this.waypoint.altitude || 0, |
||||
|
speed: this.waypoint.speed || '', |
||||
|
eta: this.waypoint.eta || '', |
||||
|
turnRadius: this.waypoint.turnRadius || 0 |
||||
|
}; |
||||
|
}, |
||||
|
closeDialog() { |
||||
|
this.$emit('input', false); |
||||
|
}, |
||||
|
saveWaypoint() { |
||||
|
this.$refs.formRef.validate((valid) => { |
||||
|
if (valid) { |
||||
|
// 发送保存事件,将更新后的数据传递给父组件 |
||||
|
this.$emit('save', { |
||||
|
...this.waypoint, |
||||
|
...this.formData |
||||
|
}); |
||||
|
this.closeDialog(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.waypoint-edit-dialog { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 1000; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.dialog-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
backdrop-filter: blur(2px); |
||||
|
} |
||||
|
|
||||
|
.dialog-content { |
||||
|
position: relative; |
||||
|
background: white; |
||||
|
border-radius: 8px; |
||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
||||
|
width: 90%; |
||||
|
max-width: 500px; |
||||
|
max-height: 90vh; |
||||
|
overflow-y: auto; |
||||
|
animation: dialog-fade-in 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes dialog-fade-in { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(-20px); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dialog-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
padding: 16px 20px; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.dialog-header h3 { |
||||
|
margin: 0; |
||||
|
font-size: 16px; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.close-btn { |
||||
|
font-size: 20px; |
||||
|
color: #999; |
||||
|
cursor: pointer; |
||||
|
transition: color 0.3s; |
||||
|
} |
||||
|
|
||||
|
.close-btn:hover { |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue