From cf3301c984e819c85662bcd93969f4b2edb88bc3 Mon Sep 17 00:00:00 2001 From: Boen_Shi Date: Thu, 30 Apr 2026 16:13:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=9B=E4=B8=80=E6=AD=A5=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=95=8C=E9=9D=A2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + components.d.ts | 2 + src/api/users.ts | 14 + src/composables/config.ts | 3 + src/composables/permission-tree.ts | 113 ++++++- src/layouts/MainLayout.vue | 11 +- src/pages/LoginPage.vue | 92 +++++- src/pages/ProfilePage.vue | 2 + src/pages/RolesPage.vue | 19 +- src/pages/ServersPage.vue | 490 ++++++++++++++++++----------- src/pages/UsersPage.vue | 131 +++++++- src/router/index.ts | 19 +- vite.config.ts | 2 +- 13 files changed, 687 insertions(+), 212 deletions(-) diff --git a/.gitignore b/.gitignore index b07a6a0..cfcb96c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ BACKAPI.md LOG.md REQUIRE.md SKILL.md +dist.zip diff --git a/components.d.ts b/components.d.ts index 8e7fbdd..1bec43c 100644 --- a/components.d.ts +++ b/components.d.ts @@ -8,6 +8,7 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + ElAlert: typeof import('element-plus/es')['ElAlert'] ElAside: typeof import('element-plus/es')['ElAside'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElButton: typeof import('element-plus/es')['ElButton'] @@ -41,6 +42,7 @@ declare module 'vue' { ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTag: typeof import('element-plus/es')['ElTag'] + ElUpload: typeof import('element-plus/es')['ElUpload'] HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] diff --git a/src/api/users.ts b/src/api/users.ts index bf1f7b1..e8677e0 100644 --- a/src/api/users.ts +++ b/src/api/users.ts @@ -19,4 +19,18 @@ export const usersApi = { syncPermissions(id: number, permissionIds: number[]) { return request.put(`/users/${id}/permissions`, { permission_ids: permissionIds }) }, + importUsers(file: File) { + const formData = new FormData() + formData.append('file', file) + return request.post('/users/import', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + }, + downloadImportTemplate() { + return request.get('/users/import/template', { + responseType: 'blob', + }) + }, } diff --git a/src/composables/config.ts b/src/composables/config.ts index 8e1d1fb..33b9911 100644 --- a/src/composables/config.ts +++ b/src/composables/config.ts @@ -1,6 +1,9 @@ import { useStorage } from '@vueuse/core' import { computed } from 'vue' +export const DEFAULT_SITE_TITLE = '生物信息团队服务器' +export const CONFIG_KEY_SITE_TITLE = 'site_title' + // 定义配置对象的类型,允许任意键值对 export interface AppConfig { [key: string]: any diff --git a/src/composables/permission-tree.ts b/src/composables/permission-tree.ts index 13b9410..a58359d 100644 --- a/src/composables/permission-tree.ts +++ b/src/composables/permission-tree.ts @@ -22,16 +22,117 @@ export function buildPermissionCascader(items: PermissionItem[]): CascaderNode[] groups.get(category)?.push(item) }) - return Array.from(groups.entries()).map(([category, groupItems]) => ({ + return Array.from(groups.entries()).map(([category, groupItems]) => { + if (category === '资源使用') { + return buildResourceUseCategoryNode(category, groupItems) + } + + return { + label: category, + value: `category:${category}`, + children: groupItems.map((item) => ({ + label: item.description ? `${item.description}` : item.name, + value: item.id, + })), + } + }) +} + +function buildResourceUseCategoryNode(category: string, groupItems: PermissionItem[]): CascaderNode { + const directItems: CascaderNode[] = [] + const serverMap = new Map() + + groupItems.forEach((item) => { + const name = String(item.name || '') + const parts = name.split('.') + const parsed = parseResourceDisplayNames(item.description) + if (!name.startsWith('resource.servers.use.') || parts.length < 4) { + directItems.push({ + label: item.description ? `${item.description}` : item.name, + value: item.id, + }) + return + } + + const fallbackServerName = String(parts[3] || '').trim() + const fallbackResourceName = String(parts[4] || '').trim() + const parsedServerName = String(parsed?.serverName || '').trim() + const serverName = (parsedServerName && parsedServerName !== '未命名服务器') ? parsedServerName : fallbackServerName + const resourceName = String(parsed?.resourceName || fallbackResourceName).trim() + if (!serverName) { + directItems.push({ + label: item.description ? `${item.description}` : item.name, + value: item.id, + }) + return + } + + if (!serverMap.has(serverName)) { + serverMap.set(serverName, { + label: serverName, + value: `server:${serverName}`, + children: [], + }) + } + + if (resourceName === '' || resourceName === serverName || fallbackResourceName === fallbackServerName) { + serverMap.get(serverName)?.children.push({ + label: `${serverName} 总权限`, + value: item.id, + }) + } else { + serverMap.get(serverName)?.children.push({ + label: `${serverName} ${resourceName}`, + value: item.id, + }) + } + }) + + const serverNodes = Array.from(serverMap.values()).sort((a, b) => a.label.localeCompare(b.label)) + serverNodes.forEach((node) => { + node.children.sort((a, b) => String(a.label).localeCompare(String(b.label))) + }) + + return { label: category, value: `category:${category}`, - children: groupItems.map((item) => ({ - label: item.description ? `${item.name}(${item.description})` : item.name, - value: item.id, - })), - })) + children: [...directItems, ...serverNodes], + } } export function extractPermissionIds(values: Array): number[] { return values.filter((item): item is number => typeof item === 'number') } + +function parseResourceDisplayNames(description?: string): { serverName: string; resourceName: string } | null { + const text = String(description || '').trim() + if (!text) { + return null + } + + const match = text.match(/服务器资源访问权限((.+?),资源ID[::]\s*\d+)/) + if (!match || !match[1]) { + return null + } + + const namePart = String(match[1]).trim() + const splitIndex = namePart.indexOf('-') + if (splitIndex <= 0 || splitIndex >= namePart.length - 1) { + if (namePart.startsWith('未命名服务器-')) { + return { + serverName: namePart.slice('未命名服务器-'.length).trim(), + resourceName: '', + } + } + + return { + serverName: namePart, + resourceName: '', + } + } + + return { + serverName: namePart.slice(0, splitIndex).trim(), + resourceName: namePart.slice(splitIndex + 1).trim(), + } +} diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index e6b21ef..29771a5 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -5,7 +5,7 @@
-
Bastion 控制台
+
{{ siteTitle }}控制台
安全访问管理
@@ -50,9 +50,10 @@ diff --git a/src/pages/ServersPage.vue b/src/pages/ServersPage.vue index e1f5e67..adc9504 100644 --- a/src/pages/ServersPage.vue +++ b/src/pages/ServersPage.vue @@ -11,12 +11,8 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
拖拽文件到这里,或点击选择
+
+ +
+ + + + + +
+ + +
- diff --git a/src/router/index.ts b/src/router/index.ts index 1ecfe15..a920b1d 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -47,7 +47,12 @@ router.beforeEach(async (to) => { const response = await authApi.me() authStore.setAuth(response.data.user, response.data.permissions) loaded = true - } catch (_error) { + } catch (error: any) { + const code = Number(error?.code || 0) + if (code === 423) { + loaded = true + return '/login' + } removeToken() authStore.clearAuth() loaded = false @@ -57,9 +62,21 @@ router.beforeEach(async (to) => { } if (token && to.meta.guest) { + const forcePasswordChange = Boolean((authStore.user as any)?.force_password_change) + if (forcePasswordChange) { + if (to.path === '/login') { + return true + } + return '/login' + } return '/' } + const forcePasswordChange = Boolean((authStore.user as any)?.force_password_change) + if (token && forcePasswordChange && to.path !== '/login') { + return '/login' + } + return true }) diff --git a/vite.config.ts b/vite.config.ts index 1cd0c9c..582116c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -33,7 +33,7 @@ export default defineConfig({ server: { proxy: { '/api': { - target: 'http://localhost:8080', // 后端地址 + target: 'http://localhost:8001', // 后端地址 changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') // 去掉 /api 前缀 }