From cbbbe4ab7a390ca83690ab3c1ba4ceb1ff695640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Mon, 10 Aug 2026 18:32:33 +0800 Subject: [PATCH] =?UTF-8?q?AI=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E3=80=81=E4=B8=AA=E4=BA=BA=E4=B8=AD=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/api/core/user.ts | 34 +- .../src/components/form/components/avatar.vue | 248 +++- apps/web-antd/src/router/access.ts | 43 +- apps/web-antd/src/router/guard.ts | 4 +- .../src/router/routes/modules/vben.ts | 11 +- apps/web-antd/src/store/auth.ts | 19 +- .../src/views/_core/profile/base-setting.vue | 145 +- .../src/views/_core/profile/index.vue | 434 +++++- .../src/views/_core/profile/login-log.vue | 104 ++ .../_core/profile/notification-setting.vue | 31 - .../src/views/_core/profile/op-log.vue | 119 ++ .../views/_core/profile/password-setting.vue | 66 +- .../views/_core/profile/security-setting.vue | 43 - .../src/views/system/config/api/endpoint.ts | 17 + .../src/views/system/config/api/index.ts | 3 + .../src/views/system/config/api/oss.ts | 52 + .../config/components/ApiEndpointManage.vue | 339 +++++ .../config/components/OssConfigManage.vue | 1187 +++++++++++++++++ .../system/config/components/OssManage.vue | 82 ++ .../config/components/OssRuntimeSelect.vue | 416 ++++++ .../src/views/system/config/index.vue | 52 +- 21 files changed, 3266 insertions(+), 183 deletions(-) create mode 100644 apps/web-antd/src/views/_core/profile/login-log.vue delete mode 100644 apps/web-antd/src/views/_core/profile/notification-setting.vue create mode 100644 apps/web-antd/src/views/_core/profile/op-log.vue delete mode 100644 apps/web-antd/src/views/_core/profile/security-setting.vue create mode 100644 apps/web-antd/src/views/system/config/api/endpoint.ts create mode 100644 apps/web-antd/src/views/system/config/api/oss.ts create mode 100644 apps/web-antd/src/views/system/config/components/ApiEndpointManage.vue create mode 100644 apps/web-antd/src/views/system/config/components/OssConfigManage.vue create mode 100644 apps/web-antd/src/views/system/config/components/OssManage.vue create mode 100644 apps/web-antd/src/views/system/config/components/OssRuntimeSelect.vue diff --git a/apps/web-antd/src/api/core/user.ts b/apps/web-antd/src/api/core/user.ts index 7757574a..28597c43 100644 --- a/apps/web-antd/src/api/core/user.ts +++ b/apps/web-antd/src/api/core/user.ts @@ -3,8 +3,40 @@ import type { UserInfo } from '@vben/types'; import { requestClient } from '#/api/request'; /** - * 获取用户信息 + * 获取当前登录用户信息(个人中心 / 顶栏) */ export async function getUserInfoApi() { return requestClient.get('admin/my-info'); } + +/** + * 更新个人资料(头像/昵称/手机/邮箱/简介) + */ +export async function updateProfile(data: Record) { + return requestClient.post('admin/update-profile', data); +} + +/** + * 修改当前用户密码(字段对齐后端 snake_case) + */ +export async function changePassword(data: { + old_password: string; + new_password: string; + confirm_password: string; +}) { + return requestClient.post('admin/change-password', data); +} + +/** + * 个人中心:我的登录日志(分页) + */ +export async function getLoginLog(data: Record = {}) { + return requestClient.get('admin/login-log', { params: data }); +} + +/** + * 个人中心:我的操作日志(分页,可带 endpoint name) + */ +export async function getOpLog(data: Record = {}) { + return requestClient.get('admin/op-log', { params: data }); +} diff --git a/apps/web-antd/src/components/form/components/avatar.vue b/apps/web-antd/src/components/form/components/avatar.vue index 7be437cf..3c9e4258 100644 --- a/apps/web-antd/src/components/form/components/avatar.vue +++ b/apps/web-antd/src/components/form/components/avatar.vue @@ -1,6 +1,13 @@ diff --git a/apps/web-antd/src/router/access.ts b/apps/web-antd/src/router/access.ts index 3a48be23..bef055cb 100644 --- a/apps/web-antd/src/router/access.ts +++ b/apps/web-antd/src/router/access.ts @@ -3,6 +3,8 @@ import type { GenerateMenuAndRoutesOptions, } from '@vben/types'; +import type { Router, RouteRecordRaw } from 'vue-router'; + import { generateAccessible } from '@vben/access'; import { preferences } from '@vben/preferences'; @@ -14,6 +16,38 @@ import { $t } from '#/locales'; const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); +/** + * 无需后端菜单授权、登录后人人可进的路由(个人中心等) + * 为什么单独挂:accessMode=backend 时只会注册菜单接口返回的路由,本地 modules 里的 Profile 进不去 + */ +function getAlwaysAccessibleRoutes(): RouteRecordRaw[] { + return [ + { + name: 'Profile', + path: '/profile', + component: () => import('#/views/_core/profile/index.vue'), + meta: { + icon: 'lucide:user', + hideInMenu: true, + title: $t('page.auth.profile'), + }, + }, + ]; +} + +/** + * 把「始终可访问」路由挂到 Root 下,避免 backend 模式漏注册 + */ +function ensureAlwaysAccessibleRoutes(router: Router) { + for (const route of getAlwaysAccessibleRoutes()) { + if (route.name && router.hasRoute(route.name)) { + continue; + } + // 挂在 Root(BasicLayout)下,保持顶栏/侧栏布局 + router.addRoute('Root', route); + } +} + async function generateAccess(options: GenerateMenuAndRoutesOptions) { const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); @@ -22,7 +56,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) { IFrameView, }; - return await generateAccessible(preferences.app.accessMode, { + const result = await generateAccessible(preferences.app.accessMode, { ...options, fetchMenuListAsync: async () => { message.loading({ @@ -37,6 +71,11 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) { layoutMap, pageMap, }); + + // backend 模式不会合并本地 modules;个人中心等公共页在此补挂 + ensureAlwaysAccessibleRoutes(options.router); + + return result; } -export { generateAccess }; +export { ensureAlwaysAccessibleRoutes, generateAccess }; diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index a1ad6d88..8bbcbd4b 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -8,7 +8,7 @@ import { startProgress, stopProgress } from '@vben/utils'; import { accessRoutes, coreRouteNames } from '#/router/routes'; import { useAuthStore } from '#/store'; -import { generateAccess } from './access'; +import { ensureAlwaysAccessibleRoutes, generateAccess } from './access'; /** * 通用守卫配置 @@ -87,6 +87,8 @@ function setupAccessGuard(router: Router) { // 是否已经生成过动态路由 if (accessStore.isAccessChecked) { + // 热更新/已登录刷新时补挂个人中心等公共页,避免 backend 模式漏路由 + ensureAlwaysAccessibleRoutes(router); return true; } diff --git a/apps/web-antd/src/router/routes/modules/vben.ts b/apps/web-antd/src/router/routes/modules/vben.ts index abcbee3e..a192fc42 100644 --- a/apps/web-antd/src/router/routes/modules/vben.ts +++ b/apps/web-antd/src/router/routes/modules/vben.ts @@ -101,16 +101,7 @@ const routes: RouteRecordRaw[] = [ order: 9999, }, }, - { - name: 'Profile', - path: '/profile', - component: () => import('#/views/_core/profile/index.vue'), - meta: { - icon: 'lucide:user', - hideInMenu: true, - title: $t('page.auth.profile'), - }, - }, + // 个人中心改由 access.ts 在后端权限模式下强制挂载(无需菜单授权) ]; export default routes; diff --git a/apps/web-antd/src/store/auth.ts b/apps/web-antd/src/store/auth.ts index 7f594daa..095f3a41 100644 --- a/apps/web-antd/src/store/auth.ts +++ b/apps/web-antd/src/store/auth.ts @@ -99,8 +99,23 @@ export const useAuthStore = defineStore('auth', () => { async function fetchUserInfo() { const userInfo = await getUserInfoApi(); - userStore.setUserInfo(userInfo); - return userInfo; + // 兼容后端 nick_name:布局/通知组件读 realName + const normalized = { + ...userInfo, + realName: + (userInfo as any)?.realName || + (userInfo as any)?.nick_name || + '', + username: + (userInfo as any)?.username || + (userInfo as any)?.phone || + '', + roles: (userInfo as any)?.roles ?? [ + (userInfo as any)?.role_value || 'user', + ], + } as UserInfo; + userStore.setUserInfo(normalized); + return normalized; } function $reset() { diff --git a/apps/web-antd/src/views/_core/profile/base-setting.vue b/apps/web-antd/src/views/_core/profile/base-setting.vue index aa8a4c26..c636e8b4 100644 --- a/apps/web-antd/src/views/_core/profile/base-setting.vue +++ b/apps/web-antd/src/views/_core/profile/base-setting.vue @@ -1,65 +1,138 @@ + + + diff --git a/apps/web-antd/src/views/_core/profile/index.vue b/apps/web-antd/src/views/_core/profile/index.vue index 8740894e..f4726d7f 100644 --- a/apps/web-antd/src/views/_core/profile/index.vue +++ b/apps/web-antd/src/views/_core/profile/index.vue @@ -1,49 +1,413 @@ + + + diff --git a/apps/web-antd/src/views/_core/profile/login-log.vue b/apps/web-antd/src/views/_core/profile/login-log.vue new file mode 100644 index 00000000..1ae144e3 --- /dev/null +++ b/apps/web-antd/src/views/_core/profile/login-log.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/apps/web-antd/src/views/_core/profile/notification-setting.vue b/apps/web-antd/src/views/_core/profile/notification-setting.vue deleted file mode 100644 index 324a4b39..00000000 --- a/apps/web-antd/src/views/_core/profile/notification-setting.vue +++ /dev/null @@ -1,31 +0,0 @@ - - diff --git a/apps/web-antd/src/views/_core/profile/op-log.vue b/apps/web-antd/src/views/_core/profile/op-log.vue new file mode 100644 index 00000000..fd7921f1 --- /dev/null +++ b/apps/web-antd/src/views/_core/profile/op-log.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/apps/web-antd/src/views/_core/profile/password-setting.vue b/apps/web-antd/src/views/_core/profile/password-setting.vue index 2f3527d4..6bcb739d 100644 --- a/apps/web-antd/src/views/_core/profile/password-setting.vue +++ b/apps/web-antd/src/views/_core/profile/password-setting.vue @@ -1,18 +1,28 @@ + + + diff --git a/apps/web-antd/src/views/_core/profile/security-setting.vue b/apps/web-antd/src/views/_core/profile/security-setting.vue deleted file mode 100644 index be30db58..00000000 --- a/apps/web-antd/src/views/_core/profile/security-setting.vue +++ /dev/null @@ -1,43 +0,0 @@ - - diff --git a/apps/web-antd/src/views/system/config/api/endpoint.ts b/apps/web-antd/src/views/system/config/api/endpoint.ts new file mode 100644 index 00000000..7fdc4d21 --- /dev/null +++ b/apps/web-antd/src/views/system/config/api/endpoint.ts @@ -0,0 +1,17 @@ +import { requestClient } from '#/api/request'; + +const prefix = 'api-endpoint/'; + +/** + * 接口注册表分页列表(用于操作日志开关管理) + */ +export async function getApiEndpointList(data: Record = {}) { + return requestClient.get(`${prefix}list`, { params: data }); +} + +/** + * 更新接口说明 / 是否记日志 / 状态(不改 url、method) + */ +export async function updateApiEndpoint(data: Record) { + return requestClient.post(`${prefix}update`, data); +} diff --git a/apps/web-antd/src/views/system/config/api/index.ts b/apps/web-antd/src/views/system/config/api/index.ts index 9e58ad57..4fa57550 100644 --- a/apps/web-antd/src/views/system/config/api/index.ts +++ b/apps/web-antd/src/views/system/config/api/index.ts @@ -1,5 +1,8 @@ import { requestClient } from '#/api/request'; +export * from './endpoint'; +export * from './oss'; + const prefix = 'ai-config/'; /** diff --git a/apps/web-antd/src/views/system/config/api/oss.ts b/apps/web-antd/src/views/system/config/api/oss.ts new file mode 100644 index 00000000..c1760cb2 --- /dev/null +++ b/apps/web-antd/src/views/system/config/api/oss.ts @@ -0,0 +1,52 @@ +import { requestClient } from '#/api/request'; + +const prefix = 'oss-config/'; + +/** + * OSS 驱动下拉/卡片选项(枚举:local/aliyun/...) + */ +export async function getOssDriverOptions() { + return requestClient.get(`${prefix}driver-options`); +} + +/** + * OSS 运行时选项:配置卡片列表 + 当前启用 ID + */ +export async function getOssRuntimeOptions() { + return requestClient.get(`${prefix}runtime-options`); +} + +/** + * 启用指定 OSS 配置(全局仅一套 active) + */ +export async function saveOssRuntime(data: { id: number }) { + return requestClient.post(`${prefix}save-runtime`, data); +} + +/** + * OSS 配置列表(密钥不回显明文,仅 has_access_key / has_secret_key) + */ +export async function getOssConfigList(data: Record = {}) { + return requestClient.get(`${prefix}list`, { params: data }); +} + +/** + * 新增 OSS 配置 + */ +export async function createOssConfig(data: Record) { + return requestClient.post(`${prefix}create`, data); +} + +/** + * 更新 OSS 配置;access_key / secret_key 留空表示不修改 + */ +export async function updateOssConfig(data: Record) { + return requestClient.post(`${prefix}update`, data); +} + +/** + * 删除 OSS 配置 + */ +export async function deleteOssConfig(ids: number[]) { + return requestClient.post(`${prefix}delete`, { ids }); +} diff --git a/apps/web-antd/src/views/system/config/components/ApiEndpointManage.vue b/apps/web-antd/src/views/system/config/components/ApiEndpointManage.vue new file mode 100644 index 00000000..1362bbf5 --- /dev/null +++ b/apps/web-antd/src/views/system/config/components/ApiEndpointManage.vue @@ -0,0 +1,339 @@ + + + + + diff --git a/apps/web-antd/src/views/system/config/components/OssConfigManage.vue b/apps/web-antd/src/views/system/config/components/OssConfigManage.vue new file mode 100644 index 00000000..97d40381 --- /dev/null +++ b/apps/web-antd/src/views/system/config/components/OssConfigManage.vue @@ -0,0 +1,1187 @@ + + + + + diff --git a/apps/web-antd/src/views/system/config/components/OssManage.vue b/apps/web-antd/src/views/system/config/components/OssManage.vue new file mode 100644 index 00000000..348d254a --- /dev/null +++ b/apps/web-antd/src/views/system/config/components/OssManage.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/apps/web-antd/src/views/system/config/components/OssRuntimeSelect.vue b/apps/web-antd/src/views/system/config/components/OssRuntimeSelect.vue new file mode 100644 index 00000000..6c8e624d --- /dev/null +++ b/apps/web-antd/src/views/system/config/components/OssRuntimeSelect.vue @@ -0,0 +1,416 @@ + + + + + diff --git a/apps/web-antd/src/views/system/config/index.vue b/apps/web-antd/src/views/system/config/index.vue index 3cafc8fc..49bd0c72 100644 --- a/apps/web-antd/src/views/system/config/index.vue +++ b/apps/web-antd/src/views/system/config/index.vue @@ -1,18 +1,21 @@