Commit 7cabf0ead5ba81586de20d360bac7e5379f97aec

Authored by xingyu
1 parent 83954241

feat: dept vxe table

yudao-ui-admin-vue3/package.json
... ... @@ -43,7 +43,7 @@
43 43 "lodash-es": "^4.17.21",
44 44 "mitt": "^3.0.0",
45 45 "nprogress": "^0.2.0",
46   - "pinia": "^2.0.23",
  46 + "pinia": "^2.0.24",
47 47 "qrcode": "^1.5.1",
48 48 "qs": "^6.11.0",
49 49 "url": "^0.11.0",
... ...
yudao-ui-admin-vue3/pnpm-lock.yaml
... ... @@ -42,7 +42,7 @@ specifiers:
42 42 lodash-es: ^4.17.21
43 43 mitt: ^3.0.0
44 44 nprogress: ^0.2.0
45   - pinia: ^2.0.23
  45 + pinia: ^2.0.24
46 46 plop: ^3.1.1
47 47 postcss: ^8.4.19
48 48 postcss-html: ^1.5.0
... ... @@ -99,7 +99,7 @@ dependencies:
99 99 lodash-es: 4.17.21
100 100 mitt: 3.0.0
101 101 nprogress: 0.2.0
102   - pinia: 2.0.23_zwu2zepfy3m6u2gunxlolp35gi
  102 + pinia: 2.0.24_zwu2zepfy3m6u2gunxlolp35gi
103 103 qrcode: 1.5.1
104 104 qs: 6.11.0
105 105 url: 0.11.0
... ... @@ -1565,10 +1565,6 @@ packages:
1565 1565 /@vue/devtools-api/6.4.3:
1566 1566 resolution: {integrity: sha512-9WCRwdROJvWcHAdyrR7SZMM/qUvllDZnpndHXokThkUsjnJ2xe4/pvsH9FZrxFe22L+JmDKczL79HjLJ7DK9rg==}
1567 1567  
1568   - /@vue/devtools-api/6.4.4:
1569   - resolution: {integrity: sha512-Ku31WzpOV/8cruFaXaEZKF81WkNnvCSlBY4eOGtz5WMSdJvX1v1WWlSMGZeqUwPtQ27ZZz7B62erEMq8JDjcXw==}
1570   - dev: false
1571   -
1572 1568 /@vue/devtools-api/6.4.5:
1573 1569 resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==}
1574 1570 dev: false
... ... @@ -5564,8 +5560,8 @@ packages:
5564 5560 dev: true
5565 5561 optional: true
5566 5562  
5567   - /pinia/2.0.23_zwu2zepfy3m6u2gunxlolp35gi:
5568   - resolution: {integrity: sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==}
  5563 + /pinia/2.0.24_zwu2zepfy3m6u2gunxlolp35gi:
  5564 + resolution: {integrity: sha512-DDLd4Iphyc+6PYYYbx7jkb6WP9gecgu9bz9huyB5rb7CdJI3DhzYiZI+/Ih8MLewRrP9DSpslF/BgSNrJtZU7A==}
5569 5565 peerDependencies:
5570 5566 '@vue/composition-api': ^1.4.0
5571 5567 typescript: '>=4.4.4'
... ... @@ -5576,7 +5572,7 @@ packages:
5576 5572 typescript:
5577 5573 optional: true
5578 5574 dependencies:
5579   - '@vue/devtools-api': 6.4.4
  5575 + '@vue/devtools-api': 6.4.5
5580 5576 typescript: 4.8.4
5581 5577 vue: 3.2.45
5582 5578 vue-demi: 0.13.11_vue@3.2.45
... ...
yudao-ui-admin-vue3/src/api/system/dept/index.ts
1 1 import request from '@/config/axios'
2   -import type { DeptVO, DeptListReqVO } from './types'
  2 +export type DeptVO = {
  3 + id: number
  4 + name: string
  5 + parentId: number
  6 + status: number
  7 + sort: number
  8 + leaderUserId: number
  9 + phone: string
  10 + email: string
  11 +}
  12 +
  13 +export interface DeptPageReqVO {
  14 + name?: string
  15 + status?: number
  16 +}
