refactor: 移除AI助手和令牌管理相关功能

移除前端AI助手组件及相关API、状态管理
移除后端令牌管理模块的模型、控制器、服务和路由
清理不再使用的依赖项和导入
更新前端设置和全局类型定义
简化用户管理界面操作逻辑
移除租户相关字段和模型继承
This commit is contained in:
zhangtao
2025-12-26 00:48:40 +08:00
parent 70762804d7
commit 21097b3e99
35 changed files with 50 additions and 2941 deletions
+1 -14
View File
@@ -8,36 +8,23 @@
class="wh-full"
>
<router-view />
<!-- AI 助手 -->
<AiAssistant v-if="enableAiAssistant" />
</el-watermark>
</el-config-provider>
</template>
<script setup lang="ts">
import { useAppStore, useSettingsStore, useUserStore } from "@/store";
import { useAppStore, useSettingsStore } from "@/store";
import { defaultSettings } from "@/settings";
import { ThemeMode } from "@/enums/settings/theme.enum";
import { ComponentSize } from "@/enums/settings/layout.enum";
import AiAssistant from "@/components/AiAssistant/index.vue";
const appStore = useAppStore();
const settingsStore = useSettingsStore();
const userStore = useUserStore();
const locale = computed(() => appStore.locale);
const size = computed(() => appStore.size as ComponentSize);
const showWatermark = computed(() => settingsStore.showWatermark);
// 只有在启用 AI 助手且用户已登录时才显示
// 使用 userInfo 作为响应式依赖,当用户退出登录时会自动更新
const enableAiAssistant = computed(() => {
const isEnabled = settingsStore.enableAiAssistant;
const isLoggedIn = userStore.basicInfo && Object.keys(userStore.basicInfo).length > 0;
return isEnabled && isLoggedIn;
});
// 明亮/暗黑主题水印字体颜色适配
const fontColor = computed(() => {
return settingsStore.theme === ThemeMode.DARK ? "rgba(255, 255, 255, .15)" : "rgba(0, 0, 0, .15)";
-180
View File
@@ -1,180 +0,0 @@
import request from "@/utils/request";
/**
* AI 命令请求参数
*/
export interface AiCommandRequest {
/** 用户输入的自然语言命令 */
command: string;
/** 当前页面路由(用于上下文) */
currentRoute?: string;
/** 当前激活的组件名称 */
currentComponent?: string;
/** 额外上下文信息 */
context?: Record<string, any>;
}
/**
* 函数调用参数
*/
export interface FunctionCall {
/** 函数名称 */
name: string;
/** 函数描述 */
description?: string;
/** 参数对象 */
arguments: Record<string, any>;
}
/**
* AI 命令解析响应
*/
export interface AiCommandResponse {
/** 解析日志ID(用于关联执行记录) */
parseLogId?: string;
/** 是否成功解析 */
success: boolean;
/** 解析后的函数调用列表 */
functionCalls: FunctionCall[];
/** AI 的理解和说明 */
explanation?: string;
/** 置信度 (0-1) */
confidence?: number;
/** 错误信息 */
error?: string;
/** 原始 LLM 响应(用于调试) */
rawResponse?: string;
}
/**
* AI 命令执行请求
*/
export interface AiExecuteRequest {
/** 关联的解析日志ID */
parseLogId?: string;
/** 原始命令(用于审计) */
originalCommand?: string;
/** 要执行的函数调用 */
functionCall: FunctionCall;
/** 确认模式:auto=自动执行, manual=需要用户确认 */
confirmMode?: "auto" | "manual";
/** 用户确认标志 */
userConfirmed?: boolean;
/** 幂等性令牌(防止重复执行) */
idempotencyKey?: string;
/** 当前页面路由 */
currentRoute?: string;
}
/**
* AI 命令执行响应
*/
export interface AiExecuteResponse {
/** 是否执行成功 */
success: boolean;
/** 执行结果数据 */
data?: any;
/** 执行结果说明 */
message?: string;
/** 影响的记录数 */
affectedRows?: number;
/** 错误信息 */
error?: string;
/** 记录ID(用于追踪) */
recordId?: string;
/** 需要用户确认 */
requiresConfirmation?: boolean;
/** 确认提示信息 */
confirmationPrompt?: string;
}
export interface AiCommandRecordPageQuery extends PageQuery {
keywords?: string;
executeStatus?: number;
parseStatus?: number;
userId?: number;
aiProvider?: string;
aiModel?: string;
functionName?: string;
createTime?: [string, string];
}
export interface AiCommandRecordVO {
id: string;
userId: number;
username: string;
originalCommand: string;
aiProvider?: string;
aiModel?: string;
parseStatus?: number;
functionCalls?: string;
explanation?: string;
confidence?: number;
parseErrorMessage?: string;
inputTokens?: number;
outputTokens?: number;
parseDurationMs?: number;
functionName?: string;
functionArguments?: string;
executeStatus?: number;
executeErrorMessage?: string;
ipAddress?: string;
createTime?: string;
updateTime?: string;
}
/**
* AI 命令 API
*/
class AiCommandApi {
/**
* 解析自然语言命令
*
* @param data 命令请求参数
* @returns 解析结果
*/
static parseCommand(data: AiCommandRequest): Promise<AiCommandResponse> {
return request<any, AiCommandResponse>({
url: "/api/v1/ai/command/parse",
method: "post",
data,
});
}
/**
* 执行已解析的命令
*
* @param data 执行请求参数
* @returns 执行结果数据(成功时返回,失败时抛出异常)
*/
static executeCommand(data: AiExecuteRequest): Promise<any> {
return request<any, any>({
url: "/api/v1/ai/command/execute",
method: "post",
data,
});
}
/**
* 获取命令记录分页列表
*/
static getCommandRecordPage(queryParams: AiCommandRecordPageQuery) {
return request<any, PageResult<AiCommandRecordVO[]>>({
url: "/api/v1/ai/command/records",
method: "get",
params: queryParams,
});
}
/**
* 撤销命令执行(如果支持)
*/
static rollbackCommand(logId: string) {
return request({
url: `/api/v1/ai/command/rollback/${logId}`,
method: "post",
});
}
}
export default AiCommandApi;
-101
View File
@@ -1,101 +0,0 @@
import request from "@/utils/request";
const API_PATH = "/system/token";
const TokenAPI = {
getTokenList(query: TokenPageQuery) {
return request<ApiResponse<PageResult<TokenTable[]>>>({
url: `${API_PATH}/list`,
method: "get",
params: query,
});
},
getTokenDetail(query: number) {
return request<ApiResponse<TokenTable>>({
url: `${API_PATH}/detail/${query}`,
method: "get",
});
},
createToken(body: TokenForm) {
return request<ApiResponse>({
url: `${API_PATH}/create`,
method: "post",
data: body,
});
},
updateToken(id: number, body: TokenForm) {
return request<ApiResponse>({
url: `${API_PATH}/update/${id}`,
method: "put",
data: body,
});
},
deleteToken(body: number[]) {
return request<ApiResponse>({
url: `${API_PATH}/delete`,
method: "delete",
data: body,
});
},
batchToken(body: BatchType) {
return request<ApiResponse>({
url: `${API_PATH}/available/setting`,
method: "patch",
data: body,
});
},
exportToken(body: TokenPageQuery) {
return request<Blob>({
url: `${API_PATH}/export`,
method: "post",
data: body,
responseType: "blob",
});
},
downloadTemplateToken() {
return request<ApiResponse>({
url: `${API_PATH}/download/template`,
method: "post",
responseType: "blob",
});
},
importToken(body: FormData) {
return request<ApiResponse>({
url: `${API_PATH}/import`,
method: "post",
data: body,
headers: {
"Content-Type": "multipart/form-data",
},
});
},
};
export default TokenAPI;
export interface TokenPageQuery extends PageQuery {
name?: string;
status?: string;
created_time?: string[];
updated_time?: string[];
created_id?: number;
updated_id?: number;
}
export interface TokenTable extends BaseType {
name?: string;
created_by?: CommonType;
updated_by?: CommonType;
}
export interface TokenForm extends BaseFormType {
name?: string;
}
@@ -1,672 +0,0 @@
<template>
<!-- 悬浮按钮 -->
<div class="ai-assistant">
<!-- AI 助手图标按钮 -->
<el-button
v-if="!dialogVisible"
class="ai-fab-button"
type="primary"
circle
size="large"
@click="handleOpen"
>
<div class="i-svg:ai ai-icon" />
</el-button>
<!-- AI 对话框 -->
<el-dialog
v-model="dialogVisible"
title="AI 智能助手"
width="600px"
:close-on-click-modal="false"
draggable
class="ai-assistant-dialog"
>
<template #header>
<div class="dialog-header">
<div class="i-svg:ai header-icon" />
<span class="title">AI 智能助手</span>
</div>
</template>
<!-- 命令输入 -->
<div class="command-input">
<el-input
v-model="command"
type="textarea"
:rows="3"
placeholder="试试说:修改test用户的姓名为测试人员&#10;或者:跳转到用户管理&#10;按 Ctrl+Enter 快速发送"
:disabled="loading"
@keydown.ctrl.enter="handleExecute"
/>
</div>
<!-- 快捷命令示例 -->
<div class="quick-commands">
<div class="section-title">💡 试试这些命令:</div>
<el-tag
v-for="example in examples"
:key="example"
class="command-tag"
@click="command = example"
>
{{ example }}
</el-tag>
</div>
<!-- AI 响应结果 -->
<div v-if="response" class="ai-response">
<el-alert :title="response.explanation" type="success" :closable="false" show-icon />
<!-- 将要执行的操作 -->
<div v-if="response.action" class="action-preview">
<div class="action-title">🎯 将要执行:</div>
<div class="action-content">
<div v-if="response.action.type === 'navigate'">
<el-icon><Position /></el-icon>
跳转到:
<strong>{{ response.action.pageName }}</strong>
<span v-if="response.action.query" class="query-info">
并搜索:
<el-tag type="warning" size="small">{{ response.action.query }}</el-tag>
</span>
</div>
<div v-if="response.action.type === 'navigate-and-execute'">
<el-icon><Position /></el-icon>
跳转至:
<strong>{{ response.action.pageName }}</strong>
<span v-if="response.action.query" class="query-info">
并搜索:
<el-tag type="warning" size="small">{{ response.action.query }}</el-tag>
</span>
<el-divider direction="vertical" />
<el-icon><Tools /></el-icon>
执行:
<strong>{{ response.action.functionCall.name }}</strong>
</div>
<div v-if="response.action.type === 'execute'">
<el-icon><Tools /></el-icon>
执行:
<strong>{{ response.action.functionName }}</strong>
</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleExecute">
<el-icon><MagicStick /></el-icon>
执行命令
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import AiCommandApi from "@/api/ai";
type ToolFunctionCall = {
name: string;
arguments: Record<string, any>;
};
// 统一的动作描述(区分“跳转”、“跳转+执行”、“仅执行”三种场景)
type AiAction =
| {
type: "navigate";
path: string;
pageName: string;
query?: string;
}
| {
type: "navigate-and-execute";
path: string;
pageName: string;
query?: string;
functionCall: ToolFunctionCall;
}
| {
type: "execute";
functionName: string;
functionCall: ToolFunctionCall;
};
type AiResponse = {
explanation: string;
action: AiAction | null;
};
const router = useRouter();
// 状态管理
const dialogVisible = ref(false);
const command = ref("");
const loading = ref(false);
const response = ref<AiResponse | null>(null);
// 快捷命令示例
const examples = [
"修改test用户的姓名为测试人员",
"获取姓名为张三的用户信息",
"跳转到用户管理",
"打开角色管理页面",
];
// 打开对话框
const handleOpen = () => {
dialogVisible.value = true;
command.value = "";
response.value = null;
};
// 关闭对话框
const handleClose = () => {
dialogVisible.value = false;
command.value = "";
response.value = null;
};
// 执行命令
const handleExecute = async () => {
const rawCommand = command.value.trim();
if (!rawCommand) {
ElMessage.warning("请输入命令");
return;
}
// 优先检测无需调用 AI 的纯跳转命令
const directNavigation = tryDirectNavigate(rawCommand);
if (directNavigation && directNavigation.action) {
response.value = directNavigation;
await executeAction(directNavigation.action);
return;
}
loading.value = true;
try {
// 调用 AI API 解析命令
const result = await AiCommandApi.parseCommand({
command: rawCommand,
currentRoute: router.currentRoute.value.path,
currentComponent: router.currentRoute.value.name as string,
context: {
userRoles: [],
},
});
if (!result.success) {
ElMessage.error(result.error || "命令解析失败");
return;
}
// 解析 AI 返回的操作类型
const action = parseAction(result, rawCommand);
response.value = {
explanation: result.explanation ?? "命令解析成功,准备执行操作",
action,
};
// 等待用户确认后执行
if (action) {
await executeAction(action);
}
} catch (error: any) {
console.error("AI 命令执行失败:", error);
ElMessage.error(error.message || "命令执行失败");
} finally {
loading.value = false;
}
};
// 路由配置映射表(支持扩展)
const routeConfig = [
{ keywords: ["用户", "user", "user list"], path: "/system/user", name: "用户管理" },
{ keywords: ["角色", "role"], path: "/system/role", name: "角色管理" },
{ keywords: ["菜单", "menu"], path: "/system/menu", name: "菜单管理" },
{ keywords: ["部门", "dept"], path: "/system/dept", name: "部门管理" },
{ keywords: ["字典", "dict"], path: "/system/dict", name: "字典管理" },
{ keywords: ["日志", "log"], path: "/system/log", name: "系统日志" },
];
// 根据函数名推断路由(如 getUserInfo -> /system/user)
const normalizeText = (text: string) => text.replace(/\s+/g, " ").trim().toLowerCase();
const inferRouteFromFunction = (functionName: string) => {
const fnLower = normalizeText(functionName);
for (const config of routeConfig) {
// 检查函数名是否包含关键词(如 getUserInfo 包含 user)
if (config.keywords.some((kw) => fnLower.includes(kw.toLowerCase()))) {
return { path: config.path, name: config.name };
}
}
return null;
};
// 根据命令文本匹配路由
const matchRouteFromCommand = (cmd: string) => {
const normalized = normalizeText(cmd);
for (const config of routeConfig) {
if (config.keywords.some((kw) => normalized.includes(kw.toLowerCase()))) {
return { path: config.path, name: config.name };
}
}
return null;
};
const extractKeywordFromCommand = (cmd: string): string => {
const normalized = normalizeText(cmd);
// 从 routeConfig 动态获取所有数据类型关键词
const allKeywords = routeConfig.flatMap((config) =>
config.keywords.map((kw) => kw.toLowerCase())
);
const keywordsPattern = allKeywords.join("|");
const patterns = [
new RegExp(`(?:查询|获取|搜索|查找|找).*?([^\\s,,。]+?)(?:的)?(?:${keywordsPattern})`, "i"),
new RegExp(`(?:${keywordsPattern}).*?([^\\s,,。]+?)(?:的|信息|详情)?`, "i"),
new RegExp(
`(?:姓名为|名字叫|叫做|名称为|名是|为)([^\\s,,。]+?)(?:的)?(?:${keywordsPattern})?`,
"i"
),
new RegExp(`([^\\s,,。]+?)(?:的)?(?:${keywordsPattern})(?:信息|详情)?`, "i"),
];
for (const pattern of patterns) {
const match = normalized.match(pattern);
if (match && match[1]) {
let extracted = match[1].trim();
extracted = extracted.replace(/姓名为|名字叫|叫做|名称为|名是|为|的|信息|详情/g, "");
if (
extracted &&
!allKeywords.some((type) => extracted.toLowerCase().includes(type.toLowerCase()))
) {
return extracted;
}
}
}
return "";
};
const tryDirectNavigate = (rawCommand: string): AiResponse | null => {
const navigationIntents = ["跳转", "打开", "进入", "前往", "去", "浏览", "查看"];
const operationIntents = [
"修改",
"更新",
"变更",
"删除",
"添加",
"创建",
"设置",
"获取",
"查询",
"搜索",
];
const hasNavigationIntent = navigationIntents.some((keyword) => rawCommand.includes(keyword));
const hasOperationIntent = operationIntents.some((keyword) => rawCommand.includes(keyword));
if (!hasNavigationIntent || hasOperationIntent) {
return null;
}
const routeInfo = matchRouteFromCommand(rawCommand);
if (!routeInfo) {
return null;
}
const keyword = extractKeywordFromCommand(rawCommand);
const action: AiAction = {
type: "navigate",
path: routeInfo.path,
pageName: routeInfo.name,
query: keyword || undefined,
};
return {
explanation: `检测到跳转命令,正在前往 ${routeInfo.name}`,
action,
};
};
// 解析 AI 返回的操作类型
const parseAction = (result: any, rawCommand: string): AiAction | null => {
const cmd = normalizeText(rawCommand);
const primaryCall = result.functionCalls?.[0];
const functionName = primaryCall?.name;
// 优先从函数名推断路由,其次从命令文本匹配
let routeInfo = functionName ? inferRouteFromFunction(functionName) : null;
if (!routeInfo) {
routeInfo = matchRouteFromCommand(cmd);
}
const routePath = routeInfo?.path || "";
const pageName = routeInfo?.name || "";
const keyword = extractKeywordFromCommand(cmd);
if (primaryCall && functionName) {
const fnNameLower = functionName.toLowerCase();
// 1) 查询类函数(query/search/list/get)-> 跳转并执行筛选操作
const isQueryFunction =
fnNameLower.includes("query") ||
fnNameLower.includes("search") ||
fnNameLower.includes("list") ||
fnNameLower.includes("get");
if (isQueryFunction) {
// 统一使用 keywords 参数(约定大于配置)
const args = (primaryCall.arguments || {}) as Record<string, unknown>;
const keywords =
typeof args.keywords === "string" && args.keywords.trim().length > 0
? args.keywords
: keyword;
if (routePath) {
return {
type: "navigate-and-execute",
path: routePath,
pageName,
functionCall: primaryCall,
query: keywords || undefined,
};
}
}
// 2) 其他操作类函数(修改/删除/创建/更新等)-> 跳转并执行
const isModifyFunction =
fnNameLower.includes("update") ||
fnNameLower.includes("modify") ||
fnNameLower.includes("edit") ||
fnNameLower.includes("delete") ||
fnNameLower.includes("remove") ||
fnNameLower.includes("create") ||
fnNameLower.includes("add") ||
fnNameLower.includes("save");
if (isModifyFunction && routePath) {
return {
type: "navigate-and-execute",
path: routePath,
pageName,
functionCall: primaryCall,
};
}
// 3) 其他未匹配的函数,如果有路由则跳转,否则执行
if (routePath) {
return {
type: "navigate-and-execute",
path: routePath,
pageName,
functionCall: primaryCall,
};
}
return {
type: "execute",
functionName,
functionCall: primaryCall,
};
}
// 4) 无函数调用,仅跳转
if (routePath) {
return {
type: "navigate",
path: routePath,
pageName,
query: keyword || undefined,
};
}
return null;
};
// 定时器引用(用于清理)
let navigationTimer: ReturnType<typeof setTimeout> | null = null;
let executeTimer: ReturnType<typeof setTimeout> | null = null;
// 执行操作
const executeAction = async (action: AiAction) => {
// 🎯 新增:跳转并执行操作
if (action.type === "navigate-and-execute") {
ElMessage.success(`正在跳转到 ${action.pageName} 并执行操作...`);
// 清理之前的定时器
if (navigationTimer) {
clearTimeout(navigationTimer);
}
// 跳转并传递待执行的操作信息
navigationTimer = setTimeout(() => {
navigationTimer = null;
const queryParams: any = {
// 通过 URL 参数传递 AI 操作信息
aiAction: encodeURIComponent(
JSON.stringify({
functionName: action.functionCall.name,
arguments: action.functionCall.arguments,
timestamp: Date.now(),
})
),
};
// 如果有查询关键字,也一并传递
if (action.query) {
queryParams.keywords = action.query;
queryParams.autoSearch = "true";
}
router.push({
path: action.path,
query: queryParams,
});
// 关闭对话框
handleClose();
}, 800);
return;
}
if (action.type === "navigate") {
// 检查是否已经在目标页面
const currentPath = router.currentRoute.value.path;
if (currentPath === action.path) {
// 如果已经在目标页面
if (action.query) {
// 有查询关键字,直接在当前页面执行搜索
ElMessage.info(`您已在 ${action.pageName} 页面,为您执行搜索:${action.query}`);
// 触发路由更新,让页面执行搜索
router.replace({
path: action.path,
query: {
keywords: action.query,
autoSearch: "true",
_t: Date.now().toString(), // 添加时间戳强制刷新
},
});
} else {
// 没有查询关键字,只是跳转,给出提示
ElMessage.warning(`您已经在 ${action.pageName} 页面了`);
}
// 关闭对话框
handleClose();
return;
}
// 不在目标页面,正常跳转
ElMessage.success(`正在跳转到 ${action.pageName}...`);
// 清理之前的定时器
if (navigationTimer) {
clearTimeout(navigationTimer);
}
// 延迟一下让用户看到提示
navigationTimer = setTimeout(() => {
navigationTimer = null;
// 跳转并传递查询参数
router.push({
path: action.path,
query: action.query
? {
keywords: action.query, // 传递关键字参数
autoSearch: "true", // 标记自动搜索
}
: undefined,
});
// 关闭对话框
handleClose();
}, 1000);
} else if (action.type === "execute") {
// 执行函数调用
ElMessage.info("功能开发中,请前往 AI 命令助手页面体验完整功能");
// 清理之前的定时器
if (executeTimer) {
clearTimeout(executeTimer);
}
// 可以跳转到完整的 AI 命令页面
executeTimer = setTimeout(() => {
executeTimer = null;
router.push("/function/ai-command");
handleClose();
}, 1000);
}
};
// 组件卸载时清理定时器
onBeforeUnmount(() => {
if (navigationTimer) {
clearTimeout(navigationTimer);
navigationTimer = null;
}
if (executeTimer) {
clearTimeout(executeTimer);
executeTimer = null;
}
});
</script>
<style scoped lang="scss">
.ai-assistant {
.ai-fab-button {
position: fixed;
right: 30px;
bottom: 80px;
z-index: 9999;
width: 60px;
height: 60px;
box-shadow: 0 4px 12px rgba(2, 119, 252, 0.4);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 6px 20px rgba(2, 119, 252, 0.6);
transform: scale(1.1);
}
.ai-icon {
width: 32px;
height: 32px;
}
}
}
.ai-assistant-dialog {
.dialog-header {
display: flex;
gap: 12px;
align-items: center;
.header-icon {
width: 28px;
height: 28px;
}
.title {
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.command-input {
margin-bottom: 16px;
}
.quick-commands {
margin-bottom: 20px;
.section-title {
margin-bottom: 8px;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.command-tag {
margin-right: 8px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.3s;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
}
}
.ai-response {
margin-top: 16px;
.action-preview {
padding: 12px;
margin-top: 12px;
background-color: var(--el-fill-color-light);
border-radius: 8px;
.action-title {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.action-content {
display: flex;
gap: 8px;
align-items: center;
color: var(--el-text-color-regular);
.el-icon {
color: var(--el-color-primary);
}
.query-info {
margin-left: 8px;
}
}
}
}
.dialog-footer {
display: flex;
gap: 12px;
justify-content: flex-end;
}
}
</style>
-269
View File
@@ -1,269 +0,0 @@
import { useRoute } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import { onMounted, onBeforeUnmount, nextTick } from "vue";
import AiCommandApi from "@/api/ai";
/**
* AI 操作处理器(简化版)
*
* 可以是简单函数,也可以是配置对象
*/
export type AiActionHandler<T = any> =
| ((args: T) => Promise<void> | void)
| {
/** 执行函数 */
execute: (args: T) => Promise<void> | void;
/** 是否需要确认(默认 true) */
needConfirm?: boolean;
/** 确认消息(支持函数或字符串) */
confirmMessage?: string | ((args: T) => string);
/** 成功消息(支持函数或字符串) */
successMessage?: string | ((args: T) => string);
/** 是否调用后端 API(默认 false,如果为 true 则自动调用 executeCommand) */
callBackendApi?: boolean;
};
/**
* AI 操作配置
*/
export interface UseAiActionOptions {
/** 操作映射表:函数名 -> 处理器 */
actionHandlers?: Record<string, AiActionHandler>;
/** 数据刷新函数(操作完成后调用) */
onRefresh?: () => Promise<void> | void;
/** 自动搜索处理函数 */
onAutoSearch?: (keywords: string) => void;
/** 当前路由路径(用于执行命令时传递) */
currentRoute?: string;
}
/**
* AI 操作 Composable
*
* 统一处理 AI 助手传递的操作,支持:
* - 自动搜索(通过 keywords + autoSearch 参数)
* - 执行 AI 操作(通过 aiAction 参数)
* - 配置化的操作处理器
*/
export function useAiAction(options: UseAiActionOptions = {}) {
const route = useRoute();
const { actionHandlers = {}, onRefresh, onAutoSearch, currentRoute = route.path } = options;
// 用于跟踪是否已卸载,防止在卸载后执行回调
let isUnmounted = false;
/**
* 执行 AI 操作(统一处理确认、执行、反馈流程)
*/
async function executeAiAction(action: any) {
if (isUnmounted) return;
// 兼容两种入参:{ functionName, arguments } 或 { functionCall: { name, arguments } }
const fnCall = action.functionCall ?? {
name: action.functionName,
arguments: action.arguments,
};
if (!fnCall?.name) {
ElMessage.warning("未识别的 AI 操作");
return;
}
// 查找对应的处理器
const handler = actionHandlers[fnCall.name];
if (!handler) {
ElMessage.warning(`暂不支持操作: ${fnCall.name}`);
return;
}
try {
// 判断处理器类型(函数 or 配置对象)
const isSimpleFunction = typeof handler === "function";
if (isSimpleFunction) {
// 简单函数形式:直接执行
await handler(fnCall.arguments);
} else {
// 配置对象形式:统一处理确认、执行、反馈
const config = handler;
// 1. 确认阶段(默认需要确认)
if (config.needConfirm !== false) {
const confirmMsg =
typeof config.confirmMessage === "function"
? config.confirmMessage(fnCall.arguments)
: config.confirmMessage || "确认执行此操作吗?";
await ElMessageBox.confirm(confirmMsg, "AI 助手操作确认", {
confirmButtonText: "确认执行",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true,
});
}
// 2. 执行阶段
if (config.callBackendApi) {
// 自动调用后端 API
await AiCommandApi.executeCommand({
originalCommand: action.originalCommand || "",
confirmMode: "manual",
userConfirmed: true,
currentRoute,
functionCall: {
name: fnCall.name,
arguments: fnCall.arguments,
},
});
} else {
// 执行自定义函数
await config.execute(fnCall.arguments);
}
// 3. 成功反馈
const successMsg =
typeof config.successMessage === "function"
? config.successMessage(fnCall.arguments)
: config.successMessage || "操作执行成功";
ElMessage.success(successMsg);
}
// 4. 刷新数据
if (onRefresh) {
await onRefresh();
}
} catch (error: any) {
// 处理取消操作
if (error === "cancel") {
ElMessage.info("已取消操作");
return;
}
console.error("AI 操作执行失败:", error);
ElMessage.error(error.message || "操作执行失败");
}
}
/**
* 执行后端命令(通用方法)
*/
async function executeCommand(
functionName: string,
args: any,
options: {
originalCommand?: string;
confirmMode?: "auto" | "manual";
needConfirm?: boolean;
confirmMessage?: string;
} = {}
) {
const {
originalCommand = "",
confirmMode = "manual",
needConfirm = false,
confirmMessage,
} = options;
// 如果需要确认,先显示确认对话框
if (needConfirm && confirmMessage) {
try {
await ElMessageBox.confirm(confirmMessage, "AI 助手操作确认", {
confirmButtonText: "确认执行",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true,
});
} catch {
ElMessage.info("已取消操作");
return;
}
}
try {
await AiCommandApi.executeCommand({
originalCommand,
confirmMode,
userConfirmed: true,
currentRoute,
functionCall: {
name: functionName,
arguments: args,
},
});
ElMessage.success("操作执行成功");
} catch (error: any) {
if (error !== "cancel") {
throw error;
}
}
}
/**
* 处理自动搜索
*/
function handleAutoSearch(keywords: string) {
if (onAutoSearch) {
onAutoSearch(keywords);
} else {
ElMessage.info(`AI 助手已为您自动搜索:${keywords}`);
}
}
/**
* 初始化:处理 URL 参数中的 AI 操作
*
* 注意:此方法只处理 AI 相关参数,不负责页面数据的初始加载
* 页面数据加载应由组件的 onMounted 钩子自行处理
*/
async function init() {
if (isUnmounted) return;
// 检查是否有 AI 助手传递的参数
const keywords = route.query.keywords as string;
const autoSearch = route.query.autoSearch as string;
const aiActionParam = route.query.aiAction as string;
// 如果没有任何 AI 参数,直接返回
if (!keywords && !autoSearch && !aiActionParam) {
return;
}
// 在 nextTick 中执行,确保页面数据已加载
nextTick(async () => {
if (isUnmounted) return;
// 1. 处理自动搜索
if (autoSearch === "true" && keywords) {
handleAutoSearch(keywords);
}
// 2. 处理 AI 操作
if (aiActionParam) {
try {
const aiAction = JSON.parse(decodeURIComponent(aiActionParam));
await executeAiAction(aiAction);
} catch (error) {
console.error("解析 AI 操作失败:", error);
ElMessage.error("AI 操作参数解析失败");
}
}
});
}
// 组件挂载时自动初始化
onMounted(() => {
init();
});
// 组件卸载时清理
onBeforeUnmount(() => {
isUnmounted = true;
});
return {
executeAiAction,
executeCommand,
handleAutoSearch,
};
}
+1 -2
View File
@@ -1,2 +1 @@
export { useAiAction } from "./ai/useAiAction";
export type { UseAiActionOptions, AiActionHandler } from "./ai/useAiAction";
// 该文件作用是:导出所有 composable 函数,作为全局函数使用
-2
View File
@@ -43,8 +43,6 @@ export const defaultSettings: AppSettings = {
guideVisible: false,
/** 是否启动引导 */
showGuide: true,
// 是否启用 AI 助手
enableAiAssistant: false,
};
// 主题色预设 - 现代化配色方案
@@ -13,7 +13,6 @@ interface SettingsState {
showWatermark: boolean;
showSettings: boolean;
showGuide: boolean; // 引导功能开关
enableAiAssistant: boolean;
// 桌面端工具显示设置
showMenuSearch: boolean;
@@ -51,12 +50,6 @@ export const useSettingsStore = defineStore("setting", () => {
defaultSettings.showWatermark
);
// 是否启用 AI 助手
const enableAiAssistant = useStorage<boolean>(
"vea:ui:enable_ai_assistant",
defaultSettings.enableAiAssistant
);
// 是否显示系统设置
const showSettings = useStorage<boolean>(
SETTINGS_KEYS.SHOW_SETTINGS,
@@ -123,7 +116,6 @@ export const useSettingsStore = defineStore("setting", () => {
showNotification,
sidebarColorScheme,
layout,
enableAiAssistant,
} as const;
// 🎯 监听器 - 主题变化
@@ -197,7 +189,6 @@ export const useSettingsStore = defineStore("setting", () => {
showWatermark.value = defaultSettings.showWatermark;
showSettings.value = defaultSettings.showSettings;
showGuide.value = defaultSettings.showGuide;
enableAiAssistant.value = defaultSettings.enableAiAssistant;
// 桌面端工具设置
showMenuSearch.value = defaultSettings.showMenuSearch;
@@ -223,7 +214,6 @@ export const useSettingsStore = defineStore("setting", () => {
showWatermark,
showSettings,
showGuide,
enableAiAssistant,
// 🎯 桌面端工具状态
showMenuSearch,
-2
View File
@@ -99,8 +99,6 @@ declare global {
guideVisible: boolean;
/** 是否启动引导 */
showGuide: boolean;
/** 是否启用AI助手 */
enableAiAssistant: boolean;
}
/**
@@ -1,793 +0,0 @@
<!-- 演示示例 -->
<template>
<div class="app-container">
<!-- 搜索区域 -->
<div v-show="visible" class="search-container">
<el-form
ref="queryFormRef"
:model="queryFormData"
label-suffix=":"
:inline="true"
@submit.prevent="handleQuery"
>
<el-form-item prop="name" label="名称">
<el-input v-model="queryFormData.name" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 170px"
clearable
>
<el-option value="0" label="启用" />
<el-option value="1" label="停用" />
</el-select>
</el-form-item>
<el-form-item v-if="isExpand" prop="created_id" label="创建人">
<UserTableSelect
v-model="queryFormData.created_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<el-form-item v-if="isExpand" prop="updated_id" label="更新人">
<UserTableSelect
v-model="queryFormData.updated_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 时间范围,收起状态下隐藏 -->
<el-form-item v-if="isExpand" prop="created_time" label="创建时间">
<DatePicker
v-model="createdDateRange"
@update:model-value="handleCreatedDateRangeChange"
/>
</el-form-item>
<el-form-item v-if="isExpand" prop="updated_time" label="更新时间">
<DatePicker
v-model="updatedDateRange"
@update:model-value="handleUpdatedDateRangeChange"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item>
<el-button
v-hasPerm="['module_system:token:query']"
type="primary"
icon="search"
@click="handleQuery"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:token:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
{{ isExpand ? "收起" : "展开" }}
<el-icon>
<template v-if="isExpand">
<ArrowUp />
</template>
<template v-else>
<ArrowDown />
</template>
</el-icon>
</el-link>
</template>
</el-form-item>
</el-form>
</div>
<!-- 内容区域 -->
<el-card class="data-table">
<template #header>
<div class="card-header">
<span>
演示示例列表
<el-tooltip content="演示示例列表">
<QuestionFilled class="w-4 h-4 mx-1" />
</el-tooltip>
</span>
</div>
</template>
<!-- 功能区域 -->
<div class="data-table__toolbar">
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button
v-hasPerm="['module_system:token:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPerm="['module_system:token:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:token:patch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick('0')">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick('1')">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</div>
<div class="data-table__toolbar--right">
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导入">
<el-button
v-hasPerm="['module_system:token:import']"
type="success"
icon="upload"
circle
@click="handleOpenImportDialog"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button
v-hasPerm="['module_system:token:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="搜索显示/隐藏">
<el-button
v-hasPerm="['*:*:*']"
type="info"
icon="search"
circle
@click="visible = !visible"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button
v-hasPerm="['module_system:token:query']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-popover placement="bottom" trigger="click">
<template #reference>
<el-button type="danger" icon="operation" circle></el-button>
</template>
<el-scrollbar max-height="350px">
<template v-for="column in tableColumns" :key="column.prop">
<el-checkbox v-if="column.prop" v-model="column.show" :label="column.label" />
</template>
</el-scrollbar>
</el-popover>
</el-col>
</el-row>
</div>
</div>
<!-- 表格区域:系统配置列表 -->
<el-table
ref="tableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
height="500"
max-height="500"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
min-width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
fixed
label="序号"
min-width="60"
>
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'name')?.show"
label="名称"
prop="name"
min-width="140"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
label="状态"
prop="status"
min-width="120"
>
<template #default="scope">
<el-tag :type="scope.row.status ? 'success' : 'info'">
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
label="描述"
prop="description"
min-width="140"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_time')?.show"
label="创建时间"
prop="created_time"
min-width="180"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_time')?.show"
label="更新时间"
prop="updated_time"
min-width="180"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_id')?.show"
label="创建人"
prop="created_id"
min-width="120"
>
<template #default="scope">
<el-tag>{{ scope.row.created_by?.name }}</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_id')?.show"
label="更新人"
prop="updated_id"
min-width="120"
>
<template #default="scope">
<el-tag>{{ scope.row.updated_by?.name }}</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="180"
>
<template #default="scope">
<el-button
v-hasPerm="['module_system:token:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:token:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:token:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="名称" :span="2">
{{ detailFormData.name }}
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? "启用" : "停用" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">
{{ detailFormData.description }}
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.created_by?.name }}
</el-descriptions-item>
<el-descriptions-item label="更新人" :span="2">
{{ detailFormData.updated_by?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_time }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" :maxlength="50" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio value="0">启用</el-radio>
<el-radio value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</el-button>
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 导入弹窗 -->
<ImportModal
v-model="importDialogVisible"
:content-config="curdContentConfig"
@upload="handleUpload"
/>
<!-- 导出弹窗 -->
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:query-params="queryFormData"
:page-data="pageTableData"
:selection-data="selectionRows"
/>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "Token",
inheritAttrs: false,
});
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { ResultEnum } from "@/enums/api/result.enum";
import TokenAPI, { TokenTable, TokenForm, TokenPageQuery } from "@/api/module_system/token";
import ImportModal from "@/components/CURD/ImportModal.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import DatePicker from "@/components/DatePicker/index.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { QuestionFilled, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
import { formatToDateTime } from "@/utils/dateUtil";
const visible = ref(true);
const queryFormRef = ref();
const dataFormRef = ref();
const total = ref(0);
const selectIds = ref<number[]>([]);
const selectionRows = ref<TokenTable[]>([]);
const loading = ref(false);
const isExpand = ref(false);
const isExpandable = ref(true);
// 分页表单
const pageTableData = ref<TokenTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "name", label: "名称", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_time", label: "创建时间", show: true },
{ prop: "updated_time", label: "更新时间", show: true },
{ prop: "created_id", label: "创建人", show: true },
{ prop: "updated_id", label: "更新人", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 仅用于导出字段的列(排除非数据列及嵌套对象列)
const exportColumns = [
{ prop: "name", label: "名称" },
{ prop: "status", label: "状态" },
{ prop: "description", label: "描述" },
{ prop: "created_time", label: "创建时间" },
{ prop: "updated_time", label: "更新时间" },
];
// 导入/导出配置
const curdContentConfig = {
permPrefix: "module_system:token",
cols: exportColumns as any,
importTemplate: () => TokenAPI.downloadTemplateToken(),
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === "string") {
query.status = query.status === "true";
}
query.page_no = 1;
query.page_size = 9999;
const all: any[] = [];
while (true) {
const res = await TokenAPI.getTokenList(query);
const items = res.data?.data?.items || [];
const total = res.data?.data?.total || 0;
all.push(...items);
if (all.length >= total || items.length === 0) break;
query.page_no += 1;
}
return all;
},
} as unknown as IContentConfig;
// 详情表单
const detailFormData = ref<TokenTable>({});
// 日期范围临时变量
const createdDateRange = ref<[Date, Date] | []>([]);
// 更新时间范围临时变量
const updatedDateRange = ref<[Date, Date] | []>([]);
// 处理创建时间范围变化
function handleCreatedDateRangeChange(range: [Date, Date]) {
createdDateRange.value = range;
if (range && range.length === 2) {
queryFormData.created_time = [formatToDateTime(range[0]), formatToDateTime(range[1])];
} else {
queryFormData.created_time = undefined;
}
}
// 处理更新时间范围变化
function handleUpdatedDateRangeChange(range: [Date, Date]) {
updatedDateRange.value = range;
if (range && range.length === 2) {
queryFormData.updated_time = [formatToDateTime(range[0]), formatToDateTime(range[1])];
} else {
queryFormData.updated_time = undefined;
}
}
// 分页查询参数
const queryFormData = reactive<TokenPageQuery>({
page_no: 1,
page_size: 10,
name: undefined,
status: undefined,
created_time: undefined,
updated_time: undefined,
created_id: undefined,
updated_id: undefined,
});
// 编辑表单
const formData = reactive<TokenForm>({
id: undefined,
name: "",
status: "0",
description: undefined,
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
const rules = reactive({
name: [{ required: true, message: "请输入名称", trigger: "blur" }],
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
});
// 导入弹窗显示状态
const importDialogVisible = ref(false);
// 导出弹窗显示状态
const exportsDialogVisible = ref(false);
// 打开导入弹窗
function handleOpenImportDialog() {
importDialogVisible.value = true;
}
// 打开导出弹窗
function handleOpenExportsModal() {
exportsDialogVisible.value = true;
}
// 列表刷新
async function handleRefresh() {
await loadingData();
}
// 加载表格数据
async function loadingData() {
loading.value = true;
try {
const response = await TokenAPI.getTokenList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}
// 查询(重置页码后获取数据)
async function handleQuery() {
queryFormData.page_no = 1;
loadingData();
}
// 选择创建人后触发查询
function handleConfirm() {
handleQuery();
}
// 重置查询
async function handleResetQuery() {
queryFormRef.value.resetFields();
queryFormData.page_no = 1;
// 重置日期范围选择器
createdDateRange.value = [];
updatedDateRange.value = [];
queryFormData.created_time = undefined;
queryFormData.updated_time = undefined;
loadingData();
}
// 定义初始表单数据常量
const initialFormData: TokenForm = {
id: undefined,
name: "",
status: "0",
description: "",
};
// 重置表单
async function resetForm() {
if (dataFormRef.value) {
dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
}
// 完全重置 formData 为初始状态
Object.assign(formData, initialFormData);
}
// 行复选框选中项变化
async function handleSelectionChange(selection: any) {
selectIds.value = selection.map((item: any) => item.id);
selectionRows.value = selection;
}
// 关闭弹窗
async function handleCloseDialog() {
dialogVisible.visible = false;
resetForm();
}
// 打开弹窗
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await TokenAPI.getTokenDetail(id);
if (type === "detail") {
dialogVisible.title = "详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === "update") {
dialogVisible.title = "修改";
Object.assign(formData, response.data.data);
}
} else {
dialogVisible.title = "新增令牌";
formData.id = undefined;
}
dialogVisible.visible = true;
}
// 提交表单(防抖)
async function handleSubmit() {
// 表单校验
dataFormRef.value.validate(async (valid: any) => {
if (valid) {
loading.value = true;
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
const id = formData.id;
if (id) {
try {
await TokenAPI.updateToken(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
} else {
try {
await TokenAPI.createToken(formData);
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}
}
});
}
// 删除、批量删除
async function handleDelete(ids: number[]) {
ElMessageBox.confirm("确认删除该项数据?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
loading.value = true;
await TokenAPI.deleteToken(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: string) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
loading.value = true;
await TokenAPI.batchToken({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
// 处理上传
const handleUpload = async (formData: FormData) => {
try {
const response = await TokenAPI.importToken(formData);
if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success(`${response.data.msg},${response.data.data}`);
importDialogVisible.value = false;
await handleQuery();
}
} catch (error: any) {
console.error(error);
}
};
onMounted(() => {
loadingData();
});
</script>
<style lang="scss" scoped></style>
@@ -266,11 +266,7 @@
size="small"
link
:disabled="scope.row.is_superuser === true"
@click="
scope.row.is_superuser === true
? ElMessage.warning('系统超管角色,不可操作')
: hancleResetPassword(scope.row)
"
@click="hancleResetPassword(scope.row)"
>
重置密码
</el-button>
@@ -291,11 +287,7 @@
link
icon="edit"
:disabled="scope.row.is_superuser === true"
@click="
scope.row.is_superuser === true
? ElMessage.warning('系统超管角色,不可操作')
: handleOpenDialog('update', scope.row.id)
"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
@@ -306,11 +298,7 @@
link
icon="delete"
:disabled="scope.row.is_superuser === true"
@click="
scope.row.is_superuser === true
? ElMessage.warning('系统超管角色,不可操作')
: handleDelete([scope.row.id])
"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>