diff --git a/apps/src/api/core/menu.ts b/apps/src/api/core/menu.ts index 021b155..dfe417c 100644 --- a/apps/src/api/core/menu.ts +++ b/apps/src/api/core/menu.ts @@ -69,10 +69,19 @@ function convertMenuNodes(menus: LegacyMenu[]): RouteRecordStringComponent[] { }); } +/** + * 获取用户所有原始菜单 + */ +export async function getLegacyMenusApi() { + return (await requestClient.get('/account/menus')) || []; +} + /** * 获取用户所有菜单 */ export async function getAllMenusApi() { - const result = await requestClient.get('/account/menus'); + const result = await getLegacyMenusApi(); return convertMenuNodes(result || []); } + +export type { LegacyMenu }; diff --git a/apps/src/api/legacy/team.ts b/apps/src/api/legacy/team.ts index 01febf5..d57f76b 100644 --- a/apps/src/api/legacy/team.ts +++ b/apps/src/api/legacy/team.ts @@ -193,6 +193,48 @@ export async function pageBdLead(params: Record) { ); } +export async function pageBdTable(params: Record) { + return requestClient.get>>( + '/team/bd/page', + { + params, + }, + ); +} + +export async function pageBdMemberTable(params: Record) { + return requestClient.get>>( + '/team/bd/member/page', + { + params, + }, + ); +} + +export async function deleteBd(id: number | string) { + return requestClient.get('/team/bd/del', { + params: { id }, + }); +} + +export async function deleteBdMember(id: number | string) { + return requestClient.get('/team/bd/member/del', { + params: { id }, + }); +} + +export async function addBd(data: Record) { + return requestClient.post('/team/bd/add', data); +} + +export async function updateBd(data: Record) { + return requestClient.post('/team/bd/update', data); +} + +export async function changeBdLeader(data: Record) { + return requestClient.post('/team/bd/change-bd-leader', data); +} + export async function deleteBdLead(id: number | string) { return requestClient.get('/team/bd/leader/del', { params: { id }, diff --git a/apps/src/preferences.ts b/apps/src/preferences.ts index d11dd3c..0105a61 100644 --- a/apps/src/preferences.ts +++ b/apps/src/preferences.ts @@ -8,6 +8,7 @@ import { defineOverridesPreferences } from '@vben/preferences'; export const overridesPreferences = defineOverridesPreferences({ // overrides app: { + accessMode: 'frontend', defaultHomePath: '/workspace', name: '数据平台', }, diff --git a/apps/src/router/access.ts b/apps/src/router/access.ts index e9e9851..fdf43fc 100644 --- a/apps/src/router/access.ts +++ b/apps/src/router/access.ts @@ -2,18 +2,169 @@ import type { ComponentRecordType, GenerateMenuAndRoutesOptions, } from '@vben/types'; +import type { RouteRecordRaw } from 'vue-router'; import { generateAccessible } from '@vben/access'; import { preferences } from '@vben/preferences'; import { message } from 'antdv-next'; -import { getAllMenusApi } from '#/api'; +import { getAllMenusApi, getLegacyMenusApi, type LegacyMenu } from '#/api'; import { BasicLayout, IFrameView } from '#/layouts'; import { $t } from '#/locales'; const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); +const ROUTE_MENU_OVERRIDES: Record< + string, + { + aliases?: string[]; + routers?: string[]; + titles?: string[]; + } +> = { + BdLead: { aliases: ['BdLead'], routers: ['team/bd-lead'] }, + DynamicTagList: { aliases: ['DynamicTag'], routers: ['dynamic/tag'] }, + DynamicUserList: { aliases: ['UserDynamicList'], routers: ['user/dynamic/list'] }, + OperateBusinessDevelopment: { + aliases: ['BusinessDevelopment'], + routers: ['room/business-development'], + titles: ['BD列表'], + }, + PropsResourceConfig: { aliases: ['PropsConfig'], routers: ['props/config'] }, + PropsSourceGroup: { + aliases: ['PropsActivityRewardCnf'], + routers: ['props/props_source_group'], + }, + SystemMenusManager: { aliases: ['MenusManager'], routers: ['menus'] }, + SystemResourceManager: { aliases: ['ResourceManager'], routers: ['resource'] }, + SystemRoleManager: { aliases: ['RolesManager'], routers: ['roles'] }, + SystemUserManager: { aliases: ['UserManager'], routers: ['user'] }, + TeamBillList: { aliases: ['TeamBill'], routers: ['team/bill'] }, + TeamMemberList: { aliases: ['TeamMember'], routers: ['team/member'] }, + UserDiamondBalance: { + aliases: ['GameDoubleLayerFruit'], + routers: ['/user/diamond-balance'], + }, + UserDiamondRunWater: { + aliases: ['GameDoubleLayerFruit'], + routers: ['/user/diamond-run-water'], + }, +}; + +function normalizeRoutePath(path?: string | null) { + return String(path || '') + .trim() + .replace(/^\/+/, '') + .replace(/\/+$/, ''); +} + +function collectRouteAliases(route: RouteRecordRaw) { + const values: string[] = []; + if (route.name) { + values.push(String(route.name)); + } + const override = ROUTE_MENU_OVERRIDES[String(route.name || '')]; + if (override?.aliases) { + values.push(...override.aliases); + } + return new Set(values.filter(Boolean)); +} + +function collectRoutePaths(route: RouteRecordRaw) { + const values = [normalizeRoutePath(route.path)]; + const aliases = Array.isArray(route.alias) + ? route.alias + : route.alias + ? [route.alias] + : []; + values.push(...aliases.map((item) => normalizeRoutePath(String(item)))); + const override = ROUTE_MENU_OVERRIDES[String(route.name || '')]; + if (override?.routers) { + values.push(...override.routers.map((item) => normalizeRoutePath(item))); + } + return new Set(values.filter(Boolean)); +} + +function collectRouteTitles(route: RouteRecordRaw) { + const values = [String(route.meta?.title || '').trim()]; + const override = ROUTE_MENU_OVERRIDES[String(route.name || '')]; + if (override?.titles) { + values.push(...override.titles.map((item) => item.trim())); + } + return new Set(values.filter(Boolean)); +} + +function buildAuthorizedMenuMatcher(menus: LegacyMenu[]) { + const aliasSet = new Set(); + const routerSet = new Set(); + const titleSet = new Set(); + const queue = [...menus]; + + while (queue.length > 0) { + const item = queue.shift(); + if (!item) { + continue; + } + if (item.alias) { + aliasSet.add(String(item.alias)); + } + if (item.router) { + routerSet.add(normalizeRoutePath(item.router)); + } + if (item.menuType === 2 && item.menuName) { + titleSet.add(String(item.menuName).trim()); + } + if (Array.isArray(item.childrens) && item.childrens.length > 0) { + queue.push(...item.childrens); + } + } + + return (route: RouteRecordRaw) => { + for (const alias of collectRouteAliases(route)) { + if (aliasSet.has(alias)) { + return true; + } + } + for (const path of collectRoutePaths(route)) { + if (routerSet.has(path)) { + return true; + } + } + for (const title of collectRouteTitles(route)) { + if (titleSet.has(title)) { + return true; + } + } + return false; + }; +} + +function filterRoutesByAuthorizedMenus( + routes: RouteRecordRaw[], + isAuthorized: (route: RouteRecordRaw) => boolean, +): RouteRecordRaw[] { + return routes.flatMap((route) => { + const nextRoute: RouteRecordRaw = { ...route }; + const children = route.children + ? filterRoutesByAuthorizedMenus(route.children, isAuthorized) + : []; + + if (route.meta?.ignoreAccess || route.meta?.hideInMenu) { + nextRoute.children = children; + return [nextRoute]; + } + + if (children.length > 0) { + nextRoute.children = children; + return [nextRoute]; + } + + delete nextRoute.children; + return isAuthorized(route) ? [nextRoute] : []; + }); +} + async function generateAccess(options: GenerateMenuAndRoutesOptions) { const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); @@ -22,8 +173,21 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) { IFrameView, }; + let routes = options.routes || []; + + if (preferences.app.accessMode === 'frontend') { + message.loading({ + content: `${$t('common.loadingMenu')}...`, + duration: 1.5, + }); + const legacyMenus = await getLegacyMenusApi(); + const isAuthorized = buildAuthorizedMenuMatcher(legacyMenus); + routes = filterRoutesByAuthorizedMenus(routes, isAuthorized); + } + return await generateAccessible(preferences.app.accessMode, { ...options, + routes, fetchMenuListAsync: async () => { message.loading({ content: `${$t('common.loadingMenu')}...`, diff --git a/apps/src/router/routes/index.ts b/apps/src/router/routes/index.ts index 5d7e7bd..fe3cd5e 100644 --- a/apps/src/router/routes/index.ts +++ b/apps/src/router/routes/index.ts @@ -3,6 +3,7 @@ import type { RouteRecordRaw } from 'vue-router'; import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; import { coreRoutes, fallbackNotFoundRoute } from './core'; +import dashboardRoutes from './modules/dashboard'; const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { eager: true, @@ -13,8 +14,14 @@ const externalRouteFiles = import.meta.glob('./external/**/*.ts', { }); // const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); +const accessRouteFiles = Object.fromEntries( + Object.entries(dynamicRouteFiles).filter( + ([path]) => !path.endsWith('/dashboard.ts'), + ), +); + /** 动态路由 */ -const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); +const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(accessRouteFiles); /** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); @@ -25,6 +32,7 @@ const staticRoutes: RouteRecordRaw[] = []; * 无需走权限验证(会一直显示在菜单中) */ const routes: RouteRecordRaw[] = [ ...coreRoutes, + ...dashboardRoutes, ...externalRoutes, fallbackNotFoundRoute, ]; diff --git a/apps/src/router/routes/modules/room.ts b/apps/src/router/routes/modules/room.ts index 83fd539..20a70ce 100644 --- a/apps/src/router/routes/modules/room.ts +++ b/apps/src/router/routes/modules/room.ts @@ -25,13 +25,6 @@ const routes: RouteRecordRaw[] = [ component: () => import('#/views/operate/set-hot-room.vue'), meta: { title: '热门房间' }, }, - { - name: 'OperateBusinessDevelopment', - path: 'business-development', - alias: '/operate/manager/room/business-development', - component: () => import('#/views/operate/business-development.vue'), - meta: { title: 'BD列表' }, - }, { name: 'OperateContributionBalance', path: 'contribution/balance', diff --git a/apps/src/router/routes/modules/team.ts b/apps/src/router/routes/modules/team.ts index 5c505c3..5123b75 100644 --- a/apps/src/router/routes/modules/team.ts +++ b/apps/src/router/routes/modules/team.ts @@ -52,6 +52,13 @@ const routes: RouteRecordRaw[] = [ component: () => import('#/views/team/application-process-list.vue'), meta: { title: '成员审核日志' }, }, + { + name: 'OperateBusinessDevelopment', + path: 'business-development', + alias: '/operate/manager/room/business-development', + component: () => import('#/views/operate/business-development.vue'), + meta: { title: 'BD列表' }, + }, { name: 'BdLead', path: 'team/bd-lead', diff --git a/apps/src/views/operate/business-development.vue b/apps/src/views/operate/business-development.vue index 0b849c9..23608c3 100644 --- a/apps/src/views/operate/business-development.vue +++ b/apps/src/views/operate/business-development.vue @@ -1,9 +1,444 @@ + + diff --git a/apps/src/views/operate/components/bd-bind-leader-modal.vue b/apps/src/views/operate/components/bd-bind-leader-modal.vue new file mode 100644 index 0000000..dcd8b02 --- /dev/null +++ b/apps/src/views/operate/components/bd-bind-leader-modal.vue @@ -0,0 +1,71 @@ + + + diff --git a/apps/src/views/operate/components/bd-bind-team-modal.vue b/apps/src/views/operate/components/bd-bind-team-modal.vue new file mode 100644 index 0000000..2546bbc --- /dev/null +++ b/apps/src/views/operate/components/bd-bind-team-modal.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/src/views/operate/components/bd-form-modal.vue b/apps/src/views/operate/components/bd-form-modal.vue new file mode 100644 index 0000000..d97a2aa --- /dev/null +++ b/apps/src/views/operate/components/bd-form-modal.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/apps/src/views/operate/components/bd-member-drawer.vue b/apps/src/views/operate/components/bd-member-drawer.vue new file mode 100644 index 0000000..e80b974 --- /dev/null +++ b/apps/src/views/operate/components/bd-member-drawer.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/apps/src/views/team/components/team-create-drawer.vue b/apps/src/views/team/components/team-create-drawer.vue index 9066b6c..9bcf19f 100644 --- a/apps/src/views/team/components/team-create-drawer.vue +++ b/apps/src/views/team/components/team-create-drawer.vue @@ -25,6 +25,9 @@ import { } from '../shared'; const props = defineProps<{ + defaultBdUserId?: string; + defaultRegion?: string; + lockBindBd?: boolean; open: boolean; sysOrigin: string; }>(); @@ -66,9 +69,11 @@ watch( return; } Object.assign(form, createForm(), { + bdUserId: props.defaultBdUserId || '', + region: props.defaultRegion || '', sysOrigin: props.sysOrigin || '', }); - bindBd.value = 'no'; + bindBd.value = props.defaultBdUserId ? 'yes' : 'no'; formSettingDefault.value = false; formContactAdd.value = false; }, @@ -158,7 +163,7 @@ async function handleSubmit() { /> - +