UPDATE: 优化选择被检设备组件
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-col :span="11">
|
||||
<el-card header-class="card-header" body-class="card-body" footer-class="card-footer">
|
||||
<template #header>
|
||||
<el-text size="large">{{ props.title }}</el-text>
|
||||
<el-text type="info" size="small">{{ statistics.checked }}/{{ statistics.total }}</el-text>
|
||||
<el-text size="large">{{ props.titles[0] }}</el-text>
|
||||
<el-text type="info" size="small">共 {{ statistics.total }} 项</el-text>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
@@ -14,11 +14,12 @@
|
||||
size="small"
|
||||
v-model="filter.checkAll"
|
||||
label="全选"
|
||||
@change="handleCheckAllChange"
|
||||
></el-checkbox>
|
||||
<el-input
|
||||
style="width: 82%; margin: 0 10px"
|
||||
size="small"
|
||||
v-model="filter.text"
|
||||
v-model="filter.leftText"
|
||||
:placeholder="props.filterPlaceholder"
|
||||
/>
|
||||
<el-dropdown size="small" @command="handleCommand">
|
||||
@@ -26,47 +27,62 @@
|
||||
<el-icon size="16"><Filter /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="">默认</el-dropdown-item>
|
||||
<el-dropdown-item command="manufacturer">设备厂家</el-dropdown-item>
|
||||
<el-dropdown-item command="cityName">所属地市</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
<el-radio-group v-model="filter.groupBy">
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="subName">
|
||||
<el-radio value="subName" size="small">所属电站</el-radio>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="cityName">
|
||||
<el-radio value="cityName" size="small">所属地市</el-radio>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="manufacturer">
|
||||
<el-radio value="manufacturer" size="small">设备厂家</el-radio>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-scrollbar ref="scrollbarRef" :height="props.height" style="margin-top: 20px">
|
||||
<el-scrollbar ref="leftScrollbarRef" :height="props.height" style="margin-top: 20px">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
ref="leftTreeRef"
|
||||
show-checkbox
|
||||
accordion
|
||||
default-expand-all
|
||||
:data="treeData"
|
||||
:data="leftTreeData"
|
||||
:default-checked-keys="defaultCheckedKeys"
|
||||
:default-expanded-keys="defaultCheckedKeys"
|
||||
:filter-node-method="filterNode"
|
||||
node-key="id"
|
||||
:style="{ padding: '0 25px' }"
|
||||
style="padding-left: 0; padding-right: 20px"
|
||||
@check-change="handleCheckChange"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty :image-size="80" description="暂无可选设备" />
|
||||
</template>
|
||||
<template #default="{ node, data }">
|
||||
<div v-if="data.id.startsWith('manufacturer')" style="display: flex; align-items: center">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
<div v-if="data.id.startsWith('subName')" style="display: flex; align-items: center">
|
||||
<el-icon style="margin-top: 2px"><Lightning /></el-icon>
|
||||
<span style="margin-left: 4px">{{ data.label }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="data.id.startsWith('manufacturer')"
|
||||
style="display: flex; align-items: center"
|
||||
>
|
||||
<el-icon style="margin-top: 2px"><OfficeBuilding /></el-icon>
|
||||
<span style="margin-left: 4px">{{ data.label }}</span>
|
||||
</div>
|
||||
<div v-else-if="data.id.startsWith('cityName')" style="display: flex; align-items: center">
|
||||
<el-icon><Location /></el-icon>
|
||||
<el-icon style="margin-top: 2px"><Location /></el-icon>
|
||||
<span style="margin-left: 4px">{{ data.label }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
style="flex: 1; display: flex; align-items: center; justify-content: space-between"
|
||||
>
|
||||
<span>
|
||||
<el-icon><Cpu /></el-icon>
|
||||
<span style="display: flex; align-items: center">
|
||||
<el-icon style="margin-top: 2px"><Cpu /></el-icon>
|
||||
<span v-if="node.level === 1" style="margin-left: 4px">
|
||||
{{ data.cityName + ' - ' + data.manufacturer + ' - ' + data.name }}
|
||||
</span>
|
||||
@@ -94,7 +110,7 @@
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
<el-icon>
|
||||
<el-icon style="margin-top: 2px; margin-right: 5px">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
@@ -109,11 +125,139 @@
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="11" :offset="1">
|
||||
<el-card header-class="card-header" body-class="card-body" footer-class="card-footer">
|
||||
<template #header>
|
||||
<el-text size="large">{{ props.titles[1] }}</el-text>
|
||||
<el-text type="info" size="small">{{ statistics.checked }} 项</el-text>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<el-button size="small" :disabled="props.disabled" @click="clearAll">清除</el-button>
|
||||
<el-input
|
||||
style="width: 80%; margin: 0 10px"
|
||||
size="small"
|
||||
v-model="filter.rightText"
|
||||
:placeholder="props.filterPlaceholder"
|
||||
/>
|
||||
<el-dropdown size="small" @command="handleCommand">
|
||||
<span class="el-dropdown-link">
|
||||
<el-icon size="16"><Filter /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-radio-group v-model="filter.groupBy">
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="subName">
|
||||
<el-radio value="subName" size="small">所属电站</el-radio>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="manufacturer">
|
||||
<el-radio value="manufacturer" size="small">设备厂家</el-radio>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="cityName">
|
||||
<el-radio value="cityName" size="small">所属地市</el-radio>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-scrollbar ref="rightScrollbarRef" :height="rightHeight" style="margin-top: 20px">
|
||||
<el-tree
|
||||
ref="rightTreeRef"
|
||||
accordion
|
||||
:data="rightTreeData"
|
||||
:filter-node-method="filterNode"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
style="padding-left: 0; padding-right: 15px"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty :image-size="80" description="暂未选择设备" />
|
||||
</template>
|
||||
<template #default="{ node, data }">
|
||||
<div v-if="data.id.startsWith('subName')" style="display: flex; align-items: center">
|
||||
<el-icon style="margin-top: 2px"><Lightning /></el-icon>
|
||||
<span style="margin-left: 4px">{{ data.label }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="data.id.startsWith('manufacturer')"
|
||||
style="display: flex; align-items: center"
|
||||
>
|
||||
<el-icon style="margin-top: 2px"><OfficeBuilding /></el-icon>
|
||||
<span style="margin-left: 4px">{{ data.label }}</span>
|
||||
</div>
|
||||
<div v-else-if="data.id.startsWith('cityName')" style="display: flex; align-items: center">
|
||||
<el-icon style="margin-top: 2px"><Location /></el-icon>
|
||||
<span style="margin-left: 4px">{{ data.label }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
style="flex: 1; display: flex; align-items: center; justify-content: space-between"
|
||||
>
|
||||
<span style="display: flex; align-items: center">
|
||||
<el-icon style="margin-top: 2px"><Cpu /></el-icon>
|
||||
<span v-if="node.level === 1" style="margin-left: 4px">
|
||||
{{ data.cityName + ' - ' + data.manufacturer + ' - ' + data.name }}
|
||||
</span>
|
||||
<span v-if="node.level === 2" style="margin-left: 4px">
|
||||
{{ data.name }}
|
||||
</span>
|
||||
</span>
|
||||
<span style="display: flex; align-items: center">
|
||||
<el-tooltip effect="light" placement="top">
|
||||
<template #content>
|
||||
<el-descriptions border size="small" title="被检设备详情">
|
||||
<el-descriptions-item label="设备名称">
|
||||
{{ data.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="设备厂家">
|
||||
{{ data.manufacturer }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属地市">
|
||||
{{ data.cityName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属供电公司">
|
||||
{{ data.gdName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属电站">
|
||||
{{ data.subName }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
<el-icon style="margin-top: 2px; margin-right: 5px">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-if="!data.disabled && !props.disabled"
|
||||
text
|
||||
circle
|
||||
@click="removeNode(data)"
|
||||
size="small"
|
||||
icon="Close"
|
||||
></el-button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script lang="ts" name="DevSelect" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import type { FilterNodeMethodFunction, ScrollbarInstance, TreeInstance } from 'element-plus'
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import type {
|
||||
FilterNodeMethodFunction,
|
||||
RenderContentContext,
|
||||
ScrollbarInstance,
|
||||
TreeInstance,
|
||||
TreeKey
|
||||
} from 'element-plus'
|
||||
import { Cpu, Lightning, Location, OfficeBuilding, Warning } from '@element-plus/icons-vue'
|
||||
|
||||
interface Tree {
|
||||
[key: string]: any
|
||||
@@ -122,10 +266,9 @@ interface Device {
|
||||
id: string
|
||||
name: string
|
||||
checked: boolean
|
||||
importFlag: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type Data = RenderContentContext['data']
|
||||
const modelValue = defineModel<string[]>({ default: [] })
|
||||
|
||||
const props = defineProps({
|
||||
@@ -137,9 +280,9 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 240
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
titles: {
|
||||
type: Array,
|
||||
default: () => ['', '']
|
||||
},
|
||||
filterPlaceholder: {
|
||||
type: String,
|
||||
@@ -150,13 +293,17 @@ const props = defineProps({
|
||||
default: false
|
||||
}
|
||||
})
|
||||
const treeRef = ref<TreeInstance>()
|
||||
const scrollbarRef = ref<ScrollbarInstance>()
|
||||
const treeData = ref<Tree[]>([])
|
||||
const leftTreeRef = ref<TreeInstance>()
|
||||
const rightTreeRef = ref<TreeInstance>()
|
||||
const leftScrollbarRef = ref<ScrollbarInstance>()
|
||||
const rightScrollbarRef = ref<ScrollbarInstance>()
|
||||
const leftTreeData = ref<Tree[]>([])
|
||||
const rightTreeData = ref<Tree[]>([])
|
||||
const defaultCheckedKeys = ref<string[]>([])
|
||||
const filter = ref({
|
||||
groupBy: '',
|
||||
text: '',
|
||||
groupBy: 'subName',
|
||||
leftText: '',
|
||||
rightText: '',
|
||||
checkAll: false
|
||||
})
|
||||
const statistics = ref({
|
||||
@@ -164,57 +311,78 @@ const statistics = ref({
|
||||
checked: 0
|
||||
})
|
||||
const disabledKeys = ref<string[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
if (props.data) {
|
||||
initTree(props.data)
|
||||
}
|
||||
})
|
||||
const rightHeight = computed(() => {
|
||||
if (!props.disabled) {
|
||||
return props.height + 45.2
|
||||
}
|
||||
return props.height
|
||||
})
|
||||
watch(
|
||||
() => props.data,
|
||||
(newVal: Device[]) => {
|
||||
if (newVal) {
|
||||
initTree(newVal)
|
||||
}
|
||||
initTree(newVal || [])
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => filter.value.checkAll,
|
||||
() => filter.value.leftText,
|
||||
val => {
|
||||
setCheckedStatus(val)
|
||||
if (val) {
|
||||
statistics.value.checked = statistics.value.total
|
||||
} else {
|
||||
statistics.value.checked = treeRef.value?.getCheckedNodes().length || 0
|
||||
}
|
||||
leftTreeRef.value!.filter(val)
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => filter.value.text,
|
||||
() => filter.value.rightText,
|
||||
val => {
|
||||
treeRef.value!.filter(val)
|
||||
rightTreeRef.value!.filter(val)
|
||||
}
|
||||
)
|
||||
|
||||
const initTree = (data: Device[]) => {
|
||||
// 禁用Key数据
|
||||
disabledKeys.value = data.filter(item => item.disabled).map(item => item.id)
|
||||
// 编辑逻辑
|
||||
if (disabledKeys.value.length > 0) {
|
||||
modelValue.value = []
|
||||
}
|
||||
treeData.value = convertToTree(data, filter.value.groupBy)
|
||||
// 左侧树数据
|
||||
leftTreeData.value = convertToTree(data, filter.value.groupBy)
|
||||
// 右侧树数据
|
||||
rightTreeData.value = convertToTree(
|
||||
data.filter(item => item.checked),
|
||||
filter.value.groupBy
|
||||
)
|
||||
defaultCheckedKeys.value = data.filter(item => item.checked).map(item => item.id)
|
||||
// 统计数据
|
||||
statistics.value.checked = defaultCheckedKeys.value.length
|
||||
statistics.value.total = data.length
|
||||
if (statistics.value.total > 0) {
|
||||
filter.value.checkAll = statistics.value.checked === statistics.value.total
|
||||
}
|
||||
}
|
||||
const setCheckedStatus = (checked: boolean) => {
|
||||
treeData.value.forEach(item => {
|
||||
if (!disabledKeys.value.includes(item.id)) {
|
||||
treeRef.value?.setChecked(item.id, checked, true)
|
||||
}
|
||||
|
||||
const handleCheckAllChange = () => {
|
||||
nextTick(() => {
|
||||
setCheckedStatus(filter.value.checkAll)
|
||||
})
|
||||
}
|
||||
const convertToTree = (data: any[], groupBy?: string | undefined) => {
|
||||
const setCheckedStatus = (checked: boolean) => {
|
||||
if (checked) {
|
||||
leftTreeRef.value?.setCheckedKeys(props.data.map(item => item.id) as TreeKey[])
|
||||
} else {
|
||||
const filterKeys = props.data
|
||||
.filter(item => !disabledKeys.value.includes(item.id))
|
||||
.map(item => item.id) as TreeKey[]
|
||||
filterKeys.forEach(key => {
|
||||
leftTreeRef.value?.setChecked(key, false, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
const convertToTree = (data: Device[], groupBy?: string | undefined) => {
|
||||
if (groupBy) {
|
||||
// 创建一个映射来存储每个分组
|
||||
const groupMap = new Map()
|
||||
@@ -268,23 +436,42 @@ const filterNode: FilterNodeMethodFunction = (value: string, data: Tree) => {
|
||||
}
|
||||
const handleCommand = (command: string) => {
|
||||
filter.value.groupBy = command
|
||||
const oldCheckedKeys = treeRef.value?.getCheckedKeys() || []
|
||||
treeData.value = convertToTree(props.data, filter.value.groupBy)
|
||||
treeRef.value?.setCheckedKeys(oldCheckedKeys)
|
||||
const oldCheckedKeys = leftTreeRef.value?.getCheckedKeys() || []
|
||||
leftTreeData.value = convertToTree(props.data, filter.value.groupBy)
|
||||
leftTreeRef.value?.setCheckedKeys(oldCheckedKeys)
|
||||
rightTreeData.value = convertToTree(
|
||||
props.data.filter(item => oldCheckedKeys.includes(item.id)),
|
||||
filter.value.groupBy
|
||||
)
|
||||
if (filter.value.checkAll) {
|
||||
setCheckedStatus(true)
|
||||
}
|
||||
scrollbarRef.value?.setScrollTop(0)
|
||||
leftScrollbarRef.value?.setScrollTop(0)
|
||||
rightScrollbarRef.value?.setScrollTop(0)
|
||||
}
|
||||
const handleCheckChange = () => {
|
||||
const checkedKeys = treeRef.value?.getCheckedKeys().filter(item => !item.toString().includes('_')) || []
|
||||
|
||||
const checkedKeys = leftTreeRef.value?.getCheckedKeys().filter(item => !item.toString().includes('_')) || []
|
||||
modelValue.value = checkedKeys
|
||||
.filter(key => !disabledKeys.value.includes(key.toString()))
|
||||
.map(key => key.toString())
|
||||
rightTreeData.value = convertToTree(
|
||||
props.data.filter(item => checkedKeys.includes(item.id)),
|
||||
filter.value.groupBy
|
||||
)
|
||||
statistics.value.checked = checkedKeys.length || 0
|
||||
filter.value.checkAll = statistics.value.checked === statistics.value.total
|
||||
}
|
||||
const clearAll = () => {
|
||||
if (statistics.value.checked > 0) {
|
||||
setCheckedStatus(false)
|
||||
}
|
||||
}
|
||||
const removeNode = (data: Data) => {
|
||||
nextTick(() => {
|
||||
leftTreeRef.value?.setChecked(data, false, true)
|
||||
rightTreeRef.value?.remove(data)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.card-header {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
>
|
||||
<el-form ref="dialogFormRef" :model="formContent" :rules="rules">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-col :span="9">
|
||||
<el-form-item :label-width="110" label="名称" prop="name">
|
||||
<el-input
|
||||
v-model="formContent.name"
|
||||
@@ -181,10 +181,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="15">
|
||||
<DevSelect
|
||||
v-model="formContent.devIds"
|
||||
title="被检设备列表"
|
||||
:titles="['被检设备列表', '已选被检设备列表']"
|
||||
filter-placeholder="请输入内容搜索"
|
||||
:data="devData"
|
||||
:height="230"
|
||||
@@ -414,7 +414,19 @@ const generateData = () => {
|
||||
i.disabled = allDisabled.value
|
||||
}
|
||||
})
|
||||
devData.value = allPqDevList
|
||||
// 排序逻辑
|
||||
devData.value = allPqDevList.sort((a, b) => {
|
||||
// 首先按 checked 排序(选中的在前)
|
||||
if (a.checked !== b.checked) {
|
||||
return a.checked ? -1 : 1
|
||||
}
|
||||
// 然后按 disabled 排序(未禁用的在前)
|
||||
if (a.disabled !== b.disabled) {
|
||||
return a.disabled ? 1 : -1
|
||||
}
|
||||
// 最后按名称排序(升序)
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
}
|
||||
|
||||
function useMetaInfo() {
|
||||
|
||||
Reference in New Issue
Block a user