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