From e9e9f8681225dada84725610ab1163174e155ee0 Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Thu, 11 Dec 2025 09:18:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=9D=83=E9=99=90=E6=A0=87?= =?UTF-8?q?=E8=AF=86=E4=BF=AE=E6=94=B9=E5=90=8C=E6=AD=A5=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/ai/index.ts | 33 ++-- src/api/system/tenant-api.ts | 68 +++++++ src/assets/images/login-bg.svg | 14 +- src/components/CURD/types.ts | 2 +- src/components/TenantSelect/index.vue | 132 +++++++++++++ src/components/TenantSelectDialog/index.vue | 187 ++++++++++++++++++ src/directives/permission/index.ts | 2 +- src/enums/system/menu-enum.ts | 7 +- .../NavBar/components/NavbarActions.vue | 13 ++ src/store/index.ts | 1 + src/store/modules/tenant-store.ts | 135 +++++++++++++ src/utils/request.ts | 15 +- src/views/ai/command-record/index.vue | 157 ++++++--------- src/views/login/components/Login.vue | 40 +++- src/views/system/config/index.vue | 2 +- src/views/system/dept/index.vue | 6 +- src/views/system/menu/index.vue | 51 +++-- src/views/system/notice/index.vue | 4 +- src/views/system/user/index.vue | 4 +- 19 files changed, 712 insertions(+), 161 deletions(-) create mode 100644 src/api/system/tenant-api.ts create mode 100644 src/components/TenantSelect/index.vue create mode 100644 src/components/TenantSelectDialog/index.vue create mode 100644 src/store/modules/tenant-store.ts diff --git a/src/api/ai/index.ts b/src/api/ai/index.ts index faf208d5..802100a1 100644 --- a/src/api/ai/index.ts +++ b/src/api/ai/index.ts @@ -90,12 +90,11 @@ export interface AiExecuteResponse { export interface AiCommandRecordPageQuery extends PageQuery { keywords?: string; - executeStatus?: string; - parseSuccess?: boolean; + executeStatus?: number; + parseStatus?: number; userId?: number; - isDangerous?: boolean; - provider?: string; - model?: string; + aiProvider?: string; + aiModel?: string; functionName?: string; createTime?: [string, string]; } @@ -105,33 +104,23 @@ export interface AiCommandRecordVO { userId: number; username: string; originalCommand: string; - provider?: string; - model?: string; - parseSuccess?: boolean; + aiProvider?: string; + aiModel?: string; + parseStatus?: number; functionCalls?: string; explanation?: string; confidence?: number; parseErrorMessage?: string; inputTokens?: number; outputTokens?: number; - totalTokens?: number; - parseTime?: number; + parseDurationMs?: number; functionName?: string; functionArguments?: string; - executeStatus?: string; - executeResult?: string; + executeStatus?: number; executeErrorMessage?: string; - affectedRows?: number; - isDangerous?: boolean; - requiresConfirmation?: boolean; - userConfirmed?: boolean; - executionTime?: number; ipAddress?: string; - userAgent?: string; - currentRoute?: string; createTime?: string; updateTime?: string; - remark?: string; } /** @@ -180,9 +169,9 @@ class AiCommandApi { /** * 撤销命令执行(如果支持) */ - static rollbackCommand(recordId: string) { + static rollbackCommand(logId: string) { return request({ - url: `/api/v1/ai/command/rollback/${recordId}`, + url: `/api/v1/ai/command/rollback/${logId}`, method: "post", }); } diff --git a/src/api/system/tenant-api.ts b/src/api/system/tenant-api.ts new file mode 100644 index 00000000..92e902c9 --- /dev/null +++ b/src/api/system/tenant-api.ts @@ -0,0 +1,68 @@ +import request from "@/utils/request"; + +const TENANT_BASE_URL = "/api/v1/tenant"; + +/** + * 租户信息 + */ +export interface TenantInfo { + /** 租户ID */ + id: number; + /** 租户名称 */ + name: string; + /** 租户编码 */ + code?: string; + /** 租户状态(1-正常 0-禁用) */ + status?: number; + /** 联系人姓名 */ + contactName?: string; + /** 联系人电话 */ + contactPhone?: string; + /** 联系人邮箱 */ + contactEmail?: string; + /** 租户域名 */ + domain?: string; + /** 租户Logo */ + logo?: string; + /** 是否默认租户 */ + isDefault?: boolean; +} + +/** + * 租户 API + */ +const TenantAPI = { + /** + * 获取当前用户的租户列表 + */ + getTenantList() { + return request({ + url: `${TENANT_BASE_URL}/list`, + method: "get", + }); + }, + + /** + * 获取当前租户信息 + */ + getCurrentTenant() { + return request({ + url: `${TENANT_BASE_URL}/current`, + method: "get", + }); + }, + + /** + * 切换租户 + * + * @param tenantId 目标租户ID + */ + switchTenant(tenantId: number) { + return request({ + url: `${TENANT_BASE_URL}/switch/${tenantId}`, + method: "post", + }); + }, +}; + +export default TenantAPI; diff --git a/src/assets/images/login-bg.svg b/src/assets/images/login-bg.svg index d2351030..b143be88 100644 --- a/src/assets/images/login-bg.svg +++ b/src/assets/images/login-bg.svg @@ -15,43 +15,43 @@ - + - + - + - + - + - + - + diff --git a/src/components/CURD/types.ts b/src/components/CURD/types.ts index a5fe55ec..628e1908 100644 --- a/src/components/CURD/types.ts +++ b/src/components/CURD/types.ts @@ -23,7 +23,7 @@ type ToolbarTable = "edit" | "view" | "delete"; export type IToolsButton = { name: string; // 按钮名称 text?: string; // 按钮文本 - perm?: Array | string; // 权限标识(可以是完整权限字符串如'sys:user:add'或操作权限如'add') + perm?: Array | string; // 权限标识(可以是完整权限字符串如'sys:user:create'或操作权限如'create') attrs?: Partial & { style?: CSSProperties }; // 按钮属性 render?: (row: IObject) => boolean; // 条件渲染 }; diff --git a/src/components/TenantSelect/index.vue b/src/components/TenantSelect/index.vue new file mode 100644 index 00000000..42f774ef --- /dev/null +++ b/src/components/TenantSelect/index.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/components/TenantSelectDialog/index.vue b/src/components/TenantSelectDialog/index.vue new file mode 100644 index 00000000..18467040 --- /dev/null +++ b/src/components/TenantSelectDialog/index.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/src/directives/permission/index.ts b/src/directives/permission/index.ts index b2d856a4..bd462078 100644 --- a/src/directives/permission/index.ts +++ b/src/directives/permission/index.ts @@ -13,7 +13,7 @@ export const hasPerm: Directive = { // 校验传入的权限值是否合法 if (!requiredPerms || (typeof requiredPerms !== "string" && !Array.isArray(requiredPerms))) { throw new Error( - "需要提供权限标识!例如:v-has-perm=\"'sys:user:add'\" 或 v-has-perm=\"['sys:user:add', 'sys:user:edit']\"" + "需要提供权限标识!例如:v-has-perm=\"'sys:user:create'\" 或 v-has-perm=\"['sys:user:create', 'sys:user:update']\"" ); } diff --git a/src/enums/system/menu-enum.ts b/src/enums/system/menu-enum.ts index 3ed2f378..5e8352d1 100644 --- a/src/enums/system/menu-enum.ts +++ b/src/enums/system/menu-enum.ts @@ -1,7 +1,6 @@ // 核心枚举定义 export enum MenuTypeEnum { - CATALOG = 2, // 目录 - MENU = 1, // 菜单 - BUTTON = 4, // 按钮 - EXTLINK = 3, // 外链 + CATALOG = "C", // 目录 + MENU = "M", // 菜单 + BUTTON = "B", // 按钮 } diff --git a/src/layouts/components/NavBar/components/NavbarActions.vue b/src/layouts/components/NavBar/components/NavbarActions.vue index 3806c3d2..484af995 100644 --- a/src/layouts/components/NavBar/components/NavbarActions.vue +++ b/src/layouts/components/NavBar/components/NavbarActions.vue @@ -26,6 +26,11 @@ + + + @@ -74,11 +79,14 @@ import Fullscreen from "@/components/Fullscreen/index.vue"; import SizeSelect from "@/components/SizeSelect/index.vue"; import LangSelect from "@/components/LangSelect/index.vue"; import Notification from "@/components/Notification/index.vue"; +import TenantSelect from "@/components/TenantSelect/index.vue"; +import { useTenantStoreHook } from "@/store/modules/tenant-store"; const { t } = useI18n(); const appStore = useAppStore(); const settingStore = useSettingsStore(); const userStore = useUserStore(); +const tenantStore = useTenantStoreHook(); const route = useRoute(); const router = useRouter(); @@ -86,6 +94,11 @@ const router = useRouter(); // 是否为桌面设备 const isDesktop = computed(() => appStore.device === DeviceEnum.DESKTOP); +// 是否显示租户选择(如果用户有多个租户或已选择租户) +const showTenantSelect = computed(() => { + return tenantStore.tenantList.length > 0 || tenantStore.currentTenantId !== null; +}); + /** * 打开个人中心页面 */ diff --git a/src/store/index.ts b/src/store/index.ts index 9e6d43bf..d401ad65 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -14,4 +14,5 @@ export * from "./modules/settings-store"; export * from "./modules/tags-view-store"; export * from "./modules/user-store"; export * from "./modules/dict-store"; +export * from "./modules/tenant-store"; export { store }; diff --git a/src/store/modules/tenant-store.ts b/src/store/modules/tenant-store.ts new file mode 100644 index 00000000..d95f529e --- /dev/null +++ b/src/store/modules/tenant-store.ts @@ -0,0 +1,135 @@ +import { store } from "@/store"; +import TenantAPI, { type TenantInfo } from "@/api/system/tenant-api"; + +const TENANT_ID_KEY = "current_tenant_id"; +const TENANT_INFO_KEY = "current_tenant_info"; + +/** + * 租户 Store + */ +export const useTenantStore = defineStore("tenant", () => { + // 当前租户ID + const currentTenantId = ref(null); + // 当前租户信息 + const currentTenant = ref(null); + // 用户可访问的租户列表 + const tenantList = ref([]); + + /** + * 初始化租户信息 + * 从 localStorage 恢复上次使用的租户 + */ + function initTenant() { + const savedTenantId = localStorage.getItem(TENANT_ID_KEY); + const savedTenantInfo = localStorage.getItem(TENANT_INFO_KEY); + + if (savedTenantId) { + currentTenantId.value = Number(savedTenantId); + } + + if (savedTenantInfo) { + try { + currentTenant.value = JSON.parse(savedTenantInfo); + } catch (e) { + console.error("解析租户信息失败", e); + } + } + } + + /** + * 获取用户租户列表 + */ + function fetchTenantList() { + return new Promise((resolve, reject) => { + TenantAPI.getTenantList() + .then((data) => { + tenantList.value = data || []; + resolve(data || []); + }) + .catch((error) => { + reject(error); + }); + }); + } + + /** + * 设置当前租户 + * + * @param tenant 租户信息 + */ + function setCurrentTenant(tenant: TenantInfo) { + currentTenantId.value = tenant.id; + currentTenant.value = tenant; + + // 保存到 localStorage + localStorage.setItem(TENANT_ID_KEY, String(tenant.id)); + localStorage.setItem(TENANT_INFO_KEY, JSON.stringify(tenant)); + } + + /** + * 切换租户 + * + * @param tenantId 目标租户ID + */ + function switchTenant(tenantId: number) { + return new Promise((resolve, reject) => { + TenantAPI.switchTenant(tenantId) + .then((tenantInfo) => { + // 后端返回切换后的租户信息 + if (tenantInfo) { + setCurrentTenant(tenantInfo); + } else { + // 如果后端未返回,从租户列表中找到对应的租户信息 + const tenant = tenantList.value.find((t) => t.id === tenantId); + if (tenant) { + setCurrentTenant(tenant); + } else { + // 如果列表中没有,重新获取租户信息 + TenantAPI.getCurrentTenant() + .then((info) => { + if (info) { + setCurrentTenant(info); + } + }) + .catch(console.error); + } + } + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); + } + + /** + * 清除租户信息 + */ + function clearTenant() { + currentTenantId.value = null; + currentTenant.value = null; + tenantList.value = []; + localStorage.removeItem(TENANT_ID_KEY); + localStorage.removeItem(TENANT_INFO_KEY); + } + + // 初始化 + initTenant(); + + return { + currentTenantId, + currentTenant, + tenantList, + fetchTenantList, + setCurrentTenant, + switchTenant, + clearTenant, + }; +}); + +/** + * 在组件外部使用 TenantStore 的钩子函数 + */ +export function useTenantStoreHook() { + return useTenantStore(store); +} diff --git a/src/utils/request.ts b/src/utils/request.ts index 6da85474..29016498 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -4,6 +4,7 @@ import { ApiCodeEnum } from "@/enums/api/code-enum"; import { AuthStorage, redirectToLogin } from "@/utils/auth"; import { useTokenRefresh } from "@/composables/auth/useTokenRefresh"; import { authConfig } from "@/settings"; +import { useTenantStoreHook } from "@/store/modules/tenant-store"; // 初始化token刷新组合式函数 const { refreshTokenAndRetry } = useTokenRefresh(); @@ -19,7 +20,7 @@ const httpRequest = axios.create({ }); /** - * 请求拦截器 - 添加 Authorization 头 + * 请求拦截器 - 添加 Authorization 头和租户ID */ httpRequest.interceptors.request.use( (config: InternalAxiosRequestConfig) => { @@ -32,6 +33,18 @@ httpRequest.interceptors.request.use( delete config.headers.Authorization; } + // 添加租户ID到请求头(如果存在) + try { + const tenantStore = useTenantStoreHook(); + const tenantId = tenantStore.currentTenantId; + if (tenantId) { + config.headers["tenant-id"] = String(tenantId); + } + } catch (error) { + // 如果租户 store 未初始化,忽略错误 + console.debug("Tenant store not available:", error); + } + return config; }, (error) => { diff --git a/src/views/ai/command-record/index.vue b/src/views/ai/command-record/index.vue index e840c7e4..199bbac6 100644 --- a/src/views/ai/command-record/index.vue +++ b/src/views/ai/command-record/index.vue @@ -13,9 +13,9 @@ /> - + - + - + - - + + @@ -56,21 +56,9 @@ clearable style="width: 140px" > - - - - - - - - - - + + + @@ -117,29 +105,27 @@ min-width="160" show-overflow-tooltip /> - - + + - - - - - +