<template>
|
<el-dialog title="选择产品结构树设备(可多选)" :visible.sync="localVisible" width="680px" top="4vh" custom-class="product-tree-dialog" append-to-body :lock-scroll="true" :close-on-click-modal="false" @open="load">
|
<div class="picker-content">
|
<el-input v-model="keyword" placeholder="输入名称或编码筛选" clearable prefix-icon="el-icon-search" style="margin-bottom:10px"></el-input>
|
<div class="tree-scroll">
|
<el-tree
|
ref="tree"
|
:data="tree"
|
node-key="id"
|
default-expand-all
|
show-checkbox
|
check-strictly
|
:filter-node-method="filterNode"
|
:props="treeProps"
|
@check="handleCheck"
|
>
|
<span class="tree-node" slot-scope="{ data }">
|
<span>{{ data.name }}</span>
|
<small v-if="data.code">{{ data.code }}</small>
|
</span>
|
</el-tree>
|
</div>
|
<div class="selected-panel">
|
<div class="picker-count">已选择 {{ checkedCount }} 台设备</div>
|
<div v-if="selectedDevices.length" class="selected-list">
|
<el-tag
|
v-for="device in selectedDevices"
|
:key="device.id"
|
closable
|
size="small"
|
@close="removeSelected(device)"
|
>
|
<span v-if="device.xNo">{{ device.xNo }} / </span>{{ device.name }}
|
</el-tag>
|
</div>
|
</div>
|
</div>
|
<template slot="footer">
|
<el-button @click="localVisible = false">取消</el-button>
|
<el-button type="primary" :disabled="checkedCount === 0" @click="confirm">确认选择</el-button>
|
</template>
|
</el-dialog>
|
</template>
|
<script>
|
import api from '@/api'
|
export default {
|
name: 'ProductTreePicker',
|
props: {
|
visible: { type: Boolean, default: false },
|
xNo: { type: String, default: '' },
|
selectedIds: { type: Array, default: () => [] }
|
},
|
data() {
|
return { localVisible: false, keyword: '', tree: [], checkedCount: 0, selectedDevices: [] }
|
},
|
computed: {
|
treeProps() {
|
return {
|
label: 'name',
|
children: 'children',
|
disabled: data => data.nodeType !== 'DEVICE'
|
}
|
}
|
},
|
watch: {
|
visible(value) {
|
this.localVisible = value
|
},
|
localVisible(value) { this.$emit('update:visible', value) },
|
keyword(value) { this.$refs.tree && this.$refs.tree.filter(value) },
|
xNo() { if (this.localVisible) this.load() },
|
selectedIds() { if (this.localVisible) this.$nextTick(this.restoreChecked) }
|
},
|
methods: {
|
load() {
|
const lockedXNo = String(this.xNo || '').trim()
|
api.get('/system/tree', { params: { type: 'product' } }).then(res => {
|
const source = res.data || []
|
this.tree = lockedXNo ? this.filterTreeByXNo(source, lockedXNo) : source
|
this.keyword = ''
|
this.$nextTick(this.restoreChecked)
|
})
|
},
|
filterTreeByXNo(nodes, xNo) {
|
const result = []
|
;(nodes || []).forEach(node => {
|
if (Number(node.nodeLevel) === 2) {
|
if (String(node.name || '').trim() === xNo) result.push(node)
|
return
|
}
|
const children = this.filterTreeByXNo(node.children || [], xNo)
|
if (children.length || node.nodeType === 'DEVICE') {
|
result.push(children.length ? { ...node, children } : node)
|
}
|
})
|
return result
|
},
|
restoreChecked() {
|
if (!this.$refs.tree) return
|
const ids = (this.selectedIds || []).map(String).filter(Boolean)
|
this.$refs.tree.setCheckedKeys(ids)
|
this.syncSelected()
|
},
|
handleCheck(node) {
|
const tree = this.$refs.tree
|
const treeNode = tree && tree.getNode(node.id)
|
if (treeNode && treeNode.checked) {
|
const selected = this.deviceNodes().map(item => this.decorateNode(item))
|
const xNos = Array.from(new Set(selected.map(item => item.xNo).filter(Boolean)))
|
if (xNos.length > 1) {
|
tree.setChecked(node.id, false, true)
|
this.$message.warning('同一故障只能选择同一个X号下的设备')
|
}
|
}
|
this.syncSelected()
|
},
|
syncSelected() {
|
const selected = this.deviceNodes().map(node => this.decorateNode(node))
|
this.selectedDevices = selected
|
this.checkedCount = selected.length
|
},
|
decorateNode(node) {
|
const pathNodes = []
|
let current = this.$refs.tree.getNode(node.id)
|
while (current && current.data) {
|
pathNodes.unshift(current.data)
|
current = current.parent
|
}
|
const xNode = pathNodes.find(item => item.nodeLevel === 2)
|
const parentPathNames = pathNodes
|
.slice(0, -1)
|
.filter(item => item.nodeLevel > 2)
|
.map(item => item.name)
|
return { ...node, pathNames: pathNodes.map(item => item.name), parentPathNames, xNo: xNode ? xNode.name : '' }
|
},
|
removeSelected(device) {
|
if (!this.$refs.tree) return
|
this.$refs.tree.setChecked(device.id, false, true)
|
this.syncSelected()
|
},
|
deviceNodes() {
|
if (!this.$refs.tree) return []
|
return this.$refs.tree.getCheckedNodes().filter(node => node.nodeType === 'DEVICE')
|
},
|
filterNode(value, data) {
|
if (!value) return true
|
return (data.name || '').includes(value) || (data.code || '').includes(value)
|
},
|
confirm() {
|
const nodes = this.deviceNodes()
|
if (!nodes.length) return this.$message.warning('请选择设备')
|
const selected = nodes.map(node => this.decorateNode(node))
|
const xNos = Array.from(new Set(selected.map(item => item.xNo).filter(Boolean)))
|
if (xNos.length > 1) {
|
return this.$message.warning('同一故障只能选择同一个X号下的设备')
|
}
|
this.$emit('select', selected)
|
this.localVisible = false
|
}
|
}
|
}
|
</script>
|
<style>
|
.product-tree-dialog {
|
display: flex;
|
flex-direction: column;
|
max-height: calc(100vh - 32px);
|
overflow: hidden;
|
}
|
.product-tree-dialog .el-dialog__header,
|
.product-tree-dialog .el-dialog__footer {
|
flex: 0 0 auto;
|
}
|
.product-tree-dialog .el-dialog__body {
|
display: flex;
|
flex: 1 1 auto;
|
min-height: 0;
|
padding-bottom: 0;
|
}
|
.picker-content {
|
display: flex;
|
flex-direction: column;
|
width: 100%;
|
height: 62vh;
|
min-height: 320px;
|
max-height: 540px;
|
}
|
.tree-scroll {
|
flex: 1 1 auto;
|
min-height: 180px;
|
overflow: auto;
|
padding: 6px;
|
border: 1px solid #ebeef5;
|
border-radius: 4px;
|
}
|
.selected-panel {
|
flex: 0 0 auto;
|
margin-top: 10px;
|
padding-top: 10px;
|
border-top: 1px solid #ebeef5;
|
}
|
.picker-count {
|
color: #6d7f91;
|
font-size: 13px;
|
line-height: 24px;
|
}
|
.selected-list {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 6px;
|
max-height: 76px;
|
overflow: auto;
|
margin-top: 6px;
|
}
|
.selected-list .el-tag {
|
max-width: 100%;
|
height: auto;
|
white-space: normal;
|
line-height: 20px;
|
}
|
.tree-node { display: flex; align-items: center; gap: 8px; }
|
.tree-node small { color: #95a2b0; }
|
</style>
|