From 9a0d7846cf512aa6feab3f9232ec012a37b8cf1d Mon Sep 17 00:00:00 2001 From: xie7654 <765462425@qq.com> Date: Thu, 3 Jul 2025 10:17:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9ele=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E7=9B=AE=E5=89=8Dvben=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=BF=98=E4=B8=8D=E6=98=AF=E7=89=B9=E5=88=AB=E5=AE=8C?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/apps/web-ele/.env.development | 4 +- web/apps/web-ele/.env.production | 4 +- web/apps/web-ele/src/adapter/vxe-table.ts | 9 +- web/apps/web-ele/src/api/core/auth.ts | 8 +- web/apps/web-ele/src/api/core/menu.ts | 4 +- web/apps/web-ele/src/api/core/user.ts | 2 +- web/apps/web-ele/src/api/system/dept.ts | 56 ++ web/apps/web-ele/src/api/system/dict_data.ts | 74 +++ web/apps/web-ele/src/api/system/dict_type.ts | 75 +++ web/apps/web-ele/src/api/system/index.ts | 3 + web/apps/web-ele/src/api/system/menu.ts | 168 ++++++ web/apps/web-ele/src/api/system/role.ts | 70 +++ .../web-ele/src/api/system/tenant_package.ts | 74 +++ web/apps/web-ele/src/api/system/tenants.ts | 71 +++ web/apps/web-ele/src/bootstrap.ts | 6 +- .../src/locales/langs/en-US/system.json | 113 ++++ .../src/locales/langs/zh-CN/system.json | 114 ++++ web/apps/web-ele/src/models/base.ts | 136 +++++ web/apps/web-ele/src/models/index.ts | 1 + .../web-ele/src/models/system/login_log.ts | 23 + web/apps/web-ele/src/models/system/post.ts | 23 + web/apps/web-ele/src/models/system/user.ts | 40 ++ web/apps/web-ele/src/preferences.ts | 1 + .../src/router/routes/modules/system.ts | 102 ++++ web/apps/web-ele/src/store/auth.ts | 11 +- web/apps/web-ele/src/store/permission.ts | 30 + web/apps/web-ele/src/utils/date.ts | 14 + web/apps/web-ele/src/utils/dict.ts | 5 + web/apps/web-ele/src/utils/permission.ts | 48 ++ .../src/views/_core/authentication/login.vue | 22 +- .../web-ele/src/views/system/dept/data.ts | 160 ++++++ .../web-ele/src/views/system/dept/list.vue | 147 +++++ .../src/views/system/dept/modules/form.vue | 80 +++ .../src/views/system/dict_data/data.ts | 211 +++++++ .../src/views/system/dict_data/list.vue | 143 +++++ .../views/system/dict_data/modules/form.vue | 82 +++ .../src/views/system/dict_type/data.ts | 147 +++++ .../src/views/system/dict_type/list.vue | 150 +++++ .../views/system/dict_type/modules/form.vue | 79 +++ .../src/views/system/login_log/data.ts | 95 ++++ .../src/views/system/login_log/list.vue | 47 ++ .../views/system/login_log/modules/form.vue | 79 +++ .../web-ele/src/views/system/menu/data.ts | 114 ++++ .../web-ele/src/views/system/menu/list.vue | 162 ++++++ .../src/views/system/menu/modules/form.vue | 524 ++++++++++++++++++ .../web-ele/src/views/system/post/data.ts | 124 +++++ .../web-ele/src/views/system/post/list.vue | 132 +++++ .../src/views/system/post/modules/form.vue | 79 +++ .../web-ele/src/views/system/role/data.ts | 118 ++++ .../web-ele/src/views/system/role/list.vue | 166 ++++++ .../src/views/system/role/modules/form.vue | 138 +++++ .../src/views/system/tenant_package/data.ts | 133 +++++ .../src/views/system/tenant_package/list.vue | 135 +++++ .../system/tenant_package/modules/form.vue | 78 +++ .../web-ele/src/views/system/tenants/data.ts | 157 ++++++ .../web-ele/src/views/system/tenants/list.vue | 131 +++++ .../src/views/system/tenants/modules/form.vue | 78 +++ .../web-ele/src/views/system/user/data.ts | 232 ++++++++ .../web-ele/src/views/system/user/list.vue | 129 +++++ .../src/views/system/user/modules/form.vue | 82 +++ .../common-ui/src/ui/authentication/login.vue | 10 +- 61 files changed, 5424 insertions(+), 29 deletions(-) create mode 100644 web/apps/web-ele/src/api/system/dept.ts create mode 100644 web/apps/web-ele/src/api/system/dict_data.ts create mode 100644 web/apps/web-ele/src/api/system/dict_type.ts create mode 100644 web/apps/web-ele/src/api/system/index.ts create mode 100644 web/apps/web-ele/src/api/system/menu.ts create mode 100644 web/apps/web-ele/src/api/system/role.ts create mode 100644 web/apps/web-ele/src/api/system/tenant_package.ts create mode 100644 web/apps/web-ele/src/api/system/tenants.ts create mode 100644 web/apps/web-ele/src/locales/langs/en-US/system.json create mode 100644 web/apps/web-ele/src/locales/langs/zh-CN/system.json create mode 100644 web/apps/web-ele/src/models/base.ts create mode 100644 web/apps/web-ele/src/models/index.ts create mode 100644 web/apps/web-ele/src/models/system/login_log.ts create mode 100644 web/apps/web-ele/src/models/system/post.ts create mode 100644 web/apps/web-ele/src/models/system/user.ts create mode 100644 web/apps/web-ele/src/router/routes/modules/system.ts create mode 100644 web/apps/web-ele/src/store/permission.ts create mode 100644 web/apps/web-ele/src/utils/date.ts create mode 100644 web/apps/web-ele/src/utils/dict.ts create mode 100644 web/apps/web-ele/src/utils/permission.ts create mode 100644 web/apps/web-ele/src/views/system/dept/data.ts create mode 100644 web/apps/web-ele/src/views/system/dept/list.vue create mode 100644 web/apps/web-ele/src/views/system/dept/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/dict_data/data.ts create mode 100644 web/apps/web-ele/src/views/system/dict_data/list.vue create mode 100644 web/apps/web-ele/src/views/system/dict_data/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/dict_type/data.ts create mode 100644 web/apps/web-ele/src/views/system/dict_type/list.vue create mode 100644 web/apps/web-ele/src/views/system/dict_type/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/login_log/data.ts create mode 100644 web/apps/web-ele/src/views/system/login_log/list.vue create mode 100644 web/apps/web-ele/src/views/system/login_log/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/menu/data.ts create mode 100644 web/apps/web-ele/src/views/system/menu/list.vue create mode 100644 web/apps/web-ele/src/views/system/menu/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/post/data.ts create mode 100644 web/apps/web-ele/src/views/system/post/list.vue create mode 100644 web/apps/web-ele/src/views/system/post/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/role/data.ts create mode 100644 web/apps/web-ele/src/views/system/role/list.vue create mode 100644 web/apps/web-ele/src/views/system/role/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/tenant_package/data.ts create mode 100644 web/apps/web-ele/src/views/system/tenant_package/list.vue create mode 100644 web/apps/web-ele/src/views/system/tenant_package/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/tenants/data.ts create mode 100644 web/apps/web-ele/src/views/system/tenants/list.vue create mode 100644 web/apps/web-ele/src/views/system/tenants/modules/form.vue create mode 100644 web/apps/web-ele/src/views/system/user/data.ts create mode 100644 web/apps/web-ele/src/views/system/user/list.vue create mode 100644 web/apps/web-ele/src/views/system/user/modules/form.vue diff --git a/web/apps/web-ele/.env.development b/web/apps/web-ele/.env.development index 8bcb432..8399365 100644 --- a/web/apps/web-ele/.env.development +++ b/web/apps/web-ele/.env.development @@ -4,10 +4,10 @@ VITE_PORT=5777 VITE_BASE=/ # 接口地址 -VITE_GLOB_API_URL=/api +VITE_GLOB_API_URL=http://127.0.0.1:8000/api # 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true +VITE_NITRO_MOCK=false # 是否打开 devtools,true 为打开,false 为关闭 VITE_DEVTOOLS=false diff --git a/web/apps/web-ele/.env.production b/web/apps/web-ele/.env.production index 5375847..e7f2e81 100644 --- a/web/apps/web-ele/.env.production +++ b/web/apps/web-ele/.env.production @@ -1,10 +1,10 @@ VITE_BASE=/ # 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api +VITE_GLOB_API_URL=http://127.0.0.1:8000/api # 是否开启压缩,可以设置为 none, brotli, gzip -VITE_COMPRESS=none +VITE_COMPRESS=gzip # 是否开启 PWA VITE_PWA=false diff --git a/web/apps/web-ele/src/adapter/vxe-table.ts b/web/apps/web-ele/src/adapter/vxe-table.ts index 40b8179..3af7868 100644 --- a/web/apps/web-ele/src/adapter/vxe-table.ts +++ b/web/apps/web-ele/src/adapter/vxe-table.ts @@ -7,6 +7,7 @@ import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; import { ElButton, ElImage } from 'element-plus'; import { useVbenForm } from './form'; +import type {Recordable} from "@vben-core/typings"; setupVbenVxeTable({ configVxeTable: (vxeUI) => { @@ -66,5 +67,11 @@ 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/web/apps/web-ele/src/api/core/auth.ts b/web/apps/web-ele/src/api/core/auth.ts index 71d9f99..e6746e2 100644 --- a/web/apps/web-ele/src/api/core/auth.ts +++ b/web/apps/web-ele/src/api/core/auth.ts @@ -22,14 +22,14 @@ export namespace AuthApi { * 登录 */ export async function loginApi(data: AuthApi.LoginParams) { - return requestClient.post('/auth/login', data); + return requestClient.post('/system/login/', data); } /** * 刷新accessToken */ export async function refreshTokenApi() { - return baseRequestClient.post('/auth/refresh', { + return baseRequestClient.post('/system/refresh/', { withCredentials: true, }); } @@ -38,7 +38,7 @@ export async function refreshTokenApi() { * 退出登录 */ export async function logoutApi() { - return baseRequestClient.post('/auth/logout', { + return baseRequestClient.post('/system/logout/', { withCredentials: true, }); } @@ -47,5 +47,5 @@ export async function logoutApi() { * 获取用户权限码 */ export async function getAccessCodesApi() { - return requestClient.get('/auth/codes'); + return requestClient.get('/system/codes/'); } diff --git a/web/apps/web-ele/src/api/core/menu.ts b/web/apps/web-ele/src/api/core/menu.ts index 9ef60b1..30a7e4a 100644 --- a/web/apps/web-ele/src/api/core/menu.ts +++ b/web/apps/web-ele/src/api/core/menu.ts @@ -6,5 +6,7 @@ import { requestClient } from '#/api/request'; * 获取用户所有菜单 */ export async function getAllMenusApi() { - return requestClient.get('/menu/all'); + return requestClient.get( + '/system/menu/user_menu', + ); } diff --git a/web/apps/web-ele/src/api/core/user.ts b/web/apps/web-ele/src/api/core/user.ts index 7e28ea8..830f4dc 100644 --- a/web/apps/web-ele/src/api/core/user.ts +++ b/web/apps/web-ele/src/api/core/user.ts @@ -6,5 +6,5 @@ import { requestClient } from '#/api/request'; * 获取用户信息 */ export async function getUserInfoApi() { - return requestClient.get('/user/info'); + return requestClient.get('/system/info/'); } diff --git a/web/apps/web-ele/src/api/system/dept.ts b/web/apps/web-ele/src/api/system/dept.ts new file mode 100644 index 0000000..950c920 --- /dev/null +++ b/web/apps/web-ele/src/api/system/dept.ts @@ -0,0 +1,56 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemDeptApi { + export interface SystemDept { + [key: string]: any; + children?: SystemDept[]; + id: string; + name: string; + remark?: string; + status: 0 | 1; + } +} + +/** + * 获取部门列表数据 + */ +async function getDeptList(params?: Recordable) { + return requestClient.get>('/system/dept/', { + params, + }); +} + +/** + * 创建部门 + * @param data 部门数据 + */ +async function createDept( + data: Omit, +) { + return requestClient.post('/system/dept/', data); +} + +/** + * 更新部门 + * + * @param id 部门 ID + * @param data 部门数据 + */ +async function updateDept( + id: string, + data: Omit, +) { + return requestClient.put(`/system/dept/${id}/`, data); +} + +/** + * 删除部门 + * @param id 部门 ID + */ +async function deleteDept(id: string) { + return requestClient.delete(`/system/dept/${id}/`); +} + +export { createDept, deleteDept, getDeptList, updateDept }; diff --git a/web/apps/web-ele/src/api/system/dict_data.ts b/web/apps/web-ele/src/api/system/dict_data.ts new file mode 100644 index 0000000..e24cf39 --- /dev/null +++ b/web/apps/web-ele/src/api/system/dict_data.ts @@ -0,0 +1,74 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemDictDataApi { + export interface SystemDictData { + [key: string]: any; + id: string; + name: string; + } +} + +/** + * 获取字典数据列表数据 + */ +async function getDictDataList(params: Recordable) { + return requestClient.get>( + '/system/dict_data/', + { + params, + }, + ); +} + +/** + * 创建字典数据 + * @param data 字典数据数据 + */ +async function createDictData( + data: Omit, +) { + return requestClient.post('/system/dict_data/', data); +} + +/** + * 更新字典数据 + * + * @param id 字典数据 ID + * @param data 字典数据数据 + */ +async function updateDictData( + id: string, + data: Omit, +) { + return requestClient.put(`/system/dict_data/${id}/`, data); +} +/** + * 更新字典数据 + * + * @param id 字典数据 ID + * @param data 字典数据数据 + */ +async function patchDictData( + id: string, + data: Omit, +) { + return requestClient.patch(`/system/dict_data/${id}/`, data); +} + +/** + * 删除字典数据 + * @param id 字典数据 ID + */ +async function deleteDictData(id: string) { + return requestClient.delete(`/system/dict_data/${id}/`); +} + +export { + createDictData, + deleteDictData, + getDictDataList, + patchDictData, + updateDictData, +}; diff --git a/web/apps/web-ele/src/api/system/dict_type.ts b/web/apps/web-ele/src/api/system/dict_type.ts new file mode 100644 index 0000000..a7ca410 --- /dev/null +++ b/web/apps/web-ele/src/api/system/dict_type.ts @@ -0,0 +1,75 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemDictTypeApi { + export interface SystemDictType { + [key: string]: any; + id: string; + name: string; + type: string; + } +} + +/** + * 获取字典类型列表数据 + */ +async function getDictTypeList(params: Recordable) { + return requestClient.get>( + '/system/dict_type/', + { + params, + }, + ); +} + +/** + * 创建字典类型 + * @param data 字典类型数据 + */ +async function createDictType( + data: Omit, +) { + return requestClient.post('/system/dict_type/', data); +} + +/** + * 更新字典类型 + * + * @param id 字典类型 ID + * @param data 字典类型数据 + */ +async function updateDictType( + id: string, + data: Omit, +) { + return requestClient.put(`/system/dict_type/${id}/`, data); +} +/** + * 更新字典类型 + * + * @param id 字典类型 ID + * @param data 字典类型数据 + */ +async function patchDictType( + id: string, + data: Omit, +) { + return requestClient.patch(`/system/dict_type/${id}/`, data); +} + +/** + * 删除字典类型 + * @param id 字典类型 ID + */ +async function deleteDictType(id: string) { + return requestClient.delete(`/system/dict_type/${id}/`); +} + +export { + createDictType, + deleteDictType, + getDictTypeList, + patchDictType, + updateDictType, +}; diff --git a/web/apps/web-ele/src/api/system/index.ts b/web/apps/web-ele/src/api/system/index.ts new file mode 100644 index 0000000..f2a248f --- /dev/null +++ b/web/apps/web-ele/src/api/system/index.ts @@ -0,0 +1,3 @@ +export * from './dept'; +export * from './menu'; +export * from './role'; diff --git a/web/apps/web-ele/src/api/system/menu.ts b/web/apps/web-ele/src/api/system/menu.ts new file mode 100644 index 0000000..a391646 --- /dev/null +++ b/web/apps/web-ele/src/api/system/menu.ts @@ -0,0 +1,168 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemMenuApi { + /** 徽标颜色集合 */ + export const BadgeVariants = [ + 'default', + 'destructive', + 'primary', + 'success', + 'warning', + ] as const; + /** 徽标类型集合 */ + export const BadgeTypes = ['dot', 'normal'] as const; + /** 菜单类型集合 */ + export const MenuTypes = [ + 'catalog', + 'menu', + 'embedded', + 'link', + 'button', + ] as const; + /** 系统菜单 */ + export interface SystemMenu { + [key: string]: any; + /** 后端权限标识 */ + auth_code: string; + /** 子级 */ + children?: SystemMenu[]; + /** 组件 */ + component?: string; + /** 菜单ID */ + id: string; + /** 菜单元数据 */ + meta?: { + /** 激活时显示的图标 */ + activeIcon?: string; + /** 作为路由时,需要激活的菜单的Path */ + activePath?: string; + /** 固定在标签栏 */ + affixTab?: boolean; + /** 在标签栏固定的顺序 */ + affixTabOrder?: number; + /** 徽标内容(当徽标类型为normal时有效) */ + badge?: string; + /** 徽标类型 */ + badgeType?: (typeof BadgeTypes)[number]; + /** 徽标颜色 */ + badgeVariants?: (typeof BadgeVariants)[number]; + /** 在菜单中隐藏下级 */ + hideChildrenInMenu?: boolean; + /** 在面包屑中隐藏 */ + hideInBreadcrumb?: boolean; + /** 在菜单中隐藏 */ + hideInMenu?: boolean; + /** 在标签栏中隐藏 */ + hideInTab?: boolean; + /** 菜单图标 */ + icon?: string; + /** 内嵌Iframe的URL */ + iframeSrc?: string; + /** 是否缓存页面 */ + keepAlive?: boolean; + /** 外链页面的URL */ + link?: string; + /** 同一个路由最大打开的标签数 */ + maxNumOfOpenTab?: number; + /** 无需基础布局 */ + noBasicLayout?: boolean; + /** 是否在新窗口打开 */ + openInNewWindow?: boolean; + /** 菜单排序 */ + order?: number; + /** 额外的路由参数 */ + query?: Recordable; + /** 菜单标题 */ + title?: string; + }; + /** 菜单名称 */ + name: string; + /** 路由路径 */ + path: string; + /** 父级ID */ + pid: string; + /** 重定向 */ + redirect?: string; + /** 菜单类型 */ + type: (typeof MenuTypes)[number]; + } +} + +/** + * 获取菜单数据列表 + */ +async function getMenuList() { + return requestClient.get>('/system/menu/'); +} + +async function isMenuNameExists( + name: string, + id?: SystemMenuApi.SystemMenu['id'], +) { + const url = id ? `/system/menu/${id}/` : `/system/menu/`; + return requestClient.get(url, { + params: { id, name }, + }); +} + +async function isMenuSearchExists( + name: string, + id?: SystemMenuApi.SystemMenu['id'], + pid?: SystemMenuApi.SystemMenu['pid'], +) { + return requestClient.get('/system/menu/name-search', { + params: { name, id, pid }, + }); +} + +async function isMenuPathExists( + path: string, + id?: SystemMenuApi.SystemMenu['id'], +) { + return requestClient.get('/system/menu/path-exists', { + params: { id, path }, + }); +} + +/** + * 创建菜单 + * @param data 菜单数据 + */ +async function createMenu( + data: Omit, +) { + return requestClient.post('/system/menu/', data); +} + +/** + * 更新菜单 + * + * @param id 菜单 ID + * @param data 菜单数据 + */ +async function updateMenu( + id: string, + data: Omit, +) { + return requestClient.put(`/system/menu/${id}/`, data); +} + +/** + * 删除菜单 + * @param id 菜单 ID + */ +async function deleteMenu(id: string) { + return requestClient.delete(`/system/menu/${id}/`); +} + +export { + createMenu, + deleteMenu, + getMenuList, + isMenuNameExists, + isMenuPathExists, + isMenuSearchExists, + updateMenu, +}; diff --git a/web/apps/web-ele/src/api/system/role.ts b/web/apps/web-ele/src/api/system/role.ts new file mode 100644 index 0000000..1b273f9 --- /dev/null +++ b/web/apps/web-ele/src/api/system/role.ts @@ -0,0 +1,70 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemRoleApi { + export interface SystemRole { + [key: string]: any; + id: string; + name: string; + permissions: []; + profile: { + create_time: string; + permissions: []; + remark?: string; + status: 0 | 1; + }; + } +} + +/** + * 获取角色列表数据 + */ +async function getRoleList(params: Recordable) { + return requestClient.get>('/system/role/', { + params, + }); +} + +/** + * 创建角色 + * @param data 角色数据 + */ +async function createRole(data: Omit) { + return requestClient.post('/system/role/', data); +} + +/** + * 更新角色 + * + * @param id 角色 ID + * @param data 角色数据 + */ +async function updateRole( + id: string, + data: Omit, +) { + return requestClient.put(`/system/role/${id}/`, data); +} +/** + * 更新角色 + * + * @param id 角色 ID + * @param data 角色数据 + */ +async function patchRole( + id: string, + data: Omit, +) { + return requestClient.patch(`/system/role/${id}/`, data); +} + +/** + * 删除角色 + * @param id 角色 ID + */ +async function deleteRole(id: string) { + return requestClient.delete(`/system/role/${id}/`); +} + +export { createRole, deleteRole, getRoleList, patchRole, updateRole }; diff --git a/web/apps/web-ele/src/api/system/tenant_package.ts b/web/apps/web-ele/src/api/system/tenant_package.ts new file mode 100644 index 0000000..0811749 --- /dev/null +++ b/web/apps/web-ele/src/api/system/tenant_package.ts @@ -0,0 +1,74 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemTenantPackageApi { + export interface SystemTenantPackage { + [key: string]: any; + id: string; + name: string; + } +} + +/** + * 获取租户列表数据 + */ +async function getTenantPackageList(params: Recordable) { + return requestClient.get>( + '/system/tenant_package/', + { + params, + }, + ); +} + +/** + * 创建租户 + * @param data 租户数据 + */ +async function createTenantPackage( + data: Omit, +) { + return requestClient.post('/system/tenant_package/', data); +} + +/** + * 更新租户 + * + * @param id 租户 ID + * @param data 租户数据 + */ +async function updateTenantPackage( + id: string, + data: Omit, +) { + return requestClient.put(`/system/tenant_package/${id}/`, data); +} +/** + * 更新租户 + * + * @param id 租户 ID + * @param data 租户数据 + */ +async function patchTenantPackage( + id: string, + data: Omit, +) { + return requestClient.patch(`/system/tenant_package/${id}/`, data); +} + +/** + * 删除租户 + * @param id 租户 ID + */ +async function deleteTenantPackage(id: string) { + return requestClient.delete(`/system/tenant_package/${id}/`); +} + +export { + createTenantPackage, + deleteTenantPackage, + getTenantPackageList, + patchTenantPackage, + updateTenantPackage, +}; diff --git a/web/apps/web-ele/src/api/system/tenants.ts b/web/apps/web-ele/src/api/system/tenants.ts new file mode 100644 index 0000000..c120614 --- /dev/null +++ b/web/apps/web-ele/src/api/system/tenants.ts @@ -0,0 +1,71 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export namespace SystemTenantsApi { + export interface SystemTenants { + [key: string]: any; + id: string; + name: string; + } +} + +/** + * 获取租户列表数据 + */ +async function getTenantsList(params: Recordable) { + return requestClient.get>( + '/system/tenants/', + { + params, + }); +} + +/** + * 创建租户 + * @param data 租户数据 + */ +async function createTenants(data: Omit) { + return requestClient.post('/system/tenants/', data); +} + +/** + * 更新租户 + * + * @param id 租户 ID + * @param data 租户数据 + */ +async function updateTenants( + id: string, + data: Omit, +) { + return requestClient.put(`/system/tenants/${id}/`, data); +} +/** + * 更新租户 + * + * @param id 租户 ID + * @param data 租户数据 + */ +async function patchTenants( + id: string, + data: Omit, +) { + return requestClient.patch(`/system/tenants/${id}/`, data); +} + +/** + * 删除租户 + * @param id 租户 ID + */ +async function deleteTenants(id: string) { + return requestClient.delete(`/system/tenants/${id}/`); +} + +export { + createTenants, + deleteTenants, + getTenantsList, + patchTenants, + updateTenants, +}; diff --git a/web/apps/web-ele/src/bootstrap.ts b/web/apps/web-ele/src/bootstrap.ts index e5befb5..313482a 100644 --- a/web/apps/web-ele/src/bootstrap.ts +++ b/web/apps/web-ele/src/bootstrap.ts @@ -6,11 +6,13 @@ import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; import '@vben/styles'; import '@vben/styles/ele'; +import ElementPlus from 'element-plus' import { useTitle } from '@vueuse/core'; import { ElLoading } from 'element-plus'; import { $t, setupI18n } from '#/locales'; +import { registerPermissionDirective } from '#/utils/permission'; import { initComponentAdapter } from './adapter/component'; import { initSetupVbenForm } from './adapter/form'; @@ -51,7 +53,8 @@ async function bootstrap(namespace: string) { // 安装权限指令 registerAccessDirective(app); - + // 注册自定义v-permission指令 + registerPermissionDirective(app); // 初始化 tippy const { initTippy } = await import('@vben/common-ui/es/tippy'); initTippy(app); @@ -62,6 +65,7 @@ async function bootstrap(namespace: string) { // 配置Motion插件 const { MotionPlugin } = await import('@vben/plugins/motion'); app.use(MotionPlugin); + app.use(ElementPlus); // 动态更新标题 watchEffect(() => { diff --git a/web/apps/web-ele/src/locales/langs/en-US/system.json b/web/apps/web-ele/src/locales/langs/en-US/system.json new file mode 100644 index 0000000..bbd9845 --- /dev/null +++ b/web/apps/web-ele/src/locales/langs/en-US/system.json @@ -0,0 +1,113 @@ +{ + "title": "System Management", + "dept": { + "name": "Department", + "title": "Department Management", + "deptName": "Department Name", + "status": "Status", + "createTime": "Created At", + "remark": "Remarks", + "operation": "Actions", + "parentDept": "Parent Department" + }, + "menu": { + "title": "Menu Management", + "parent": "Parent Menu", + "menuTitle": "Menu Title", + "menuName": "Menu Name", + "name": "Menu", + "type": "Menu Type", + "typeCatalog": "Catalog", + "typeMenu": "Menu", + "typeButton": "Button", + "typeLink": "External Link", + "typeEmbedded": "Embedded Page", + "icon": "Icon", + "activeIcon": "Active Icon", + "activePath": "Active Path", + "path": "Route Path", + "component": "Component", + "status": "Status", + "sort": "sort", + "auth_code": "Permission Code", + "badge": "Badge", + "operation": "Actions", + "linkSrc": "Link URL", + "affixTab": "Pin Tab", + "keepAlive": "Keep Alive", + "hideInMenu": "Hide in Menu", + "hideInTab": "Hide in Tab Bar", + "hideChildrenInMenu": "Hide Children in Menu", + "hideInBreadcrumb": "Hide in Breadcrumb", + "advancedSettings": "Advanced Settings", + "activePathMustExist": "The path must correspond to a valid menu", + "activePathHelp": "When navigating to this route, if it doesn’t appear in the menu, you must specify the menu path that should be activated.", + "badgeType": { + "title": "Badge Type", + "dot": "Dot", + "normal": "Text", + "none": "None" + }, + "badgeVariants": "Badge Style" + }, + "role": { + "title": "Role Management", + "list": "Role List", + "name": "Role", + "roleName": "Role Name", + "id": "Role ID", + "status": "Status", + "remark": "Remarks", + "createTime": "Created At", + "operation": "Actions", + "permissions": "Permissions", + "setPermissions": "Configure Permissions" + }, + "tenant": { + "name": "Tenant", + "contact_mobile": "Contact Mobile", + "website": "Website", + "package_id": "Package Name", + "expire_time": "Expiration Time", + "account_count": "Account Limit", + "tenantName": "Tenant Name" + }, + "tenant_package": { + "name": "Tenant Package", + "contact_mobile": "Contact Mobile", + "website": "Website", + "package_id": "Package Name", + "expire_time": "Expiration Time", + "account_count": "Account Limit", + "tenantName": "Tenant Name" + }, + "dict_type": { + "name": "Dictionary Name", + "title": "Dictionary Name", + "type": "Dictionary Type" + }, + "dict_data": { + "name": "Dictionary Label", + "title": "Dictionary Data", + "type": "Dictionary Value" + }, + "post": { + "name": "Post", + "title": "Post Management" + }, + "user": { + "name": "User", + "title": "User Management" + }, + "login_log": { + "name": "login log", + "title": "login log" + }, + "status": "Status", + "remark": "Remarks", + "creator": "creator", + "modifier": "modifier", + "createTime": "Created At", + "operation": "Actions", + "updateTime": "Updated At" +} diff --git a/web/apps/web-ele/src/locales/langs/zh-CN/system.json b/web/apps/web-ele/src/locales/langs/zh-CN/system.json new file mode 100644 index 0000000..2cb169f --- /dev/null +++ b/web/apps/web-ele/src/locales/langs/zh-CN/system.json @@ -0,0 +1,114 @@ +{ + "title": "系统管理", + "dept": { + "list": "部门列表", + "createTime": "创建时间", + "deptName": "部门名称", + "name": "部门", + "operation": "操作", + "parentDept": "上级部门", + "remark": "备注", + "status": "状态", + "title": "部门管理" + }, + "menu": { + "list": "菜单列表", + "activeIcon": "激活图标", + "sort": "排序", + "activePath": "激活路径", + "activePathHelp": "跳转到当前路由时,需要激活的菜单路径。\n当不在导航菜单中显示时,需要指定激活路径", + "activePathMustExist": "该路径未能找到有效的菜单", + "advancedSettings": "其它设置", + "affixTab": "固定在标签", + "auth_code": "权限标识", + "badge": "徽章内容", + "badgeVariants": "徽标样式", + "badgeType": { + "dot": "点", + "none": "无", + "normal": "文字", + "title": "徽标类型" + }, + "component": "页面组件", + "hideChildrenInMenu": "隐藏子菜单", + "hideInBreadcrumb": "在面包屑中隐藏", + "hideInMenu": "隐藏菜单", + "hideInTab": "在标签栏中隐藏", + "icon": "图标", + "keepAlive": "缓存标签页", + "linkSrc": "链接地址", + "menuName": "菜单名称", + "menuTitle": "标题", + "name": "菜单", + "operation": "操作", + "parent": "上级菜单", + "path": "路由地址", + "status": "状态", + "title": "菜单管理", + "type": "类型", + "typeButton": "按钮", + "typeCatalog": "目录", + "typeEmbedded": "内嵌", + "typeLink": "外链", + "typeMenu": "菜单" + }, + "role": { + "title": "角色管理", + "list": "角色列表", + "name": "角色", + "roleName": "角色名称", + "id": "角色ID", + "status": "状态", + "remark": "备注", + "createTime": "创建时间", + "operation": "操作", + "permissions": "权限", + "setPermissions": "授权" + }, + "tenant": { + "name": "租户", + "contact_mobile": "联系手机", + "website": "绑定域名", + "package_id": "租户套餐编号", + "expire_time": "过期时间", + "account_count": "账号数量", + "tenantName": "租户名称" + }, + "tenant_package": { + "name": "租户套餐", + "website": "绑定域名", + "package_id": "租户套餐编号", + "expire_time": "过期时间", + "account_count": "账号数量", + "tenantName": "租户名称" + }, + "dict_type": { + "name": "字典名称", + "title": "字典名称", + "type": "字典类型" + }, + "dict_data": { + "name": "字典数据", + "title": "字典数据", + "type": "字典键值" + }, + "post": { + "name": "岗位", + "title": "岗位管理" + }, + "user": { + "name": "用户", + "title": "用户管理" + }, + "login_log": { + "name": "登录日志", + "title": "登录日志" + }, + "status": "状态", + "remark": "备注", + "creator": "创建人", + "modifier": "修改人", + "createTime": "创建时间", + "operation": "操作", + "updateTime": "更新时间" +} diff --git a/web/apps/web-ele/src/models/base.ts b/web/apps/web-ele/src/models/base.ts new file mode 100644 index 0000000..a679174 --- /dev/null +++ b/web/apps/web-ele/src/models/base.ts @@ -0,0 +1,136 @@ +import type { Recordable } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +export interface CoreModel { + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; +} + +// 通用Model基类 +export class BaseModel< + T, + CreateData = Omit, + UpdateData = Partial, +> { + protected baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + /** + * 通用操作方法 + * @param url 操作路径 + * @param data 请求数据 + * @param id 是否针对单条记录的操作 + * @param method 请求方法 + */ + async action( + url: string, + data: any = {}, + id: null | number = null, + method = 'post', + ) { + const baseUrl = id + ? `${this.baseUrl}${id}/${url}/` + : `${this.baseUrl}${url}/`; + + const config = + method === 'get' + ? { + url: baseUrl, + method: 'get', + params: data, + } + : { + url: baseUrl, + method, + data, + }; + + return requestClient.request(url, config); + } + + /** + * 创建记录 + */ + async create(data: CreateData) { + return requestClient.post(this.baseUrl, data); + } + + /** + * 删除记录 + */ + async delete(id: number) { + return requestClient.delete(`${this.baseUrl}${id}/`); + } + + /** + * 导出数据 + */ + /** + * 导出数据 + */ + async export(params: Recordable = {}) { + return requestClient.get(`${this.baseUrl}export/`, { + params, + responseType: 'blob', // 二进制流 + }); + } + + /** + * 获取列表数据 + */ + async list(params: Recordable = {}) { + return requestClient.get>(this.baseUrl, { params }); + } + + /** + * 部分更新记录 + */ + async patch(id: number, data: Partial) { + return requestClient.patch(`${this.baseUrl}${id}/`, data); + } + + /** + * 获取单条记录 + */ + async retrieve(id: number) { + return requestClient.get(`${this.baseUrl}${id}/`); + } + /** + * 全量更新记录 + */ + async update(id: number, data: UpdateData) { + return requestClient.put(`${this.baseUrl}${id}/`, data); + } +} +// +// // 字典类型专用Model +// export class SystemDictTypeModel extends BaseModel { +// constructor() { +// super('/system/dict_type/'); +// } +// } +// +// // AmazonListingModel示例 +// export class AmazonListingModel extends BaseModel { +// constructor() { +// super('amazon/amazon_listing/'); +// } +// } +// const dictTypeModel = new SystemDictTypeModel(); +// +// // 获取列表 +// dictTypeModel.list().then(({ data }) => console.log(data)); +// +// // 创建记录 +// dictTypeModel.create({ name: '新字典', type: 'new_type' }); +// +// // 更新记录 +// dictTypeModel.patch('123', { name: '更新后的字典' }); diff --git a/web/apps/web-ele/src/models/index.ts b/web/apps/web-ele/src/models/index.ts new file mode 100644 index 0000000..8a185aa --- /dev/null +++ b/web/apps/web-ele/src/models/index.ts @@ -0,0 +1 @@ +export * from './base'; diff --git a/web/apps/web-ele/src/models/system/login_log.ts b/web/apps/web-ele/src/models/system/login_log.ts new file mode 100644 index 0000000..2d5df7b --- /dev/null +++ b/web/apps/web-ele/src/models/system/login_log.ts @@ -0,0 +1,23 @@ +import { BaseModel } from '#/models/base'; + +export namespace SystemLoginLogApi { + export interface SystemLoginLog { + id: number; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + username: string; + result: number; + user_ip: string; + user_agent: string; + } +} + +export class SystemLoginLogModel extends BaseModel { + constructor() { + super('/system/login_log/'); + } +} diff --git a/web/apps/web-ele/src/models/system/post.ts b/web/apps/web-ele/src/models/system/post.ts new file mode 100644 index 0000000..b22fc08 --- /dev/null +++ b/web/apps/web-ele/src/models/system/post.ts @@ -0,0 +1,23 @@ +import { BaseModel } from '#/models/base'; + +export namespace SystemPostApi { + export interface SystemPost { + id: number; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + code: string; + name: string; + sort: number; + status: number; + } +} + +export class SystemPostModel extends BaseModel { + constructor() { + super('/system/post/'); + } +} diff --git a/web/apps/web-ele/src/models/system/user.ts b/web/apps/web-ele/src/models/system/user.ts new file mode 100644 index 0000000..d061a66 --- /dev/null +++ b/web/apps/web-ele/src/models/system/user.ts @@ -0,0 +1,40 @@ +import { BaseModel } from '#/models/base'; + +export namespace SystemUserApi { + export interface SystemUser { + id: number; + password: string; + last_login: string; + is_superuser: boolean; + username: string; + first_name: string; + last_name: string; + email: string; + is_staff: boolean; + is_active: boolean; + date_joined: string; + remark: string; + creator: string; + modifier: string; + update_time: string; + create_time: string; + is_deleted: boolean; + mobile: string; + nickname: string; + gender: number; + language: string; + city: string; + province: string; + country: string; + avatar_url: string; + status: number; + login_date: string; + login_ip: any; + } +} + +export class SystemUserModel extends BaseModel { + constructor() { + super('/system/user/'); + } +} diff --git a/web/apps/web-ele/src/preferences.ts b/web/apps/web-ele/src/preferences.ts index b2e9ace..72d8e40 100644 --- a/web/apps/web-ele/src/preferences.ts +++ b/web/apps/web-ele/src/preferences.ts @@ -9,5 +9,6 @@ export const overridesPreferences = defineOverridesPreferences({ // overrides app: { name: import.meta.env.VITE_APP_TITLE, + accessMode: 'backend', // 或 'frontend' }, }); diff --git a/web/apps/web-ele/src/router/routes/modules/system.ts b/web/apps/web-ele/src/router/routes/modules/system.ts new file mode 100644 index 0000000..279deef --- /dev/null +++ b/web/apps/web-ele/src/router/routes/modules/system.ts @@ -0,0 +1,102 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'ion:settings-outline', + order: 9997, + title: $t('system.title'), + }, + name: 'System', + path: '/system', + children: [ + { + path: '/system/user', + name: 'SystemUser', + meta: { + icon: 'mdi:account-group', + title: $t('system.user.title'), + }, + component: () => import('#/views/system/user/list.vue'), + }, + { + path: '/system/role', + name: 'SystemRole', + meta: { + icon: 'mdi:account-group', + title: $t('system.role.title'), + }, + component: () => import('#/views/system/role/list.vue'), + }, + { + path: '/system/menu', + name: 'SystemMenu', + meta: { + icon: 'mdi:menu', + title: $t('system.menu.title'), + }, + component: () => import('#/views/system/menu/list.vue'), + }, + { + path: '/system/dept', + name: 'SystemDept', + meta: { + icon: 'charm:organisation', + title: $t('system.dept.title'), + }, + component: () => import('#/views/system/dept/list.vue'), + }, + { + path: '/system/post', + name: 'SystemPost', + meta: { + icon: 'charm:organisation', + title: $t('system.post.title'), + }, + component: () => import('#/views/system/post/list.vue'), + }, + { + path: '/system/dict_type', + name: 'SystemDictType', + meta: { + icon: 'mdi:menu', + title: '字典列表', + }, + component: () => import('#/views/system/dict_type/list.vue'), + }, + { + path: '/system/dict_data', + name: 'SystemDictData', + meta: { + icon: 'mdi:menu', + title: '字典数据', + hideInMenu: true, // 关键配置:设置为 true 时菜单将被隐藏 + // affix: true, + }, + component: () => import('#/views/system/dict_data/list.vue'), + }, + // { + // path: '/system/tenants', + // name: 'SystemTenants', + // meta: { + // icon: 'mdi:menu', + // title: '租户列表', + // }, + // component: () => import('#/views/system/tenants/list.vue'), + // }, + // { + // path: '/system/tenant_package', + // name: 'SystemTenantPackage', + // meta: { + // icon: 'charm:organisation', + // title: '租户套餐', + // }, + // component: () => import('#/views/system/tenant_package/list.vue'), + // }, + ], + }, +]; + +export default routes; diff --git a/web/apps/web-ele/src/store/auth.ts b/web/apps/web-ele/src/store/auth.ts index 74fadfe..987b7e1 100644 --- a/web/apps/web-ele/src/store/auth.ts +++ b/web/apps/web-ele/src/store/auth.ts @@ -13,9 +13,13 @@ import { defineStore } from 'pinia'; import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; import { $t } from '#/locales'; +import { usePermissionStore } from './permission'; + export const useAuthStore = defineStore('auth', () => { const accessStore = useAccessStore(); const userStore = useUserStore(); + const permissionStore = usePermissionStore(); + const router = useRouter(); const loginLoading = ref(false); @@ -63,7 +67,7 @@ export const useAuthStore = defineStore('auth', () => { if (userInfo?.realName) { ElNotification({ - message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, + message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.username}`, title: $t('authentication.loginSuccess'), type: 'success', }); @@ -102,6 +106,11 @@ export const useAuthStore = defineStore('auth', () => { let userInfo: null | UserInfo = null; userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); + // 设置权限 + if (userInfo && Array.isArray(userInfo.permissions)) { + permissionStore.setPermissions(userInfo.permissions); + } + return userInfo; } diff --git a/web/apps/web-ele/src/store/permission.ts b/web/apps/web-ele/src/store/permission.ts new file mode 100644 index 0000000..f46d6d8 --- /dev/null +++ b/web/apps/web-ele/src/store/permission.ts @@ -0,0 +1,30 @@ +import { ref } from 'vue'; + +import { defineStore } from 'pinia'; + +export const usePermissionStore = defineStore('permission', () => { + // 权限码列表 + const permissions = ref([]); + + // 设置权限码 + function setPermissions(perms: string[]) { + permissions.value = perms || []; + } + + // 判断是否有某个权限 + function hasPermission(code: string): boolean { + return permissions.value.includes(code); + } + + // 重置 + function $reset() { + permissions.value = []; + } + + return { + permissions, + setPermissions, + hasPermission, + $reset, + }; +}) diff --git a/web/apps/web-ele/src/utils/date.ts b/web/apps/web-ele/src/utils/date.ts new file mode 100644 index 0000000..77473cf --- /dev/null +++ b/web/apps/web-ele/src/utils/date.ts @@ -0,0 +1,14 @@ +import dayjs from 'dayjs'; + +/** + * 格式化 ISO 时间字符串为 'YYYY-MM-DD HH:mm:ss' + * @param value ISO 时间字符串 + * @param format 自定义格式,默认 'YYYY-MM-DD HH:mm:ss' + */ +export function format_datetime( + value: Date | string, + format = 'YYYY-MM-DD HH:mm:ss', +): string { + if (!value) return ''; + return dayjs(value).format(format); +} diff --git a/web/apps/web-ele/src/utils/dict.ts b/web/apps/web-ele/src/utils/dict.ts new file mode 100644 index 0000000..84c1aef --- /dev/null +++ b/web/apps/web-ele/src/utils/dict.ts @@ -0,0 +1,5 @@ +export enum DICT_TYPE { + USER_TYPE = 'user_type', + + // TEMU_ORDER_STATUS = 'temu_order_status', +} diff --git a/web/apps/web-ele/src/utils/permission.ts b/web/apps/web-ele/src/utils/permission.ts new file mode 100644 index 0000000..be93c50 --- /dev/null +++ b/web/apps/web-ele/src/utils/permission.ts @@ -0,0 +1,48 @@ +import type { App, DirectiveBinding } from 'vue'; + +import { usePermissionStore } from '#/store/permission'; + +/** + * 权限按钮option生成工具 + * @param code 权限码 + * @param option 按钮option或字符串 + * @returns 有权限返回option,无权限返回false + */ +export function op(code: string, option: any) { + const permissionStore = usePermissionStore(); + return permissionStore.hasPermission(code) ? option : false; +} + +/** + * 全局权限判断函数,适用于模板v-if + */ +export function hasPermission(code: string): boolean { + const permissionStore = usePermissionStore(); + return permissionStore.hasPermission(code); +} + +/** + * v-permission 自定义指令 + * 用法:v-permission="'system:user:create'" + * 或 v-permission="['system:user:create', 'system:user:update']" + */ +const permissionDirective = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const codes = Array.isArray(binding.value) + ? binding.value + : [binding.value]; + const permissionStore = usePermissionStore(); + const has = codes.some((code) => permissionStore.hasPermission(code)); + if (!has) { + el.parentNode && el.remove(); + } + }, +}; + +/** + * 注册全局权限指令 + * @param app Vue App 实例 + */ +export function registerPermissionDirective(app: App) { + app.directive('permission', permissionDirective); +} diff --git a/web/apps/web-ele/src/views/_core/authentication/login.vue b/web/apps/web-ele/src/views/_core/authentication/login.vue index 099e4c8..1f8a977 100644 --- a/web/apps/web-ele/src/views/_core/authentication/login.vue +++ b/web/apps/web-ele/src/views/_core/authentication/login.vue @@ -2,9 +2,9 @@ import type { VbenFormSchema } from '@vben/common-ui'; import type { BasicOption } from '@vben/types'; -import { computed, markRaw } from 'vue'; +import { computed } from 'vue'; -import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; +import { AuthenticationLogin, z } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { useAuthStore } from '#/store'; @@ -24,7 +24,7 @@ const MOCK_USER_OPTIONS: BasicOption[] = [ }, { label: 'User', - value: 'jack', + value: 'chenze', }, ]; @@ -57,7 +57,7 @@ const formSchema = computed((): VbenFormSchema[] => { ); if (findUser) { form.setValues({ - password: '123456', + password: 'admin123', username: findUser.value, }); } @@ -78,13 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => { label: $t('authentication.password'), rules: z.string().min(1, { message: $t('authentication.passwordTip') }), }, - { - component: markRaw(SliderCaptcha), - fieldName: 'captcha', - rules: z.boolean().refine((value) => value, { - message: $t('authentication.verifyRequiredTip'), - }), - }, + // { + // component: markRaw(SliderCaptcha), + // fieldName: 'captcha', + // rules: z.boolean().refine((value) => value, { + // message: $t('authentication.verifyRequiredTip'), + // }), + // }, ]; }); diff --git a/web/apps/web-ele/src/views/system/dept/data.ts b/web/apps/web-ele/src/views/system/dept/data.ts new file mode 100644 index 0000000..8255e4c --- /dev/null +++ b/web/apps/web-ele/src/views/system/dept/data.ts @@ -0,0 +1,160 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemDeptApi } from '#/api/system/dept'; + +import { z } from '#/adapter/form'; +import { getDeptList } from '#/api/system/dept'; +import { $t } from '#/locales'; +import { format_datetime } from '#/utils/date'; +import { op } from '#/utils/permission'; + +/** + * 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + api: getDeptList, + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + }, + fieldName: 'pid', + label: $t('system.dept.parentDept'), + }, + { + component: 'Input', + fieldName: 'name', + label: $t('system.dept.deptName'), + rules: z + .string() + .min(2, $t('ui.formRules.minLength', [$t('system.dept.deptName'), 2])) + .max( + 20, + $t('ui.formRules.maxLength', [$t('system.dept.deptName'), 20]), + ), + }, + { + component: 'InputNumber', + fieldName: 'sort', + label: '显示排序', + }, + { + component: 'Input', + fieldName: 'leader', + label: '负责人', + }, + { + component: 'Input', + fieldName: 'phone', + label: '联系电话', + }, + { + component: 'Input', + fieldName: 'email', + label: '邮箱', + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: $t('common.enabled'), value: 1 }, + { label: $t('common.disabled'), value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: $t('system.dept.status'), + }, + { + component: 'Textarea', + componentProps: { + maxLength: 50, + rows: 3, + showCount: true, + }, + fieldName: 'remark', + label: $t('system.dept.remark'), + rules: z + .string() + .max(50, $t('ui.formRules.maxLength', [$t('system.dept.remark'), 50])) + .optional(), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + align: 'left', + field: 'name', + fixed: 'left', + title: $t('system.dept.deptName'), + treeNode: true, + width: 150, + }, + { + field: 'sort', + title: '排序', + }, + { + cellRender: { name: 'CellTag' }, + field: 'status', + title: $t('system.dept.status'), + width: 100, + }, + { + field: 'create_time', + title: $t('system.dept.createTime'), + width: 180, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'remark', + title: $t('system.dept.remark'), + }, + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.dept.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + op('system:dept:create', { code: 'append', text: '新增下级' }), + op('system:dept:edit', 'edit'), + op('system:dept:delete', { + code: 'delete', + disabled: (row: SystemDeptApi.SystemDept) => { + return !!(row.children && row.children.length > 0); + }, + }), + ].filter(Boolean), + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: $t('system.dept.operation'), + width: 200, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/dept/list.vue b/web/apps/web-ele/src/views/system/dept/list.vue new file mode 100644 index 0000000..2f64006 --- /dev/null +++ b/web/apps/web-ele/src/views/system/dept/list.vue @@ -0,0 +1,147 @@ + + diff --git a/web/apps/web-ele/src/views/system/dept/modules/form.vue b/web/apps/web-ele/src/views/system/dept/modules/form.vue new file mode 100644 index 0000000..8c7b5c5 --- /dev/null +++ b/web/apps/web-ele/src/views/system/dept/modules/form.vue @@ -0,0 +1,80 @@ + + + diff --git a/web/apps/web-ele/src/views/system/dict_data/data.ts b/web/apps/web-ele/src/views/system/dict_data/data.ts new file mode 100644 index 0000000..913e398 --- /dev/null +++ b/web/apps/web-ele/src/views/system/dict_data/data.ts @@ -0,0 +1,211 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemDictDataApi } from '#/api/system/dict_data'; + +import { z } from '#/adapter/form'; +import { getDictTypeList } from '#/api/system/dict_type'; +import { $t } from '#/locales'; +import {format_datetime} from "#/utils/date"; + +/** + * 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + api: getDictTypeList, + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + }, + fieldName: 'dict_type', + label: '字典类型', + }, + { + component: 'Input', + fieldName: 'label', + label: '字典标签', + rules: z + .string() + .min(2, $t('ui.formRules.minLength', [$t('system.dict_data.type'), 2])) + .max( + 20, + $t('ui.formRules.maxLength', [$t('system.dict_data.type'), 20]), + ), + }, + { + component: 'Input', + fieldName: 'value', + label: '字典键值', + rules: z + .string() + .min(2, $t('ui.formRules.minLength', [$t('system.dict_data.type'), 2])) + .max( + 50, + $t('ui.formRules.maxLength', [$t('system.dict_data.type'), 50]), + ), + }, + { + component: 'InputNumber', + fieldName: 'sort', + label: '字典排序', + }, + { + component: 'ApiSelect', + fieldName: 'color_type', + label: '颜色类型', + componentProps: { + name: 'CellTag', + options: [ + { + value: 'default', + label: '默认', + }, + { + value: 'primary', + label: '主要', + }, + { + value: 'success', + label: '成功', + }, + { + value: 'info', + label: '信息', + }, + { + value: 'warning', + label: '警告', + }, + { + value: 'danger', + label: '危险', + }, + ], + } + }, + { + component: 'Input', + fieldName: 'css_class', + label: 'CSS Class', + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: '状态', + }, + { + component: 'Input', + componentProps: { + maxLength: 50, + rows: 3, + showCount: true, + }, + fieldName: 'remark', + label: '备注', + rules: z + .string() + .max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50])) + .optional(), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + align: 'left', + field: 'id', + fixed: 'left', + title: '字典编码', + treeNode: true, + width: 150, + }, + { + field: 'label', + title: '字典标签', + }, + { + field: 'value', + title: '字典键值', + }, + { + field: 'sort', + title: '字典排序', + }, + { + field: 'color_type', + title: '颜色类型', + }, + { + field: 'css_class', + title: 'CSS Class', + }, + { + cellRender: { + name: 'CellTag', + }, + field: 'status', + title: '状态', + width: 100, + }, + { + field: 'remark', + title: '备注', + }, + { + field: 'create_time', + title: '创建时间', + width: 180, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.dict_data.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + 'edit', // 默认的编辑按钮 + { + code: 'delete', // 默认的删除按钮 + disabled: (row: SystemDictDataApi.SystemDictData) => { + return !!(row.children && row.children.length > 0); + }, + }, + ], + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: '操作', + width: 200, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/dict_data/list.vue b/web/apps/web-ele/src/views/system/dict_data/list.vue new file mode 100644 index 0000000..8d9e40e --- /dev/null +++ b/web/apps/web-ele/src/views/system/dict_data/list.vue @@ -0,0 +1,143 @@ + + diff --git a/web/apps/web-ele/src/views/system/dict_data/modules/form.vue b/web/apps/web-ele/src/views/system/dict_data/modules/form.vue new file mode 100644 index 0000000..fb65d56 --- /dev/null +++ b/web/apps/web-ele/src/views/system/dict_data/modules/form.vue @@ -0,0 +1,82 @@ + + + + diff --git a/web/apps/web-ele/src/views/system/dict_type/data.ts b/web/apps/web-ele/src/views/system/dict_type/data.ts new file mode 100644 index 0000000..1ac5fc4 --- /dev/null +++ b/web/apps/web-ele/src/views/system/dict_type/data.ts @@ -0,0 +1,147 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemDictTypeApi } from '#/api/system/dict_type'; + +import { z } from '#/adapter/form'; +import { $t } from '#/locales'; +import { format_datetime } from '#/utils/date'; + +/** + * 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: '字典名称', + rules: z + .string() + .min(2, $t('ui.formRules.minLength', [$t('system.dict_type.name'), 2])) + .max( + 20, + $t('ui.formRules.maxLength', [$t('system.dict_type.name'), 20]), + ), + }, + { + component: 'Input', + fieldName: 'type', + label: '字典类型', + rules: z + .string() + .min(2, $t('ui.formRules.minLength', [$t('system.dict_type.type'), 2])) + .max( + 20, + $t('ui.formRules.maxLength', [$t('system.dict_type.type'), 20]), + ), + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: '状态', + }, + { + component: 'Input', + componentProps: { + maxLength: 50, + rows: 3, + showCount: true, + }, + fieldName: 'remark', + label: '备注', + rules: z + .string() + .max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50])) + .optional(), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + align: 'left', + field: 'id', + fixed: 'left', + title: '字典编号', + treeNode: true, + width: 150, + }, + { + field: 'name', + title: '字典名称', + }, + { + field: 'type', + title: '字典类型', + width: 180, + }, + { + cellRender: { + name: 'CellTag', + + }, + field: 'status', + title: '状态', + width: 100, + }, + { + field: 'remark', + title: '备注', + }, + { + field: 'create_time', + title: '创建时间', + width: 180, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.dict_type.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + 'edit', // 默认的编辑按钮 + { + code: 'view', // 新增查看详情按钮(可自定义code) + text: '数据', // 按钮文本(国际化) + }, + { + code: 'delete', // 默认的删除按钮 + disabled: (row: SystemDictTypeApi.SystemDictType) => { + return !!(row.children && row.children.length > 0); + }, + }, + ], + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: '操作', + width: 200, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/dict_type/list.vue b/web/apps/web-ele/src/views/system/dict_type/list.vue new file mode 100644 index 0000000..835d42a --- /dev/null +++ b/web/apps/web-ele/src/views/system/dict_type/list.vue @@ -0,0 +1,150 @@ + + diff --git a/web/apps/web-ele/src/views/system/dict_type/modules/form.vue b/web/apps/web-ele/src/views/system/dict_type/modules/form.vue new file mode 100644 index 0000000..6c952df --- /dev/null +++ b/web/apps/web-ele/src/views/system/dict_type/modules/form.vue @@ -0,0 +1,79 @@ + + + + diff --git a/web/apps/web-ele/src/views/system/login_log/data.ts b/web/apps/web-ele/src/views/system/login_log/data.ts new file mode 100644 index 0000000..be38c54 --- /dev/null +++ b/web/apps/web-ele/src/views/system/login_log/data.ts @@ -0,0 +1,95 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { SystemLoginLogApi } from '#/models/system/login_log'; + +import { z } from '#/adapter/form'; +import { $t } from '#/locales'; +import { format_datetime } from '#/utils/date'; + +/** + * 获取编辑表单的字段配置 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'username', + label: 'username', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['username'])) + .max(100, $t('ui.formRules.maxLength', ['username', 100])), + }, + { + component: 'InputNumber', + fieldName: 'result', + label: 'result', + }, + { + component: 'Input', + fieldName: 'user_ip', + label: 'user ip', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['user ip'])) + .max(100, $t('ui.formRules.maxLength', ['user ip', 100])), + }, + { + component: 'Input', + fieldName: 'user_agent', + label: 'user agent', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['user agent'])) + .max(100, $t('ui.formRules.maxLength', ['user agent', 100])), + }, + { + component: 'Input', + fieldName: 'remark', + label: 'remark', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['remark'])) + .max(100, $t('ui.formRules.maxLength', ['remark', 100])), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + */ +export function useColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + }, + { + field: 'username', + title: '用户名', + }, + { + cellRender: { + name: 'CellTag', + }, + field: 'result_text', + title: '登录结果', + }, + { + field: 'user_ip', + title: '登录地址', + }, + { + field: 'user_agent', + title: '浏览器', + }, + { + field: 'create_time', + title: $t('system.createTime'), + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/login_log/list.vue b/web/apps/web-ele/src/views/system/login_log/list.vue new file mode 100644 index 0000000..22745c1 --- /dev/null +++ b/web/apps/web-ele/src/views/system/login_log/list.vue @@ -0,0 +1,47 @@ + + + diff --git a/web/apps/web-ele/src/views/system/login_log/modules/form.vue b/web/apps/web-ele/src/views/system/login_log/modules/form.vue new file mode 100644 index 0000000..6fddbb9 --- /dev/null +++ b/web/apps/web-ele/src/views/system/login_log/modules/form.vue @@ -0,0 +1,79 @@ + + + + diff --git a/web/apps/web-ele/src/views/system/menu/data.ts b/web/apps/web-ele/src/views/system/menu/data.ts new file mode 100644 index 0000000..494b8dd --- /dev/null +++ b/web/apps/web-ele/src/views/system/menu/data.ts @@ -0,0 +1,114 @@ +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemMenuApi } from '#/api/system/menu'; + +import { $t } from '#/locales'; + +export function getMenuTypeOptions() { + return [ + { + color: 'processing', + label: $t('system.menu.typeCatalog'), + value: 'catalog', + }, + { color: 'default', label: $t('system.menu.typeMenu'), value: 'menu' }, + { color: 'error', label: $t('system.menu.typeButton'), value: 'button' }, + { + color: 'success', + label: $t('system.menu.typeEmbedded'), + value: 'embedded', + }, + { color: 'warning', label: $t('system.menu.typeLink'), value: 'link' }, + ]; +} + +export function useColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + align: 'left', + field: 'meta.title', + fixed: 'left', + slots: { default: 'title' }, + title: $t('system.menu.menuTitle'), + treeNode: true, + width: 250, + }, + { + align: 'center', + cellRender: { name: 'CellTag', options: getMenuTypeOptions() }, + field: 'type', + title: $t('system.menu.type'), + width: 100, + }, + { + field: 'auth_code', + title: $t('system.menu.auth_code'), + width: 200, + }, + { + field: 'sort', + title: $t('system.menu.sort'), + width: 200, + }, + { + align: 'left', + field: 'path', + title: $t('system.menu.path'), + width: 200, + }, + + { + align: 'left', + field: 'component', + formatter: ({ row }) => { + switch (row.type) { + case 'catalog': + case 'menu': { + return row.component ?? ''; + } + case 'embedded': { + return row.meta?.iframeSrc ?? ''; + } + case 'link': { + return row.meta?.link ?? ''; + } + } + return ''; + }, + minWidth: 200, + title: $t('system.menu.component'), + }, + { + cellRender: { name: 'CellTag' }, + field: 'status', + title: $t('system.menu.status'), + width: 100, + }, + + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'append', + text: '新增下级', + }, + 'edit', // 默认的编辑按钮 + 'delete', // 默认的删除按钮 + ], + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: $t('system.menu.operation'), + width: 200, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/menu/list.vue b/web/apps/web-ele/src/views/system/menu/list.vue new file mode 100644 index 0000000..9ab4564 --- /dev/null +++ b/web/apps/web-ele/src/views/system/menu/list.vue @@ -0,0 +1,162 @@ + + + diff --git a/web/apps/web-ele/src/views/system/menu/modules/form.vue b/web/apps/web-ele/src/views/system/menu/modules/form.vue new file mode 100644 index 0000000..6964958 --- /dev/null +++ b/web/apps/web-ele/src/views/system/menu/modules/form.vue @@ -0,0 +1,524 @@ + + diff --git a/web/apps/web-ele/src/views/system/post/data.ts b/web/apps/web-ele/src/views/system/post/data.ts new file mode 100644 index 0000000..c719fd7 --- /dev/null +++ b/web/apps/web-ele/src/views/system/post/data.ts @@ -0,0 +1,124 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemPostApi } from '#/models/system/post'; + +import { z } from '#/adapter/form'; +import { $t } from '#/locales'; +import { format_datetime } from '#/utils/date'; + +/** + * 获取编辑表单的字段配置 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: '名称', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['名称'])) + .max(100, $t('ui.formRules.maxLength', ['名称', 100])), + }, + { + component: 'Input', + fieldName: 'code', + label: '编码', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['编码'])) + .max(100, $t('ui.formRules.maxLength', ['编码', 100])), + }, + { + component: 'InputNumber', + fieldName: 'sort', + label: '排序', + rules: z.number(), + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: '开启', value: true }, + { label: '关闭', value: false }, + ], + optionType: 'button', + }, + defaultValue: true, + fieldName: 'status', + label: '是否启用', + }, + { + component: 'Input', + fieldName: 'remark', + label: '备注', + }, + ]; +} + +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'id', + }, + { + field: 'name', + title: '岗位名称', + }, + { + field: 'code', + title: '岗位编码', + }, + { + field: 'sort', + title: '排序', + }, + { + cellRender: { + name: 'CellTag', + }, + field: 'status', + title: '状态', + width: 100, + }, + { + field: 'remark', + title: '备注', + }, + { + field: 'create_time', + title: '创建时间', + width: 180, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.post.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + 'edit', // 默认的编辑按钮 + { + code: 'delete', // 默认的删除按钮 + }, + ], + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: '操作', + width: 200, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/post/list.vue b/web/apps/web-ele/src/views/system/post/list.vue new file mode 100644 index 0000000..68699af --- /dev/null +++ b/web/apps/web-ele/src/views/system/post/list.vue @@ -0,0 +1,132 @@ + + + diff --git a/web/apps/web-ele/src/views/system/post/modules/form.vue b/web/apps/web-ele/src/views/system/post/modules/form.vue new file mode 100644 index 0000000..7f347fd --- /dev/null +++ b/web/apps/web-ele/src/views/system/post/modules/form.vue @@ -0,0 +1,79 @@ + + + + diff --git a/web/apps/web-ele/src/views/system/role/data.ts b/web/apps/web-ele/src/views/system/role/data.ts new file mode 100644 index 0000000..c596b86 --- /dev/null +++ b/web/apps/web-ele/src/views/system/role/data.ts @@ -0,0 +1,118 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemRoleApi } from '#/api/system/role'; + +import { $t } from '#/locales'; +import { format_datetime } from '#/utils/date'; + +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: $t('system.role.roleName'), + rules: 'required', + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: $t('common.enabled'), value: 1 }, + { label: $t('common.disabled'), value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: $t('system.role.status'), + }, + { + component: 'Textarea', + fieldName: 'remark', + label: $t('system.role.remark'), + }, + { + component: 'Input', + fieldName: 'permissions', + formItemClass: 'items-start', + label: $t('system.role.setPermissions'), + modelPropName: 'modelValue', + }, + ]; +} + +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: $t('system.role.roleName'), + }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: [ + { label: $t('common.enabled'), value: 1 }, + { label: $t('common.disabled'), value: 0 }, + ], + }, + fieldName: 'status', + label: $t('system.role.status'), + }, + ]; +} + +export function useColumns( + onActionClick: OnActionClickFn, + onStatusChange?: (newStatus: any, row: T) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: $t('system.role.roleName'), + width: 200, + }, + { + field: 'id', + title: $t('system.role.id'), + width: 200, + }, + { + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: onStatusChange ? 'CellSwitch' : 'CellTag', + }, + field: 'status', + title: $t('system.role.status'), + width: 100, + }, + { + field: 'remark', + minWidth: 100, + title: $t('system.role.remark'), + }, + { + field: 'create_time', + title: $t('system.role.createTime'), + width: 200, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.role.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + }, + field: 'operation', + fixed: 'right', + title: $t('system.role.operation'), + width: 130, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/role/list.vue b/web/apps/web-ele/src/views/system/role/list.vue new file mode 100644 index 0000000..99932b1 --- /dev/null +++ b/web/apps/web-ele/src/views/system/role/list.vue @@ -0,0 +1,166 @@ + + diff --git a/web/apps/web-ele/src/views/system/role/modules/form.vue b/web/apps/web-ele/src/views/system/role/modules/form.vue new file mode 100644 index 0000000..0253cef --- /dev/null +++ b/web/apps/web-ele/src/views/system/role/modules/form.vue @@ -0,0 +1,138 @@ + + + diff --git a/web/apps/web-ele/src/views/system/tenant_package/data.ts b/web/apps/web-ele/src/views/system/tenant_package/data.ts new file mode 100644 index 0000000..92b84d9 --- /dev/null +++ b/web/apps/web-ele/src/views/system/tenant_package/data.ts @@ -0,0 +1,133 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemTenantPackageApi } from '#/api/system/tenant_package'; + +import { z } from '#/adapter/form'; +import { getTenantPackageList } from '#/api/system/tenant_package'; +import { $t } from '#/locales'; + +/** + * 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: '套餐名', + rules: z + .string() + .min(2, $t('ui.formRules.minLength', [$t('system.dept.deptName'), 2])) + .max( + 20, + $t('ui.formRules.maxLength', [$t('system.dept.deptName'), 20]), + ), + }, + // 菜单权限 + { + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + api: getTenantPackageList, + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + }, + fieldName: 'pid', + label: $t('system.dept.parentDept'), + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: '状态', + }, + { + component: 'Input', + componentProps: { + maxLength: 50, + rows: 3, + showCount: true, + }, + fieldName: 'remark', + label: '备注', + rules: z + .string() + .max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50])) + .optional(), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + align: 'left', + field: 'name', + fixed: 'left', + title: '套餐名', + treeNode: true, + width: 150, + }, + { + cellRender: { name: 'CellTag' }, + field: 'status', + title: '状态', + width: 100, + }, + { + field: 'remark', + title: '备注', + }, + { + field: 'create_time', + title: '创建时间', + width: 180, + }, + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.dept.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + 'edit', // 默认的编辑按钮 + { + code: 'delete', // 默认的删除按钮 + disabled: (row: SystemTenantPackageApi.SystemTenantPackage) => { + return !!(row.children && row.children.length > 0); + }, + }, + ], + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: '操作', + width: 200, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/tenant_package/list.vue b/web/apps/web-ele/src/views/system/tenant_package/list.vue new file mode 100644 index 0000000..fbca1d9 --- /dev/null +++ b/web/apps/web-ele/src/views/system/tenant_package/list.vue @@ -0,0 +1,135 @@ + + diff --git a/web/apps/web-ele/src/views/system/tenant_package/modules/form.vue b/web/apps/web-ele/src/views/system/tenant_package/modules/form.vue new file mode 100644 index 0000000..3a2558f --- /dev/null +++ b/web/apps/web-ele/src/views/system/tenant_package/modules/form.vue @@ -0,0 +1,78 @@ + + + diff --git a/web/apps/web-ele/src/views/system/tenants/data.ts b/web/apps/web-ele/src/views/system/tenants/data.ts new file mode 100644 index 0000000..a4fccdc --- /dev/null +++ b/web/apps/web-ele/src/views/system/tenants/data.ts @@ -0,0 +1,157 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemTenantsApi } from '#/api/system/tenants'; + +import { z } from '#/adapter/form'; +import { $t } from '#/locales'; + +/** + * 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: $t('system.tenant.tenantName'), + rules: z + .string() + .min( + 2, + $t('ui.formRules.minLength', [$t('system.tenant.tenantName'), 2]), + ) + .max( + 20, + $t('ui.formRules.maxLength', [$t('system.tenant.tenantName'), 20]), + ), + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: $t('common.enabled'), value: 1 }, + { label: $t('common.disabled'), value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: $t('system.status'), + }, + { + component: 'Textarea', + componentProps: { + maxLength: 50, + rows: 3, + showCount: true, + }, + fieldName: 'remark', + label: $t('system.remark'), + rules: z + .string() + .max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50])) + .optional(), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + align: 'left', + field: 'name', + fixed: 'left', + title: $t('system.tenant.tenantName'), + treeNode: true, + width: 150, + }, + // `contact_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '联系人', + // `contact_mobile` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系手机', + // `status` tinyint NOT NULL DEFAULT '0' COMMENT '租户状态(0正常 1停用)', + // `website` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '绑定域名', + // `package_id` bigint NOT NULL COMMENT '租户套餐编号', + // `expire_time` datetime NOT NULL COMMENT '过期时间', + // `account_count` int NOT NULL COMMENT '账号数量', + { + cellRender: { name: 'CellTag' }, + field: 'contact_name', + title: $t('system.tenant.contact_mobile'), + width: 100, + }, + { + cellRender: { name: 'CellTag' }, + field: 'website', + title: $t('system.tenant.website'), + width: 100, + }, + { + cellRender: { name: 'CellTag' }, + field: 'package_id', + title: $t('system.tenant.package_id'), + width: 100, + }, + { + cellRender: { name: 'CellTag' }, + field: 'expire_time', + title: $t('system.tenant.expire_time'), + width: 100, + }, + { + cellRender: { name: 'CellTag' }, + field: 'account_count', + title: $t('system.tenant.account_count'), + width: 100, + }, + { + cellRender: { name: 'CellTag' }, + field: 'status', + title: $t('system.status'), + width: 100, + }, + { + field: 'create_time', + title: $t('system.createTime'), + width: 180, + }, + { + field: 'remark', + title: $t('system.remark'), + }, + { + align: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + 'edit', // 默认的编辑按钮 + { + code: 'delete', // 默认的删除按钮 + disabled: (row: SystemTenantsApi.SystemTenants) => { + return !!(row.children && row.children.length > 0); + }, + }, + ], + }, + field: 'operation', + fixed: 'right', + headerAlign: 'center', + showOverflow: false, + title: $t('system.operation'), + width: 200, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/tenants/list.vue b/web/apps/web-ele/src/views/system/tenants/list.vue new file mode 100644 index 0000000..926d0d1 --- /dev/null +++ b/web/apps/web-ele/src/views/system/tenants/list.vue @@ -0,0 +1,131 @@ + + diff --git a/web/apps/web-ele/src/views/system/tenants/modules/form.vue b/web/apps/web-ele/src/views/system/tenants/modules/form.vue new file mode 100644 index 0000000..a420970 --- /dev/null +++ b/web/apps/web-ele/src/views/system/tenants/modules/form.vue @@ -0,0 +1,78 @@ + + + diff --git a/web/apps/web-ele/src/views/system/user/data.ts b/web/apps/web-ele/src/views/system/user/data.ts new file mode 100644 index 0000000..517626c --- /dev/null +++ b/web/apps/web-ele/src/views/system/user/data.ts @@ -0,0 +1,232 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/models/system/user'; + +import { z } from '#/adapter/form'; +import { getDeptList, getRoleList } from '#/api/system'; +import { $t } from '#/locales'; +import { SystemPostModel } from '#/models/system/post'; +import { format_datetime } from '#/utils/date'; + +const systemPost = new SystemPostModel(); + +/** + * 获取编辑表单的字段配置 + */ +export function useSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'username', + label: '用户名', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['用户名'])) + .max(100, $t('ui.formRules.maxLength', ['用户名', 100])), + }, + { + component: 'Input', + fieldName: 'email', + label: '电子邮件地址', + rules: z + .string() + .min(1, $t('ui.formRules.required', ['电子邮件地址'])) + .max(100, $t('ui.formRules.maxLength', ['电子邮件地址', 100])), + }, + { + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + multiple: true, // 允许多选 + api: getDeptList, + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + }, + fieldName: 'dept', + label: $t('system.dept.name'), + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + multiple: true, // 允许多选 + mode: 'multiple', // 允许多选 + api: getRoleList, + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + }, + fieldName: 'role', + label: $t('system.role.name'), + }, + { + component: 'ApiSelect', + componentProps: { + allowClear: true, + multiple: true, // 允许多选 + mode: 'multiple', // 允许多选 + api: () => systemPost.list(), + class: 'w-full', + resultField: 'items', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + }, + fieldName: 'post', + label: $t('system.post.name'), + }, + { + component: 'Input', + fieldName: 'mobile', + label: '手机号', + }, + { + component: 'Input', + fieldName: 'nickname', + label: '昵称', + }, + { + component: 'InputPassword', + fieldName: 'password', + label: '密码', + }, + { + component: 'Input', + fieldName: 'city', + label: '城市', + }, + { + component: 'Input', + fieldName: 'province', + label: '省份', + }, + { + component: 'Input', + fieldName: 'country', + label: '国家', + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'status', + label: $t('system.status'), + }, + { + component: 'Input', + fieldName: 'remark', + label: $t('system.remark'), + }, + ]; +} + +/** + * 获取表格列配置 + * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头 + * @param onActionClick 表格操作按钮点击事件 + */ + +export function useColumns( + onActionClick?: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: 'ID', + }, + { + field: 'username', + title: '用户名', + width: 100, + }, + + { + field: 'is_superuser', + title: '超级用户状态', + }, + { + field: 'date_joined', + title: '加入日期', + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'mobile', + title: 'mobile', + }, + { + cellRender: { + name: 'CellTag', + }, + field: 'status', + title: $t('system.status'), + width: 100, + }, + { + field: 'login_ip', + title: 'login ip', + width: 150, + }, + { + field: 'last_login', + title: '最后登录', + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'remark', + title: $t('system.remark'), + }, + { + field: 'creator', + title: $t('system.creator'), + width: 80, + }, + { + field: 'modifier', + title: $t('system.modifier'), + width: 80, + }, + { + field: 'update_time', + title: $t('system.updateTime'), + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + field: 'create_time', + title: $t('system.createTime'), + width: 150, + formatter: ({ cellValue }) => format_datetime(cellValue), + }, + { + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: $t('system.user.name'), + onClick: onActionClick, + }, + name: 'CellOperation', + options: ['edit', 'delete'], + }, + field: 'action', + fixed: 'right', + title: '操作', + width: 120, + }, + ]; +} diff --git a/web/apps/web-ele/src/views/system/user/list.vue b/web/apps/web-ele/src/views/system/user/list.vue new file mode 100644 index 0000000..9ca6b05 --- /dev/null +++ b/web/apps/web-ele/src/views/system/user/list.vue @@ -0,0 +1,129 @@ + + + diff --git a/web/apps/web-ele/src/views/system/user/modules/form.vue b/web/apps/web-ele/src/views/system/user/modules/form.vue new file mode 100644 index 0000000..0c8d644 --- /dev/null +++ b/web/apps/web-ele/src/views/system/user/modules/form.vue @@ -0,0 +1,82 @@ + + + + diff --git a/web/packages/effects/common-ui/src/ui/authentication/login.vue b/web/packages/effects/common-ui/src/ui/authentication/login.vue index 96d1e4a..e5f1afb 100644 --- a/web/packages/effects/common-ui/src/ui/authentication/login.vue +++ b/web/packages/effects/common-ui/src/ui/authentication/login.vue @@ -31,12 +31,12 @@ const props = withDefaults(defineProps(), { loading: false, qrCodeLoginPath: '/auth/qrcode-login', registerPath: '/auth/register', - showCodeLogin: true, - showForgetPassword: true, - showQrcodeLogin: true, - showRegister: true, + showCodeLogin: false, + showForgetPassword: false, + showQrcodeLogin: false, + showRegister: false, showRememberMe: true, - showThirdPartyLogin: true, + showThirdPartyLogin: false, submitButtonText: '', subTitle: '', title: '',