167 lines
5.3 KiB
Vue
167 lines
5.3 KiB
Vue
<template>
|
||
<MyDialog :model-value="modelValue" @update:model-value="$emit('update:modelValue', $event)" title="连接管理" :width="780">
|
||
<!-- 新增连线(非拖拽方式) -->
|
||
<div class="cm-add-row">
|
||
<el-select v-model="form.from" placeholder="源目标" filterable style="width:230px;" @change="form.to = ''">
|
||
<el-option-group v-for="g in groupedOptions" :key="g.label" :label="g.label">
|
||
<el-option v-for="o in g.items" :key="o.key" :label="o.name" :value="o.key" />
|
||
</el-option-group>
|
||
</el-select>
|
||
<span class="cm-arrow">→</span>
|
||
<el-select v-model="form.to" placeholder="目标目标" filterable style="width:230px;">
|
||
<el-option-group v-for="g in groupedOptions" :key="g.label" :label="g.label">
|
||
<el-option v-for="o in g.items" :key="o.key" :label="o.name" :value="o.key" />
|
||
</el-option-group>
|
||
</el-select>
|
||
<el-select v-model="form.effect" style="width:96px;">
|
||
<el-option label="正向" value="positive" />
|
||
<el-option label="负向" value="negative" />
|
||
</el-select>
|
||
<el-button type="primary" :disabled="!form.from || !form.to" @click="onAdd">添加连线</el-button>
|
||
</div>
|
||
|
||
<div v-if="connections.length === 0" class="cm-empty">
|
||
暂无连线,请通过上方表单或画布拖拽(下层→上层)创建
|
||
</div>
|
||
<table v-else class="cm-table">
|
||
<thead>
|
||
<tr><th>#</th><th>源目标</th><th>方向</th><th>目标目标</th><th>强度</th><th>操作</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="(conn, idx) in connections" :key="idx">
|
||
<td class="cm-idx">{{ idx + 1 }}</td>
|
||
<td :title="conn.from">{{ nodeLabel(conn.from) }}</td>
|
||
<td class="cm-dir">→</td>
|
||
<td :title="conn.to">{{ nodeLabel(conn.to) }}</td>
|
||
<td>
|
||
<el-tag
|
||
:type="conn.effect === 'negative' ? 'danger' : 'success'"
|
||
size="small"
|
||
style="cursor:pointer;"
|
||
@click="toggleEffect(idx)"
|
||
title="点击切换正/负向"
|
||
>{{ conn.effect === 'negative' ? '负向' : '正向' }} ↻</el-tag>
|
||
</td>
|
||
<td>
|
||
<el-button size="small" type="danger" text @click="$emit('delete-connection', idx)">删除</el-button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="cm-hint">
|
||
提示:拖拽连线按 BSC 支撑方向(下层→上层 或 同层)自动校验;本面板可维护任意方向连线。
|
||
</div>
|
||
</MyDialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue'
|
||
import MyDialog from '../MyDialog.vue'
|
||
|
||
const props = defineProps<{
|
||
modelValue: boolean
|
||
connections: any[]
|
||
nodeOptions: { key: string; name: string; layerName: string }[]
|
||
layerLabels?: Record<string, string>
|
||
}>()
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'update:modelValue', v: boolean): void
|
||
(e: 'add-connection', payload: { from: string; to: string; effect?: string }): void
|
||
(e: 'delete-connection', idx: number): void
|
||
(e: 'update-connection', idx: number, patch: any): void
|
||
}>()
|
||
|
||
const form = ref<{ from: string; to: string; effect: string }>({ from: '', to: '', effect: 'positive' })
|
||
|
||
/** 按层分组的节点选项 */
|
||
const groupedOptions = computed(() => {
|
||
const groups: { label: string; items: { key: string; name: string }[] }[] = []
|
||
const byLayer: Record<string, { key: string; name: string }[]> = {}
|
||
for (const o of props.nodeOptions) {
|
||
const ln = o.layerName || '其他'
|
||
if (!byLayer[ln]) byLayer[ln] = []
|
||
byLayer[ln].push({ key: o.key, name: o.name })
|
||
}
|
||
for (const label of Object.keys(byLayer)) {
|
||
groups.push({ label, items: byLayer[label] })
|
||
}
|
||
return groups
|
||
})
|
||
|
||
function nodeLabel(key: string): string {
|
||
const o = props.nodeOptions.find(n => n.key === key)
|
||
return o ? `${o.layerName} · ${o.name}` : key
|
||
}
|
||
|
||
function toggleEffect(idx: number) {
|
||
const conn = props.connections[idx]
|
||
if (!conn) return
|
||
emit('update-connection', idx, { effect: conn.effect === 'negative' ? 'positive' : 'negative' })
|
||
}
|
||
|
||
function onAdd() {
|
||
if (!form.value.from || !form.value.to) return
|
||
emit('add-connection', { from: form.value.from, to: form.value.to, effect: form.value.effect })
|
||
form.value = { from: '', to: '', effect: 'positive' }
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.cm-add-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px 0 14px;
|
||
border-bottom: 1px dashed #e4e7ed;
|
||
margin-bottom: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.cm-arrow {
|
||
color: #909399;
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
.cm-empty {
|
||
text-align: center;
|
||
color: #909399;
|
||
padding: 32px 0;
|
||
font-size: 13px;
|
||
}
|
||
.cm-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
}
|
||
.cm-table th {
|
||
text-align: left;
|
||
padding: 8px 10px;
|
||
background: #f5f7fa;
|
||
color: #606266;
|
||
font-weight: 600;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
}
|
||
.cm-table td {
|
||
padding: 8px 10px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
color: #303133;
|
||
max-width: 240px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.cm-table tr:hover td {
|
||
background: #f8faff;
|
||
}
|
||
.cm-idx { color: #909399; width: 32px; }
|
||
.cm-dir { color: #409eff; text-align: center; width: 36px; }
|
||
.cm-hint {
|
||
margin-top: 12px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
background: #f8f9fb;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
}
|
||
</style>
|