From 8a5777c6bbecf3cbac8925987515e2ea01b24486 Mon Sep 17 00:00:00 2001 From: Kven <2955163637@qq.com> Date: Sat, 10 May 2025 12:39:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(@vben/web-antd):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E6=A8=A1=E5=9D=97=E5=92=8C=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/adapter/vxe-table.ts | 191 +++++++++++++- apps/web-antd/src/api/core/role.ts | 59 +++-- .../src/router/routes/modules/dept.ts | 18 -- .../src/router/routes/modules/role.ts | 18 -- .../src/router/routes/modules/system.ts | 46 ++++ .../src/router/routes/modules/user.ts | 18 -- apps/web-antd/src/views/role/data.ts | 14 +- apps/web-antd/src/views/role/list.vue | 13 +- apps/web-antd/src/views/role/modules/form.vue | 31 ++- apps/web-antd/src/views/user/user-edit.vue | 245 ------------------ .../src/views/user/user-table-view.vue | 200 -------------- apps/web-antd/vite.config.mts | 8 +- packages/icons/src/iconify/index.ts | 1 + 13 files changed, 310 insertions(+), 552 deletions(-) delete mode 100644 apps/web-antd/src/router/routes/modules/dept.ts delete mode 100644 apps/web-antd/src/router/routes/modules/role.ts create mode 100644 apps/web-antd/src/router/routes/modules/system.ts delete mode 100644 apps/web-antd/src/router/routes/modules/user.ts delete mode 100644 apps/web-antd/src/views/user/user-edit.vue delete mode 100644 apps/web-antd/src/views/user/user-table-view.vue diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index d296b20..870604d 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -1,8 +1,15 @@ +import type { Recordable } from '@vben/types'; + import { h } from 'vue'; +import { IconifyIcon } from '@vben/icons'; +import { $te } from '@vben/locales'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; +import { isFunction, isString } from '@vben/utils'; -import { Button, Image } from 'ant-design-vue'; +import { Button, Image, Popconfirm, Switch } from 'ant-design-vue'; + +import { $t } from '#/locales'; import { useVbenForm } from './form'; @@ -55,6 +62,181 @@ setupVbenVxeTable({ ); }, }); + vxeUI.renderer.add('CellSwitch', { + renderTableDefault({ attrs, props }, { column, row }) { + const loadingKey = `__loading_${column.field}`; + const finallyProps = { + checkedChildren: $t('common.enabled'), + checkedValue: true, + unCheckedChildren: $t('common.disabled'), + unCheckedValue: false, + ...props, + checked: row[column.field], + loading: row[loadingKey] ?? false, + 'onUpdate:checked': onChange, + }; + async function onChange(newVal: any) { + row[loadingKey] = true; + try { + const result = await attrs?.beforeChange?.(newVal, row); + if (result !== false) { + row[column.field] = newVal; + } + } finally { + row[loadingKey] = false; + } + } + return h(Switch, finallyProps); + }, + }); + /** + * 注册表格的操作按钮渲染器 + */ + vxeUI.renderer.add('CellOperation', { + renderTableDefault({ attrs, options, props }, { column, row }) { + const defaultProps = { size: 'small', type: 'link', ...props }; + let align = 'end'; + switch (column.align) { + case 'center': { + align = 'center'; + break; + } + case 'left': { + align = 'start'; + break; + } + default: { + align = 'end'; + break; + } + } + const presets: Recordable> = { + delete: { + danger: true, + text: $t('common.delete'), + }, + edit: { + text: $t('common.edit'), + }, + }; + const operations: Array> = ( + options || ['edit', 'delete'] + ) + .map((opt) => { + if (isString(opt)) { + return presets[opt] + ? { code: opt, ...presets[opt], ...defaultProps } + : { + code: opt, + text: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt, + ...defaultProps, + }; + } else { + return { ...defaultProps, ...presets[opt.code], ...opt }; + } + }) + .map((opt) => { + const optBtn: Recordable = {}; + Object.keys(opt).forEach((key) => { + optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key]; + }); + return optBtn; + }) + .filter((opt) => opt.show !== false); + + function renderBtn(opt: Recordable, listen = true) { + return h( + Button, + { + ...props, + ...opt, + icon: undefined, + onClick: listen + ? () => + attrs?.onClick?.({ + code: opt.code, + row, + }) + : undefined, + }, + { + default: () => { + const content = []; + if (opt.icon) { + content.push( + h(IconifyIcon, { class: 'size-5', icon: opt.icon }), + ); + } + content.push(opt.text); + return content; + }, + }, + ); + } + + function renderConfirm(opt: Recordable) { + let viewportWrapper: HTMLElement | null = null; + return h( + Popconfirm, + { + /** + * 当popconfirm用在固定列中时,将固定列作为弹窗的容器时可能会因为固定列较窄而无法容纳弹窗 + * 将表格主体区域作为弹窗容器时又会因为固定列的层级较高而遮挡弹窗 + * 将body或者表格视口区域作为弹窗容器时又会导致弹窗无法跟随表格滚动。 + * 鉴于以上各种情况,一种折中的解决方案是弹出层展示时,禁止操作表格的滚动条。 + * 这样既解决了弹窗的遮挡问题,又不至于让弹窗随着表格的滚动而跑出视口区域。 + */ + getPopupContainer(el) { + viewportWrapper = el.closest('.vxe-table--viewport-wrapper'); + return document.body; + }, + placement: 'topLeft', + title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']), + ...props, + ...opt, + icon: undefined, + onOpenChange: (open: boolean) => { + // 当弹窗打开时,禁止表格的滚动 + if (open) { + viewportWrapper?.style.setProperty('pointer-events', 'none'); + } else { + viewportWrapper?.style.removeProperty('pointer-events'); + } + }, + onConfirm: () => { + attrs?.onClick?.({ + code: opt.code, + row, + }); + }, + }, + { + default: () => renderBtn({ ...opt }, false), + description: () => + h( + 'div', + { class: 'truncate' }, + $t('ui.actionMessage.deleteConfirm', [ + row[attrs?.nameField || 'name'], + ]), + ), + }, + ); + } + + const btns = operations.map((opt) => + opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt), + ); + return h( + 'div', + { + class: 'flex table-operations', + style: { justifyContent: align }, + }, + btns, + ); + }, + }); // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 // vxeUI.formats.add @@ -63,5 +245,12 @@ setupVbenVxeTable({ }); export { useVbenVxeGrid }; +export type OnActionClickParams> = { + code: string; + row: T; +}; +export type OnActionClickFn> = ( + params: OnActionClickParams, +) => void; export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-antd/src/api/core/role.ts b/apps/web-antd/src/api/core/role.ts index 968454d..7d8789e 100644 --- a/apps/web-antd/src/api/core/role.ts +++ b/apps/web-antd/src/api/core/role.ts @@ -2,55 +2,72 @@ import { requestClient } from '#/api/request'; export namespace RoleApi { export interface Role { + [key: string]: any; name: string; dataScope: string; - permissionIds: (number | undefined)[]; - remark: string; - authorities: (number | undefined)[]; - id?: string; - } - - // 基础信息 - export interface RoleRecord extends Role { + enabled: boolean; + deptId: string; + remark?: string; + createBy: string; + createTime: string; + createId: string; + // authorities: (number | undefined)[]; id: string; } - export interface RoleListRecord extends RoleRecord { + export interface RoleListParams { + page: number; + size: number; + sort: string; + name?: string; + enabled?: boolean; + deptId?: string; + } + + export interface RoleListRecord extends Role { name: string; } } // 查询所有的角色列表、 -export function queryRoleList(data: any) { - // return axios.get('/api/rest/role',data); - return requestClient.get('/api/rest/role', { data }); +export function queryRoleList(data: RoleApi.RoleListParams) { + // return axios.get('/rest/role',data); + return requestClient.get>('/rest/role', { params: data }); } // 切换启用状态 -export function enabled(id: string) { - return requestClient.patch(`/api/rest/role/${id}/toggle`); +export function enabledRole(id: string) { + return requestClient.patch(`/rest/role/${id}/toggle`); } // 删除 export function removeRole(id: string) { - return requestClient.delete(`/api/rest/role/${id}`); + return requestClient.delete(`/rest/role/${id}`); } // 添加 export function createRole(data: RoleApi.Role) { - return requestClient.post(`/api/rest/role`, data); + return requestClient.post(`/rest/role`, data); } // 更新 -export function updateRole(data: RoleApi.RoleRecord) { - return requestClient.patch(`/api/rest/role/${data.id}`, data); +export function updateRole(id: any, data: RoleApi.Role) { + return requestClient.patch(`/rest/role/${id}`, data); } // 获取详情 export function getDetail(id: string) { - return requestClient.get(`/api/rest/role/${id}`); + return requestClient.get(`/rest/role/${id}`); } -// export function queryRoles(params?: ListParams>) { -// return queryList(`/api/rest/role/query`, params); +export const queryMenuList = (data: string) => { + return requestClient.get('/rest/menu/tree', { + params: { + name: data, + }, + }); +}; + +// export function queryRoles(params?: ListParams>) { +// return queryList(`/rest/role/query`, params); // } diff --git a/apps/web-antd/src/router/routes/modules/dept.ts b/apps/web-antd/src/router/routes/modules/dept.ts deleted file mode 100644 index 0ff9f74..0000000 --- a/apps/web-antd/src/router/routes/modules/dept.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { RiDept } from '@vben/icons'; - -const routes: RouteRecordRaw[] = [ - { - name: 'dept', - path: '/dept', - component: () => import('#/views/dept/list.vue'), - meta: { - icon: RiDept, - title: '部门管理', - order: 5, - }, - }, -]; - -export default routes; diff --git a/apps/web-antd/src/router/routes/modules/role.ts b/apps/web-antd/src/router/routes/modules/role.ts deleted file mode 100644 index 7916b6e..0000000 --- a/apps/web-antd/src/router/routes/modules/role.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { EosRole } from '@vben/icons'; - -const routes: RouteRecordRaw[] = [ - { - name: 'role', - path: '/role', - component: () => import('#/views/role/list.vue'), - meta: { - icon: EosRole, - title: '角色管理', - order: 5, - }, - }, -]; - -export default routes; diff --git a/apps/web-antd/src/router/routes/modules/system.ts b/apps/web-antd/src/router/routes/modules/system.ts new file mode 100644 index 0000000..f880611 --- /dev/null +++ b/apps/web-antd/src/router/routes/modules/system.ts @@ -0,0 +1,46 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { EosRole, IconSystem, MdiUser, RiDept } from '@vben/icons'; + +const routes: RouteRecordRaw[] = [ + { + name: 'system', + path: '/system', + meta: { + icon: IconSystem, + title: '系统管理', + order: 4, + }, + children: [ + { + name: 'role', + path: '/system/role', + component: () => import('#/views/role/list.vue'), + meta: { + icon: EosRole, + title: '角色管理', + }, + }, + { + name: 'dept', + path: '/system/dept', + component: () => import('#/views/dept/list.vue'), + meta: { + icon: RiDept, + title: '部门管理', + }, + }, + { + name: 'user', + path: '/system/user', + component: () => import('#/views/user/list.vue'), + meta: { + icon: MdiUser, + title: '用户管理', + }, + }, + ], + }, +]; + +export default routes; diff --git a/apps/web-antd/src/router/routes/modules/user.ts b/apps/web-antd/src/router/routes/modules/user.ts deleted file mode 100644 index bd6c1e2..0000000 --- a/apps/web-antd/src/router/routes/modules/user.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { MdiUser } from '@vben/icons'; - -const routes: RouteRecordRaw[] = [ - { - name: 'user', - path: '/user', - component: () => import('#/views/user/index.vue'), - meta: { - icon: MdiUser, - title: '用户管理', - order: 4, - }, - }, -]; - -export default routes; diff --git a/apps/web-antd/src/views/role/data.ts b/apps/web-antd/src/views/role/data.ts index f2065df..efda1e2 100644 --- a/apps/web-antd/src/views/role/data.ts +++ b/apps/web-antd/src/views/role/data.ts @@ -58,7 +58,7 @@ export function useGridFormSchema(): VbenFormSchema[] { { label: $t('common.disabled'), value: 0 }, ], }, - fieldName: 'status', + fieldName: 'enabled', label: '状态', }, { @@ -80,13 +80,13 @@ export function useColumns( ): VxeTableGridOptions['columns'] { return [ { - field: 'name', - title: '角色名称', + field: 'id', + title: '角色ID', width: 200, }, { - field: 'id', - title: '角色ID', + field: 'name', + title: '角色名称', width: 200, }, { @@ -94,7 +94,7 @@ export function useColumns( attrs: { beforeChange: onStatusChange }, name: onStatusChange ? 'CellSwitch' : 'CellTag', }, - field: 'status', + field: 'enabled', title: '角色状态', width: 100, }, @@ -121,7 +121,7 @@ export function useColumns( field: 'operation', fixed: 'right', title: '操作', - width: 130, + width: 200, }, ]; } diff --git a/apps/web-antd/src/views/role/list.vue b/apps/web-antd/src/views/role/list.vue index fcaecb3..1c453b7 100644 --- a/apps/web-antd/src/views/role/list.vue +++ b/apps/web-antd/src/views/role/list.vue @@ -13,7 +13,7 @@ import { Plus } from '@vben/icons'; import { Button, message, Modal } from 'ant-design-vue'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; -import { queryRoleList, removeRole, updateRole } from '#/api'; +import { enabledRole, queryRoleList, removeRole } from '#/api'; import { $t } from '#/locales'; import { useColumns, useGridFormSchema } from './data'; @@ -28,7 +28,6 @@ const [Grid, gridApi] = useVbenVxeGrid({ formOptions: { fieldMappingTime: [['createTime', ['startTime', 'endTime']]], schema: useGridFormSchema(), - submitOnChange: true, }, gridOptions: { columns: useColumns(onActionClick, onStatusChange), @@ -37,11 +36,15 @@ const [Grid, gridApi] = useVbenVxeGrid({ proxyConfig: { ajax: { query: async ({ page }, formValues) => { - return await queryRoleList({ + const res = await queryRoleList({ page: page.currentPage, - pageSize: page.pageSize, + size: page.pageSize, ...formValues, }); + return { + total: res.total, + items: res.records, + }; }, }, }, @@ -108,7 +111,7 @@ async function onStatusChange(newStatus: number, row: RoleApi.Role) { `你要将${row.name}的状态切换为 【${status[newStatus.toString()]}】 吗?`, `切换状态`, ); - await updateRole(row.id, { status: newStatus }); + await enabledRole(row.id); return true; } catch { return false; diff --git a/apps/web-antd/src/views/role/modules/form.vue b/apps/web-antd/src/views/role/modules/form.vue index d90f31c..da13f89 100644 --- a/apps/web-antd/src/views/role/modules/form.vue +++ b/apps/web-antd/src/views/role/modules/form.vue @@ -13,15 +13,14 @@ import { IconifyIcon } from '@vben/icons'; import { Spin } from 'ant-design-vue'; import { useVbenForm } from '#/adapter/form'; -// import { getMenuList } from '#/api'; -import { createRole, updateRole } from '#/api'; +import { createRole, queryMenuList, updateRole } from '#/api'; import { $t } from '#/locales'; import { useFormSchema } from '../data'; const emits = defineEmits(['success']); -const formData = ref(); +const formData = ref(); const [Form, formApi] = useVbenForm({ schema: useFormSchema(), @@ -49,7 +48,7 @@ const [Drawer, drawerApi] = useVbenDrawer({ }, onOpenChange(isOpen) { if (isOpen) { - const data = drawerApi.getData(); + const data = drawerApi.getData(); formApi.resetForm(); if (data) { formData.value = data; @@ -59,22 +58,22 @@ const [Drawer, drawerApi] = useVbenDrawer({ id.value = undefined; } - // if (permissions.value.length === 0) { - // loadPermissions(); - // } + if (permissions.value.length === 0) { + loadPermissions(); + } } }, }); -// async function loadPermissions() { -// loadingPermissions.value = true; -// try { -// const res = await getMenuList(); -// permissions.value = res as unknown as DataNode[]; -// } finally { -// loadingPermissions.value = false; -// } -// } +async function loadPermissions() { + loadingPermissions.value = true; + try { + const res = await queryMenuList('all'); + permissions.value = res as unknown as DataNode[]; + } finally { + loadingPermissions.value = false; + } +} const getDrawerTitle = computed(() => { return formData.value?.id diff --git a/apps/web-antd/src/views/user/user-edit.vue b/apps/web-antd/src/views/user/user-edit.vue deleted file mode 100644 index c37a3c0..0000000 --- a/apps/web-antd/src/views/user/user-edit.vue +++ /dev/null @@ -1,245 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/user/user-table-view.vue b/apps/web-antd/src/views/user/user-table-view.vue deleted file mode 100644 index 00207db..0000000 --- a/apps/web-antd/src/views/user/user-table-view.vue +++ /dev/null @@ -1,200 +0,0 @@ - - - - - diff --git a/apps/web-antd/vite.config.mts b/apps/web-antd/vite.config.mts index 7a5f58b..20a0018 100644 --- a/apps/web-antd/vite.config.mts +++ b/apps/web-antd/vite.config.mts @@ -10,13 +10,14 @@ export default defineConfig(async () => { changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), // mock代理目标地址 - target: 'http://localhost:8081/api', + // target: 'http://localhost:8081/api', + target: 'http://43.139.10.64:8082/api', ws: true, }, '/docx': { changeOrigin: true, rewrite: (path) => path.replace(/^\/docx/, ''), - target: 'http://47.112.173.8:6805/static', + target: 'http://47.112.173.8:6802/static', }, '/pptx': { changeOrigin: true, @@ -26,7 +27,8 @@ export default defineConfig(async () => { '/v1': { changeOrigin: true, rewrite: (path) => path.replace(/^\/v1/, ''), - target: 'http://localhost:8081/v1', + // target: 'http://localhost:8081/v1', + target: 'http://43.139.10.64:8082/v1', }, }, }, diff --git a/packages/icons/src/iconify/index.ts b/packages/icons/src/iconify/index.ts index e6daf48..f8fd594 100644 --- a/packages/icons/src/iconify/index.ts +++ b/packages/icons/src/iconify/index.ts @@ -15,4 +15,5 @@ export const MdiUser = createIconifyIcon('mdi:user'); export const MageRobot = createIconifyIcon('mage:robot'); export const RiDept = createIconifyIcon('ri:door-open-line'); export const EosRole = createIconifyIcon('eos-icons:role-binding-outlined'); +export const IconSystem = createIconifyIcon('icon-park-twotone:system'); // export const MdiUser = createIconifyIcon('mdi:user');