refactor(ai): optimize AI action initialization and user state handling

This commit is contained in:
Ray.Hao
2025-11-18 00:18:25 +08:00
parent 0fbd489e49
commit 0ce931ee39
5 changed files with 103 additions and 124 deletions

View File

@@ -16,23 +16,24 @@
</template>
<script setup lang="ts">
import { useAppStore, useSettingsStore } from "@/store";
import { useAppStore, useSettingsStore, useUserStore } from "@/store";
import { defaultSettings } from "@/settings";
import { ThemeMode, ComponentSize } from "@/enums";
import AiAssistant from "@/components/AiAssistant/index.vue";
import { AuthStorage } from "@/utils/auth";
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 = !!AuthStorage.getAccessToken();
const isLoggedIn = userStore.userInfo && Object.keys(userStore.userInfo).length > 0;
return isEnabled && isLoggedIn;
});

View File

@@ -1,5 +1,6 @@
import { useRoute } from "vue-router";
import AiCommandApi from "@/api/ai";
import { ElMessage, ElMessageBox } from "element-plus";
/**
* AI 操作处理器(简化版)
@@ -57,8 +58,6 @@ export function useAiAction(options: UseAiActionOptions = {}) {
// 用于跟踪是否已卸载,防止在卸载后执行回调
let isUnmounted = false;
// 存储待清理的 nextTick 回调
let pendingCallbacks: (() => void)[] = [];
/**
* 执行 AI 操作(统一处理确认、执行、反馈流程)
@@ -174,7 +173,6 @@ export function useAiAction(options: UseAiActionOptions = {}) {
// 如果需要确认,先显示确认对话框
if (needConfirm && confirmMessage) {
const { ElMessageBox } = await import("element-plus");
try {
await ElMessageBox.confirm(confirmMessage, "AI 助手操作确认", {
confirmButtonText: "确认执行",
@@ -222,7 +220,7 @@ export function useAiAction(options: UseAiActionOptions = {}) {
/**
* 初始化:处理 URL 参数中的 AI 操作
*/
function init() {
async function init() {
if (isUnmounted) return;
// 检查是否有 AI 助手传递的搜索参数
@@ -230,47 +228,35 @@ export function useAiAction(options: UseAiActionOptions = {}) {
const autoSearch = route.query.autoSearch as string;
const aiActionParam = route.query.aiAction as string;
// 判断是否有 AI 相关参数
const hasAiParams = (autoSearch === "true" && keywords) || aiActionParam;
// 在 nextTick 中执行,确保 DOM 已更新
nextTick(async () => {
if (isUnmounted) return;
// 处理自动搜索
if (autoSearch === "true" && keywords) {
const callback = () => {
if (!isUnmounted) {
handleAutoSearch(keywords);
// 1. 优先执行 onInit 初始化数据(如果配置了)
if (onInit) {
try {
await onInit();
} catch (error) {
console.error("初始化数据失败:", error);
}
};
pendingCallbacks.push(callback);
nextTick(callback);
}
}
// 处理 AI 操作
if (aiActionParam) {
const callback = () => {
if (!isUnmounted) {
try {
const aiAction = JSON.parse(decodeURIComponent(aiActionParam));
executeAiAction(aiAction);
} catch (error) {
console.error("解析 AI 操作失败:", error);
ElMessage.error("AI 操作参数解析失败");
}
}
};
pendingCallbacks.push(callback);
nextTick(callback);
}
// 2. 处理自动搜索
if (autoSearch === "true" && keywords) {
handleAutoSearch(keywords);
}
// 如果没有 AI 参数,调用 onInit 回调(适合初始加载数据
if (!hasAiParams && onInit) {
const callback = () => {
if (!isUnmounted) {
onInit();
// 3. 处理 AI 操作(在数据加载后执行
if (aiActionParam) {
try {
const aiAction = JSON.parse(decodeURIComponent(aiActionParam));
await executeAiAction(aiAction);
} catch (error) {
console.error("解析 AI 操作失败:", error);
ElMessage.error("AI 操作参数解析失败");
}
};
pendingCallbacks.push(callback);
nextTick(callback);
}
}
});
}
// 组件挂载时自动初始化
@@ -281,8 +267,6 @@ export function useAiAction(options: UseAiActionOptions = {}) {
// 组件卸载时清理
onBeforeUnmount(() => {
isUnmounted = true;
// 清理待执行的回调(虽然 nextTick 的回调无法直接取消,但至少标记为已卸载)
pendingCallbacks = [];
});
return {

View File

@@ -12,11 +12,11 @@ import { computed, ref } from "vue";
* const { selectedIds, handleSelectionChange, clearSelection } = useTableSelection<UserVO>();
* ```
*/
export function useTableSelection<T extends { id: number }>() {
export function useTableSelection<T extends { id: string | number }>() {
/**
* 选中的数据项ID列表
*/
const selectedIds = ref<number[]>([]);
const selectedIds = ref<(string | number)[]>([]);
/**
* 表格选中项变化处理
@@ -38,7 +38,7 @@ export function useTableSelection<T extends { id: number }>() {
* @param id 要检查的ID
* @returns 是否被选中
*/
function isSelected(id: number): boolean {
function isSelected(id: string | number): boolean {
return selectedIds.value.includes(id);
}

View File

@@ -63,15 +63,17 @@ const formComponents = {
// 投票通知
const voteUrl = "https://gitee.com/activity/2025opensource?ident=I6VXEH";
// 保存通知实例,用于在组件卸载时关闭
let notificationInstance: ReturnType<typeof ElNotification> | null = null;
// 显示投票通知
const showVoteNotification = () => {
ElNotification({
notificationInstance = ElNotification({
title: "⭐ Gitee 2025 开源评选 · 诚邀您的支持! 🙏",
message: `我正在参加 Gitee 2025 最受欢迎的开源软件投票活动,快来给我投票吧!<br/><a href="${voteUrl}" target="_blank" style="color: var(--el-color-primary); text-decoration: none; font-weight: 500;">点击投票 →</a>`,
type: "success",
position: "bottom-right",
duration: 0, // 不自动关闭
duration: 0,
dangerouslyUseHTMLString: true,
});
};
@@ -82,6 +84,14 @@ onMounted(() => {
showVoteNotification();
}, 500);
});
// 组件卸载时关闭通知
onBeforeUnmount(() => {
if (notificationInstance) {
notificationInstance.close();
notificationInstance = null;
}
});
</script>
<style lang="scss" scoped>

View File

@@ -394,56 +394,10 @@ async function fetchUserList(): Promise<void> {
// ==================== 表格选择 ====================
const { selectedIds, hasSelection, handleSelectionChange } = useTableSelection<UserPageVO>();
// ==================== AI 助手相关 ====================
useAiAction({
actionHandlers: {
/**
* AI 修改用户昵称
* 使用配置对象方式:自动处理确认、执行、反馈
*/
updateUserNickname: {
needConfirm: true,
callBackendApi: true, // 自动调用后端 API
confirmMessage: (args: any) =>
`AI 助手将执行以下操作:<br/>
<strong>修改用户:</strong> ${args.username}<br/>
<strong>新昵称:</strong> ${args.nickname}<br/><br/>
确认执行吗?`,
successMessage: (args: any) => `已将用户 ${args.username} 的昵称修改为 ${args.nickname}`,
execute: async () => {
// callBackendApi=true 时execute 可以为空
// Composable 会自动调用后端 API
},
},
/**
* AI 查询用户
* 使用配置对象方式:查询操作不需要确认
*/
queryUser: {
needConfirm: false, // 查询操作无需确认
successMessage: (args: any) => `已搜索:${args.keywords}`,
execute: async (args: any) => {
queryParams.keywords = args.keywords;
await handleQuery();
},
},
},
onRefresh: fetchData,
onAutoSearch: (keywords: string) => {
queryParams.keywords = keywords;
setTimeout(() => {
handleQuery();
ElMessage.success(`AI 助手已为您自动搜索:${keywords}`);
}, 300);
},
onInit: handleQuery,
});
// ==================== 查询操作 ====================
/**
* 查询用户列表(重置到第一页)
* 查询用户列表
*/
function handleQuery(): Promise<void> {
queryParams.pageNum = 1;
@@ -459,9 +413,6 @@ function handleResetQuery(): void {
queryParams.createTime = undefined;
handleQuery();
}
// handleSelectionChange 已由 useTableSelection 提供
// ==================== 用户操作 ====================
/**
@@ -569,8 +520,8 @@ const handleSubmit = useDebounceFn(async () => {
* 删除用户
* @param id 用户ID单个删除时传入
*/
function handleDelete(id?: number): void {
const userIds = id ? String(id) : selectedIds.value.join(",");
function handleDelete(id?: string): void {
const userIds = id ? id : selectedIds.value.join(",");
if (!userIds) {
ElMessage.warning("请勾选删除项");
@@ -578,9 +529,16 @@ function handleDelete(id?: number): void {
}
// 安全检查:防止删除当前登录用户
if (isCurrentUserInDeleteList(id)) {
ElMessage.error("不能删除当前登录用户");
return;
const currentUserId = userStore.userInfo?.userId;
if (currentUserId) {
const isCurrentUserInList = id
? id === currentUserId
: selectedIds.value.some((selectedId) => String(selectedId) === currentUserId);
if (isCurrentUserInList) {
ElMessage.error("不能删除当前登录用户");
return;
}
}
ElMessageBox.confirm("确认删除选中的用户吗?", "警告", {
@@ -606,23 +564,6 @@ function handleDelete(id?: number): void {
});
}
/**
* 检查删除列表中是否包含当前登录用户
* @param singleId 单个删除的用户ID
*/
function isCurrentUserInDeleteList(singleId?: number): boolean {
const currentUserId = userStore.userInfo?.userId;
if (!currentUserId) return false;
// 单个删除检查
if (singleId) {
return String(singleId) === currentUserId;
}
// 批量删除检查
return selectedIds.value.some((id) => String(id) === currentUserId);
}
// ==================== 导入导出 ====================
/**
@@ -666,6 +607,49 @@ async function handleExport(): Promise<void> {
}
}
// 初始化数据加载由 useAiAction 的 onInit 回调统一处理
// 无需手动在 onMounted 中调用 handleQuery()
// ==================== AI 助手相关 ====================
useAiAction({
actionHandlers: {
/**
* AI 修改用户昵称
* 使用配置对象方式:自动处理确认、执行、反馈
*/
updateUserNickname: {
needConfirm: true,
callBackendApi: true, // 自动调用后端 API
confirmMessage: (args: any) =>
`AI 助手将执行以下操作:<br/>
<strong>修改用户:</strong> ${args.username}<br/>
<strong>新昵称:</strong> ${args.nickname}<br/><br/>
确认执行吗?`,
successMessage: (args: any) => `已将用户 ${args.username} 的昵称修改为 ${args.nickname}`,
execute: async () => {
// callBackendApi=true 时execute 可以为空
// Composable 会自动调用后端 API
},
},
/**
* AI 查询用户
* 使用配置对象方式:查询操作不需要确认
*/
queryUser: {
needConfirm: false, // 查询操作无需确认
successMessage: (args: any) => `已搜索:${args.keywords}`,
execute: async (args: any) => {
queryParams.keywords = args.keywords;
await handleQuery();
},
},
},
onRefresh: fetchUserList,
onAutoSearch: (keywords: string) => {
queryParams.keywords = keywords;
setTimeout(() => {
handleQuery();
ElMessage.success(`AI 助手已为您自动搜索:${keywords}`);
}, 300);
},
onInit: handleQuery,
});
</script>