diff --git a/config/unplugin/auto-import.ts b/config/unplugin/auto-import.ts index 1036483..69e7b9a 100644 --- a/config/unplugin/auto-import.ts +++ b/config/unplugin/auto-import.ts @@ -21,7 +21,7 @@ export function configAutoImport() { '@vueuse/core', { dayjs: [['default', 'dayjs']], - 'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'isNumber'], + 'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'uniq', 'isNumber', 'uniqBy', 'isEmpty'], '@/hooks': ['useModal'], }, ], diff --git a/package.json b/package.json index 1fed2eb..a1c4938 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dayjs": "^1.11.7", "echarts": "^5.6.0", "html2canvas": "^1.4.1", + "jspdf": "^3.0.1", "lodash-es": "^4.17.21", "mitt": "^3.0.0", "normalize.css": "^8.0.1", diff --git a/src/App.vue b/src/App.vue index 674532c..325eb50 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,7 +7,11 @@ @@ -95,26 +49,26 @@ const handleDopdownClick = (index: any, ind: any) => {
diff --git a/src/components/upload/ImageUpload.vue b/src/components/upload/ImageUpload.vue index 0054575..9a0be66 100644 --- a/src/components/upload/ImageUpload.vue +++ b/src/components/upload/ImageUpload.vue @@ -75,7 +75,6 @@ let previousFileListLength = 0; //删除图片 const onChange = (fileList) => { if (fileList.length < previousFileListLength) { - // 在这里执行你需要的操作 if (props.limit === 1) { if (fileList.length === 0) { emit('update:modelValue', ''); @@ -90,7 +89,6 @@ const onChange = (fileList) => { } } - // 更新上一次的文件列表长度 previousFileListLength = fileList.length; }; diff --git a/src/permission/permission.ts b/src/permission/permission.ts new file mode 100644 index 0000000..7c12722 --- /dev/null +++ b/src/permission/permission.ts @@ -0,0 +1,10 @@ +import { useUserStore } from '@/stores/modules/user'; + +export function checkRoutePermission(routeName: string) { + const userStore = useUserStore(); + const allowAccessRoutes = userStore.allowAccessRoutes; + + if (!routeName) return false; + + return allowAccessRoutes.includes(routeName); +} diff --git a/src/router/guard/userLoginInfo.ts b/src/router/guard/userLoginInfo.ts index 752d6d9..30e1169 100644 --- a/src/router/guard/userLoginInfo.ts +++ b/src/router/guard/userLoginInfo.ts @@ -5,6 +5,8 @@ import type { Router } from 'vue-router'; import NProgress from 'nprogress'; import { goUserLogin } from '@/utils/user'; +// import router from '@/router'; +import { checkRoutePermission } from '@/permission/permission'; import { useUserStore } from '@/stores/modules/user'; @@ -13,15 +15,27 @@ export default function setupUserLoginInfoGuard(router: Router) { NProgress.start(); const userStore = useUserStore(); + const routeName = to?.name as string; const requiresAuth = to?.meta?.requiresAuth || false; - const isLogin = !!userStore.isLogin; + const requireLogin = to?.meta?.requireLogin || false; - if (requiresAuth && !isLogin) { + if (requireLogin && !userStore.isLogin) { goUserLogin(); next(); return; } + if (requiresAuth) { + const hasPermission = checkRoutePermission(routeName); + if (!hasPermission) { + AMessage.error('您没有权限访问该页面'); + next('/'); + return; + } + next(); + return; + } + next(); }); router.afterEach((to) => { diff --git a/src/router/index.ts b/src/router/index.ts index ea87ab5..2471c01 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,15 +4,15 @@ */ import { createRouter, createWebHistory } from 'vue-router'; import { appRoutes } from './routes'; -import { REDIRECT_MAIN, NOT_FOUND_ROUTE } from './routes/base'; +import { NOT_FOUND_ROUTE } from './routes/base'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; - +import { MENU_GROUP_IDS } from './constants'; import createRouteGuard from './guard'; NProgress.configure({ showSpinner: false }); // NProgress Configuration -// console.log({ appRoutes }); -const router = createRouter({ + +export const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { @@ -21,6 +21,7 @@ const router = createRouter({ component: () => import('@/views/components/login'), meta: { requiresAuth: false, + requireLogin: false, }, }, { @@ -29,27 +30,31 @@ const router = createRouter({ component: () => import('@/views/components/workplace'), meta: { hideSidebar: true, - requiresAuth: true, + requiresAuth: false, + requireLogin: true, + id: MENU_GROUP_IDS.WORK_BENCH_ID, }, }, { path: '/permission', name: 'permission', component: () => import('@/views/components/permission/choose-enterprise.vue'), - meta: { - requiresAuth: true, - }, - }, - { - path: '/auth', - name: 'auth', - component: () => import('@/views/components/permission/auth.vue'), meta: { requiresAuth: false, + requireLogin: true, }, }, + // { + // path: '/auth', + // name: 'auth', + // component: () => import('@/views/components/permission/auth.vue'), + // meta: { + // requiresAuth: false, + // requireLogin: true, + // }, + // }, ...appRoutes, - REDIRECT_MAIN, + // REDIRECT_MAIN, NOT_FOUND_ROUTE, ], scrollBehavior() { diff --git a/src/router/routes/base.ts b/src/router/routes/base.ts index d99033f..f10ef89 100644 --- a/src/router/routes/base.ts +++ b/src/router/routes/base.ts @@ -1,28 +1,35 @@ import type { RouteRecordRaw } from 'vue-router'; import { REDIRECT_ROUTE_NAME } from '@/router/constants'; -export const REDIRECT_MAIN: RouteRecordRaw = { - path: '/redirect', - name: 'redirect', - meta: { - requiresAuth: true, - hideInMenu: true, - }, - children: [ - { - path: '/redirect/:path', - name: REDIRECT_ROUTE_NAME, - component: () => import('@/layouts/Basic.vue'), - meta: { - requiresAuth: true, - hideInMenu: true, - }, - }, - ], -}; +// export const REDIRECT_MAIN: RouteRecordRaw = { +// path: '/redirect', +// name: 'redirect', +// meta: { +// requiresAuth: false, +// requireLogin: false, +// hideInMenu: true, +// }, +// children: [ +// { +// path: '/redirect/:path', +// name: REDIRECT_ROUTE_NAME, +// component: () => import('@/layouts/Basic.vue'), +// meta: { +// requiresAuth: false, +// requireLogin: false, +// hideInMenu: true, +// }, +// }, +// ], +// }; export const NOT_FOUND_ROUTE: RouteRecordRaw = { path: '/:pathMatch(.*)*', name: 'notFound', component: () => import('@/layouts/NotFound.vue'), + meta: { + requiresAuth: false, + hideInMenu: true, + hideSidebar: true, + }, }; diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts index 4b5aa03..433925f 100644 --- a/src/router/routes/index.ts +++ b/src/router/routes/index.ts @@ -1,4 +1,6 @@ import type { RouteRecordNormalized } from 'vue-router'; +// import { REDIRECT_MAIN, NOT_FOUND_ROUTE } from './base'; +import { MENU_GROUP_IDS } from '@/router/constants'; const modules = import.meta.glob('./modules/*.ts', { eager: true }); // const externalModules = import.meta.glob('./externalModules/*.ts', { diff --git a/src/router/routes/modules/dataEngine.ts b/src/router/routes/modules/dataEngine.ts index 9a06af3..fb2a333 100644 --- a/src/router/routes/modules/dataEngine.ts +++ b/src/router/routes/modules/dataEngine.ts @@ -15,8 +15,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ locale: '全域数据引擎', icon: IconBookmark, requiresAuth: true, + requireLogin: true, roles: ['*'], - requiresSidebar: true, id: MENU_GROUP_IDS.DATA_ENGINE_ID, }, children: [ @@ -26,8 +26,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '行业热门话题洞察', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 2, }, component: () => import('@/views/components/dataEngine/hotTranslation.vue'), }, @@ -37,8 +37,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '行业词云', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 3, }, component: () => import('@/views/components/dataEngine/hotCloud.vue'), }, @@ -48,8 +48,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '行业关键词动向', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 4, }, component: () => import('@/views/components/dataEngine/keyWord.vue'), }, @@ -59,8 +59,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '用户痛点观察', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 5, }, component: () => import('@/views/components/dataEngine/userPainPoints.vue'), }, @@ -70,8 +70,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '重点品牌动向', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 6, }, component: () => import('@/views/components/dataEngine/keyBrandMovement.vue'), }, @@ -81,8 +81,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '用户画像', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 7, }, component: () => import('@/views/components/dataEngine/userPersona.vue'), }, diff --git a/src/router/routes/modules/management.ts b/src/router/routes/modules/management.ts index eafde1a..2c5e104 100644 --- a/src/router/routes/modules/management.ts +++ b/src/router/routes/modules/management.ts @@ -14,9 +14,9 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '管理中心', icon: IconBookmark, - requiresAuth: true, + requiresAuth: false, + requireLogin: true, roles: ['*'], - requiresSidebar: true, id: MENU_GROUP_IDS.MANAGEMENT_ID, }, children: [ @@ -26,7 +26,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ component: () => import('@/views/components/management/person'), meta: { locale: '个人信息', - requiresAuth: true, + requiresAuth: false, + requireLogin: true, roles: ['*'], }, }, @@ -36,7 +37,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ component: () => import('@/views/components/management/enterprise'), meta: { locale: '企业信息', - requiresAuth: true, + requiresAuth: false, + requireLogin: true, roles: ['*'], }, }, @@ -46,7 +48,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ component: () => import('@/views/components/management/account'), meta: { locale: '账号管理', - requiresAuth: true, + requiresAuth: false, + requireLogin: true, roles: ['*'], }, }, diff --git a/src/router/routes/modules/propertyMarketing.ts b/src/router/routes/modules/propertyMarketing.ts index 621ff25..58e5ed4 100644 --- a/src/router/routes/modules/propertyMarketing.ts +++ b/src/router/routes/modules/propertyMarketing.ts @@ -19,8 +19,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ locale: '品牌资产管理', icon: IconRepository, requiresAuth: true, + requireLogin: true, roles: ['*'], - requiresSidebar: true, id: MENU_GROUP_IDS.PROPERTY_ID, }, children: [ @@ -30,8 +30,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '品牌信息', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 11, }, component: () => import('@/views/property-marketing/brands/brand-materials/index.vue'), }, @@ -45,8 +45,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ locale: '账号资源中心', icon: IconMediaAccount, requiresAuth: true, + requireLogin: true, roles: ['*'], - requiresSidebar: true, id: MENU_GROUP_IDS.PROPERTY_ID, }, children: [ @@ -56,8 +56,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '账号管理', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 12, }, component: () => import('@/views/property-marketing/media-account/account-manage'), }, @@ -67,6 +67,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '账号数据看板', requiresAuth: true, + requireLogin: true, roles: ['*'], }, component: () => import('@/views/property-marketing/media-account/account-dashboard'), @@ -77,6 +78,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '账号详情', requiresAuth: true, + requireLogin: true, roles: ['*'], hideInMenu: true, activeMenu: 'MediaAccountAccountDashboard', @@ -93,8 +95,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ locale: '投放资源中心', icon: IconPutAccount, requiresAuth: true, + requireLogin: true, roles: ['*'], - requiresSidebar: true, id: MENU_GROUP_IDS.PROPERTY_ID, }, children: [ @@ -104,8 +106,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '账户管理', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 13, }, component: () => import('@/views/property-marketing/put-account/account-manage'), }, @@ -115,6 +117,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '账户数据', requiresAuth: true, + requireLogin: true, roles: ['*'], }, component: () => import('@/views/property-marketing/put-account/account-data'), @@ -125,6 +128,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '投放表现分析', requiresAuth: true, + requireLogin: true, roles: ['*'], }, component: () => import('@/views/property-marketing/put-account/account-dashboard'), @@ -135,6 +139,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '投放指南', requiresAuth: true, + requireLogin: true, roles: ['*'], }, component: () => import('@/views/property-marketing/put-account/investment-guidelines'), @@ -161,8 +166,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ locale: '智能方案管理', icon: IconIntelligentSolution, requiresAuth: true, + requireLogin: true, roles: ['*'], - requiresSidebar: true, id: MENU_GROUP_IDS.PROPERTY_ID, }, children: [ @@ -172,8 +177,8 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '业务洞察报告', requiresAuth: true, + requireLogin: true, roles: ['*'], - menuId: 14, }, component: () => import('@/views/property-marketing/intelligent-solution/businessAnalysisReport'), }, @@ -183,6 +188,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [ meta: { locale: '竟品对比报告', requiresAuth: true, + requireLogin: true, roles: ['*'], }, component: () => import('@/views/property-marketing/intelligent-solution/competitiveProductAnalysisReport'), diff --git a/src/router/typeings.d.ts b/src/router/typeings.d.ts index ca4e1f6..de97cea 100644 --- a/src/router/typeings.d.ts +++ b/src/router/typeings.d.ts @@ -14,5 +14,6 @@ declare module 'vue-router' { noAffix?: boolean; // if set true, the tag will not affix in the tab-bar ignoreCache?: boolean; // if set true, the page will not be cached hideSidebar?: boolean; + requireLogin?: boolean; // 是否需要登陆才能访问 } } diff --git a/src/stores/modules/enterprise/index.ts b/src/stores/modules/enterprise/index.ts index 9257e2d..b245cb8 100644 --- a/src/stores/modules/enterprise/index.ts +++ b/src/stores/modules/enterprise/index.ts @@ -1,4 +1,7 @@ import { fetchEnterpriseInfo } from '@/api/all/login'; +import { useSidebarStore } from '@/stores/modules/side-bar'; +import { useUserStore } from '@/stores/modules/user'; +import { glsWithCatch, slsWithCatch, rlsWithCatch } from '@/utils/stroage'; interface EnterpriseInfo { id: number; @@ -7,6 +10,7 @@ interface EnterpriseInfo { used_update_name_count: number; sub_account_quota: number; used_sub_account_count: number; + permissions: string[]; } interface EnterpriseState { @@ -15,24 +19,14 @@ interface EnterpriseState { export const useEnterpriseStore = defineStore('enterprise', { state: (): EnterpriseState => ({ - enterpriseInfo: (() => { - const stored = localStorage.getItem('enterpriseInfo'); - if (stored) { - try { - return JSON.parse(stored) as EnterpriseInfo; - } catch { - return null; - } - } - return null; - })(), + enterpriseInfo: (glsWithCatch('enterpriseInfo') && JSON.parse(glsWithCatch('enterpriseInfo') as string)) || null, }), actions: { setEnterpriseInfo(enterpriseInfo: EnterpriseInfo) { this.enterpriseInfo = enterpriseInfo; - localStorage.setItem('enterpriseInfo', JSON.stringify(enterpriseInfo)); + slsWithCatch('enterpriseInfo', JSON.stringify(enterpriseInfo)); }, - clearEnterpriseInfo() { + clearUserEnterpriseInfo() { this.enterpriseInfo = null; localStorage.removeItem('enterpriseInfo'); }, @@ -51,14 +45,12 @@ export const useEnterpriseStore = defineStore('enterprise', { this.enterpriseInfo.used_sub_account_count++; } }, - getEnterpriseInfo(): EnterpriseInfo | null { - return this.enterpriseInfo; - }, - async updateEnterpriseInfo() { - const res = await fetchEnterpriseInfo(this.enterpriseInfo!.id); - const { code, data } = res; - if (code === 200) { - this.setEnterpriseInfo(data); + async getEnterpriseInfo() { + if (this.enterpriseInfo) { + const { code, data } = await fetchEnterpriseInfo(this.enterpriseInfo!.id); + if (code === 200) { + this.setEnterpriseInfo(data); + } } }, }, diff --git a/src/stores/modules/side-bar/constants.ts b/src/stores/modules/side-bar/constants.ts new file mode 100644 index 0000000..30c732f --- /dev/null +++ b/src/stores/modules/side-bar/constants.ts @@ -0,0 +1,90 @@ +import { MENU_GROUP_IDS } from '@/router/constants'; +export const MENU_LIST = [ + { + id: MENU_GROUP_IDS.WORK_BENCH_ID, + name: '工作台', + routeName: 'Home', + includeRouteNames: ['Home'], + requiresAuth: false, + permissionKey: '', // 权限key,如果为空,则表示该菜单不需要权限,与后端约定 + }, + { + id: MENU_GROUP_IDS.DATA_ENGINE_ID, + name: '全域数据分析', + permissionKey: 'data_analysis', + requiresAuth: true, + children: [ + { + name: '行业热门话题洞察', + routeName: 'DataEngineHotTranslation', + includeRouteNames: ['DataEngineHotTranslation'], + }, + { + name: '行业词云', + routeName: 'DataEngineHotCloud', + includeRouteNames: ['DataEngineHotCloud'], + }, + { + name: '行业关键词动向', + routeName: 'DataEngineKeyWord', + includeRouteNames: ['DataEngineKeyWord'], + }, + { + name: '用户痛点观察', + routeName: 'DataEngineUserPainPoints', + includeRouteNames: ['DataEngineUserPainPoints'], + }, + { + name: '重点品牌动向', + routeName: 'DataEngineKeyBrandMovement', + includeRouteNames: ['DataEngineKeyBrandMovement'], + }, + { + name: '用户画像', + routeName: 'DataEngineUserPersona', + includeRouteNames: ['DataEngineUserPersona'], + }, + ], + }, + { + id: MENU_GROUP_IDS.PROPERTY_ID, + name: '营销资产中台', + permissionKey: 'marketing_asset', + requiresAuth: true, + children: [ + { + name: '品牌资产管理', + routeName: 'RepositoryBrandMaterials', + includeRouteNames: ['RepositoryBrandMaterials'], + }, + { + name: '账号资源中心', + routeName: 'MediaAccountAccountManagement', + includeRouteNames: [ + 'MediaAccountAccountManagement', + 'MediaAccountAccountDashboard', + 'MediaAccountAccountDetails', + ], + }, + { + name: '投放资源中心', + routeName: 'PutAccountAccountManagement', + includeRouteNames: [ + 'PutAccountAccountManagement', + 'PutAccountAccountData', + 'PutAccountAccountDashboard', + 'PutAccountInvestmentGuidelines', + 'guideDetail', + ], + }, + { + name: '智能方案管理', + routeName: 'IntelligentSolutionBusinessAnalysisReport', + includeRouteNames: [ + 'IntelligentSolutionBusinessAnalysisReport', + 'IntelligentSolutionCompetitiveProductAnalysisReport', + ], + }, + ], + }, +]; diff --git a/src/stores/modules/side-bar/index.ts b/src/stores/modules/side-bar/index.ts index 2b52e25..cca787f 100644 --- a/src/stores/modules/side-bar/index.ts +++ b/src/stores/modules/side-bar/index.ts @@ -3,19 +3,23 @@ * @Date: 2025-06-23 22:13:30 */ import { defineStore } from 'pinia'; -import { MENU_GROUP_IDS } from '@/router/constants'; -import { appRoutes } from '@/router/routes'; +import router from '@/router'; import type { RouteLocationNormalized } from 'vue-router'; - -const { DATA_ENGINE_ID, MANAGEMENT_ID } = MENU_GROUP_IDS; +import { MENU_LIST } from './constants'; +import { useEnterpriseStore } from '@/stores/modules/enterprise'; interface sidebarState { activeMenuId: number | null; + menuList: any[]; + allowAccessRoutes: any[]; } export const useSidebarStore = defineStore('sidebar', { state: (): sidebarState => ({ activeMenuId: null, + menuList: [], + + allowAccessRoutes: [], // 允许访问的路由列表 }), actions: { clearActiveMenuId() { @@ -24,14 +28,25 @@ export const useSidebarStore = defineStore('sidebar', { setActiveMenuId(id: number) { this.activeMenuId = id; }, + clearUserNavbarMenuList() { + this.menuList = []; + }, + // navbar菜单列表由企业对应权限决定 + getUserNavbarMenuList() { + const enterpriseStore = useEnterpriseStore(); + this.menuList = MENU_LIST.filter( + (item) => !item.permissionKey || enterpriseStore.enterpriseInfo?.permissions?.includes(item.permissionKey), + ); + }, // 根据当前路由自动设置 activeMenuId setActiveMenuIdByRoute(route: RouteLocationNormalized) { - // console.log('setActiveMenuIdByRoute '); + const appRoutes = router.options?.routes ?? []; + // 查找当前路由所属的菜单组 const findMenuGroup = (routes: any[]): number | null => { for (const routeItem of routes) { // 检查子路由 - if (routeItem.children && routeItem.children.length > 0) { + if (routeItem.children?.length > 0) { // 检查当前路由是否是这个父路由的子路由 const isChildRoute = routeItem.children.some((child: any) => child.name === route.name); if (isChildRoute) { @@ -42,12 +57,16 @@ export const useSidebarStore = defineStore('sidebar', { if (childResult !== null) { return routeItem.meta?.id || childResult; } + } else { + if (routeItem.name === route.name) { + return routeItem.meta?.id || null; + } } } return null; }; - const menuId = findMenuGroup(appRoutes); + const menuId = findMenuGroup(appRoutes as any); if (menuId !== null) { this.activeMenuId = menuId; } diff --git a/src/stores/modules/user/index.ts b/src/stores/modules/user/index.ts index c605776..5a26ab0 100644 --- a/src/stores/modules/user/index.ts +++ b/src/stores/modules/user/index.ts @@ -1,5 +1,10 @@ +import type { RouteRecordNormalized } from 'vue-router'; + import { defineStore } from 'pinia'; import { fetchProfileInfo } from '@/api/all/login'; +import { useSidebarStore } from '@/stores/modules/side-bar'; +import router from '@/router'; +import { glsWithCatch, slsWithCatch, rlsWithCatch } from '@/utils/stroage'; interface UserInfo { id: number; @@ -10,16 +15,10 @@ interface UserInfo { // 添加其他用户属性... } -interface CompanyInfo { - id: number; - name: string; - // 添加其他公司属性... -} - interface UserState { token: string; userInfo: UserInfo | null; - companyInfo: CompanyInfo | null; + allowAccessRoutes: string[]; // isLogin: boolean; } @@ -31,9 +30,10 @@ interface UserInfo { export const useUserStore = defineStore('user', { state: (): UserState => ({ - token: localStorage.getItem('accessToken') || '', - userInfo: null, - companyInfo: null, + token: glsWithCatch('accessToken') || '', + userInfo: (glsWithCatch('userInfo') && JSON.parse(glsWithCatch('userInfo') as string)) || null, + allowAccessRoutes: + (glsWithCatch('allowAccessRoutes') && JSON.parse(glsWithCatch('allowAccessRoutes') as string)) || [], // 允许访问的路由列表 }), getters: { isLogin(): boolean { @@ -44,12 +44,12 @@ export const useUserStore = defineStore('user', { // 设置 Token setToken(token: string) { this.token = `Bearer ${token}`; - localStorage.setItem('accessToken', this.token); + slsWithCatch('accessToken', this.token); }, deleteToken() { this.token = ''; - localStorage.removeItem('accessToken'); + rlsWithCatch('accessToken'); }, // 获取 Token @@ -60,14 +60,54 @@ export const useUserStore = defineStore('user', { // 设置用户信息 setUserInfo(userInfo: UserInfo | null) { this.userInfo = userInfo; + slsWithCatch('userInfo', JSON.stringify(userInfo)); + }, + clearUserInfo() { + this.userInfo = null; + rlsWithCatch('userInfo'); }, // 获取用户信息 - async fetchUserInfo() { + async getUserInfo() { const { code, data } = await fetchProfileInfo(); if (code === 200) { this.setUserInfo(data); } }, + clearUserAllowAccessRoutes() { + this.allowAccessRoutes = []; + rlsWithCatch('allowAccessRoutes'); + }, + getUserAllowAccessRoutes() { + const sidebarStore = useSidebarStore(); + const menuList = sidebarStore.menuList; + const appRoutes = router.getRoutes(); + + appRoutes.forEach((route: any) => { + if (!route.meta?.requiresAuth) { + this.allowAccessRoutes.push(route.name); + } + }); + + const pushAllowAccessRoutes = (includeRouteNames: string[]) => { + const matchedRoute = appRoutes + .filter((route: any) => includeRouteNames.includes(route.name)) + .map((route: any) => route.name); + this.allowAccessRoutes.push(...matchedRoute); + }; + + menuList.forEach((item) => { + if (item.children && item.children.length > 0) { + item.children.forEach((child: any) => { + pushAllowAccessRoutes(child.includeRouteNames); + }); + } else { + pushAllowAccessRoutes(item.includeRouteNames); + } + }); + + this.allowAccessRoutes = uniq(this.allowAccessRoutes); + slsWithCatch('allowAccessRoutes', JSON.stringify(this.allowAccessRoutes)); + }, }, }); diff --git a/src/styles/font.scss b/src/styles/font.scss index dc5bf95..ea0f7c1 100644 --- a/src/styles/font.scss +++ b/src/styles/font.scss @@ -1,24 +1,30 @@ @font-face { - font-family: 'PuHuiTi-Medium'; - src: url('@/assets/fonts/Alibaba-PuHuiTi-Medium.otf'); + font-family: 'PuHuiTi-Regular'; + src: url('@/assets/fonts/Alibaba-PuHuiTi-Regular.woff2') format('woff2'); + font-display: swap; } @font-face { - font-family: 'PuHuiTi-Regular'; - src: url('@/assets/fonts/Alibaba-PuHuiTi-Regular.otf'); + font-family: 'PuHuiTi-Medium'; + src: url('@/assets/fonts/Alibaba-PuHuiTi-Medium.woff2') format('woff2'); + font-display: swap; } @font-face { font-family: 'PuHuiTi-Bold'; - src: url('@/assets/fonts/Alibaba-PuHuiTi-Bold.otf'); + src: url('@/assets/fonts/Alibaba-PuHuiTi-Bold.woff2') format('woff2'); + font-display: swap; +} + +// 使用统一的字体族名 +.font-family-puhui-regular { + font-family: 'PuHuiTi-Regular' !important; +} + +.font-family-puhui-medium { + font-family: 'PuHuiTi-Medium' !important; } .font-family-puhui-bold { - font-family: PuHuiTi-Bold !important; -} -.font-family-puhui-medium { - font-family: PuHuiTi-Medium !important; -} -.font-family-puhui-regular { - font-family: PuHuiTi-Regular !important; + font-family: 'PuHuiTi-Bold' !important; } diff --git a/src/utils/stroage.ts b/src/utils/stroage.ts new file mode 100644 index 0000000..0313b36 --- /dev/null +++ b/src/utils/stroage.ts @@ -0,0 +1,47 @@ +export const glsWithCatch = (key: string) => { + try { + return localStorage?.getItem(key); + } catch (error) { + console.log(error); + } +}; + +export const slsWithCatch = (key: string, value: any) => { + try { + localStorage?.setItem(key, value); + } catch (error) { + console.log(error); + } +}; + +export const rlsWithCatch = (key: string) => { + try { + localStorage?.removeItem(key); + } catch (error) { + console.log(error); + } +}; + +export const gssWithCatch = (key: string) => { + try { + return sessionStorage?.getItem(key); + } catch (error) { + console.log(error); + } +}; + +export const sssWithCatch = (key: string, value: any) => { + try { + sessionStorage?.setItem(key, value); + } catch (error) { + console.log(error); + } +}; + +export const rssWithCatch = (key: string) => { + try { + sessionStorage?.removeItem(key); + } catch (error) { + console.log(error); + } +}; diff --git a/src/utils/user.ts b/src/utils/user.ts index 2ebb9b1..8a6ff1a 100644 --- a/src/utils/user.ts +++ b/src/utils/user.ts @@ -12,9 +12,24 @@ import { useSidebarStore } from '@/stores/modules/side-bar'; export function goUserLogin(query?: any) { router.push({ name: 'UserLogin', query }); } +// 初始化企业信息、navbar菜单、允许访问的路由 +export const getUserEnterpriseInfo = async () => { + const enterpriseStore = useEnterpriseStore(); + const sidebarStore = useSidebarStore(); + const userStore = useUserStore(); + + await enterpriseStore.getEnterpriseInfo(); + sidebarStore.getUserNavbarMenuList(); // 初始化navbar菜单 + userStore.getUserAllowAccessRoutes(); // 初始化允许访问的路由 +}; // 登录处理 export async function handleUserLogin() { + const userStore = useUserStore(); + + await userStore.getUserInfo(); // 初始化用户信息 + await getUserEnterpriseInfo(); + handleUserHome(); } @@ -28,9 +43,13 @@ export function handleUserLogout() { const enterpriseStore = useEnterpriseStore(); const sidebarStore = useSidebarStore(); - userStore.deleteToken(); - enterpriseStore.clearEnterpriseInfo(); + userStore.clearUserInfo(); + enterpriseStore.clearUserEnterpriseInfo(); + sidebarStore.clearUserNavbarMenuList(); + userStore.clearUserAllowAccessRoutes(); + sidebarStore.clearActiveMenuId(); + userStore.deleteToken(); goUserLogin(); } diff --git a/src/views/components/login/index.vue b/src/views/components/login/index.vue index a77e6b0..efdf9ba 100644 --- a/src/views/components/login/index.vue +++ b/src/views/components/login/index.vue @@ -225,7 +225,6 @@ const clearError = (field: string) => { const handleOk = async () => { visible.value = false; - await enterpriseStore.updateEnterpriseInfo(); handleUserLogin(); }; @@ -269,11 +268,9 @@ const getProfileInfo = async () => { mobileNumber.value = data['mobile']; accounts.value = enterprises; enterpriseStore.setEnterpriseInfo(data); - userStore.setUserInfo(data); if (enterprises.length > 0) { if (enterprises.length === 1) { - await enterpriseStore.updateEnterpriseInfo(); handleUserLogin(); } else { // 多个企业时候需要弹窗让用户选择企业 diff --git a/src/views/components/management/account/index.vue b/src/views/components/management/account/index.vue index b34f8b0..4d95ce7 100644 --- a/src/views/components/management/account/index.vue +++ b/src/views/components/management/account/index.vue @@ -99,7 +99,7 @@ const addAccountVisible = ref(false); const deleteVisible = ref(false); const deleteTitle = ref(''); -const enterpriseInfo = store.getEnterpriseInfo(); +const enterpriseInfo = store.enterpriseInfo; const okText = computed(() => { if (!canAddAccount.value) { diff --git a/src/views/components/management/enterprise/index.vue b/src/views/components/management/enterprise/index.vue index 9956eba..6826ec3 100644 --- a/src/views/components/management/enterprise/index.vue +++ b/src/views/components/management/enterprise/index.vue @@ -48,7 +48,7 @@ const form = reactive({ name: '', }); -const enterpriseInfo = store.getEnterpriseInfo(); +const enterpriseInfo = store.enterpriseInfo; const columns = [ { diff --git a/src/views/components/workplace/modules/product.vue b/src/views/components/workplace/modules/product.vue index c59c429..9a5216a 100644 --- a/src/views/components/workplace/modules/product.vue +++ b/src/views/components/workplace/modules/product.vue @@ -26,7 +26,7 @@ v-if="props.product.status === Status.Enable || props.product.status === Status.ON_TRIAL" class="primary-button" type="primary" - @click="gotoModule(props.product.menu_id)" + @click="gotoModule(props.product.id)" > 进入模块 @@ -70,13 +70,16 @@ import { trialProduct } from '@/api/all'; import { useRouter } from 'vue-router'; import CustomerServiceModal from '@/components/customer-service-modal.vue'; import { appRoutes } from '@/router/routes'; + import { useSidebarStore } from '@/stores/modules/side-bar'; +import { useEnterpriseStore } from '@/stores/modules/enterprise'; +import { useUserStore } from '@/stores'; +import { getUserEnterpriseInfo } from '@/utils/user'; const props = defineProps<{ product: Product; }>(); const emit = defineEmits(['refresh']); -const sidebarStore = useSidebarStore(); enum Status { Disable = 0, // 禁用 @@ -87,30 +90,37 @@ enum Status { } interface Product { - id: number; status: Status; name: string; image: string; desc: string; - menu_id: number; + id: number; expired_at?: number; } const visible = ref(false); const router = useRouter(); +const enterpriseStore = useEnterpriseStore(); +const userStore = useUserStore(); +const sidebarStore = useSidebarStore(); const handleTrial = async (id: any) => { - await trialProduct(id); - AMessage.success('试用成功!'); - emit('refresh'); + const { code } = await trialProduct(id); + if (code === 200) { + getUserEnterpriseInfo(); + + AMessage.success('试用成功!'); + emit('refresh'); + } }; const gotoModule = (menuId: number) => { - const _target = appRoutes.find((v) => v.meta.id === menuId); - if (_target) { - console.log({ _target }); - router.push({ name: _target.name }); - } + const routeMap: Record = { + '1': 'DataEngineHotTranslation', + '2': 'RepositoryBrandMaterials', + }; + console.log(routeMap[menuId]); + router.push({ name: routeMap[menuId] }); };