diff --git a/.env b/.env index 544c2b5..3ac03ac 100644 --- a/.env +++ b/.env @@ -6,10 +6,10 @@ VITE_VERSION = 2.4.2.4 VITE_PORT = 3006 # 网站地址前缀 -VITE_BASE_URL = /art-design-pro/ +VITE_BASE_URL = / # API 地址前缀 -VITE_API_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default +VITE_API_URL = https://mzl.dev.hnxinyouhe.com # 权限模式( frontend | backend ) VITE_ACCESS_MODE = frontend diff --git a/.env.development b/.env.development index 8683771..67f5f87 100644 --- a/.env.development +++ b/.env.development @@ -5,7 +5,10 @@ VITE_BASE_URL = / # API 地址前缀 # VITE_API_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default -VITE_API_URL = http://219.157.255.213:25702 +# VITE_API_URL = http://219.157.255.213:25702 +VITE_API_URL = https://mzl.dev.hnxinyouhe.com +# VITE_API_URL = https://fdesk.hnxinyouhe.com + # Delete console VITE_DROP_CONSOLE = false \ No newline at end of file diff --git a/.env.production b/.env.production index 7edcd70..4625c59 100644 --- a/.env.production +++ b/.env.production @@ -1,10 +1,10 @@ # 【生产】环境变量 # 网站地址前缀 -VITE_BASE_URL = /art-design-pro/ +VITE_BASE_URL = / # API 地址前缀 -VITE_API_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default +VITE_API_URL = https://mzl.dev.hnxinyouhe.com # Delete console VITE_DROP_CONSOLE = true \ No newline at end of file diff --git a/index.html b/index.html index f4eb083..654b3b6 100644 --- a/index.html +++ b/index.html @@ -10,5 +10,6 @@
+ diff --git a/src/App.vue b/src/App.vue index c1d43aa..741fbfd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -23,6 +23,7 @@ onBeforeMount(() => { setThemeTransitionClass(true) + useUserStore().restoreTokenTimer() }) onMounted(() => { diff --git a/src/api/menuApi.ts b/src/api/menuApi.ts index b0a458b..b78161a 100644 --- a/src/api/menuApi.ts +++ b/src/api/menuApi.ts @@ -1,7 +1,7 @@ import { asyncRoutes } from '@/router/routes/asyncRoutes' import { menuDataToRouter } from '@/router/utils/menuToRouter' import { AppRouteRecord } from '@/types/router' - +import { useUserStore } from '@/store/modules/user' interface MenuResponse { menuList: AppRouteRecord[] } @@ -10,10 +10,15 @@ interface MenuResponse { export const menuService = { async getMenuList(delay = 300): Promise { try { + const userStore = useUserStore() // 模拟接口返回的菜单数据 - const menuData = asyncRoutes + const menuData = userStore.info.menu + const menuData1 = asyncRoutes // 处理菜单数据 - const menuList = menuData.map((route) => menuDataToRouter(route)) + // const menuList1 = menuData1.map((route) => menuDataToRouter(route)) + const menuList = menuData.map((route: any) => menuDataToRouter(route)) + // console.log(menuList1) + // console.log(menuList) // 模拟接口延迟 await new Promise((resolve) => setTimeout(resolve, delay)) diff --git a/src/api/usersApi.ts b/src/api/usersApi.ts index 155451a..04359ca 100644 --- a/src/api/usersApi.ts +++ b/src/api/usersApi.ts @@ -1,19 +1,53 @@ import request from '@/utils/http' export class UserService { + //获取公司 + static getCompanyList() { + return request.get({ + url: '/common/preset/corps' + }) + } + //注册 + static register(params: any) { + return request.post({ + url: '/common/auth/register', + params + // showErrorMessage: false // 不显示错误消息 + }) + } + //获取 飞书信息 + static getfeishuAppList(params: { corp_id: string }) { + return request.get({ + url: '/common/preset/feishu/app', + params + }) + } // 登录 static login(params: Api.Auth.LoginParams) { return request.post({ - url: '/api/auth/login', + url: '/common/auth/login', params // showErrorMessage: false // 不显示错误消息 }) } + // 刷新token + static refreshToken(params: { refresh_token: string }) { + return request.post({ + url: '/common/auth/refresh/token', + params + }) + } + //飞书凭证 + static getfeishuscope() { + return request.get({ + url: '/common/auth/feishu/scope' + }) + } // 获取用户信息 static getUserInfo() { return request.get({ - url: '/api/user/info' + url: '/common/user/info' }) } @@ -24,4 +58,12 @@ export class UserService { params }) } + + //角色 列表 + static getRoleList(params: Api.Common.PaginatingParams) { + return request.get({ + url: '/admin/role/list', + params + }) + } } diff --git a/src/api/workApi.ts b/src/api/workApi.ts index 1faeb70..2abd077 100644 --- a/src/api/workApi.ts +++ b/src/api/workApi.ts @@ -8,6 +8,13 @@ export class WorkService { params }) } + // 获取我的工单列表 + static getWorkselfList(params: Api.Common.PaginatingParams) { + return request.get({ + url: '/ticket/ticket/self', + params + }) + } //添加工单 static WorkAdd(params: Api.Work.AddParams) { return request.post({ diff --git a/src/assets/img/login/feishu.png b/src/assets/img/login/feishu.png new file mode 100644 index 0000000..b4e416c Binary files /dev/null and b/src/assets/img/login/feishu.png differ diff --git a/src/assets/img/login/lf_bg.png b/src/assets/img/login/lf_bg.png new file mode 100644 index 0000000..dac4ec9 Binary files /dev/null and b/src/assets/img/login/lf_bg.png differ diff --git a/src/components/core/layouts/art-header-bar/index.vue b/src/components/core/layouts/art-header-bar/index.vue index 26a5ce1..0048ee9 100644 --- a/src/components/core/layouts/art-header-bar/index.vue +++ b/src/components/core/layouts/art-header-bar/index.vue @@ -24,7 +24,7 @@ - + -
+
@@ -65,19 +65,19 @@
-
+ -
+
@@ -150,7 +150,7 @@
    -
  • +
  • {{ $t('topBar.user.lockScreen') }} diff --git a/src/components/core/views/login/LoginLeftView.vue b/src/components/core/views/login/LoginLeftView.vue index 08831ef..2312ab3 100644 --- a/src/components/core/views/login/LoginLeftView.vue +++ b/src/components/core/views/login/LoginLeftView.vue @@ -2,14 +2,15 @@ diff --git a/src/composables/useCommon.ts b/src/composables/useCommon.ts index d9ae424..96dd05d 100644 --- a/src/composables/useCommon.ts +++ b/src/composables/useCommon.ts @@ -8,7 +8,8 @@ export function useCommon() { // 是否是前端控制模式 const isFrontendMode = computed(() => { - return import.meta.env.VITE_ACCESS_MODE === 'frontend' + // /(frontend | backend)frontend:前端控制模式 backend:后端控制模式 + return import.meta.env.VITE_ACCESS_MODE === 'backend' }) // 刷新页面 diff --git a/src/config/index.ts b/src/config/index.ts index d3c33ab..045de05 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -9,7 +9,7 @@ import { configImages } from './assets/images' const appConfig: SystemConfig = { // 系统信息 systemInfo: { - name: 'Art Design Pro' // 系统名称 + name: 'XYH系统管理' // 系统名称 }, // Element Plus 主题 elementPlusTheme: { diff --git a/src/locales/langs/en.json b/src/locales/langs/en.json index 7b023b0..d024f8f 100644 --- a/src/locales/langs/en.json +++ b/src/locales/langs/en.json @@ -145,7 +145,8 @@ "success": { "title": "Login successful", "message": "Welcome back" - } + }, + "otherLogin": "Other login methods" }, "forgetPassword": { "title": "Forgot password?", diff --git a/src/locales/langs/zh.json b/src/locales/langs/zh.json index d67bd4c..dd51a4e 100644 --- a/src/locales/langs/zh.json +++ b/src/locales/langs/zh.json @@ -141,7 +141,8 @@ "success": { "title": "登录成功", "message": "欢迎回来" - } + }, + "otherLogin": "其他登录方式" }, "forgetPassword": { "title": "忘记密码?", diff --git a/src/router/index copy 2.ts b/src/router/index copy 2.ts new file mode 100644 index 0000000..5859a5e --- /dev/null +++ b/src/router/index copy 2.ts @@ -0,0 +1,21 @@ +import type { App } from 'vue' +import { createRouter, createWebHashHistory } from 'vue-router' +import { staticRoutes } from './routes/staticRoutes' +import { configureNProgress } from './utils/utils' +import { setupBeforeEachGuard } from './guards/beforeEach' +import { setupAfterEachGuard } from './guards/afterEach' + +// 创建路由实例 +export const router = createRouter({ + history: createWebHashHistory(), + routes: staticRoutes, // 静态路由 + scrollBehavior: () => ({ left: 0, top: 0 }) // 滚动行为 +}) + +// 初始化路由 +export function initRouter(app: App): void { + configureNProgress() // 顶部进度条 + setupBeforeEachGuard(router) // 路由前置守卫 + setupAfterEachGuard(router) // 路由后置守卫 + app.use(router) +} diff --git a/src/router/index.ts b/src/router/index.ts index 5859a5e..72946ea 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,5 +1,5 @@ import type { App } from 'vue' -import { createRouter, createWebHashHistory } from 'vue-router' +import { createRouter, createWebHistory } from 'vue-router' import { staticRoutes } from './routes/staticRoutes' import { configureNProgress } from './utils/utils' import { setupBeforeEachGuard } from './guards/beforeEach' @@ -7,7 +7,7 @@ import { setupAfterEachGuard } from './guards/afterEach' // 创建路由实例 export const router = createRouter({ - history: createWebHashHistory(), + history: createWebHistory(), routes: staticRoutes, // 静态路由 scrollBehavior: () => ({ left: 0, top: 0 }) // 滚动行为 }) diff --git a/src/router/routes/asyncRoutes.ts b/src/router/routes/asyncRoutes.ts index 4902053..b635653 100644 --- a/src/router/routes/asyncRoutes.ts +++ b/src/router/routes/asyncRoutes.ts @@ -56,6 +56,7 @@ export const asyncRoutes: AppRouteRecord[] = [ path: '/work', name: 'Work', component: RoutesAlias.Home, + // component:'/work', meta: { title: 'menus.work.title', icon: '', @@ -64,7 +65,7 @@ export const asyncRoutes: AppRouteRecord[] = [ children: [ { path: 'order', - name: 'Order', + name: '工单列表', component: RoutesAlias.Order, meta: { title: 'menus.work.order', diff --git a/src/router/routes/staticRoutes.ts b/src/router/routes/staticRoutes.ts index d3308c3..f9e3a8c 100644 --- a/src/router/routes/staticRoutes.ts +++ b/src/router/routes/staticRoutes.ts @@ -29,6 +29,12 @@ export const staticRoutes: AppRouteRecordRaw[] = [ component: () => import('@views/auth/forget-password/index.vue'), meta: { title: 'menus.forgetPassword.title', isHideTab: true, noLogin: true, setTheme: true } }, + { + path: '/auth/feishu-callback', + name: 'FeishuCallback', + component: () => import('@/views/auth/feishu-callback.vue'), + meta: { title: '飞书回调', noLogin: true, isHideTab: true } + }, { path: '/exception', component: Home, diff --git a/src/router/routesAlias.ts b/src/router/routesAlias.ts index 48f5e0f..4e4d965 100644 --- a/src/router/routesAlias.ts +++ b/src/router/routesAlias.ts @@ -55,4 +55,5 @@ export enum RoutesAlias { } // 主页路由 -export const HOME_PAGE = RoutesAlias.Dashboard +// export const HOME_PAGE = RoutesAlias.Dashboard +export const HOME_PAGE = '/work/order' diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 5a0568b..5a1e6a8 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -8,6 +8,8 @@ import { AppRouteRecord } from '@/types/router' import { setPageTitle } from '@/router/utils/utils' import { resetRouterState } from '@/router/guards/beforeEach' import { RoutesAlias } from '@/router/routesAlias' +import { UserService } from '@/api/usersApi' // 新增导入 +let refreshTimeout: ReturnType | null = null // 定时器变量 // 用户 export const useUserStore = defineStore( @@ -21,6 +23,10 @@ export const useUserStore = defineStore( const searchHistory = ref([]) const accessToken = ref('') const refreshToken = ref('') + const companyid = ref('') + const companyabbr = ref('') + const loginType = ref<'feishu' | 'upwd'>('upwd') + const tokenExpireAt = ref(0) const getUserInfo = computed(() => info.value) const getSettingState = computed(() => useSettingStore().$state) @@ -38,7 +44,9 @@ export const useUserStore = defineStore( setPageTitle(router.currentRoute.value) language.value = lang } - + const setLoginType = (type: 'feishu' | 'upwd') => { + loginType.value = type + } const setSearchHistory = (list: AppRouteRecord[]) => { searchHistory.value = list } @@ -51,11 +59,47 @@ export const useUserStore = defineStore( lockPassword.value = password } - const setToken = (newAccessToken: string, newRefreshToken?: string) => { + const setToken = (newAccessToken: string, newRefreshToken?: string, expiresIn?: number) => { accessToken.value = newAccessToken if (newRefreshToken) { refreshToken.value = newRefreshToken } + if (expiresIn) { + // 记录绝对过期时间 + const expireAt = Date.now() + expiresIn * 1000 + tokenExpireAt.value = expireAt + localStorage.setItem('token_expire_at', String(expireAt)) + scheduleTokenRefresh(expiresIn) + } + } + const setCompany= (id: string, abbr: string) => { + companyid.value = id + companyabbr.value = abbr + } + function restoreTokenTimer() { + const expireAt = Number(localStorage.getItem('token_expire_at') || '0') + if (accessToken.value && expireAt > Date.now()) { + const leftSeconds = Math.floor((expireAt - Date.now()) / 1000) + scheduleTokenRefresh(leftSeconds) + } + } + + function scheduleTokenRefresh(expiresIn: number) { + if (refreshTimeout) clearTimeout(refreshTimeout) + // 提前1分钟刷新 + const refreshTime = (expiresIn - 60) * 1000 + if (refreshTime > 0) { + refreshTimeout = setTimeout(refreshTokenAction, refreshTime) + } + } + + async function refreshTokenAction() { + try { + const { data } = await UserService.refreshToken({ refresh_token: refreshToken.value }) + setToken(data.access_token, data.refresh_token, data.expires_in) + } catch (e) { + logOut() + } } const logOut = () => { @@ -65,6 +109,7 @@ export const useUserStore = defineStore( lockPassword.value = '' accessToken.value = '' refreshToken.value = '' + if (refreshTimeout) clearTimeout(refreshTimeout) // 清理定时器 useWorktabStore().opened = [] sessionStorage.removeItem('iframeRoutes') resetRouterState(router) @@ -77,9 +122,13 @@ export const useUserStore = defineStore( isLock, lockPassword, info, + companyid, + companyabbr, searchHistory, + loginType, accessToken, refreshToken, + tokenExpireAt, getUserInfo, getSettingState, getWorktabState, @@ -90,7 +139,10 @@ export const useUserStore = defineStore( setLockStatus, setLockPassword, setToken, - logOut + setCompany, + setLoginType, + logOut, + restoreTokenTimer } }, { diff --git a/src/typings/api.d.ts b/src/typings/api.d.ts index ef2a475..144e729 100644 --- a/src/typings/api.d.ts +++ b/src/typings/api.d.ts @@ -35,14 +35,22 @@ declare namespace Api { namespace Auth { /** 登录参数 */ interface LoginParams { - userName: string - password: string + account?: string + password?: string + feishu_code?: string } /** 登录响应 */ interface LoginResponse { - token: string - refreshToken: string + data: any + } + /** 登录响应 */ + interface registerResponse { + code: any + } + /** 授权响应 */ + interface Authfeishuscope { + auth_url: any } } @@ -57,6 +65,7 @@ declare namespace Api { avatar?: string email?: string phone?: string + menu?: any } /** 用户列表数据 */ @@ -130,4 +139,21 @@ declare namespace Api { total: number } } + /** 角色 */ + namespace Role { + /** 品牌信息 */ + interface RoleInfo { + data: never[] + id: number + name: string + } + + /** 品牌列表数据 */ + interface RoleListData { + records: RoleInfo[] + current: number + size: number + total: number + } + } } diff --git a/src/utils/http/error.ts b/src/utils/http/error.ts index 2407b7e..4f07ed7 100644 --- a/src/utils/http/error.ts +++ b/src/utils/http/error.ts @@ -6,7 +6,7 @@ import { $t } from '@/locales' // 错误响应接口 export interface ErrorResponse { code: number - msg: string + message: string data?: unknown } @@ -94,7 +94,7 @@ export function handleError(error: AxiosError): never { } const statusCode = error.response?.status - const errorMessage = error.response?.data?.msg || error.message + const errorMessage = error.response?.data?.message || error.message const requestConfig = error.config // 处理网络错误 @@ -106,9 +106,10 @@ export function handleError(error: AxiosError): never { } // 处理 HTTP 状态码错误 - const message = statusCode - ? getErrorMessage(statusCode) - : errorMessage || $t('httpMsg.requestFailed') + // const message = statusCode + // ? getErrorMessage(statusCode) + // : errorMessage || $t('httpMsg.requestFailed') + const message = errorMessage throw new HttpError(message, statusCode || ApiStatus.error, { data: error.response.data, url: requestConfig?.url, @@ -126,7 +127,7 @@ export function showError(error: HttpError, showMessage: boolean = true): void { ElMessage.error(error.message) } // 记录错误日志 - console.error('[HTTP Error]', error.toLogData()) + // console.error('[HTTP Error]', error.toLogData()) } /** diff --git a/src/utils/http/index.ts b/src/utils/http/index.ts index 2c180e8..7aeebf8 100644 --- a/src/utils/http/index.ts +++ b/src/utils/http/index.ts @@ -45,10 +45,15 @@ axiosInstance.interceptors.request.use( (request: InternalAxiosRequestConfig) => { const { accessToken } = useUserStore() const { language } = useUserStore() - request.headers.set('lang', language) + const { loginType } = useUserStore() + const { companyabbr } = useUserStore() + request.headers.set('Fdesk-Language', language) + request.headers.set('Fdesk-Corp-Abbr', companyabbr) + request.headers.set('Fdesk-System-Code', 'fdesk') + request.headers.set('Fdesk-Login-Type', loginType) // 设置 token 和 请求头 if (accessToken) { - request.headers.set('Authorization', accessToken) + request.headers.set('Fdesk-Auth-Token','Bearer '+accessToken) request.headers.set('Content-Type', 'application/json') } @@ -76,6 +81,7 @@ axiosInstance.interceptors.response.use( } }, (error) => { + return Promise.reject(handleError(error)) } ) diff --git a/src/utils/http/status.ts b/src/utils/http/status.ts index a0e8972..2354235 100644 --- a/src/utils/http/status.ts +++ b/src/utils/http/status.ts @@ -2,8 +2,8 @@ * 接口状态码 */ export enum ApiStatus { - // success = 200, // 成功 - success = 0, // 成功 + success = 200, // 成功 + // success = 0, // 成功 error = 400, // 错误 unauthorized = 401, // 未授权 forbidden = 403, // 禁止访问 diff --git a/src/views/auth/feishu-callback.vue b/src/views/auth/feishu-callback.vue new file mode 100644 index 0000000..5489a59 --- /dev/null +++ b/src/views/auth/feishu-callback.vue @@ -0,0 +1,57 @@ + + + + + \ No newline at end of file diff --git a/src/views/auth/login/index.vue b/src/views/auth/login/index.vue index 3c9d9e6..c0c0091 100644 --- a/src/views/auth/login/index.vue +++ b/src/views/auth/login/index.vue @@ -27,6 +27,24 @@ + +
    + 公司 +
    + +
@@ -34,85 +52,114 @@