Gaokun Wang 1 mesiac pred
rodič
commit
bb82d19314

+ 46 - 0
src/api/interface/system/org.ts

@@ -0,0 +1,46 @@
+/**
+ * 部门查询参数
+ */
+export interface DeptQuery extends PageQuery {
+  deptName?: string
+  status?: number
+}
+
+/**
+ * 部门类型
+ */
+export interface DeptVO extends BaseEntity {
+  id: number | string
+  parentName: string
+  parentId: number | string
+  children: DeptVO[]
+  deptId: number | string
+  deptName: string
+  orderNum: number
+  leader: string
+  phone: string
+  email: string
+  status: string
+  delFlag: string
+  ancestors: string
+  menuId: string | number
+}
+
+/**
+ * 部门表单类型
+ */
+export interface DeptForm {
+  parentName?: string
+  parentId?: number | string
+  children?: DeptForm[]
+  deptId?: number | string
+  deptName?: string
+  orderNum?: number
+  leader?: string
+  phone?: string
+  email?: string
+  status?: string
+  version?: number
+  delFlag?: string
+  ancestors?: string
+}

+ 13 - 0
src/api/module/system/org.ts

@@ -0,0 +1,13 @@
+import http from '@/axios'
+import { DeptVO } from '@/api/interface/system/org'
+
+class OrgApi {
+  /**
+   * @name 查询组织树结构
+   * @returns returns
+   */
+  static treeRole = (): Promise<ResultData<any>> => {
+    return http.get<DeptVO>({ url: '/system/org/tree' })
+  }
+}
+export default OrgApi

+ 39 - 0
src/components/TreeFilter/index.scss

@@ -0,0 +1,39 @@
+.filter {
+  box-sizing: border-box;
+  width: 220px;
+  height: 100%;
+  padding: 18px;
+  margin-right: 10px;
+  border-right: 1px solid var(--border-color);
+  .title {
+    margin: 0 0 15px;
+    font-size: 18px;
+    font-weight: bold;
+    color: var(--el-color-info-dark-2);
+    letter-spacing: 0.5px;
+  }
+  .el-input {
+    margin: 0 0 15px;
+  }
+  .el-scrollbar {
+    :deep(.el-tree) {
+      height: 80%;
+      overflow: auto;
+      .el-tree-node__content {
+        height: 33px;
+      }
+    }
+    :deep(.el-tree--highlight-current) {
+      .el-tree-node.is-current > .el-tree-node__content {
+        background-color: var(--el-color-primary);
+        .el-tree-node__label,
+        .el-tree-node__expand-icon {
+          color: white;
+        }
+        .is-leaf {
+          color: transparent;
+        }
+      }
+    }
+  }
+}

+ 177 - 0
src/components/TreeFilter/index.vue

@@ -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>

+ 3 - 0
src/types/auto-components.d.ts

