From 0fed947230fd256497659b4c125214f2ffff4829 Mon Sep 17 00:00:00 2001 From: chenminjie <943130926@qq.com> Date: Tue, 26 Nov 2024 06:04:36 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E3=80=90web-ant=E3=80=91=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E8=AE=A4=E8=AF=81=E6=A8=A1=E5=9D=97=E5=B9=B6=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构认证存储以分别处理认证和权限信息 - 重构验证码组件 - 移除字典数据相关的API和存储功能 - 更新路由生成逻辑和权限控制 - 添加分页和请求客户端相关类型定义 - 更新依赖包版本(crypto-js, qs)及其类型定义 --- apps/web-antd/.env | 9 +- apps/web-antd/.env.development | 11 +- apps/web-antd/package.json | 4 - apps/web-antd/src/adapter/vxe-table.ts | 17 ++ apps/web-antd/src/api/core/auth.ts | 108 +++------ apps/web-antd/src/api/request.ts | 60 ++--- .../src/api/system/dict-data/index.ts | 89 ++++++++ .../src/api/system/dict-type/index.ts | 1 + .../web-antd/src/api/system/dict/dict.data.ts | 49 ----- .../web-antd/src/api/system/dict/dict.type.ts | 44 ---- apps/web-antd/src/layouts/basic.vue | 4 +- apps/web-antd/src/preferences.ts | 2 +- apps/web-antd/src/router/access.ts | 79 +------ apps/web-antd/src/router/guard.ts | 8 +- apps/web-antd/src/router/helper.ts | 7 +- apps/web-antd/src/store/auth.ts | 75 +++---- apps/web-antd/src/store/dict.ts | 83 ------- apps/web-antd/src/store/index.ts | 1 - apps/web-antd/src/types/index.ts | 2 - apps/web-antd/src/types/user.ts | 22 -- apps/web-antd/src/utils/auth.ts | 45 ---- apps/web-antd/src/utils/index.ts | 1 - .../src/views/_core/authentication/login.vue | 181 +++++++-------- packages/effects/common-ui/package.json | 2 + .../common-ui/src/components/captcha/index.ts | 4 +- .../verification}/Verify/VerifyPoints.vue | 182 ++++++++------- .../verification}/Verify/VerifySlide.vue | 208 ++++++++---------- .../captcha/verification}/Verify/index.ts | 0 .../components/captcha/verification/index.vue | 150 +++++++++++++ .../captcha/verification}/style/verify.css | 2 +- .../captcha/verification/types/index.d.ts | 25 +++ .../captcha/verification}/utils/ase.ts | 0 .../captcha/verification}/utils/util.ts | 0 .../effects/plugins/src/vxe-table/init.ts | 2 + packages/effects/request/package.json | 4 +- .../src/request-client/preset-interceptors.ts | 6 +- .../src/request-client/request-client.ts | 5 + .../request/src/request-client/types.ts | 15 +- packages/stores/src/modules/dict.ts | 63 ++++++ packages/stores/src/modules/index.ts | 2 + packages/stores/src/modules/tenant.ts | 33 +++ packages/stores/src/modules/user.ts | 4 + packages/types/src/index.ts | 1 + .../menus.ts => packages/types/src/menu.ts | 0 packages/types/src/user.ts | 28 ++- .../src/helpers/generate-routes-backend.ts | 2 +- pnpm-lock.yaml | 44 +++- pnpm-workspace.yaml | 4 + 48 files changed, 874 insertions(+), 814 deletions(-) create mode 100644 apps/web-antd/src/api/system/dict-data/index.ts create mode 100644 apps/web-antd/src/api/system/dict-type/index.ts delete mode 100644 apps/web-antd/src/api/system/dict/dict.data.ts delete mode 100644 apps/web-antd/src/api/system/dict/dict.type.ts delete mode 100644 apps/web-antd/src/store/dict.ts delete mode 100644 apps/web-antd/src/types/index.ts delete mode 100644 apps/web-antd/src/types/user.ts delete mode 100644 apps/web-antd/src/utils/auth.ts delete mode 100644 apps/web-antd/src/utils/index.ts rename {apps/web-antd/src/components/Verification/src => packages/effects/common-ui/src/components/captcha/verification}/Verify/VerifyPoints.vue (63%) rename {apps/web-antd/src/components/Verification/src => packages/effects/common-ui/src/components/captcha/verification}/Verify/VerifySlide.vue (68%) rename {apps/web-antd/src/components/Verification/src => packages/effects/common-ui/src/components/captcha/verification}/Verify/index.ts (100%) create mode 100644 packages/effects/common-ui/src/components/captcha/verification/index.vue rename {apps/web-antd/src/components/Verification/src => packages/effects/common-ui/src/components/captcha/verification}/style/verify.css (99%) create mode 100644 packages/effects/common-ui/src/components/captcha/verification/types/index.d.ts rename {apps/web-antd/src/components/Verification/src => packages/effects/common-ui/src/components/captcha/verification}/utils/ase.ts (100%) rename {apps/web-antd/src/components/Verification/src => packages/effects/common-ui/src/components/captcha/verification}/utils/util.ts (100%) create mode 100644 packages/stores/src/modules/dict.ts create mode 100644 packages/stores/src/modules/tenant.ts rename apps/web-antd/src/types/menus.ts => packages/types/src/menu.ts (100%) diff --git a/apps/web-antd/.env b/apps/web-antd/.env index 975ea83c..affa2148 100644 --- a/apps/web-antd/.env +++ b/apps/web-antd/.env @@ -3,14 +3,11 @@ VITE_APP_TITLE=芋道管理系统 # 应用命名空间,用于缓存、store等功能的前缀,确保隔离 VITE_APP_NAMESPACE=yudao-vben-antd +# 是否开启模拟数据 +VITE_NITRO_MOCK=false # 租户开关 VITE_APP_TENANT_ENABLE=true # 验证码的开关 -VITE_APP_CAPTCHA_ENABLE=false - -# 默认账户密码 -VITE_APP_DEFAULT_LOGIN_TENANT=芋道源码 -VITE_APP_DEFAULT_LOGIN_USERNAME=admin -VITE_APP_DEFAULT_LOGIN_PASSWORD=admin123 +VITE_APP_CAPTCHA_ENABLE=true diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development index 3c441c7f..fa8b5b0c 100644 --- a/apps/web-antd/.env.development +++ b/apps/web-antd/.env.development @@ -5,12 +5,15 @@ VITE_BASE=/ # 接口地址 VITE_GLOB_API_URL=/admin-api - -# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=false - # 是否打开 devtools,true 为打开,false 为关闭 VITE_DEVTOOLS=false # 是否注入全局loading VITE_INJECT_APP_LOADING=true + +# 默认租户名称 +VITE_APP_DEFAULT_TENANT_NAME=芋道源码 +# 默认登录用户名 +VITE_APP_DEFAULT_USERNAME=admin +# 默认登录密码 +VITE_APP_DEFAULT_PASSWORD=admin123 diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index 8906b9c8..42afa3c6 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -42,13 +42,9 @@ "@vben/utils": "workspace:*", "@vueuse/core": "catalog:", "ant-design-vue": "catalog:", - "crypto-js": "^4.2.0", "dayjs": "catalog:", "pinia": "catalog:", "vue": "catalog:", "vue-router": "catalog:" - }, - "devDependencies": { - "@types/crypto-js": "^4.2.2" } } diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index 6afe838b..0a346d3a 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -20,6 +20,17 @@ setupVbenVxeTable({ // 全局禁用vxe-table的表单配置,使用formOptions enabled: false, }, + toolbarConfig: { + import: true, + export: true, + refresh: true, + print: true, + zoom: true, + custom: true, + }, + customConfig: { + mode: 'modal', + }, proxyConfig: { autoLoad: true, response: { @@ -29,6 +40,12 @@ setupVbenVxeTable({ showActiveMsg: true, showResponseMsg: false, }, + pagerConfig: { + enabled: true, + }, + sortConfig: { + multiple: true, + }, round: true, showOverflow: true, size: 'small', diff --git a/apps/web-antd/src/api/core/auth.ts b/apps/web-antd/src/api/core/auth.ts index 4efd5029..802094c1 100644 --- a/apps/web-antd/src/api/core/auth.ts +++ b/apps/web-antd/src/api/core/auth.ts @@ -1,7 +1,6 @@ -import type { YudaoUserInfo } from '#/types'; +import type { AuthPermissionInfo } from '@vben/types'; import { baseRequestClient, requestClient } from '#/api/request'; -import { getRefreshToken } from '#/utils'; export namespace AuthApi { /** 登录接口参数 */ @@ -13,40 +12,47 @@ export namespace AuthApi { /** 登录接口返回值 */ export interface LoginResult { - userId: number | string; accessToken: string; refreshToken: string; + userId: number; expiresTime: number; } - export interface RefreshTokenResult { - data: string; - status: number; - } - export interface SmsCodeVO { - mobile: string; - scene: number; - } - - export interface SmsLoginVO { - mobile: string; - code: string; + export interface TenantSimple { + id: number; + name: string; } } /** * 登录 */ -export function loginApi(data: AuthApi.LoginParams) { +export async function loginApi(data: AuthApi.LoginParams) { return requestClient.post('/system/auth/login', data); } /** * 刷新accessToken */ -export function refreshTokenApi() { +export async function refreshTokenApi(refreshToken: string) { return requestClient.post( - `/system/auth/refresh-token?refreshToken=${getRefreshToken()}`, + `/system/auth/refresh-token?refreshToken=${refreshToken}`, + ); +} + +/** + * 退出登录 + */ +export async function logoutApi() { + return requestClient.post('/system/auth/logout'); +} + +/** + * 获取用户权限信息 + */ +export function getAuthPermissionInfoApi() { + return requestClient.get( + '/system/auth/get-permission-info', ); } @@ -55,7 +61,7 @@ export function refreshTokenApi() { * @param name 租户名 * @returns 租户编号 */ -export function getTenantIdByName(name: string) { +export async function getTenantIdByName(name: string) { return requestClient.get( `/system/tenant/get-id-by-name?name=${name}`, ); @@ -66,68 +72,18 @@ export function getTenantIdByName(name: string) { * @param website 域名 * @returns 租户信息 */ -export function getTenantByWebsite(website: string) { - return requestClient.get(`/system/tenant/get-by-website?website=${website}`); -} - -/** - * 退出登录 - */ -export function logoutApi() { - return requestClient.post('/system/auth/logout'); -} - -/** - * 获取用户权限信息 - */ -export function getUserInfo() { - return requestClient.get('/system/auth/get-permission-info'); -} - -/** - * 获取登录验证码 - */ -export function sendSmsCode(data: AuthApi.SmsCodeVO) { - return requestClient.post('/system/auth/send-sms-code', data); -} - -/** - * 短信验证码登录 - */ -export function smsLogin(data: AuthApi.SmsLoginVO) { - return requestClient.post('/system/auth/sms-login', data); -} - -/** - * 社交快捷登录,使用 code 授权码 - */ -export function socialLogin(type: string, code: string, state: string) { - return requestClient.post('/system/auth/social-login', { - type, - code, - state, - }); -} - -/** - * 社交授权的跳转 - */ -export function socialAuthRedirect(type: number, redirectUri: string) { - return requestClient.get( - `/system/auth/social-auth-redirect?type=${type}&redirectUri=${redirectUri}`, +export async function getTenantByWebsite(website: string) { + return requestClient.get( + `/system/tenant/get-by-website?website=${website}`, ); } -/** - * 获取验证图片 以及token - */ -export function getCaptcha(data: any) { +// 获取验证图片 以及token +export async function getCaptcha(data: any) { return baseRequestClient.post('/system/captcha/get', data); } -/** - * 滑动或者点选验证 - */ -export function checkCaptcha(data: any) { +// 滑动或者点选验证 +export async function checkCaptcha(data: any) { return baseRequestClient.post('/system/captcha/check', data); } diff --git a/apps/web-antd/src/api/request.ts b/apps/web-antd/src/api/request.ts index 819b9c22..f5a81a26 100644 --- a/apps/web-antd/src/api/request.ts +++ b/apps/web-antd/src/api/request.ts @@ -10,19 +10,15 @@ import { errorMessageResponseInterceptor, RequestClient, } from '@vben/request'; -import { useAccessStore } from '@vben/stores'; +import { useAccessStore, useTenantStore } from '@vben/stores'; import { message } from 'ant-design-vue'; import { useAuthStore } from '#/store'; -import { getTenantId } from '#/utils'; import { refreshTokenApi } from './core'; -const { apiURL, tenantEnable } = useAppConfig( - import.meta.env, - import.meta.env.PROD, -); +const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); function createRequestClient(baseURL: string) { const client = new RequestClient({ @@ -52,9 +48,12 @@ function createRequestClient(baseURL: string) { */ async function doRefreshToken() { const accessStore = useAccessStore(); - const resp = await refreshTokenApi(); - const newToken = resp.refreshToken; + const resp = await refreshTokenApi(accessStore.refreshToken ?? ''); + const newToken = resp.accessToken; + const newRefreshToken = resp.refreshToken; + accessStore.setAccessToken(newToken); + accessStore.setRefreshToken(newRefreshToken); return newToken; } @@ -66,11 +65,11 @@ function createRequestClient(baseURL: string) { client.addRequestInterceptor({ fulfilled: async (config) => { const accessStore = useAccessStore(); - const tenantId = getTenantId(); + const tenantStore = useTenantStore(); + config.headers.Authorization = formatToken(accessStore.accessToken); config.headers['Accept-Language'] = preferences.app.locale; - config.headers['tenant-id'] = - tenantEnable && tenantId ? tenantId : undefined; + config.headers['tenant-id'] = tenantStore.tenantId ?? undefined; return config; }, }); @@ -78,32 +77,13 @@ function createRequestClient(baseURL: string) { // response数据解构 client.addResponseInterceptor({ fulfilled: (response) => { - // const { config, data: responseData, status, request } = response; - const { data: responseData, request } = response; - // 这个判断的目的是:excel 导出等情况下,系统执行异常,此时返回的是 json,而不是二进制数据 - if ( - (request.responseType === 'blob' || - request.responseType === 'arraybuffer') && - responseData?.code === undefined - ) { - return responseData; - } + const { data: responseData, status } = response; - const { code, data: result } = responseData; - if (responseData && Reflect.has(responseData, 'code') && code === 0) { - return result; - } - - switch (code) { - case 401: { - response.status = 401; - throw Object.assign({}, response, { response }); - } - default: { - response.status = code; - throw Object.assign({}, response, { response }); - } + const { code, data } = responseData; + if (status >= 200 && status < 400 && code === 0) { + return data; } + throw Object.assign({}, response, { response }); }, }); @@ -124,7 +104,8 @@ function createRequestClient(baseURL: string) { // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg // 当前mock接口返回的错误字段是 error 或者 message const responseData = error?.response?.data ?? {}; - const errorMessage = responseData?.error ?? responseData?.message ?? ''; + const errorMessage = + responseData?.error ?? responseData?.message ?? responseData.msg ?? ''; // 如果没有错误信息,则会根据状态码进行提示 message.error(errorMessage || msg); }), @@ -133,11 +114,8 @@ function createRequestClient(baseURL: string) { return client; } -export type PageParam = { - pageNo?: number; - pageSize?: number; -}; - export const requestClient = createRequestClient(apiURL); export const baseRequestClient = new RequestClient({ baseURL: apiURL }); + +export type * from '@vben/request'; diff --git a/apps/web-antd/src/api/system/dict-data/index.ts b/apps/web-antd/src/api/system/dict-data/index.ts new file mode 100644 index 00000000..5b89e1ac --- /dev/null +++ b/apps/web-antd/src/api/system/dict-data/index.ts @@ -0,0 +1,89 @@ +import { type PageParam, requestClient } from '#/api/request'; + +export namespace DictDataApi { + /** + * 字典数据信息 Response VO + */ + export type DictDataRespVO = { + colorType?: string; + createTime?: Date; + cssClass?: string; + dictType: string; + id?: number; + label: string; + remark?: string; + sort?: number; + status?: number; + value: string; + }; + + /** + * 字典类型分页列表 Request VO + */ + export interface DictDataPageReqVO extends PageParam { + dictType?: string; + label?: string; + status?: number; + } + + /** + * 字典数据创建/修改 Request VO + */ + export interface DictDataSaveReqVO { + colorType?: string; + cssClass?: string; + dictType: string; + id?: number; + label: string; + remark?: string; + sort?: number; + status?: number; + value: string; + } + + /** + * 字典数据(精简) Response VO + */ + export interface DictDataSimpleRespVO { + colorType?: string; + cssClass?: string; + dictType: string; + label: string; + value: string; + } +} + +// 查询字典数据(精简)列表 +export const getSimpleDictDataList = () => { + return requestClient.get('/system/dict-data/simple-list'); +}; + +// 查询字典数据列表 +export const getDictDataPage = (params: PageParam) => { + return requestClient.get('/system/dict-data/page', { params }); +}; + +// 查询字典数据详情 +export const getDictData = (id: number) => { + return requestClient.get(`/system/dict-data/get?id=${id}`); +}; + +// 新增字典数据 +export const createDictData = (data: DictDataApi.DictDataSaveReqVO) => { + return requestClient.post('/system/dict-data/create', data); +}; + +// 修改字典数据 +export const updateDictData = (data: DictDataApi.DictDataSaveReqVO) => { + return requestClient.put('/system/dict-data/update', data); +}; + +// 删除字典数据 +export const deleteDictData = (id: number) => { + return requestClient.delete(`/system/dict-data/delete?id=${id}`); +}; + +// 导出字典类型数据 +export const exportDictData = (params: DictDataApi.DictDataPageReqVO) => { + return requestClient.download('/system/dict-data/export', { params }); +}; diff --git a/apps/web-antd/src/api/system/dict-type/index.ts b/apps/web-antd/src/api/system/dict-type/index.ts new file mode 100644 index 00000000..e8f0f04c --- /dev/null +++ b/apps/web-antd/src/api/system/dict-type/index.ts @@ -0,0 +1 @@ +export namespace DictTypeApi {} diff --git a/apps/web-antd/src/api/system/dict/dict.data.ts b/apps/web-antd/src/api/system/dict/dict.data.ts deleted file mode 100644 index c5f9f498..00000000 --- a/apps/web-antd/src/api/system/dict/dict.data.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { requestClient } from '#/api/request'; - -export type DictDataVO = { - colorType: string; - createTime: Date; - cssClass: string; - dictType: string; - id: number | undefined; - label: string; - remark: string; - sort: number | undefined; - status: number; - value: string; -}; - -// 查询字典数据(精简)列表 -export function getSimpleDictDataList() { - return requestClient.get('/system/dict-data/simple-list'); -} - -// 查询字典数据列表 -export function getDictDataPage(params: any) { - return requestClient.get('/system/dict-data/page', params); -} - -// 查询字典数据详情 -export function getDictData(id: number) { - return requestClient.get(`/system/dict-data/get?id=${id}`); -} - -// 新增字典数据 -export function createDictData(data: DictDataVO) { - return requestClient.post('/system/dict-data/create', data); -} - -// 修改字典数据 -export function updateDictData(data: DictDataVO) { - return requestClient.put('/system/dict-data/update', data); -} - -// 删除字典数据 -export function deleteDictData(id: number) { - return requestClient.delete(`/system/dict-data/delete?id=${id}`); -} - -// 导出字典类型数据 -export function exportDictData(params: any) { - return requestClient.download('/system/dict-data/export', params); -} diff --git a/apps/web-antd/src/api/system/dict/dict.type.ts b/apps/web-antd/src/api/system/dict/dict.type.ts deleted file mode 100644 index 59af5711..00000000 --- a/apps/web-antd/src/api/system/dict/dict.type.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { requestClient } from '#/api/request'; - -export type DictTypeVO = { - createTime: Date; - id: number | undefined; - name: string; - remark: string; - status: number; - type: string; -}; - -// 查询字典(精简)列表 -export function getSimpleDictTypeList() { - return requestClient.get('/system/dict-type/list-all-simple'); -} - -// 查询字典列表 -export function getDictTypePage(params: any) { - return requestClient.get('/system/dict-type/page', params); -} - -// 查询字典详情 -export function getDictType(id: number) { - return requestClient.get(`/system/dict-type/get?id=${id}`); -} - -// 新增字典 -export function createDictType(data: DictTypeVO) { - return requestClient.post('/system/dict-type/create', data); -} - -// 修改字典 -export function updateDictType(data: DictTypeVO) { - return requestClient.put('/system/dict-type/update', data); -} - -// 删除字典 -export function deleteDictType(id: number) { - return requestClient.delete(`/system/dict-type/delete?id=${id}`); -} -// 导出字典类型 -export function exportDictType(params: any) { - return requestClient.download('/system/dict-type/export', params); -} diff --git a/apps/web-antd/src/layouts/basic.vue b/apps/web-antd/src/layouts/basic.vue index 5c23354f..a8e790c6 100644 --- a/apps/web-antd/src/layouts/basic.vue +++ b/apps/web-antd/src/layouts/basic.vue @@ -101,7 +101,7 @@ const menus = computed(() => [ ]); const avatar = computed(() => { - return userStore.userInfo?.user.avatar ?? preferences.app.defaultAvatar; + return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar; }); async function handleLogout() { @@ -138,7 +138,7 @@ watch( diff --git a/apps/web-antd/src/preferences.ts b/apps/web-antd/src/preferences.ts index dc3cc35f..4e28df96 100644 --- a/apps/web-antd/src/preferences.ts +++ b/apps/web-antd/src/preferences.ts @@ -8,9 +8,9 @@ import { defineOverridesPreferences } from '@vben/preferences'; export const overridesPreferences = defineOverridesPreferences({ // overrides app: { + name: import.meta.env.VITE_APP_TITLE, /** 后端路由模式 */ accessMode: 'backend', - name: import.meta.env.VITE_APP_TITLE, enableRefreshToken: true, }, }); diff --git a/apps/web-antd/src/router/access.ts b/apps/web-antd/src/router/access.ts index db9fda0c..de599e86 100644 --- a/apps/web-antd/src/router/access.ts +++ b/apps/web-antd/src/router/access.ts @@ -1,16 +1,14 @@ import type { ComponentRecordType, GenerateMenuAndRoutesOptions, - RouteRecordStringComponent, } from '@vben/types'; import { generateAccessible } from '@vben/access'; import { preferences } from '@vben/preferences'; -import { useUserStore } from '@vben/stores'; -import { cloneDeep } from '@vben/utils'; import { message } from 'ant-design-vue'; +import { getAuthPermissionInfoApi } from '#/api'; import { BasicLayout, IFrameView } from '#/layouts'; import { $t } from '#/locales'; @@ -18,75 +16,6 @@ import { buildMenus } from './helper'; const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); -/** - * base路由 - */ -const baseMenus: RouteRecordStringComponent[] = [ - { - component: 'BasicLayout', - meta: { - order: -1, - title: 'page.dashboard.title', - }, - name: 'Dashboard', - path: '/', - redirect: '/analytics', - children: [ - { - name: 'Analytics', - path: '/analytics', - component: '/dashboard/analytics/index', - meta: { - affixTab: true, - icon: 'lucide:area-chart', - title: 'page.dashboard.analytics', - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: '/dashboard/workspace/index', - meta: { - icon: 'carbon:workspace', - title: 'page.dashboard.workspace', - }, - }, - { - name: 'VbenAbout', - path: '/about', - component: '/_core/about/index.vue', - meta: { - icon: 'lucide:copyright', - title: 'demos.vben.about', - }, - }, - ], - }, - { - component: 'BasicLayout', - meta: { - icon: 'ant-design:user-outlined', - order: -1, - title: '个人中心', - hideInMenu: true, - }, - name: 'profile', - path: '/profile', - children: [ - { - name: 'UserProfile', - path: '/profile/index', - component: '/_core/profile/profile.vue', - meta: { - icon: 'ant-design:user-outlined', - title: '个人中心', - hideInMenu: true, - }, - }, - ], - }, -]; - async function generateAccess(options: GenerateMenuAndRoutesOptions) { const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); @@ -102,10 +31,10 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) { content: `${$t('common.loadingMenu')}...`, duration: 1.5, }); - const userStore = useUserStore(); - const menus = userStore.userInfo?.menus; + const authPermissionInfo = await getAuthPermissionInfoApi(); + const menus = authPermissionInfo.menus; const routes = buildMenus(menus); - const menuList = [...cloneDeep(baseMenus), ...routes]; + const menuList = [...routes]; return menuList; }, // 可以指定没有权限跳转403页面 diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index fce5a892..94213301 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -87,9 +87,11 @@ function setupAccessGuard(router: Router) { // 生成路由表 // 当前登录用户拥有的角色标识列表 - const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); - const userRoles = userInfo.roles ?? []; - + let userRoles = userStore.userRoles; + if (!userRoles) { + const authPermissionInfo = await authStore.getAuthPermissionInfo(); + userRoles = authPermissionInfo?.roles ?? []; + } // 生成菜单和路由 const { accessibleMenus, accessibleRoutes } = await generateAccess({ roles: userRoles, diff --git a/apps/web-antd/src/router/helper.ts b/apps/web-antd/src/router/helper.ts index 3c365baa..b47b3f2e 100644 --- a/apps/web-antd/src/router/helper.ts +++ b/apps/web-antd/src/router/helper.ts @@ -1,6 +1,7 @@ -import type { RouteRecordStringComponent } from '@vben/types'; - -import type { AppRouteRecordRaw } from '#/types'; +import type { + AppRouteRecordRaw, + RouteRecordStringComponent, +} from '@vben/types'; import { isHttpUrl } from '@vben/utils'; diff --git a/apps/web-antd/src/store/auth.ts b/apps/web-antd/src/store/auth.ts index f90bac07..22f75e22 100644 --- a/apps/web-antd/src/store/auth.ts +++ b/apps/web-antd/src/store/auth.ts @@ -1,6 +1,4 @@ -import type { Recordable } from '@vben/types'; - -import type { YudaoUserInfo } from '#/types'; +import type { AuthPermissionInfo, Recordable } from '@vben/types'; import { ref } from 'vue'; import { useRouter } from 'vue-router'; @@ -11,16 +9,12 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { notification } from 'ant-design-vue'; import { defineStore } from 'pinia'; -import { getUserInfo, loginApi, logoutApi } from '#/api'; +import { getAuthPermissionInfoApi, loginApi, logoutApi } from '#/api'; import { $t } from '#/locales'; -import { setAccessToken, setRefreshToken } from '#/utils'; - -import { useDictStore } from './dict'; export const useAuthStore = defineStore('auth', () => { const accessStore = useAccessStore(); const userStore = useUserStore(); - const dictStore = useDictStore(); const router = useRouter(); const loginLoading = ref(false); @@ -35,44 +29,37 @@ export const useAuthStore = defineStore('auth', () => { onSuccess?: () => Promise | void, ) { // 异步处理用户登录操作并获取 accessToken - let userInfo: null | YudaoUserInfo = null; + let authPermissionInfo: AuthPermissionInfo | null = null; try { loginLoading.value = true; - const { accessToken, expiresTime, refreshToken } = await loginApi(params); + const { accessToken, refreshToken } = await loginApi(params); // 如果成功获取到 accessToken if (accessToken) { + // 将 accessToken 存储到 accessStore 中 accessStore.setAccessToken(accessToken); accessStore.setRefreshToken(refreshToken); - setAccessToken(accessToken, expiresTime); - setRefreshToken(refreshToken); - // 获取用户信息并存储到 accessStore 中 - const fetchUserInfoResult = await fetchUserInfo(); - userInfo = fetchUserInfoResult; - if (userInfo) { - if (userInfo.roles) { - userStore.setUserRoles(userInfo.roles); - } - // userStore.setMenus(userInfo.menus); - accessStore.setAccessCodes(userInfo.permissions); - if (accessStore.loginExpired) { - accessStore.setLoginExpired(false); - } else { - onSuccess - ? await onSuccess?.() - : await router.push(userInfo.homePath || DEFAULT_HOME_PATH); - } + authPermissionInfo = await getAuthPermissionInfo(); - dictStore.setDictMap(); + if (accessStore.loginExpired) { + accessStore.setLoginExpired(false); + } else { + // 执行成功回调 + await onSuccess?.(); + // 跳转首页 + await router.push(authPermissionInfo.homePath || DEFAULT_HOME_PATH); + } - if (userInfo?.realName) { - notification.success({ - description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, - duration: 3, - message: $t('authentication.loginSuccess'), - }); - } + if ( + authPermissionInfo?.user.realName || + authPermissionInfo.user.nickname + ) { + notification.success({ + description: `${$t('authentication.loginSuccessDesc')}:${authPermissionInfo?.user.realName ?? authPermissionInfo?.user.nickname}`, + duration: 3, + message: $t('authentication.loginSuccess'), + }); } } } finally { @@ -80,7 +67,7 @@ export const useAuthStore = defineStore('auth', () => { } return { - userInfo, + authPermissionInfo, }; } @@ -104,11 +91,13 @@ export const useAuthStore = defineStore('auth', () => { }); } - async function fetchUserInfo() { - let userInfo: null | YudaoUserInfo = null; - userInfo = await getUserInfo(); - userStore.setUserInfo(userInfo); - return userInfo; + async function getAuthPermissionInfo() { + let authPermissionInfo: AuthPermissionInfo | null = null; + authPermissionInfo = await getAuthPermissionInfoApi(); + userStore.setUserInfo(authPermissionInfo.user); + userStore.setUserRoles(authPermissionInfo.roles); + accessStore.setAccessCodes(authPermissionInfo.permissions); + return authPermissionInfo; } function $reset() { @@ -118,7 +107,7 @@ export const useAuthStore = defineStore('auth', () => { return { $reset, authLogin, - fetchUserInfo, + getAuthPermissionInfo, loginLoading, logout, }; diff --git a/apps/web-antd/src/store/dict.ts b/apps/web-antd/src/store/dict.ts deleted file mode 100644 index ca2825d0..00000000 --- a/apps/web-antd/src/store/dict.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { StorageManager } from '@vben/utils'; - -import { acceptHMRUpdate, defineStore } from 'pinia'; - -import { getSimpleDictDataList } from '#/api/system/dict/dict.data'; - -const DICT_STORAGE_KEY = 'DICT_STORAGE__'; - -interface DictValueType { - value: any; - label: string; - colorType?: string; - cssClass?: string; -} - -// interface DictTypeType { -// dictType: string; -// dictValue: DictValueType[]; -// } - -interface DictState { - dictMap: Map; - isSetDict: boolean; -} - -const storage = new StorageManager({ - prefix: import.meta.env.VITE_APP_NAMESPACE, - storageType: 'sessionStorage', -}); - -export const useDictStore = defineStore('dict', { - actions: { - async setDictMap() { - try { - const dataRes = await getSimpleDictDataList(); - - const dictDataMap = new Map(); - - dataRes.forEach((item: any) => { - let dictTypeArray = dictDataMap.get(item.dictType); - if (!dictTypeArray) { - dictTypeArray = []; - } - dictTypeArray.push({ - value: item.value, - label: item.label, - colorType: item.colorType, - cssClass: item.cssClass, - }); - dictDataMap.set(item.dictType, dictTypeArray); - }); - - this.dictMap = dictDataMap; - this.isSetDict = true; - - // 将字典数据存储到 sessionStorage 中 - storage.setItem(DICT_STORAGE_KEY, dictDataMap, 60); - } catch (error) { - console.error('Failed to set dictionary values:', error); - } - }, - }, - getters: { - getDictMap: (state) => state.dictMap, - getDictData: (state) => (dictType: string) => { - return state.dictMap.get(dictType); - }, - getDictOptions: (state) => (dictType: string) => { - return state.dictMap.get(dictType); - }, - }, - persist: [{ pick: ['dictMap', 'isSetDict'] }], - state: (): DictState => ({ - dictMap: new Map(), - isSetDict: false, - }), -}); - -// 解决热更新问题 -const hot = import.meta.hot; -if (hot) { - hot.accept(acceptHMRUpdate(useDictStore, hot)); -} diff --git a/apps/web-antd/src/store/index.ts b/apps/web-antd/src/store/index.ts index b6a7763b..269586ee 100644 --- a/apps/web-antd/src/store/index.ts +++ b/apps/web-antd/src/store/index.ts @@ -1,2 +1 @@ export * from './auth'; -export * from './dict'; diff --git a/apps/web-antd/src/types/index.ts b/apps/web-antd/src/types/index.ts deleted file mode 100644 index f65cf3a8..00000000 --- a/apps/web-antd/src/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './menus'; -export * from './user'; diff --git a/apps/web-antd/src/types/user.ts b/apps/web-antd/src/types/user.ts deleted file mode 100644 index 570a7328..00000000 --- a/apps/web-antd/src/types/user.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { BasicUserInfo } from '@vben/types'; - -import type { AppRouteRecordRaw } from '#/types'; - -/** 用户信息 */ -type ExBasicUserInfo = { - deptId: number; -} & BasicUserInfo; - -/** 用户信息 */ -interface YudaoUserInfo extends ExBasicUserInfo { - permissions: string[]; - menus: AppRouteRecordRaw[]; - /** - * 首页地址 - */ - homePath: string; - roles: string[]; - user: ExBasicUserInfo; -} - -export type { ExBasicUserInfo, YudaoUserInfo }; diff --git a/apps/web-antd/src/utils/auth.ts b/apps/web-antd/src/utils/auth.ts deleted file mode 100644 index 2b068515..00000000 --- a/apps/web-antd/src/utils/auth.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { StorageManager } from '@vben/utils'; -// token key -const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN__'; - -const REFRESH_TOKEN_KEY = 'REFRESH_TOKEN__'; - -const TENANT_ID_KEY = 'TENANT_ID__'; - -const storage = new StorageManager({ - prefix: import.meta.env.VITE_APP_NAMESPACE, - storageType: 'sessionStorage', -}); - -function getAccessToken(): null | string { - return storage.getItem(ACCESS_TOKEN_KEY); -} - -function setAccessToken(value: string, unix: number) { - return storage.setItem(ACCESS_TOKEN_KEY, value, unix - Date.now()); -} - -function getRefreshToken(): null | string { - return storage.getItem(REFRESH_TOKEN_KEY); -} - -function setRefreshToken(value: string) { - return storage.setItem(REFRESH_TOKEN_KEY, value); -} - -function getTenantId(): null | number { - return storage.getItem(TENANT_ID_KEY); -} - -function setTenantId(value: number) { - return storage.setItem(TENANT_ID_KEY, value); -} - -export { - getAccessToken, - getRefreshToken, - getTenantId, - setAccessToken, - setRefreshToken, - setTenantId, -}; diff --git a/apps/web-antd/src/utils/index.ts b/apps/web-antd/src/utils/index.ts deleted file mode 100644 index 269586ee..00000000 --- a/apps/web-antd/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './auth'; diff --git a/apps/web-antd/src/views/_core/authentication/login.vue b/apps/web-antd/src/views/_core/authentication/login.vue index b708a431..4138dd55 100644 --- a/apps/web-antd/src/views/_core/authentication/login.vue +++ b/apps/web-antd/src/views/_core/authentication/login.vue @@ -1,129 +1,132 @@ diff --git a/packages/effects/common-ui/package.json b/packages/effects/common-ui/package.json index 2ff5fdf3..dac0277f 100644 --- a/packages/effects/common-ui/package.json +++ b/packages/effects/common-ui/package.json @@ -31,11 +31,13 @@ "@vben/types": "workspace:*", "@vueuse/core": "catalog:", "@vueuse/integrations": "catalog:", + "crypto-js": "catalog:", "qrcode": "catalog:", "vue": "catalog:", "vue-router": "catalog:" }, "devDependencies": { + "@types/crypto-js": "catalog:", "@types/qrcode": "catalog:" } } diff --git a/packages/effects/common-ui/src/components/captcha/index.ts b/packages/effects/common-ui/src/components/captcha/index.ts index 6ad68c49..e155c635 100644 --- a/packages/effects/common-ui/src/components/captcha/index.ts +++ b/packages/effects/common-ui/src/components/captcha/index.ts @@ -1,6 +1,8 @@ export { default as PointSelectionCaptcha } from './point-selection-captcha/index.vue'; -export { default as PointSelectionCaptchaCard } from './point-selection-captcha/index.vue'; +export { default as PointSelectionCaptchaCard } from './point-selection-captcha/index.vue'; export { default as SliderCaptcha } from './slider-captcha/index.vue'; export { default as SliderRotateCaptcha } from './slider-rotate-captcha/index.vue'; export type * from './types'; + +export { default as Verification } from './verification/index.vue'; diff --git a/apps/web-antd/src/components/Verification/src/Verify/VerifyPoints.vue b/packages/effects/common-ui/src/components/captcha/verification/Verify/VerifyPoints.vue similarity index 63% rename from apps/web-antd/src/components/Verification/src/Verify/VerifyPoints.vue rename to packages/effects/common-ui/src/components/captcha/verification/Verify/VerifyPoints.vue index 3e962794..f1dc502d 100644 --- a/apps/web-antd/src/components/Verification/src/Verify/VerifyPoints.vue +++ b/packages/effects/common-ui/src/components/captcha/verification/Verify/VerifyPoints.vue @@ -1,9 +1,8 @@ -