赞
踩
1.组件封装
- <!-- 树状选择器 -->
- <script lang="ts">
- import type { TreeNode } from 'element-plus/es/components/tree-v2/src/types'
- import type { TreeNodeData } from 'element-plus/es/components/tree/src/tree.type'
- import type { PropType } from 'vue'
- import { defineComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue'
- interface PropsIter {
- value: string
- label: string
- children: string
- disabled?: boolean
- }
- const TreeProps: PropsIter = {
- value: 'id',
- label: 'name',
- children: 'children',
- }
- interface TreeIter {
- id: string
- label: string
- children?: TreeIter[]
- }
- export default defineComponent({
- props: {
- // 组件绑定的options
- options: {
- type: Array as PropType<TreeIter[]>,
- required: true,
- },
- // 配置选项
- keyProps: Object as PropType<PropsIter>,
- // 双向绑定值
- modelValue: [String, Number],
- // 组件样式宽
- width: {
- type: String,
- default: '240px',
- },
- // 空占位字符
- placeholder: String,
- },
- emits: ['update:modelValue'],
- setup(props, { emit }) {
- // 解决 props道具变异
- const { modelValue } = toRefs(props)
- const select: { value: string | number | undefined; currentNodeLabel: string | number | undefined; currentNodeKey: string | number | undefined } = reactive({
- value: modelValue.value,
- currentNodeKey: '',
- currentNodeLabel: '',
- })
- const treeSelect = ref<HTMLElement | null>(null)
- const blur = ref<HTMLElement | null>()
- const nodeClick = (data: TreeNodeData, node: TreeNode) => {
- select.currentNodeKey = data.id
- select.currentNodeLabel = data.label || data.name
- select.value = data.id
- emit('update:modelValue', select.value);
- // 关闭下拉框
- (treeSelect.value as any).handleClose()
- nextTick(() => {
- (treeSelect.value as any).handleClose()
- })
- }
- // 筛选方法
- const treeV2: any = ref<HTMLElement | null>(null)
- const selectFilter = (query: string) => {
- treeV2.value.filter(query)
- }
- // ztree-v2 筛选方法
- const treeFilter = (query: string, node: TreeNode) => {
- return node.label?.indexOf(query) !== -1
- }
- // 直接清空选择数据
- const clearSelected = () => {
- select.currentNodeKey = ''
- select.currentNodeLabel = ''
- select.value = ''
- emit('update:modelValue', undefined)
- }
- // setCurrent通过select.value 设置下拉选择tree 显示绑定的v-model值
- // 可能存在问题:当动态v-model赋值时 options的数据还没有加载完成就会失效,下拉选择时会警告 placeholder
- const setCurrent = () => {
- select.currentNodeKey = select.value
- treeV2.value.setCurrentKey(select.value)
- const data: TreeNodeData | undefined = treeV2.value.getCurrentNode(select.value)
- select.currentNodeLabel = data?.label || data?.name
- }
- // 监听外部清空数据源 清空组件数据
- watch(modelValue, (v) => {
- if (v === undefined && select.currentNodeKey !== '') {
- clearSelected()
- }
- // 动态赋值
- if (v) {
- select.value = v
- setCurrent()
- }
- })
- // 回显数据
- onMounted(async () => {
- await nextTick()
- if (select.value) {
- setCurrent()
- }
- })
- return {
- treeSelect,
- treeV2,
- TreeProps,
- ...toRefs(select),
- nodeClick,
- selectFilter,
- treeFilter,
- clearSelected,
- }
- },
- })
- </script>
-
- <template>
- <div class="tree_box" :style="width && { width: width.includes('px') ? width : width }">
- <el-select
- ref="treeSelect" v-model="value" clearable filterable :placeholder="placeholder || '请选择'"
- :filter-method="selectFilter" @clear="clearSelected"
- >
- <el-option :value="currentNodeKey" :label="currentNodeLabel">
- <el-tree-v2
- id="tree_v2" ref="treeV2" :data="options" :props="keyProps || TreeProps"
- :current-node-key="currentNodeKey" default-expand-all :expand-on-click-node="false"
- :filter-method="treeFilter" @node-click="nodeClick"
- />
- </el-option>
- </el-select>
- </div>
- </template>
-
- <style lang="scss" scoped>
- .tree_box {
- width: 214px;
- }
-
- .el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
- height: auto;
- max-height: 274px;
- padding: 0;
- overflow: hidden;
- overflow-y: auto;
- }
-
- .el-select-dropdown__item.selected {
- font-weight: normal;
- }
-
- ul li :deep(.el-tree .el-tree-node__content) {
- height: auto;
- padding: 0 20px;
- }
-
- .el-tree-node__label {
- font-weight: normal;
- }
-
- .el-tree :deep(.is-current .el-tree-node__label) {
- color: #409eff;
- font-weight: 700;
- }
-
- .el-tree :deep(.is-current .el-tree-node__children .el-tree-node__label) {
- color: #606266;
- font-weight: normal;
- }
-
- .selectInput {
- padding: 0 5px;
- box-sizing: border-box;
- }
-
- .el-select {
- width: 100% !important;
- }
- </style>
-
2.组件使用
- <com-tree-select
- v-model="ruleForm.mesureDept" :options="useDeptList as any" placeholder="使用部门"
- :tree-props="deptProps"
- />
3.数据定义
- const deptProps = reactive({
- parent: 'pid', value: 'id', label: 'name', children: 'children',
- })
- const useDeptList = ref<deptType[]>([]) // 使用部门列表
-
- // 获取部门列表
- getDeptTreeList().then((res) => {
- // 转成树结构
- useDeptList.value = toTreeList(res.data, '0', true)
- })
4.转树结构方法
- // 数据结构转换工具
- // 定义数组项的数据类型,包含id、name、parentId基本属性
- interface ArrayItem {
- pid: string
- id: string
- name?: string
- }
- // 定义树节点的数据类型,包含id、name、可能存在的子节点
- interface TreeNode {
- id: string
- name?: string
- pid: string
- children?: TreeNode[] // 叶子节点没有子节点
- }
- /**
- * 判断是否有转树的必要
- * @param plainList 平行数据列表
- * @param id 祖宗id
- * @returns {boolean} 有返回true,无返回false
- */
- export function judgeTree(plainList: ArrayItem[], id?: '0') {
- if (plainList && plainList.length > 0) {
- let flag = false // 是否需要转成树结构
- const pid = id
- for (const item of plainList) {
- if (item.pid !== pid) { // 只要有一个元素的pid没有指向第一个元素的父id,认为有必要转换成树,否则认为无必要
- flag = true
- break
- }
- }
- return flag
- }
- else { return false }
- }
-
- /**
- * 平面数据数据转树结构
- * @param plainList 平行数据列表
- * @param id 祖宗id
- * @param isSelect 是否是下拉需要顶级的树
- * @returns {*}
- */
- export function toTreeList<T extends ArrayItem>(plainList: T[], rootId = '0', isSelect = false): T[] {
- const pid = findPid(plainList)
- if (pid.length > 1) { // 如果有多个pid,直接返回列表, 不去构造树
- return plainList
- }
- else {
- const tree = cleanChildren(buildTree<T>(plainList, rootId, isSelect))
- return tree
- }
- }
- // 构建树
- /**
- *
- * @param plainList 待转换数组
- * @param id 父节点
- * @param isSelect 是否是下拉框所使用的树
- * @returns 树节点列表
- */
- function buildTree<T extends TreeNode>(plainList: T[], id = '0', isSelect = false): T[] | [] {
- // 递归函数
- const fa = (parentId: string): Array<T> | [] => {
- const temp = []
- for (let i = 0; i < plainList.length; i++) {
- const n: TreeNode = { ...plainList[i] }
- const id = `${n.id}`
- const pid = `${n.pid}`
- if (pid === parentId) {
- n.children = fa(id)
- temp.push(n)
- }
- }
- return temp as T[]
- }
- // 如果是下拉框需要使用的树,首先寻找顶级,将顶级也放入列表
- if (isSelect) {
- let flag = 1
- const list = []
- if (Array.isArray(plainList)) {
- for (const item of plainList) {
- const n: T = { ...item }
- const nid = `${n.id}`
- if (nid === id) {
- n.children = fa(id)
- flag = 0
- list.push(n)
- return list
- }
- else {
- continue
- }
- }
- }
-
- if (flag === 1) { // 没有找到父级,按原流程走
- return fa(id)
- }
- else {
- return []
- }
- }
- else {
- return fa(id)
- }
- }
-
- // 清除children为空列表的children项
- function cleanChildren<T extends TreeNode>(data: T[]): T[] {
- const fa = (list: TreeNode[]): T[] => {
- list.map((e) => {
- if (e && e.children && e.children.length) {
- fa(e.children)
- }
- else {
- delete e.children
- }
- return e
- })
- return list as T[]
- }
- return fa(data)
- }
- /**
- *
- * @param plainList 寻找列表中的父id
- * @returns 父id列表
- */
- function findPid(plainList: Array<ArrayItem>): Array<string> {
- const pidList = new Set<string>()
- if (plainList) {
- for (const item of plainList) { // 1.添加所有的父id
- pidList.add(item.pid)
- }
- for (const item of plainList) { // 2.删除所有的子id
- if (pidList.has(item.id)) {
- pidList.delete(item.id)
- }
- }
- const arr = Array.from(pidList) // 剩下的就是最终的父节点
- return arr
- }
- else {
- return []
- }
- }
-
- // 从树列表中删除指定元素
- export function deleteItem(list: Array<TreeNode>, des: TreeNode) {
- const del = (list: Array<TreeNode>, item: TreeNode) => {
- for (let i = 0; i < list.length; i++) {
- if (list[i].id === item.id) {
- list.splice(i, 1)
- return
- }
- else { // 遍历子孙,继续递归寻找
- if (list[i].children && list[i].children!.length > 0) {
- del(list[i].children!, item)
- }
- }
- }
- }
- del(list, des)
- }
-
- interface treeItem {
- id: string
- open: string | boolean
- checked: string | boolean
- }
- /**
- *获取列表中的展开项和选中项
- * @param plainList
- * @param id
- * @returns{展开项, 选中项}
- */
- export function getShowItem(plainList: treeItem[]): { expandList: string[]; openedList: string[] } {
- const expandList = []
- const openedList = []
- for (let i = 0; i < plainList.length; i++) {
- if (plainList[i].open === 'true' || plainList[i].open === true) {
- expandList.push(plainList[i].id)
- }
- if (plainList[i].checked === 'true' || plainList[i].checked === true) {
- openedList.push(plainList[i].id)
- }
- }
- return { expandList, openedList }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。