3 17  
4 18 // 查询部门(精简)列表
5   -export const listSimpleDeptApi = () => {
6   - return request.get({ url: '/system/dept/list-all-simple' })
  19 +export const listSimpleDeptApi = async () => {
  20 + return await request.get({ url: '/system/dept/list-all-simple' })
7 21 }
8 22  
9 23 // 查询部门列表
10   -export const getDeptPageApi = (params: DeptListReqVO) => {
11   - return request.get({ url: '/system/dept/list', params })
  24 +export const getDeptPageApi = async (params: DeptPageReqVO) => {
  25 + return await request.get({ url: '/system/dept/list', params })
12 26 }
13 27  
14 28 // 查询部门详情
15   -export const getDeptApi = (id: number) => {
16   - return request.get({ url: '/system/dept/get?id=' + id })
  29 +export const getDeptApi = async (id: number) => {
  30 + return await request.get({ url: '/system/dept/get?id=' + id })
17 31 }
18 32  
19 33 // 新增部门
20   -export const createDeptApi = (data: DeptVO) => {
21   - return request.post({ url: '/system/dept/create', data: data })
  34 +export const createDeptApi = async (data: DeptVO) => {
  35 + return await request.post({ url: '/system/dept/create', data: data })
22 36 }
23 37  
24 38 // 修改部门
25   -export const updateDeptApi = (params: DeptVO) => {
26   - return request.put({ url: '/system/dept/update', data: params })
  39 +export const updateDeptApi = async (params: DeptVO) => {
  40 + return await request.put({ url: '/system/dept/update', data: params })
27 41 }
28 42  
29 43 // 删除部门
30   -export const deleteDeptApi = (id: number) => {
31   - return request.delete({ url: '/system/dept/delete?id=' + id })
  44 +export const deleteDeptApi = async (id: number) => {
  45 + return await request.delete({ url: '/system/dept/delete?id=' + id })
32 46 }
... ...
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts
... ... @@ -49,7 +49,7 @@ export const modelSchema = reactive<FormSchema[]>([
49 49 {
50 50 label: '显示排序',
51 51 field: 'sort',
52   - component: 'InputNumber'
  52 + component: 'Input'
53 53 },
54 54 {
55 55 label: '状态',
... ...
yudao-ui-admin-vue3/src/views/system/dept/index.vue
1 1 <template>
2   - <div class="flex">
3   - <el-card class="w-1/3 dept" :gutter="12" shadow="always">
4   - <template #header>
5   - <div class="card-header">
6   - <span>部门列表</span>
7   - <XButton
8   - type="primary"
9   - preIcon="ep:zoom-in"
10   - title="新增根节点"
11   - v-hasPermi="['system:dept:create']"
12   - @click="handleCreate"
  2 + <ContentWrap>
  3 + <!-- 搜索工作栏 -->
  4 + <el-form :model="queryParams" ref="queryForm" :inline="true">
  5 + <el-form-item label="部门名称" prop="name">
  6 + <el-input v-model="queryParams.name" placeholder="请输入部门名称" />
  7 + </el-form-item>
  8 + <el-form-item label="状态" prop="status">
  9 + <el-select v-model="queryParams.status" placeholder="请选择部门状态">
  10 + <el-option
  11 + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
  12 + :key="dict.value"
  13 + :label="dict.label"
  14 + :value="dict.value"
13 15 />
14   - </div>
  16 + </el-select>
  17 + </el-form-item>
  18 + <el-form-item>
  19 + <!-- 操作:搜索 -->
  20 + <XButton
  21 + type="primary"
  22 + preIcon="ep:search"
  23 + :title="t('common.query')"
  24 + @click="handleQuery()"
  25 + />
  26 + <!-- 操作:重置 -->
  27 + <XButton preIcon="ep:refresh-right" :title="t('common.reset')" @click="resetQuery()" />
  28 + </el-form-item>
  29 + </el-form>
  30 + <vxe-toolbar>
  31 + <template #buttons>
  32 + <!-- 操作:新增 -->
  33 + <XButton
  34 + type="primary"
  35 + preIcon="ep:zoom-in"
  36 + :title="t('action.add')"
  37 + v-hasPermi="['system:dept:create']"
  38 + @click="handleCreate()"
  39 + />
  40 + <XButton title="展开所有" @click="xTable?.setAllTreeExpand(true)" />
  41 + <XButton title="关闭所有" @click="xTable?.clearTreeExpand()" />
15 42 </template>
16   - <div class="custom-tree-container">
17   - <!-- <p>部门列表</p> -->
18   - <!-- 操作工具栏 -->
19   - <el-input v-model="filterText" placeholder="搜索部门" />
20   - <el-tree
21   - ref="treeRef"
  43 + </vxe-toolbar>
  44 + <!-- 列表 -->
  45 + <vxe-table
  46 + show-overflow
  47 + keep-source
  48 + ref="xTable"
  49 + :loading="tableLoading"
  50 + :row-config="{ keyField: 'id' }"
  51 + :column-config="{ resizable: true }"
  52 + :tree-config="{ transform: true, rowField: 'id', parentField: 'parentId' }"
  53 + :print-config="{}"
  54 + :export-config="{}"
  55 + :data="tableData"
  56 + class="xtable"
  57 + >
  58 + <vxe-column title="部门名称" field="name" width="200" tree-node />
  59 + <vxe-column title="负责人" field="leaderUserId" :formatter="userNicknameFormat" />
  60 + <vxe-column title="排序" field="sort" />
  61 + <vxe-column title="状态" field="status">
  62 + <template #default="{ row }">
  63 + <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
  64 + </template>
  65 + </vxe-column>
  66 + <vxe-column title="创建时间" field="createTime" formatter="formatDate" />
  67 + <vxe-column title="操作" width="200">
  68 + <template #default="{ row }">
  69 + <!-- 操作:修改 -->
  70 + <XTextButton
  71 + preIcon="ep:edit"
  72 + :title="t('action.edit')"
  73 + v-hasPermi="['system:dept:update']"
  74 + @click="handleUpdate(row.id)"
  75 + />
  76 + <!-- 操作:删除 -->
  77 + <XTextButton
  78 + preIcon="ep:delete"
  79 + :title="t('action.del')"
  80 + v-hasPermi="['system:dept:delete']"
  81 + @click="handleDelete(row.id)"
  82 + />
  83 + </template>
  84 + </vxe-column>
  85 + </vxe-table>
  86 + </ContentWrap>
  87 + <!-- 添加或修改菜单对话框 -->
  88 + <XModal id="deptModel" v-model="dialogVisible" :title="dialogTitle">
  89 + <!-- 对话框(添加 / 修改) -->
  90 + <!-- 操作工具栏 -->
  91 + <Form ref="formRef" :schema="modelSchema" :rules="rules">
  92 + <template #parentId>
  93 + <el-tree-select
22 94 node-key="id"
23   - :data="deptOptions"
  95 + v-model="deptParentId"
24 96 :props="defaultProps"
25   - :highlight-current="true"
26   - default-expand-all
27   - :filter-node-method="filterNode"
28   - :expand-on-click-node="false"
29   - >
30   - <template #default="{ node, data }">
31   - <span class="custom-tree-node">
32   - <span>{{ node.label }}</span>
33   - <span>
34   - <XTextButton
35   - preIcon="ep:zoom-in"
36   - :title="t('action.add')"
37   - v-hasPermi="['system:dept:create']"
38   - @click="handleCreate(data)"
39   - />
40   - <XTextButton
41   - preIcon="ep:edit"
42   - :title="t('action.edit')"
43   - v-hasPermi="['system:dept:update']"
44   - @click="handleUpdate(data)"
45   - />
46   - <XTextButton
47   - preIcon="ep:delete"
48   - :title="t('action.del')"
49   - v-hasPermi="['system:dept:delete']"
50   - @click="handleDelete(data)"
51   - />
52   - </span>
53   - </span>
54   - </template>
55   - </el-tree>
56   - </div>
57   - </el-card>
58   - <el-card class="w-2/3 dept" style="margin-left: 10px" :gutter="12" shadow="hover">
59   - <template #header>
60   - <div class="card-header">
61   - <span>{{ formTitle }}</span>
62   - </div>
63   - </template>
64   - <div v-if="!showForm">
65   - <span><p>请从左侧选择部门</p></span>
66   - </div>
67   - <div v-if="showForm">
68   - <!-- 操作工具栏 -->
69   - <Form ref="formRef" :schema="modelSchema" :rules="rules">
70   - <template #parentId>
71   - <el-tree-select
72   - node-key="id"
73   - v-model="deptParentId"
74   - :props="defaultProps"
75   - :data="deptOptions"
76   - check-strictly
77   - />
78   - </template>
79   - <template #leaderUserId>
80   - <el-select v-model="leaderUserId">
81   - <el-option
82   - v-for="item in userOption"
83   - :key="parseInt(item.id)"
84   - :label="item.nickname"
85   - :value="parseInt(item.id)"
86   - />
87   - </el-select>
88   - </template>
89   - </Form>
90   - <!-- 按钮:保存 -->
91   - <XButton
92   - type="primary"
93   - :title="t('action.save')"
94   - v-hasPermi="['system:dept:update']"
95   - :loading="loading"
96   - @click="submitForm()"
  97 + :data="deptOptions"
  98 + :default-expanded-keys="[100]"
  99 + check-strictly
97 100 />
98   - <!-- 按钮:关闭 -->
99   - <XButton :loading="loading" :title="t('dialog.close')" @click="showForm = false" />
100   - </div>
101   - </el-card>
102   - </div>
  101 + </template>
  102 + <template #leaderUserId>
  103 + <el-select v-model="leaderUserId">
  104 + <el-option
  105 + v-for="item in userOption"
  106 + :key="parseInt(item.id)"
  107 + :label="item.nickname"
  108 + :value="parseInt(item.id)"
  109 + />
  110 + </el-select>
  111 + </template>
  112 + </Form>
  113 + <template #footer>
  114 + <!-- 按钮:保存 -->
  115 + <XButton
  116 + v-if="['create', 'update'].includes(actionType)"
  117 + type="primary"
  118 + :loading="actionLoading"
  119 + @click="submitForm()"
  120 + :title="t('action.save')"
  121 + />
  122 + <!-- 按钮:关闭 -->
  123 + <XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" />
  124 + </template>
  125 + </XModal>
103 126 </template>
104 127 <script setup lang="ts">
  128 +import { nextTick, onMounted, reactive, ref, unref } from 'vue'
105 129 import { useI18n } from '@/hooks/web/useI18n'
106   -import { ElInput, ElCard, ElTree, ElTreeSelect, ElSelect, ElOption } from 'element-plus'
107   -import { handleTree } from '@/utils/tree'
108   -import { onMounted, ref, unref, watch } from 'vue'
109   -import * as DeptApi from '@/api/system/dept'
110   -import { Form, FormExpose } from '@/components/Form'
111   -import { modelSchema, rules } from './dept.data'
112   -import { DeptVO } from '@/api/system/dept/types'
113 130 import { useMessage } from '@/hooks/web/useMessage'
  131 +import { ElForm, ElFormItem, ElInput, ElSelect, ElTreeSelect, ElOption } from 'element-plus'
  132 +import { VxeColumn, VxeTable, VxeTableInstance, VxeToolbar } from 'vxe-table'
  133 +import { modelSchema } from './dept.data'
  134 +import * as DeptApi from '@/api/system/dept'
114 135 import { getListSimpleUsersApi } from '@/api/system/user'
115   -const message = useMessage()
  136 +import { required } from '@/utils/formRules.js'
  137 +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  138 +import { handleTree } from '@/utils/tree'
  139 +import { FormExpose } from '@/components/Form'
116 140  
  141 +const { t } = useI18n() // 国际化
  142 +const message = useMessage() // 消息弹窗
  143 +// 列表相关的变量
  144 +const xTable = ref<VxeTableInstance>()
  145 +const tableLoading = ref(false)
  146 +const tableData = ref()
  147 +// 弹窗相关的变量
  148 +const dialogVisible = ref(false) // 是否显示弹出层
  149 +const dialogTitle = ref('edit') // 弹出层标题
  150 +const actionType = ref('') // 操作按钮的类型
  151 +const actionLoading = ref(false) // 遮罩层
  152 +const deptParentId = ref(0) // 上级ID
  153 +const leaderUserId = ref()
  154 +const formRef = ref<FormExpose>() // 表单 Ref
  155 +const deptOptions = ref() // 树形结构
  156 +const userOption = ref()
  157 +// 新增和修改的表单校验
  158 +const rules = reactive({
  159 + name: [required],
  160 + sort: [required],
  161 + path: [required],
  162 + status: [required]
  163 +})
  164 +
  165 +// 下拉框[上级]的配置项目
117 166 const defaultProps = {
  167 + checkStrictly: true,
118 168 children: 'children',
119 169 label: 'name',
120 170 value: 'id'
121 171 }
122   -const { t } = useI18n() // 国际化
123   -const loading = ref(false) // 遮罩层
124   -const dialogVisible = ref(false) // 是否显示弹出层
125   -const showForm = ref(false) // 显示form表单
126   -const formTitle = ref('部门信息') // 显示form标题
127   -const deptParentId = ref(0) // 上级ID
128   -// 创建form表单
129   -const formRef = ref<FormExpose>()
130   -
131   -// ========== 创建部门树结构 ==========
132   -const filterText = ref('')
133   -const deptOptions = ref() // 树形结构
134   -const treeRef = ref<InstanceType<typeof ElTree>>()
  172 +// 获取下拉框[上级]的数据
135 173 const getTree = async () => {
136 174 const res = await DeptApi.listSimpleDeptApi()
137 175 deptOptions.value = handleTree(res)
  176 + console.info(deptOptions.value)
138 177 }
139   -const filterNode = (value: string, data: Tree) => {
140   - if (!value) return true
141   - return data.name.includes(value)
142   -}
143   -watch(filterText, (val) => {
144   - treeRef.value!.filter(val)
145   -})
146   -// 用户列表
147   -const userOption = ref()
148   -const leaderUserId = ref()
149 178 const getUserList = async () => {
150 179 const res = await getListSimpleUsersApi()
151 180 userOption.value = res
152 181 }
153   -// 新增
154   -const handleCreate = (data: { id: number }) => {
155   - // 重置表单
156   - deptParentId.value = data.id
157   - formTitle.value = '新增部门'
158   - showForm.value = true
  182 +// ========== 查询 ==========
  183 +const queryParams = reactive<DeptApi.DeptPageReqVO>({
  184 + name: undefined,
  185 + status: undefined
  186 +})
  187 +// 执行查询
  188 +const getList = async () => {
  189 + tableLoading.value = true
  190 + const res = await DeptApi.getDeptPageApi(queryParams)
  191 + tableData.value = res
  192 + tableLoading.value = false
  193 +}
  194 +
  195 +// 查询操作
  196 +const handleQuery = async () => {
  197 + await getList()
  198 +}
  199 +
  200 +// 重置操作
  201 +const resetQuery = async () => {
  202 + queryParams.name = undefined
  203 + queryParams.status = undefined
  204 + await getList()
  205 +}
  206 +
  207 +// ========== 新增/修改 ==========
  208 +
  209 +// 设置标题
  210 +const setDialogTile = (type: string) => {
  211 + dialogTitle.value = t('action.' + type)
  212 + actionType.value = type
  213 + dialogVisible.value = true
  214 +}
  215 +
  216 +// 新增操作
  217 +const handleCreate = async () => {
  218 + deptParentId.value = 0
  219 + leaderUserId.value = null
  220 + setDialogTile('create')
159 221 }
160   -// 编辑
161   -const handleUpdate = async (data: { id: number }) => {
162   - const res = await DeptApi.getDeptApi(data.id)
163   - formTitle.value = '修改- ' + res?.name
164   - deptParentId.value = res.parentId
  222 +
  223 +// 修改操作
  224 +const handleUpdate = async (rowId: number) => {
  225 + setDialogTile('update')
  226 + // 设置数据
  227 + const res = await DeptApi.getDeptApi(rowId)
  228 + console.info(res)
  229 + deptParentId.value = res.deptParentId
165 230 leaderUserId.value = res.leaderUserId
  231 + await nextTick()
166 232 unref(formRef)?.setValues(res)
167   - showForm.value = true
168   -}
169   -// 删除
170   -const handleDelete = async (data: { id: number }) => {
171   - message
172   - .confirm(t('common.delDataMessage'), t('common.confirmTitle'))
173   - .then(async () => {
174   - await DeptApi.deleteDeptApi(data.id)
175   - message.success(t('common.delSuccess'))
176   - })
177   - .catch(() => {})
178   - await getTree()
179 233 }
180   -// 提交按钮
  234 +
  235 +// 提交新增/修改的表单
181 236 const submitForm = async () => {
182 237 const elForm = unref(formRef)?.getElFormRef()
183 238 if (!elForm) return
184 239 elForm.validate(async (valid) => {
185 240 if (valid) {
186   - loading.value = true
  241 + actionLoading.value = true
187 242 // 提交请求
188 243 try {
189   - const data = unref(formRef)?.formModel as DeptVO
  244 + const data = unref(formRef)?.formModel as DeptApi.DeptVO
190 245 data.parentId = deptParentId.value
191 246 data.leaderUserId = leaderUserId.value
192   - if (formTitle.value.startsWith('新增')) {
  247 + if (dialogTitle.value.startsWith('新增')) {
193 248 await DeptApi.createDeptApi(data)
194   - } else if (formTitle.value.startsWith('修改')) {
  249 + } else if (dialogTitle.value.startsWith('修改')) {
195 250 await DeptApi.updateDeptApi(data)
196 251 }
197 252 // 操作成功,重新加载列表
198 253 dialogVisible.value = false
199 254 } finally {
200   - loading.value = false
  255 + actionLoading.value = false
201 256 }
202 257 }
203 258 })
204 259 }
  260 +
  261 +// 删除操作
  262 +const handleDelete = async (rowId: number) => {
  263 + message.delConfirm().then(async () => {
  264 + await DeptApi.deleteDeptApi(rowId)
  265 + message.success(t('common.delSuccess'))
  266 + await getList()
  267 + })
  268 +}
  269 +
  270 +const userNicknameFormat = (row) => {
  271 + if (!row && !row.row && !row.row.leaderUserId) {
  272 + return '未设置'
  273 + }
  274 + for (const user of userOption.value) {
  275 + if (row.row.leaderUserId === user.id) {
  276 + return user.nickname
  277 + }
  278 + }
  279 + return '未知【' + row.row.leaderUserId + '】'
  280 +}
  281 +
  282 +// ========== 初始化 ==========
205 283 onMounted(async () => {
206 284 await getTree()
207 285 await getUserList()
  286 + await getList()
208 287 })
209 288 </script>
210   -
211   -<style scoped>
212   -.dept {
213   - height: 600px;
214   - max-height: 1800px;
215   -}
216   -.card-header {
217   - display: flex;
218   - justify-content: space-between;
219   - align-items: center;
220   -}
221   -.custom-tree-node {
222   - flex: 1;
223   - display: flex;
224   - align-items: center;
225   - justify-content: space-between;
226   - font-size: 14px;
227   - padding-right: 8px;
228   -}
229   -</style>
... ...
yudao-ui-admin-vue3/vite.config.ts
... ... @@ -13,7 +13,7 @@ import { createHtmlPlugin } from &#39;vite-plugin-html&#39;
13 13 import viteCompression from 'vite-plugin-compression'
14 14 import VueMarcos from 'unplugin-vue-macros/vite'
15 15  
16   -// 当前执行node命令时文件夹的地址(工作目录)
  16 +// 当前执行node命令时文件夹的地址(工作目录)
17 17 const root = process.cwd()
18 18  
19 19 // 路径查找
... ...