258 lines
5.7 KiB
Vue
258 lines
5.7 KiB
Vue
<template>
|
|
<div class="location-tree-node">
|
|
<!-- 节点头部 -->
|
|
<div class="node-header" :class="{ expanded: isExpanded }">
|
|
<div class="node-title">
|
|
<span
|
|
v-if="node.children && node.children.length > 0"
|
|
class="expand-icon"
|
|
@click="toggleExpand"
|
|
>
|
|
{{ isExpanded ? '▼' : '▶' }}
|
|
</span>
|
|
<span v-else class="expand-icon-placeholder" />
|
|
|
|
<span class="location-name">{{ node.name }}</span>
|
|
<el-tag type="info" size="small">{{ getDeviceCount(node.locationId) }}个设备</el-tag>
|
|
</div>
|
|
|
|
<div class="node-actions">
|
|
<el-button type="text" size="small" @click="$emit('edit', node)">编辑</el-button>
|
|
<el-button type="text" size="small" @click="$emit('add-device', node)">添加设备</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 设备列表 -->
|
|
<div v-if="isExpanded" class="device-list">
|
|
<div
|
|
v-for="device in getLocationDevices(node.locationId)"
|
|
:key="device.meterId"
|
|
class="device-item"
|
|
@click="$emit('device-click', device)"
|
|
>
|
|
<div class="device-info">
|
|
<span class="device-code">{{ device.meterCode }}</span>
|
|
<span class="device-energy">{{ getEnergyName(device.energyTypeId) }}</span>
|
|
<el-tag
|
|
:type="getStatusType(device.status)"
|
|
size="small"
|
|
>
|
|
{{ getStatusLabel(device.status) }}
|
|
</el-tag>
|
|
</div>
|
|
<div class="device-location">
|
|
{{ device.model }} / {{ device.manufacturer }}
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="getLocationDevices(node.locationId).length === 0" class="no-devices">
|
|
暂无设备
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 子节点 -->
|
|
<div v-if="isExpanded && node.children && node.children.length > 0" class="children-nodes">
|
|
<location-tree-node
|
|
v-for="child in node.children"
|
|
:key="child.locationId"
|
|
:node="child"
|
|
:meter-list="meterList"
|
|
:energy-type-list="energyTypeList"
|
|
@edit="$emit('edit', $event)"
|
|
@add-device="$emit('add-device', $event)"
|
|
@device-click="$emit('device-click', $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'LocationTreeNode',
|
|
props: {
|
|
node: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
meterList: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
energyTypeList: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
},
|
|
emits: ['edit', 'add-device', 'device-click'],
|
|
data() {
|
|
return {
|
|
isExpanded: false
|
|
};
|
|
},
|
|
methods: {
|
|
toggleExpand() {
|
|
this.isExpanded = !this.isExpanded;
|
|
},
|
|
|
|
getLocationDevices(locationId) {
|
|
return this.meterList.filter(m => m.locationId === locationId);
|
|
},
|
|
|
|
getDeviceCount(locationId) {
|
|
return this.getLocationDevices(locationId).length;
|
|
},
|
|
|
|
getEnergyName(energyTypeId) {
|
|
const energy = this.energyTypeList.find(item => item.energyTypeId === energyTypeId);
|
|
return energy ? energy.name : '-';
|
|
},
|
|
|
|
getStatusLabel(status) {
|
|
const statusMap = {
|
|
0: '在用',
|
|
1: '停用',
|
|
2: '维护'
|
|
};
|
|
return statusMap[status] || '未知';
|
|
},
|
|
|
|
getStatusType(status) {
|
|
const typeMap = {
|
|
0: 'success',
|
|
1: 'info',
|
|
2: 'warning'
|
|
};
|
|
return typeMap[status] || 'info';
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.location-tree-node {
|
|
margin-bottom: 8px;
|
|
border: 1px solid #ebeef5;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
|
|
.node-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
background: #f5f7fa;
|
|
cursor: pointer;
|
|
transition: background 0.3s ease;
|
|
|
|
&:hover {
|
|
background: #e8f4f8;
|
|
}
|
|
|
|
&.expanded {
|
|
background: #e8f4f8;
|
|
}
|
|
|
|
.node-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex: 1;
|
|
|
|
.expand-icon {
|
|
width: 20px;
|
|
height: 20px;
|
|
cursor: pointer;
|
|
transition: transform 0.3s ease;
|
|
color: #409eff;
|
|
|
|
&:hover {
|
|
transform: scale(1.2);
|
|
}
|
|
}
|
|
|
|
.expand-icon-placeholder {
|
|
width: 20px;
|
|
height: 20px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.location-name {
|
|
font-weight: 600;
|
|
color: #303133;
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
|
|
.node-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
}
|
|
|
|
.device-list {
|
|
padding: 12px 16px;
|
|
background: #fafafa;
|
|
border-top: 1px solid #ebeef5;
|
|
|
|
.device-item {
|
|
padding: 10px 12px;
|
|
background: #ffffff;
|
|
border-radius: 4px;
|
|
margin-bottom: 8px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
border: 1px solid #e8e8e8;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
&:hover {
|
|
border-color: #409eff;
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
|
transform: translateX(4px);
|
|
}
|
|
|
|
.device-info {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
margin-bottom: 4px;
|
|
|
|
.device-code {
|
|
font-weight: 600;
|
|
color: #303133;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.device-energy {
|
|
color: #909399;
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
|
|
.device-location {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
padding-left: 8px;
|
|
}
|
|
}
|
|
|
|
.no-devices {
|
|
text-align: center;
|
|
color: #909399;
|
|
padding: 20px 0;
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
|
|
.children-nodes {
|
|
padding: 12px 16px;
|
|
background: #fafafa;
|
|
border-top: 1px solid #ebeef5;
|
|
margin-left: 16px;
|
|
border-left: 2px solid #d9d9d9;
|
|
}
|
|
}
|
|
</style>
|