From abcec2371e4dd919625ea779e83856089915718e Mon Sep 17 00:00:00 2001 From: K <1175047471@qq.com> Date: Wed, 3 Apr 2024 10:39:00 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E7=99=BB=E9=99=86=E3=80=81=E6=9D=83?= =?UTF-8?q?=E9=99=90=E6=8E=A5=E5=8F=A3=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 4 +- src/api/base/user/index.ts | 51 ++++++++++++++++- src/api/base/user/types.ts | 24 ++++---- .../header/components/lock/LockModal.vue | 2 +- .../header/components/user-dropdown/index.vue | 6 +- src/router/guard/permissionGuard.ts | 13 ++--- src/store/modules/lock.ts | 25 ++++---- src/store/modules/user.ts | 57 +++++++++++++++---- src/types/axios.d.ts | 1 + src/utils/http/axios/index.ts | 10 +++- src/views/base/lock/LockPage.vue | 2 +- src/views/base/login/LoginForm.vue | 36 ++++++++++-- src/views/base/login/SessionTimeoutLogin.vue | 4 +- .../workbench/components/WorkbenchHeader.vue | 2 +- 14 files changed, 174 insertions(+), 63 deletions(-) diff --git a/.env.development b/.env.development index 0011e520..b90d28f4 100644 --- a/.env.development +++ b/.env.development @@ -7,13 +7,13 @@ VITE_PUBLIC_PATH = / # 本地开发代理,可以解决跨域及多地址代理 # 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题 # 可以有多个,注意多个不能换行,否则代理将会失效 -VITE_PROXY = [] +VITE_PROXY = [["/api","https://cop-demo.szsciit.com/api"]] # 是否删除Console.log VITE_DROP_CONSOLE = false # 接口地址,如果没有跨域问题,直接在这里配置即可 -VITE_GLOB_API_URL = http://192.168.1.100:48081/admin-api +VITE_GLOB_API_URL = # 文件上传接口 可选 VITE_GLOB_UPLOAD_URL = /upload diff --git a/src/api/base/user/index.ts b/src/api/base/user/index.ts index 92f64c09..8235bbac 100644 --- a/src/api/base/user/index.ts +++ b/src/api/base/user/index.ts @@ -4,13 +4,23 @@ import type { ErrorMessageMode } from '@/types/axios' export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { return defHttp.post<LoginResult>({ - url: '/auth/login', - params, + url: '/api/deframe-auth/oauth/token', + params: { + username: params.username, + password: params.password, + grant_type: 'captcha', + scope: 'all', + type: 'account', + }, headers: { - 'tenant-id': '000000', + 'Tenant-Id': params.tenantId, + 'Captcha-Key': params.captchaKey, + 'Captcha-Code': params.captchaCode, }, }, { errorMessageMode: mode, + joinParamsToUrl: true, + isTransformResponse: false, }) } @@ -22,8 +32,43 @@ export function getUserInfo() { }) } +export interface __MenuItem { + id: string + parentId: string + code: string + name: string + alias: string + path: string + source: string + sort: number + category: number + action: number + children?: __MenuItem[] +} + +export function getUserRouters() { + return defHttp.get<__MenuItem[]>({ + url: '/api/deframe-system/menu/routes', + }) +} + +export function getUserButtons() { + return defHttp.get({ + url: '/api/deframe-system/menu/buttons', + }) +} + export function doLogout() { return defHttp.post({ url: '/auth/logout', }) } + +export function getLoginCaptcha() { + return defHttp.get<{ image: string, key: string }>({ + url: '/api/deframe-auth/oauth/captcha', + }, { + isTransformResponse: false, + withoutAuth: true, + }) +} diff --git a/src/api/base/user/types.ts b/src/api/base/user/types.ts index d5dc3837..a5edfdcd 100644 --- a/src/api/base/user/types.ts +++ b/src/api/base/user/types.ts @@ -3,13 +3,15 @@ import type { MenuItem } from '@/api/system/menu/types' export interface LoginParams { username: string password: string + tenantId: string + captchaKey: string + captchaCode: string } -export interface LoginResult { +export interface LoginResult extends User { userId: string - accessToken: string - refreshToken: string - expiresTime: number + access_token: string + refresh_token: string } export interface UserInfo { @@ -19,14 +21,12 @@ export interface UserInfo { } export interface User { - id: string - tenantId: string + user_id: string + tenant_id: string account: string - realName: string + real_name: string + nick_name: string avatar?: string - email?: string - mobile?: string - remark?: string - deptId: string - roleId: string + role_id: string + role_name: string } diff --git a/src/layouts/default/header/components/lock/LockModal.vue b/src/layouts/default/header/components/lock/LockModal.vue index 61a2445d..af2092b2 100644 --- a/src/layouts/default/header/components/lock/LockModal.vue +++ b/src/layouts/default/header/components/lock/LockModal.vue @@ -13,7 +13,7 @@ const { t } = useI18n() const userStore = useUserStore() const lockStore = useLockStore() -const getRealName = computed(() => userStore.getUserInfo?.user.realName) +const getRealName = computed(() => userStore.getUserInfo?.user.real_name) const [register, { closeModal }] = useModalInner() const [registerForm, { validateFields, resetFields }] = useForm({ diff --git a/src/layouts/default/header/components/user-dropdown/index.vue b/src/layouts/default/header/components/user-dropdown/index.vue index 571cfcc4..ef7fb05d 100644 --- a/src/layouts/default/header/components/user-dropdown/index.vue +++ b/src/layouts/default/header/components/user-dropdown/index.vue @@ -32,8 +32,8 @@ const { getShowDoc, getUseLockPage } = useHeaderSetting() const userStore = useUserStore() const getUserInfo = computed(() => { - const { realName = '', avatar } = userStore.getUserInfo.user || {} - return { realName, avatar: avatar || headerImg } + const { real_name = '', avatar } = userStore.getUserInfo.user || {} + return { real_name, avatar: avatar || headerImg } }) const [register, { openModal }] = useModal() @@ -76,7 +76,7 @@ function handleMenuClick(e: MenuInfo) { </Avatar> <span :class="`${prefixCls}__info hidden md:block`"> <span :class="`${prefixCls}__name`" class="truncate"> - {{ getUserInfo.realName }} + {{ getUserInfo.real_name }} </span> </span> </span> diff --git a/src/router/guard/permissionGuard.ts b/src/router/guard/permissionGuard.ts index 3302c6dd..aabf7d51 100644 --- a/src/router/guard/permissionGuard.ts +++ b/src/router/guard/permissionGuard.ts @@ -6,7 +6,9 @@ import { PageEnum } from '@/enums/pageEnum' import { useUserStoreWithOut } from '@/store/modules/user' import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic' -import { useSystemEnumStoreWithOut } from '@/store/modules/systemEnum' +import { getAuthCache } from '@/utils/auth' +import { USER_INFO_KEY } from '@/enums/cacheEnum' +import type { UserInfo } from '@/api/base/user/types' // import { RootRoute } from '@/router/routes' @@ -19,7 +21,6 @@ const whitePathList: PageEnum[] = [LOGIN_PATH] export function createPermissionGuard(router: Router) { const userStore = useUserStoreWithOut() const permissionStore = usePermissionStoreWithOut() - const systemEnumStore = useSystemEnumStoreWithOut() router.beforeEach(async (to, from, next) => { // if ( @@ -33,13 +34,14 @@ export function createPermissionGuard(router: Router) { // } const token = userStore.getAccessToken + const userInfo = getAuthCache(USER_INFO_KEY) as UserInfo // Whitelist can be directly entered if (whitePathList.includes(to.path as PageEnum)) { if (to.path === LOGIN_PATH && token) { const isSessionTimeout = userStore.getSessionTimeout try { - await userStore.afterLoginAction() + await userStore.afterLoginAction(false, userInfo?.user) if (!isSessionTimeout) { next((to.query?.redirect as string) || '/') return @@ -80,13 +82,10 @@ export function createPermissionGuard(router: Router) { return } - if (!systemEnumStore.initialized) - systemEnumStore.initialize() - // get userinfo while last fetch time is empty if (userStore.getLastUpdateTime === 0) { try { - await userStore.getUserInfoAction() + await userStore.getUserInfoAction(userInfo?.user) } catch (err) { next() diff --git a/src/store/modules/lock.ts b/src/store/modules/lock.ts index 4b683466..b491f15b 100644 --- a/src/store/modules/lock.ts +++ b/src/store/modules/lock.ts @@ -1,5 +1,6 @@ import { defineStore } from 'pinia' -import { useUserStore } from './user' + +// import { useUserStore } from './user' import type { LockInfo } from '@/types/store' import { LOCK_INFO_KEY } from '@/enums/cacheEnum' @@ -29,7 +30,7 @@ export const useLockStore = defineStore('app-lock', { }, // Unlock async unLock(password?: string) { - const userStore = useUserStore() + // const userStore = useUserStore() if (this.lockInfo?.pwd === password) { this.resetLockInfo() return true @@ -37,17 +38,17 @@ export const useLockStore = defineStore('app-lock', { const tryLogin = async () => { // TODO 滑块验证码 try { - const username = userStore.getUserInfo?.user.realName - const res = await userStore.login({ - username, - password: password!, - goHome: false, - mode: 'none', - }) - if (res) - this.resetLockInfo() + // const username = userStore.getUserInfo?.user.real_name + // const res = await userStore.login({ + // username, + // password: password!, + // goHome: false, + // mode: 'none', + // }) + // if (res) + // this.resetLockInfo() - return res + // return res } catch (error) { return false diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 0ead3ce1..2e0870fe 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -13,8 +13,10 @@ import { usePermissionStore } from '@/store/modules/permission' import { useI18n } from '@/hooks/web/useI18n' import { useMessage } from '@/hooks/web/useMessage' import { getAuthCache, setAuthCache, setTenantId } from '@/utils/auth' -import { doLogout, getUserInfo, loginApi } from '@/api/base/user' -import type { LoginParams, UserInfo } from '@/api/base/user/types' +import type { __MenuItem } from '@/api/base/user' +import { doLogout, getUserButtons, getUserRouters, loginApi } from '@/api/base/user' +import type { LoginParams, User, UserInfo } from '@/api/base/user/types' +import type { MenuItem } from '@/api/system/menu/types' interface UserState { userInfo: Nullable<UserInfo> @@ -98,22 +100,22 @@ export const useUserStore = defineStore('app-user', { try { const { goHome = true, mode, ...loginParams } = params const data = await loginApi(loginParams, mode) - const { accessToken, refreshToken } = data + const { access_token, refresh_token, ...user } = data // save token - this.setAccessToken(accessToken) - this.setRefreshToken(refreshToken) - return this.afterLoginAction(goHome) + this.setAccessToken(access_token) + this.setRefreshToken(refresh_token) + return this.afterLoginAction(goHome, user) } catch (error) { return Promise.reject(error) } }, - async afterLoginAction(goHome?: boolean): Promise<UserInfo | null> { + async afterLoginAction(goHome?: boolean, user?: User): Promise<UserInfo | null> { if (!this.getAccessToken) return null // get user info - const userInfo = await this.getUserInfoAction() + const userInfo = await this.getUserInfoAction(user!) const sessionTimeout = this.sessionTimeout if (sessionTimeout) { @@ -136,12 +138,45 @@ export const useUserStore = defineStore('app-user', { } return userInfo }, - async getUserInfoAction(): Promise<UserInfo | null> { + async getUserInfoAction(user: User): Promise<UserInfo | null> { if (!this.getAccessToken) return null - const userInfo = await getUserInfo() - setTenantId(userInfo.user.tenantId) + function normalizeMenus(__menus: __MenuItem[]): MenuItem[] { + const menus: MenuItem[] = [] + for (let i = 0; i < __menus.length; i++) { + const __menu = __menus[i] + menus.push({ + id: __menu.id, + parentId: __menu.parentId, + name: __menu.name, + component: `${__menu.path}.vue`, + // componentName?: string + path: __menu.path, + icon: __menu.source, + sort: __menu.sort, + children: __menu.children ? normalizeMenus(__menu.children) : [], + code: __menu.code, + type: 1, + visible: 1, + keepAlive: 0, + }) + } + return menus + } + + let userInfo: UserInfo | null = null + try { + const [menus, buttons] = await Promise.all([ + getUserRouters(), + getUserButtons(), + ]) + userInfo = { menus: normalizeMenus(menus), buttons, user } + } + catch { + return null + } + setTenantId(userInfo.user.tenant_id) usePermissionStore().changePermissionCode(userInfo.buttons) this.setUserInfo(userInfo) return userInfo diff --git a/src/types/axios.d.ts b/src/types/axios.d.ts index 03a19c2f..2a1f4109 100644 --- a/src/types/axios.d.ts +++ b/src/types/axios.d.ts @@ -28,6 +28,7 @@ export interface RequestOptions { withToken?: boolean // 请求重试机制 retryRequest?: RetryRequest + withoutAuth?: boolean } export interface RetryRequest { diff --git a/src/utils/http/axios/index.ts b/src/utils/http/axios/index.ts index 3c38e8b0..f3e778f0 100644 --- a/src/utils/http/axios/index.ts +++ b/src/utils/http/axios/index.ts @@ -4,6 +4,7 @@ import type { AxiosInstance, AxiosResponse } from 'axios' import { clone } from 'lodash-es' import axios from 'axios' +import CryptoJS from 'crypto-js' import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform' import { VAxios } from './Axios' import { checkStatus } from './checkStatus' @@ -27,6 +28,8 @@ const { createMessage, createErrorModal, createSuccessModal } = useMessage() // 请求白名单,无须token的接口 const whiteList: string[] = ['/login', '/refresh-token'] +const auth = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse('cop_boss:cop_boss_secret')) + /** * @description: 数据处理,方便区分多种处理方式 */ @@ -195,15 +198,18 @@ const transform: AxiosTransform = { const token = getAccessToken() if (token && !isToken) { // jwt token - (config as Recordable).headers.Authorization = options.authenticationScheme + (config as Recordable).headers['DEFrame-Auth'] = options.authenticationScheme ? `${options.authenticationScheme} ${token}` - : token + : `bearer ${token}` } // 设置租户 const tenantId = getTenantId() if (tenantId) config.headers['tenant-id'] = tenantId + if (!(config as any).requestOptions.withoutAuth) + config.headers.Authorization = `Basic ${auth}` + return config }, diff --git a/src/views/base/lock/LockPage.vue b/src/views/base/lock/LockPage.vue index b24ebb55..2516d7dd 100644 --- a/src/views/base/lock/LockPage.vue +++ b/src/views/base/lock/LockPage.vue @@ -85,7 +85,7 @@ function handleShowForm(show = false) { <div :class="`${prefixCls}-entry__header enter-x`"> <img :src="userinfo.user.avatar || headerImg" :class="`${prefixCls}-entry__header-img`"> <p :class="`${prefixCls}-entry__header-name`"> - {{ userinfo.user.realName }} + {{ userinfo.user.real_name }} </p> </div> <InputPassword v-model:value="password" :placeholder="t('sys.lock.placeholder')" class="enter-x" /> diff --git a/src/views/base/login/LoginForm.vue b/src/views/base/login/LoginForm.vue index 64d78615..3752a4d9 100644 --- a/src/views/base/login/LoginForm.vue +++ b/src/views/base/login/LoginForm.vue @@ -1,12 +1,17 @@ <script lang="ts" setup> import { computed, reactive, ref, unref } from 'vue' import { Col, Form, Input, Row } from 'ant-design-vue' +import { useAsyncState } from '@vueuse/core' +import CryptoJS from 'crypto-js' +import { LoadingOutlined } from '@ant-design/icons-vue' import LoginFormTitle from './LoginFormTitle.vue' import { LoginStateEnum, useFormRules, useFormValid, useLoginState } from './useLogin' import { useI18n } from '@/hooks/web/useI18n' import { useMessage } from '@/hooks/web/useMessage' import { useUserStore } from '@/store/modules/user' import { useDesign } from '@/hooks/web/useDesign' +import { getLoginCaptcha } from '@/api/base/user' +import type { LoginParams } from '@/api/base/user/types' const FormItem = Form.Item const InputPassword = Input.Password @@ -22,9 +27,12 @@ const { getFormRules } = useFormRules() const formRef = ref() const loading = ref(false) -const formData = reactive({ +const formData = reactive<LoginParams>({ + tenantId: '345618', username: 'admin', - password: '123456', + password: '&demo8&!', + captchaKey: '', + captchaCode: '', }) const { validForm } = useFormValid(formRef) @@ -37,19 +45,21 @@ async function handleLogin() { try { loading.value = true const userInfo = await userStore.login({ - password: data.password, - username: data.username, - mode: 'none', // 不要默认的错误提示 + ...formData, + captchaKey: captcha.value!.key, + password: CryptoJS.MD5(formData.password).toString(), + mode: 'none', }) if (userInfo) { notification.success({ message: t('sys.login.loginSuccessTitle'), - description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.user.realName}`, + description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.user.real_name}`, duration: 3, }) } } catch (error) { + refreshCaptcha() createErrorModal({ title: t('sys.api.errorTip'), content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'), @@ -60,6 +70,8 @@ async function handleLogin() { loading.value = false } } + +const { state: captcha, execute: refreshCaptcha, isLoading: isLoadingCaptcha } = useAsyncState(getLoginCaptcha, undefined) </script> <template> @@ -68,6 +80,9 @@ async function handleLogin() { v-show="getShow" ref="formRef" class="enter-x p-4" :model="formData" :rules="getFormRules" @keypress.enter="handleLogin" > + <FormItem name="tenantId" class="enter-x"> + <Input v-model:value="formData.tenantId" size="large" placeholder="租户编码" class="fix-auto-fill" /> + </FormItem> <FormItem name="username" class="enter-x"> <Input v-model:value="formData.username" size="large" :placeholder="t('sys.login.userName')" @@ -83,6 +98,15 @@ async function handleLogin() { class="fix-auto-fill" /> </FormItem> + <FormItem> + <div flex="~ justify-between items-center gap-12px"> + <Input v-model:value="formData.captchaCode" size="large" placeholder="验证码" class="fix-auto-fill w-0 flex-1 min-w-auto!" /> + <div w="100px" text="center"> + <LoadingOutlined v-if="isLoadingCaptcha" /> + <img v-else w-full :src="captcha?.image" @click="refreshCaptcha()"> + </div> + </div> + </FormItem> <Row class="enter-x"> <Col :span="12"> diff --git a/src/views/base/login/SessionTimeoutLogin.vue b/src/views/base/login/SessionTimeoutLogin.vue index 43fed8ea..037d7176 100644 --- a/src/views/base/login/SessionTimeoutLogin.vue +++ b/src/views/base/login/SessionTimeoutLogin.vue @@ -10,11 +10,11 @@ const userId = ref<Nullable<number | string>>(0) onMounted(() => { // 记录当前的UserId - userId.value = userStore.getUserInfo?.user.id + userId.value = userStore.getUserInfo?.user.user_id }) onBeforeUnmount(() => { - if (userId.value && userId.value !== userStore.getUserInfo.user.id) + if (userId.value && userId.value !== userStore.getUserInfo.user.user_id) document.location.reload() else if (permissionStore.getLastBuildMenuTime === 0) diff --git a/src/views/dashboard/workbench/components/WorkbenchHeader.vue b/src/views/dashboard/workbench/components/WorkbenchHeader.vue index 9cdeff9a..125e49ec 100644 --- a/src/views/dashboard/workbench/components/WorkbenchHeader.vue +++ b/src/views/dashboard/workbench/components/WorkbenchHeader.vue @@ -13,7 +13,7 @@ const userinfo = computed(() => userStore.getUserInfo) <Avatar :src="userinfo.user.avatar || headerImg" :size="72" class="!mx-auto !block" /> <div class="mt-2 flex flex-col justify-center md:ml-6 md:mt-0"> <h1 class="text-md md:text-lg"> - 早安, {{ userinfo.user.realName }}, 开始您一天的工作吧! + 早安, {{ userinfo.user.real_name }}, 开始您一天的工作吧! </h1> <span class="text-secondary"> 今日晴,20℃ - 32℃! </span> </div>