|
@@ -0,0 +1,177 @@
|
|
|
+<template>
|
|
|
+ <div class="card filter">
|
|
|
+ <h4 v-if="title" class="title sle">
|
|
|
+ {{ title }}
|
|
|
+ <el-tooltip effect="dark" content="数量仅展示直属组织即当前节点直接下层,不包含其后代" placement="top">
|
|
|
+ <i :class="'iconfont icon-yiwen'" />
|
|
|
+ </el-tooltip>
|
|
|
+ </h4>
|
|
|
+ <el-input v-model="filterText" placeholder="输入关键字进行过滤" clearable />
|
|
|
+ <el-scrollbar :style="{ height: title ? `calc(100% - 95px)` : `calc(100% - 56px)` }">
|
|
|
+ <el-tree
|
|
|
+ v-if="reload"
|
|
|
+ ref="treeRef"
|
|
|
+ default-expand-all
|
|
|
+ :node-key="id"
|
|
|
+ :data="multiple ? treeData : treeAllData"
|
|
|
+ :show-checkbox="multiple"
|
|
|
+ :check-strictly="checkStrictly"
|
|
|
+ :current-node-key="!multiple ? selected : ''"
|
|
|
+ :highlight-current="!multiple"
|
|
|
+ :expand-on-click-node="false"
|
|
|
+ :check-on-click-node="multiple"
|
|
|
+ :props="defaultProps"
|
|
|
+ :filter-node-method="filterNode"
|
|
|
+ :default-checked-keys="multiple ? selected : []"
|
|
|
+ @node-click="handleNodeClick"
|
|
|
+ v-loading="loading">
|
|
|
+ <template #default="scope">
|
|
|
+ <span class="el-tree-node__label">
|
|
|
+ <slot :row="scope">
|
|
|
+ {{ scope.node.label }}
|
|
|
+ <!-- <span class="el-tree-node__label info-count">
|
|
|
+ <slot :row="scope"> ({{ scope.data.userTotal }}) </slot>
|
|
|
+ </span> -->
|
|
|
+ </slot>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-tree>
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="OrgTree">
|
|
|
+import { ElTree } from 'element-plus'
|
|
|
+
|
|
|
+// 接收父组件参数并设置默认值
|
|
|
+interface OrgTreeProps {
|
|
|
+ requestApi?: (data?: any) => Promise<any> // 请求分类数据的 api ==> 非必传
|
|
|
+ data?: { [key: string]: any }[] // 分类数据,如果有分类数据,则不会执行 api 请求 ==> 非必传
|
|
|
+ title?: string // DeptTree 标题 ==> 非必传
|
|
|
+ id?: string // 选择的id ==> 非必传,默认为 “id”
|
|
|
+ label?: string // 显示的label ==> 非必传,默认为 “label”
|
|
|
+ multiple?: boolean // 是否为多选 ==> 非必传,默认为 false
|
|
|
+ defaultValue?: any // 默认选中的值 ==> 非必传
|
|
|
+ checkStrictly?: boolean
|
|
|
+}
|
|
|
+const loading = ref(false)
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<OrgTreeProps>(), {
|
|
|
+ id: 'id',
|
|
|
+ label: 'label',
|
|
|
+ multiple: false,
|
|
|
+ checkStrictly: false
|
|
|
+})
|
|
|
+
|
|
|
+const defaultProps = {
|
|
|
+ children: 'children',
|
|
|
+ label: props.label
|
|
|
+}
|
|
|
+
|
|
|
+const treeRef = ref<InstanceType<typeof ElTree>>()
|
|
|
+const treeData = ref<{ [key: string]: any }[]>([])
|
|
|
+const treeAllData = ref<{ [key: string]: any }[]>([])
|
|
|
+
|
|
|
+const selected = ref()
|
|
|
+const setSelected = () => {
|
|
|
+ if (props.multiple) selected.value = Array.isArray(props.defaultValue) ? props.defaultValue : [props.defaultValue]
|
|
|
+ else selected.value = props.defaultValue
|
|
|
+}
|
|
|
+
|
|
|
+onBeforeMount(() => {
|
|
|
+ getTreeApi()
|
|
|
+})
|
|
|
+
|
|
|
+const getTreeApi = async () => {
|
|
|
+ loading.value = true
|
|
|
+ setSelected()
|
|
|
+ if (props.requestApi) {
|
|
|
+ await delayLoading(200)
|
|
|
+ const { data } = await props.requestApi!()
|
|
|
+ loading.value = false
|
|
|
+ if (typeof data === 'object' && Object.keys(data).length === 0) {
|
|
|
+ treeData.value = []
|
|
|
+ treeAllData.value = []
|
|
|
+ } else {
|
|
|
+ treeData.value = data
|
|
|
+ treeAllData.value = data
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用 nextTick 防止打包后赋值不生效,开发环境是正常的
|
|
|
+watch(
|
|
|
+ () => props.defaultValue,
|
|
|
+ () => nextTick(() => setSelected()),
|
|
|
+ { deep: true, immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.data,
|
|
|
+ () => {
|
|
|
+ if (props.data?.length) {
|
|
|
+ treeData.value = props.data
|
|
|
+ treeAllData.value = props.data
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { deep: true, immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+const filterText = ref('')
|
|
|
+watch(filterText, val => {
|
|
|
+ treeRef.value!.filter(val)
|
|
|
+})
|
|
|
+
|
|
|
+// 过滤
|
|
|
+const filterNode = (value: string, _data: { [key: string]: any }, node: any) => {
|
|
|
+ if (!value) return true
|
|
|
+ let parentNode = node.parent,
|
|
|
+ labels = [node.label],
|
|
|
+ level = 1
|
|
|
+ while (level < node.level) {
|
|
|
+ labels = [...labels, parentNode.label]
|
|
|
+ parentNode = parentNode.parent
|
|
|
+ level++
|
|
|
+ }
|
|
|
+ return labels.some(label => label.indexOf(value) !== -1)
|
|
|
+}
|
|
|
+
|
|
|
+// emit
|
|
|
+const emit = defineEmits<{
|
|
|
+ change: [value: any]
|
|
|
+}>()
|
|
|
+
|
|
|
+// 单选
|
|
|
+const handleNodeClick = (data: { [key: string]: any }) => {
|
|
|
+ if (props.multiple) return
|
|
|
+ console.log('data[props.id]', data[props.id])
|
|
|
+ emit('change', data[props.id])
|
|
|
+}
|
|
|
+
|
|
|
+const reload = ref(true)
|
|
|
+const refresh = () => {
|
|
|
+ reload.value = false
|
|
|
+ nextTick(() => {
|
|
|
+ getTreeApi()
|
|
|
+ reload.value = true
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const delayLoading = async (loadingTime: number | undefined) => {
|
|
|
+ const defaultLoadingTime = 0 // 默认的 loading 延迟时间为 1 秒
|
|
|
+ let actualLoadingTime = loadingTime
|
|
|
+ // 判断 loadingTime 是否为特殊参数
|
|
|
+ if (typeof loadingTime === 'undefined' || loadingTime === null || loadingTime === -1) {
|
|
|
+ actualLoadingTime = defaultLoadingTime
|
|
|
+ }
|
|
|
+ // 等待 loading 延迟时间
|
|
|
+ await new Promise(resolve => setTimeout(resolve, actualLoadingTime))
|
|
|
+}
|
|
|
+
|
|
|
+// 暴露给父组件使用
|
|
|
+defineExpose({ treeData, treeAllData, treeRef, refresh })
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+@use './index';
|
|
|
+</style>
|