You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
12 KiB
461 lines
12 KiB
<template>
|
|
<!-- 冲突列表弹窗:类似 4T,可拖动、可调整大小,带分页 -->
|
|
<div v-show="visible" class="conflict-drawer-wrap">
|
|
<div class="conflict-drawer-container" :style="panelStyle">
|
|
<!-- 标题栏:可拖动 -->
|
|
<div class="conflict-drawer-header" @mousedown="onDragStart">
|
|
<span class="conflict-drawer-title">冲突列表</span>
|
|
<button class="conflict-drawer-close" @mousedown.stop @click="handleClose">×</button>
|
|
</div>
|
|
|
|
<!-- 筛选与每页条数 -->
|
|
<div class="conflict-drawer-toolbar">
|
|
<div class="toolbar-row">
|
|
<span class="toolbar-label">{{ $t('rightPanel.conflictFilter') }}:</span>
|
|
<el-select v-model="filterType" size="mini" class="filter-select">
|
|
<el-option :label="$t('rightPanel.conflictTypeAll')" value="all" />
|
|
<el-option :label="$t('rightPanel.conflictTypeTime')" value="time" />
|
|
<el-option :label="$t('rightPanel.conflictTypeSpace')" value="space" />
|
|
<el-option :label="$t('rightPanel.conflictTypeSpectrum')" value="spectrum" />
|
|
</el-select>
|
|
</div>
|
|
<div class="toolbar-row">
|
|
<span class="toolbar-label">每页条数:</span>
|
|
<el-select v-model="pageSize" size="mini" class="page-size-select">
|
|
<el-option :label="'5'" :value="5" />
|
|
<el-option :label="'10'" :value="10" />
|
|
<el-option :label="'20'" :value="20" />
|
|
<el-option :label="'50'" :value="50" />
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 列表区域 -->
|
|
<div class="conflict-drawer-body">
|
|
<div v-if="loading" class="conflict-loading">
|
|
正在检测冲突…
|
|
</div>
|
|
<div v-else-if="paginatedList.length > 0" class="conflict-list">
|
|
<div
|
|
v-for="conflict in paginatedList"
|
|
:key="conflict.id"
|
|
class="conflict-item"
|
|
>
|
|
<div class="conflict-header">
|
|
<i class="el-icon-warning conflict-icon"></i>
|
|
<span class="conflict-title">{{ conflict.title }}</span>
|
|
<el-tag size="mini" :type="conflictTypeTagType(conflict.type)" class="conflict-type-tag">{{ conflictTypeLabel(conflict.type) }}</el-tag>
|
|
<el-tag v-if="conflict.severity === 'high'" size="mini" type="danger">{{ $t('rightPanel.serious') }}</el-tag>
|
|
</div>
|
|
<div class="conflict-details">
|
|
<div class="detail-item">
|
|
<span class="label">{{ $t('rightPanel.involvedRoutes') }}:</span>
|
|
<span class="value">{{ conflict.routeName || (conflict.routeNames && conflict.routeNames.join('、')) }}</span>
|
|
</div>
|
|
<div v-if="conflict.fromWaypoint && conflict.toWaypoint" class="detail-item">
|
|
<span class="label">问题航段:</span>
|
|
<span class="value">{{ conflict.fromWaypoint }} → {{ conflict.toWaypoint }}</span>
|
|
</div>
|
|
<div v-if="conflict.time" class="detail-item">
|
|
<span class="label">{{ $t('rightPanel.conflictTime') }}:</span>
|
|
<span class="value">{{ conflict.time }}</span>
|
|
</div>
|
|
<div v-if="conflict.position" class="detail-item">
|
|
<span class="label">{{ $t('rightPanel.conflictPosition') }}:</span>
|
|
<span class="value">{{ conflict.position }}</span>
|
|
</div>
|
|
<div v-if="conflict.suggestion" class="detail-item suggestion">
|
|
<span class="label">建议:</span>
|
|
<span class="value">{{ conflict.suggestion }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="conflict-actions">
|
|
<el-button type="text" size="mini" class="blue-text-btn" @click="$emit('view-conflict', conflict)">
|
|
{{ $t('rightPanel.locate') }}
|
|
</el-button>
|
|
<el-button type="text" size="mini" class="blue-text-btn" @click="$emit('resolve-conflict', conflict)">
|
|
{{ $t('rightPanel.resolveConflict') }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="!loading" class="no-conflict">
|
|
<i class="el-icon-success no-conflict-icon"></i>
|
|
<p>{{ filteredConflicts.length > 0 ? $t('rightPanel.noMatchFilter') : $t('rightPanel.noConflict') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 分页 -->
|
|
<div v-if="filteredConflicts.length > pageSize" class="conflict-drawer-footer">
|
|
<el-pagination
|
|
small
|
|
layout="prev, pager, next, total"
|
|
:total="filteredConflicts.length"
|
|
:page-size="pageSize"
|
|
:current-page.sync="currentPage"
|
|
:pager-count="5"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 右下角调整大小手柄 -->
|
|
<div class="conflict-resize-handle" @mousedown="onResizeStart" title="拖动调整大小"></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'ConflictDrawer',
|
|
props: {
|
|
visible: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
conflicts: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
loading: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
filterType: 'all',
|
|
pageSize: 10,
|
|
currentPage: 1,
|
|
isDragging: false,
|
|
dragStartX: 0,
|
|
dragStartY: 0,
|
|
panelLeft: null,
|
|
panelTop: null,
|
|
panelWidth: 420,
|
|
panelHeight: 480,
|
|
isResizing: false,
|
|
resizeStartX: 0,
|
|
resizeStartY: 0,
|
|
resizeStartW: 0,
|
|
resizeStartH: 0
|
|
}
|
|
},
|
|
computed: {
|
|
filteredConflicts() {
|
|
const list = this.conflicts || []
|
|
if (this.filterType === 'all') return list
|
|
return list.filter(c => c.type === this.filterType)
|
|
},
|
|
paginatedList() {
|
|
const list = this.filteredConflicts
|
|
const start = (this.currentPage - 1) * this.pageSize
|
|
return list.slice(start, start + this.pageSize)
|
|
},
|
|
panelStyle() {
|
|
const left = this.panelLeft != null ? this.panelLeft : (window.innerWidth - this.panelWidth) / 2 - 20
|
|
const top = this.panelTop != null ? this.panelTop : 80
|
|
return {
|
|
left: `${Math.max(0, left)}px`,
|
|
top: `${Math.max(0, top)}px`,
|
|
width: `${this.panelWidth}px`,
|
|
height: `${this.panelHeight}px`
|
|
}
|
|
}
|
|
},
|
|
watch: {
|
|
filterType() {
|
|
this.currentPage = 1
|
|
},
|
|
pageSize() {
|
|
this.currentPage = 1
|
|
}
|
|
},
|
|
methods: {
|
|
handleClose() {
|
|
this.$emit('update:visible', false)
|
|
},
|
|
conflictTypeLabel(type) {
|
|
const t = type || ''
|
|
if (t === 'time') return this.$t('rightPanel.conflictTypeTime')
|
|
if (t === 'space') return this.$t('rightPanel.conflictTypeSpace')
|
|
if (t === 'spectrum') return this.$t('rightPanel.conflictTypeSpectrum')
|
|
return this.$t('rightPanel.conflictTypeAll')
|
|
},
|
|
conflictTypeTagType(type) {
|
|
if (type === 'time') return 'warning'
|
|
if (type === 'space') return 'danger'
|
|
if (type === 'spectrum') return 'primary'
|
|
return 'info'
|
|
},
|
|
onDragStart(e) {
|
|
e.preventDefault()
|
|
this.isDragging = true
|
|
const currentLeft = this.panelLeft != null ? this.panelLeft : (window.innerWidth - this.panelWidth) / 2 - 20
|
|
const currentTop = this.panelTop != null ? this.panelTop : 80
|
|
this.dragStartX = e.clientX - currentLeft
|
|
this.dragStartY = e.clientY - currentTop
|
|
document.addEventListener('mousemove', this.onDragMove)
|
|
document.addEventListener('mouseup', this.onDragEnd)
|
|
},
|
|
onDragMove(e) {
|
|
if (!this.isDragging) return
|
|
e.preventDefault()
|
|
let left = e.clientX - this.dragStartX
|
|
let top = e.clientY - this.dragStartY
|
|
left = Math.max(0, Math.min(window.innerWidth - this.panelWidth, left))
|
|
top = Math.max(0, Math.min(window.innerHeight - this.panelHeight, top))
|
|
this.panelLeft = left
|
|
this.panelTop = top
|
|
},
|
|
onDragEnd() {
|
|
this.isDragging = false
|
|
document.removeEventListener('mousemove', this.onDragMove)
|
|
document.removeEventListener('mouseup', this.onDragEnd)
|
|
},
|
|
onResizeStart(e) {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
this.isResizing = true
|
|
this.resizeStartX = e.clientX
|
|
this.resizeStartY = e.clientY
|
|
this.resizeStartW = this.panelWidth
|
|
this.resizeStartH = this.panelHeight
|
|
document.addEventListener('mousemove', this.onResizeMove)
|
|
document.addEventListener('mouseup', this.onResizeEnd)
|
|
},
|
|
onResizeMove(e) {
|
|
if (!this.isResizing) return
|
|
e.preventDefault()
|
|
const dx = e.clientX - this.resizeStartX
|
|
const dy = e.clientY - this.resizeStartY
|
|
this.panelWidth = Math.max(320, Math.min(800, this.resizeStartW + dx))
|
|
this.panelHeight = Math.max(300, Math.min(window.innerHeight - 60, this.resizeStartH + dy))
|
|
},
|
|
onResizeEnd() {
|
|
this.isResizing = false
|
|
document.removeEventListener('mousemove', this.onResizeMove)
|
|
document.removeEventListener('mouseup', this.onResizeEnd)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.conflict-drawer-wrap {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: transparent;
|
|
z-index: 200;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.conflict-drawer-wrap .conflict-drawer-container {
|
|
pointer-events: auto;
|
|
position: fixed;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
border: 1px solid #e0edff;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.conflict-drawer-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 16px;
|
|
background: #f0f7ff;
|
|
border-bottom: 1px solid #cce5ff;
|
|
cursor: move;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.conflict-drawer-title {
|
|
font-weight: 600;
|
|
color: #1994fe;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.conflict-drawer-close {
|
|
width: 28px;
|
|
height: 28px;
|
|
border: 1px solid #cce5ff;
|
|
background: #fff;
|
|
color: #1994fe;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
font-size: 18px;
|
|
line-height: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
}
|
|
|
|
.conflict-drawer-close:hover {
|
|
background: #e8f4ff;
|
|
}
|
|
|
|
.conflict-drawer-toolbar {
|
|
padding: 10px 16px;
|
|
border-bottom: 1px solid #eee;
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.toolbar-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.toolbar-label {
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
.filter-select,
|
|
.page-size-select {
|
|
width: 100px;
|
|
}
|
|
|
|
.conflict-drawer-body {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 12px;
|
|
}
|
|
|
|
.conflict-loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px;
|
|
color: #909399;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.conflict-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.conflict-item {
|
|
background: #fafafa;
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
border: 1px solid rgba(245, 108, 108, 0.2);
|
|
}
|
|
|
|
.conflict-item:hover {
|
|
background: #fff5f5;
|
|
}
|
|
|
|
.conflict-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.conflict-icon {
|
|
color: #f56c6c;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.conflict-title {
|
|
flex: 1;
|
|
font-weight: 600;
|
|
color: #f56c6c;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.conflict-type-tag {
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.conflict-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.conflict-details .detail-item {
|
|
font-size: 12px;
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
|
|
.conflict-details .label {
|
|
color: #999;
|
|
min-width: 70px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.conflict-details .value {
|
|
color: #333;
|
|
}
|
|
|
|
.conflict-details .suggestion .value {
|
|
color: #e6a23c;
|
|
}
|
|
|
|
.conflict-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.blue-text-btn {
|
|
color: #1994fe;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.no-conflict {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px 20px;
|
|
color: #67c23a;
|
|
}
|
|
|
|
.no-conflict-icon {
|
|
font-size: 48px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.no-conflict p {
|
|
margin: 0;
|
|
font-size: 14px;
|
|
color: #666;
|
|
}
|
|
|
|
.conflict-drawer-footer {
|
|
padding: 8px 16px;
|
|
border-top: 1px solid #eee;
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.conflict-resize-handle {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: 20px;
|
|
height: 20px;
|
|
cursor: nwse-resize;
|
|
user-select: none;
|
|
z-index: 10;
|
|
background: linear-gradient(to top left, transparent 50%, rgba(25, 148, 254, 0.2) 50%);
|
|
}
|
|
|
|
.conflict-resize-handle:hover {
|
|
background: linear-gradient(to top left, transparent 50%, rgba(25, 148, 254, 0.4) 50%);
|
|
}
|
|
</style>
|
|
|