@@ -32,6 +32,7 @@ declare module 'vue' {
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSpace: typeof import('element-plus/es')['ElSpace']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
@@ -39,6 +40,7 @@ declare module 'vue' {
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElTree: typeof import('element-plus/es')['ElTree']
     Grid: typeof import('./../components/Grid/index.vue')['default']
     GridItem: typeof import('./../components/Grid/GridItem.vue')['default']
     HelloWorld: typeof import('./../components/HelloWorld.vue')['default']
@@ -50,6 +52,7 @@ declare module 'vue' {
     SearchForm: typeof import('./../components/SearchForm/index.vue')['default']
     SearchFormItem: typeof import('./../components/SearchForm/SearchFormItem.vue')['default']
     TableColumn: typeof import('./../components/ProTable/TableColumn.vue')['default']
+    TreeFilter: typeof import('./../components/TreeFilter/index.vue')['default']
   }
   export interface GlobalDirectives {
     vLoading: typeof import('element-plus/es')['ElLoadingDirective']

+ 57 - 0
src/views/demo/index.vue

@@ -0,0 +1,57 @@
+<template>
+  <div class="table-box">
+    <ProTable
+      ref="proTableRef"
+      title="角色列表"
+      row-key="roleId"
+      :indent="20"
+      :columns="columns"
+      :search-columns="searchColumns"
+      :request-api="getTableList">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" icon="CirclePlus"> 新增角色 </el-button>
+        <el-button type="danger" icon="Delete" plain :disabled="!scope.isSelected"> 批量删除角色 </el-button>
+      </template>
+
+      <template #operation="{ row }">
+        <el-button type="primary" link icon="Lock"> 权限 </el-button>
+        <el-button type="primary" link icon="EditPen"> 编辑 </el-button>
+        <el-button v-if="row.id !== 1" type="primary" link icon="Delete"> 删除 </el-button>
+      </template>
+    </ProTable>
+  </div>
+</template>
+<script lang="tsx" setup name="Home">
+import { RoleInfo, RoleQuery } from '@/api/interface/system/role'
+import RoleApi from '@/api/module/system/role'
+import { ColumnProps, SearchProps } from '@/components/ProTable/interface'
+
+// 表格配置项
+const columns: ColumnProps<RoleInfo>[] = [
+  { type: 'selection', width: 60, selectable: row => row.isLock == '1' },
+  { prop: 'roleId', label: '编号', width: 80 },
+  { prop: 'name', label: '角色名称' },
+  { prop: 'code', tag: true, label: '权限标识' },
+  { prop: 'remark', label: '备注' },
+  { prop: 'createByName', label: '创建人' },
+  { prop: 'createTime', label: '创建时间' },
+  { prop: 'updateTime', label: '修改时间' },
+  { prop: 'operation', label: '操作', width: 250, fixed: 'right' }
+]
+
+// 表格配置项
+const searchColumns: SearchProps[] = [
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'code', label: '标识', el: 'input' }
+]
+
+// 获取table列表
+const getTableList = (params: RoleQuery) => RoleApi.pageRoleApi(params)
+</script>

+ 62 - 0
src/views/demo/tree.vue

@@ -0,0 +1,62 @@
+<template>
+  <div class="main-box">
+    <TreeFilter title="组织架构" :request-api="OrgApi.treeRole" label="name" :default-value="'0'" />
+
+    <div class="table-box">
+      <ProTable
+        ref="proTableRef"
+        title="角色列表"
+        row-key="roleId"
+        :indent="20"
+        :columns="columns"
+        :search-columns="searchColumns"
+        :request-api="getTableList">
+        <!-- 表格 header 按钮 -->
+        <template #tableHeader="scope">
+          <el-button type="primary" icon="CirclePlus"> 新增角色 </el-button>
+          <el-button type="danger" icon="Delete" plain :disabled="!scope.isSelected"> 批量删除角色 </el-button>
+        </template>
+
+        <template #operation="{ row }">
+          <el-button type="primary" link icon="Lock"> 权限 </el-button>
+          <el-button type="primary" link icon="EditPen"> 编辑 </el-button>
+          <el-button v-if="row.id !== 1" type="primary" link icon="Delete"> 删除 </el-button>
+        </template>
+      </ProTable>
+    </div>
+  </div>
+</template>
+<script lang="tsx" setup name="Home">
+import { RoleInfo, RoleQuery } from '@/api/interface/system/role'
+import OrgApi from '@/api/module/system/org'
+import RoleApi from '@/api/module/system/role'
+import { ColumnProps, SearchProps } from '@/components/ProTable/interface'
+
+// 表格配置项
+const columns: ColumnProps<RoleInfo>[] = [
+  { type: 'selection', width: 60, selectable: row => row.isLock == '1' },
+  { prop: 'roleId', label: '编号', width: 80 },
+  { prop: 'name', label: '角色名称' },
+  { prop: 'code', tag: true, label: '权限标识' },
+  { prop: 'remark', label: '备注' },
+  { prop: 'createByName', label: '创建人' },
+  { prop: 'createTime', label: '创建时间' },
+  { prop: 'updateTime', label: '修改时间' },
+  { prop: 'operation', label: '操作', width: 250, fixed: 'right' }
+]
+
+// 表格配置项
+const searchColumns: SearchProps[] = [
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'name', label: '角色名称', el: 'input' },
+  { prop: 'code', label: '标识', el: 'input' }
+]
+
+// 获取table列表
+const getTableList = (params: RoleQuery) => RoleApi.pageRoleApi(params)
+</script>

+ 95 - 20
src/views/home/index.vue

@@ -1,32 +1,107 @@
 <template>
-  <div class="table-box">
-    <ProTable
-      ref="proTableRef"
-      title="角色列表"
-      row-key="roleId"
-      :indent="20"
-      :columns="columns"
-      :search-columns="searchColumns"
-      :request-api="getTableList">
-      <!-- 表格 header 按钮 -->
-      <template #tableHeader="scope">
-        <el-button type="primary" icon="CirclePlus"> 新增角色 </el-button>
-        <el-button type="danger" icon="Delete" plain :disabled="!scope.isSelected"> 批量删除角色 </el-button>
-      </template>
+  <div class="main-box">
+    <TreeFilter title="组织架构" :request-api="OrgApi.treeRole" label="name" :default-value="'0'" />
 
-      <template #operation="{ row }">
-        <el-button type="primary" link icon="Lock"> 权限 </el-button>
-        <el-button type="primary" link icon="EditPen"> 编辑 </el-button>
-        <el-button v-if="row.id !== 1" type="primary" link icon="Delete"> 删除 </el-button>
-      </template>
-    </ProTable>
+    <div class="table-box">
+      <ProTable
+        ref="proTableRef"
+        title="角色列表"
+        row-key="roleId"
+        :indent="20"
+        :columns="columns"
+        :search-columns="searchColumns"
+        :request-api="getTableList">
+        <!-- 表格 header 按钮 -->
+        <template #tableHeader="scope">
+          <el-button type="primary" icon="CirclePlus"> 新增角色 </el-button>
+          <el-button type="danger" icon="Delete" plain :disabled="!scope.isSelected"> 批量删除角色 </el-button>
+        </template>
+
+        <template #operation="{ row }">
+          <el-button type="primary" link icon="Lock"> 权限 </el-button>
+          <el-button type="primary" link icon="EditPen"> 编辑 </el-button>
+          <el-button v-if="row.id !== 1" type="primary" link icon="Delete"> 删除 </el-button>
+        </template>
+      </ProTable>
+    </div>
   </div>
 </template>
 <script lang="tsx" setup name="Home">
 import { RoleInfo, RoleQuery } from '@/api/interface/system/role'
+import OrgApi from '@/api/module/system/org'
 import RoleApi from '@/api/module/system/role'
 import { ColumnProps, SearchProps } from '@/components/ProTable/interface'
+interface Tree {
+  label: string
+  children?: Tree[]
+}
+
+const handleNodeClick = (data: Tree) => {
+  console.log(data)
+}
+
+const data: Tree[] = [
+  {
+    label: 'Level one 1',
+    children: [
+      {
+        label: 'Level two 1-1',
+        children: [
+          {
+            label: 'Level three 1-1-1'
+          }
+        ]
+      }
+    ]
+  },
+  {
+    label: 'Level one 2',
+    children: [
+      {
+        label: 'Level two 2-1',
+        children: [
+          {
+            label: 'Level three 2-1-1'
+          }
+        ]
+      },
+      {
+        label: 'Level two 2-2',
+        children: [
+          {
+            label: 'Level three 2-2-1'
+          }
+        ]
+      }
+    ]
+  },
+  {
+    label: 'Level one 3',
+    children: [
+      {
+        label: 'Level two 3-1',
+        children: [
+          {
+            label: 'Level three 3-1-1'
+          }
+        ]
+      },
+      {
+        label: 'Level two 3-2',
+        children: [
+          {
+            label: 'Level three 3-2-1'
+          }
+        ]
+      }
+    ]
+  }
+]
 
+const defaultProps = {
+  children: 'children',
+  label: 'label'
+}
 // 表格配置项
 const columns: ColumnProps<RoleInfo>[] = [
   { type: 'selection', width: 60, selectable: row => row.isLock == '1' },

+ 3 - 1
vite.config.ts

@@ -68,7 +68,9 @@ export default defineConfig(({ mode, command }: ConfigEnv): UserConfig => {
         'element-plus/es/components/switch/style/css',
         'element-plus/es/components/pagination/style/css',
         'element-plus/es/components/space/style/css',
-        'element-plus/es/components/tooltip/style/css'
+        'element-plus/es/components/tooltip/style/css',
+        'element-plus/es/components/tree/style/css',
+        'element-plus/es/components/scrollbar/style/css'
       ]
     }
   }