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>