chore(deps): upgrade dependencies and refactor AI action handling

This commit is contained in:
Ray.Hao
2025-11-17 21:43:20 +08:00
parent 5aa6773cac
commit 0fbd489e49
7 changed files with 478 additions and 237 deletions

View File

@@ -52,21 +52,21 @@
"@wangeditor-next/editor": "^5.6.47",
"@wangeditor-next/editor-for-vue": "^5.1.14",
"animate.css": "^4.1.1",
"axios": "^1.13.1",
"axios": "^1.13.2",
"codemirror": "^5.65.20",
"codemirror-editor-vue3": "^2.8.0",
"default-passive-events": "^2.0.0",
"echarts": "^6.0.0",
"element-plus": "^2.11.7",
"element-plus": "^2.11.8",
"exceljs": "^4.4.0",
"lodash-es": "^4.17.21",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^8.3.0",
"pinia": "^3.0.3",
"pinia": "^3.0.4",
"qs": "^6.14.0",
"sortablejs": "^1.15.6",
"vue": "^3.5.22",
"vue": "^3.5.24",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.1.12",
"vue-router": "^4.6.3",
@@ -79,15 +79,15 @@
"@iconify/utils": "^2.3.0",
"@types/codemirror": "^5.60.17",
"@types/lodash-es": "^4.17.12",
"@types/node": "^24.10.0",
"@types/node": "^24.10.1",
"@types/nprogress": "^0.2.3",
"@types/path-browserify": "^1.0.3",
"@types/qs": "^6.14.0",
"@types/sortablejs": "^1.15.9",
"@typescript-eslint/eslint-plugin": "^8.46.3",
"@typescript-eslint/parser": "^8.46.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"@vitejs/plugin-vue": "^6.0.1",
"autoprefixer": "^10.4.21",
"autoprefixer": "^10.4.22",
"commitizen": "^4.3.1",
"cz-git": "^1.12.0",
"eslint": "^9.39.1",
@@ -101,7 +101,7 @@
"postcss-html": "^1.8.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.6.2",
"sass": "^1.93.3",
"sass": "^1.94.0",
"stylelint": "^16.25.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^6.1.0",
@@ -109,13 +109,13 @@
"stylelint-config-recommended-scss": "^14.1.0",
"stylelint-config-recommended-vue": "^1.6.1",
"stylelint-prettier": "^5.0.3",
"terser": "^5.44.0",
"terser": "^5.44.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.3",
"unocss": "^66.5.4",
"typescript-eslint": "^8.46.4",
"unocss": "^66.5.6",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0",
"vite": "^7.1.12",
"vite": "^7.2.2",
"vite-plugin-mock-dev-server": "^2.0.2",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^2.2.12"

View File

@@ -183,7 +183,7 @@ const handleExecute = async () => {
// 优先检测无需调用 AI 的纯跳转命令
const directNavigation = tryDirectNavigate(rawCommand);
if (directNavigation) {
if (directNavigation && directNavigation.action) {
response.value = directNavigation;
await executeAction(directNavigation.action);
return;

View File

@@ -10,3 +10,5 @@ export { useDeviceDetection } from "./layout/useDeviceDetection";
export { useAiAction } from "./useAiAction";
export type { UseAiActionOptions, AiActionHandler } from "./useAiAction";
export { useTableSelection } from "./useTableSelection";

View File

@@ -1,12 +1,25 @@
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { onMounted, onBeforeUnmount, nextTick } from "vue";
import AiCommandApi from "@/api/ai";
/**
* AI 操作处理器类型
* AI 操作处理器(简化版)
*
* 可以是简单函数,也可以是配置对象
*/
export type AiActionHandler = (args: any) => Promise<void> | void;
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 操作配置
@@ -18,6 +31,8 @@ export interface UseAiActionOptions {
onRefresh?: () => Promise<void> | void;
/** 自动搜索处理函数 */
onAutoSearch?: (keywords: string) => void;
/** 初始化回调(在没有 AI 参数时调用,适合初始加载数据) */
onInit?: () => Promise<void> | void;
/** 当前路由路径(用于执行命令时传递) */
currentRoute?: string;
}
@@ -32,7 +47,13 @@ export interface UseAiActionOptions {
*/
export function useAiAction(options: UseAiActionOptions = {}) {
const route = useRoute();
const { actionHandlers = {}, onRefresh, onAutoSearch, currentRoute = route.path } = options;
const {
actionHandlers = {},
onRefresh,
onAutoSearch,
onInit,
currentRoute = route.path,
} = options;
// 用于跟踪是否已卸载,防止在卸载后执行回调
let isUnmounted = false;
@@ -40,10 +61,11 @@ export function useAiAction(options: UseAiActionOptions = {}) {
let pendingCallbacks: (() => void)[] = [];
/**
* 执行 AI 操作
* 执行 AI 操作(统一处理确认、执行、反馈流程)
*/
async function executeAiAction(action: any) {
if (isUnmounted) return;
// 兼容两种入参:{ functionName, arguments } 或 { functionCall: { name, arguments } }
const fnCall = action.functionCall ?? {
name: action.functionName,
@@ -63,12 +85,68 @@ export function useAiAction(options: UseAiActionOptions = {}) {
}
try {
await handler(fnCall.arguments);
// 操作成功后刷新数据
// 判断处理器类型(函数 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 || "操作执行失败");
}
@@ -152,6 +230,9 @@ 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;
// 处理自动搜索
if (autoSearch === "true" && keywords) {
const callback = () => {
@@ -179,6 +260,17 @@ export function useAiAction(options: UseAiActionOptions = {}) {
pendingCallbacks.push(callback);
nextTick(callback);
}
// 如果没有 AI 参数,调用 onInit 回调(适合初始加载数据)
if (!hasAiParams && onInit) {
const callback = () => {
if (!isUnmounted) {
onInit();
}
};
pendingCallbacks.push(callback);
nextTick(callback);
}
}
// 组件挂载时自动初始化

View File

@@ -0,0 +1,63 @@
import { computed, ref } from "vue";
/**
* 表格行选择 Composable
*
* @description 提供统一的表格行选择逻辑包括选中ID管理和清空选择
* @template T 数据项类型,必须包含 id 属性
* @returns 返回选中的ID列表、选择变化处理函数、清空选择函数
*
* @example
* ```typescript
* const { selectedIds, handleSelectionChange, clearSelection } = useTableSelection<UserVO>();
* ```
*/
export function useTableSelection<T extends { id: number }>() {
/**
* 选中的数据项ID列表
*/
const selectedIds = ref<number[]>([]);
/**
* 表格选中项变化处理
* @param selection 选中的行数据列表
*/
function handleSelectionChange(selection: T[]): void {
selectedIds.value = selection.map((item) => item.id);
}
/**
* 清空选择
*/
function clearSelection(): void {
selectedIds.value = [];
}
/**
* 检查指定ID是否被选中
* @param id 要检查的ID
* @returns 是否被选中
*/
function isSelected(id: number): boolean {
return selectedIds.value.includes(id);
}
/**
* 获取选中的数量
*/
const selectedCount = computed(() => selectedIds.value.length);
/**
* 是否有选中项
*/
const hasSelection = computed(() => selectedIds.value.length > 0);
return {
selectedIds,
selectedCount,
hasSelection,
handleSelectionChange,
clearSelection,
isSelected,
};
}

View File

@@ -60,6 +60,28 @@ const formComponents = {
register: defineAsyncComponent(() => import("./components/Register.vue")),
resetPwd: defineAsyncComponent(() => import("./components/ResetPwd.vue")),
};
// 投票通知
const voteUrl = "https://gitee.com/activity/2025opensource?ident=I6VXEH";
// 显示投票通知
const showVoteNotification = () => {
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, // 不自动关闭
dangerouslyUseHTMLString: true,
});
};
// 延迟显示
onMounted(() => {
setTimeout(() => {
showVoteNotification();
}, 500);
});
</script>
<style lang="scss" scoped>

View File

@@ -67,7 +67,7 @@
v-hasPerm="'sys:user:delete'"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
:disabled="!hasSelection"
@click="handleDelete()"
>
删除
@@ -125,7 +125,7 @@
icon="RefreshLeft"
size="small"
link
@click="hancleResetPassword(scope.row)"
@click="handleResetPassword(scope.row)"
>
重置密码
</el-button>
@@ -158,7 +158,7 @@
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="fetchData"
@pagination="fetchUserList"
/>
</el-card>
</el-col>
@@ -245,57 +245,117 @@
</template>
<script setup lang="ts">
import { nextTick } from "vue";
import { useAppStore } from "@/store/modules/app-store";
import { DeviceEnum } from "@/enums/settings/device-enum";
import { useRoute } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import { useAiAction } from "@/composables";
import AiCommandApi from "@/api/ai";
// ==================== 1. Vue 核心 API ====================
import { computed, reactive, ref } from "vue";
import { useDebounceFn } from "@vueuse/core";
import UserAPI, { UserForm, UserPageQuery, UserPageVO } from "@/api/system/user-api";
// ==================== 2. Element Plus ====================
import { ElMessage, ElMessageBox } from "element-plus";
// ==================== 3. 类型定义 ====================
import type { UserForm, UserPageQuery, UserPageVO } from "@/api/system/user-api";
// ==================== 4. API 服务 ====================
import UserAPI from "@/api/system/user-api";
import DeptAPI from "@/api/system/dept-api";
import RoleAPI from "@/api/system/role-api";
// ==================== 5. Store ====================
import { useAppStore } from "@/store/modules/app-store";
import { useUserStore } from "@/store";
// ==================== 6. Enums ====================
import { DeviceEnum } from "@/enums/settings/device-enum";
// ==================== 7. Composables ====================
import { useAiAction, useTableSelection } from "@/composables";
// ==================== 8. 组件 ====================
import DeptTree from "./components/DeptTree.vue";
import UserImport from "./components/UserImport.vue";
import { useUserStore } from "@/store";
const userStore = useUserStore();
// ==================== 组件配置 ====================
defineOptions({
name: "User",
name: "SystemUser",
inheritAttrs: false,
});
// ==================== Store 实例 ====================
const appStore = useAppStore();
const route = useRoute();
const userStore = useUserStore();
// ==================== 响应式状态 ====================
// DOM 引用
const queryFormRef = ref();
const userFormRef = ref();
// 列表查询参数
const queryParams = reactive<UserPageQuery>({
pageNum: 1,
pageSize: 10,
});
// 列表数据
const pageData = ref<UserPageVO[]>();
const total = ref(0);
const loading = ref(false);
// 弹窗状态
const dialog = reactive({
visible: false,
title: "新增用户",
});
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "600px" : "90%"));
// 表单数据
const formData = reactive<UserForm>({
status: 1,
});
// 下拉选项数据
const deptOptions = ref<OptionType[]>();
const roleOptions = ref<OptionType[]>();
// 导入弹窗
const importDialogVisible = ref(false);
// ==================== 计算属性 ====================
/**
* 抽屉尺寸(响应式)
*/
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "600px" : "90%"));
// ==================== 表单验证规则 ====================
const rules = reactive({
username: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
nickname: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
deptId: [{ required: true, message: "所属部门不能为空", trigger: "blur" }],
roleIds: [{ required: true, message: "用户角色不能为空", trigger: "blur" }],
username: [
{
required: true,
message: "用户不能为空",
trigger: "blur",
},
],
nickname: [
{
required: true,
message: "用户昵称不能为空",
trigger: "blur",
},
],
deptId: [
{
required: true,
message: "所属部门不能为空",
trigger: "blur",
},
],
roleIds: [
{
required: true,
message: "用户角色不能为空",
trigger: "blur",
},
],
email: [
{
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
@@ -312,80 +372,61 @@ const rules = reactive({
],
});
// 选中的用户ID
const selectIds = ref<number[]>([]);
// 部门下拉数据源
const deptOptions = ref<OptionType[]>();
// 角色下拉数据源
const roleOptions = ref<OptionType[]>();
// 导入弹窗显示状态
const importDialogVisible = ref(false);
// ==================== 数据加载 ====================
// 获取数据
async function fetchData() {
/**
* 获取用户列表数据
*/
async function fetchUserList(): Promise<void> {
loading.value = true;
try {
const data = await UserAPI.getPage(queryParams);
pageData.value = data.list;
total.value = data.total;
} catch (error) {
ElMessage.error("获取用户列表失败");
console.error("获取用户列表失败:", error);
} finally {
loading.value = false;
}
}
// ==================== AI 助手相关 ====================
// ==================== 表格选择 ====================
const { selectedIds, hasSelection, handleSelectionChange } = useTableSelection<UserPageVO>();
// 使用 AI 操作 Composable
// ==================== AI 助手相关 ====================
useAiAction({
actionHandlers: {
/** AI 修改用户昵称 */
updateUserNickname: async (args: any) => {
const username = args?.username;
const nickname = args?.nickname;
try {
await ElMessageBox.confirm(
`AI 助手将执行以下操作:<br/>
<strong>修改用户:</strong> ${username}<br/>
<strong>新昵称</strong> ${nickname}<br/><br/>
确认执行吗?`,
"AI 助手操作确认",
{
confirmButtonText: "确认执行",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true,
}
);
const result = await AiCommandApi.executeCommand({
originalCommand: `修改用户 ${username} 的昵称为 ${nickname}`,
confirmMode: "manual",
userConfirmed: true,
currentRoute: route.path,
functionCall: {
name: "updateUserNickname",
arguments: { username, nickname },
},
});
ElMessage.success(result?.message || "修改用户昵称成功");
} catch (error: any) {
if (error !== "cancel") {
ElMessage.error(error?.message || "操作失败");
} else {
ElMessage.info("已取消操作");
}
}
/**
* 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: async (args: any) => {
const keywords = args?.keywords;
if (keywords) {
queryParams.keywords = keywords;
/**
* AI 查询用户
* 使用配置对象方式:查询操作不需要确认
*/
queryUser: {
needConfirm: false, // 查询操作无需确认
successMessage: (args: any) => `已搜索:${args.keywords}`,
execute: async (args: any) => {
queryParams.keywords = args.keywords;
await handleQuery();
ElMessage.success(`已搜索:${keywords}`);
}
},
},
},
onRefresh: fetchData,
@@ -396,214 +437,235 @@ useAiAction({
ElMessage.success(`AI 助手已为您自动搜索:${keywords}`);
}, 300);
},
onInit: handleQuery,
});
// 查询(重置页码后获取数据)
function handleQuery() {
// ==================== 查询操作 ====================
/**
* 查询用户列表(重置到第一页)
*/
function handleQuery(): Promise<void> {
queryParams.pageNum = 1;
return fetchData();
return fetchUserList();
}
// 重置查询
function handleResetQuery() {
/**
* 重置查询条件
*/
function handleResetQuery(): void {
queryFormRef.value.resetFields();
queryParams.deptId = undefined;
queryParams.createTime = undefined;
handleQuery();
}
// 选中项发生变化
function handleSelectionChange(selection: any[]) {
selectIds.value = selection.map((item) => item.id);
}
// handleSelectionChange 已由 useTableSelection 提供
// 重置密码
function hancleResetPassword(row: UserPageVO) {
ElMessageBox.prompt("请输入用户【" + row.username + "】的新密码", "重置密码", {
confirmButtonText: "确定",
cancelButtonText: "取消",
}).then(
({ value }) => {
if (!value || value.length < 6) {
ElMessage.warning("密码至少需要6位字符请重新输入");
return false;
}
UserAPI.resetPassword(row.id, value).then(() => {
ElMessage.success("密码重置成功,新密码是:" + value);
});
},
() => {
ElMessage.info("已取消重置密码");
}
);
}
// ==================== 用户操作 ====================
/**
* 打开弹窗
*
* @param id 用户ID
* 重置用户密码
* @param row 用户数据
*/
async function handleOpenDialog(id?: string) {
dialog.visible = true;
// 加载角色下拉数据源
roleOptions.value = await RoleAPI.getOptions();
// 加载部门下拉数据源
deptOptions.value = await DeptAPI.getOptions();
function handleResetPassword(row: UserPageVO): void {
ElMessageBox.prompt(`请输入用户【${row.username}】的新密码`, "重置密码", {
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPattern: /.{6,}/,
inputErrorMessage: "密码至少需要6位字符",
})
.then(({ value }) => {
return UserAPI.resetPassword(row.id, value);
})
.then(() => {
ElMessage.success("密码重置成功");
})
.catch((error) => {
if (error !== "cancel") {
ElMessage.error("密码重置失败");
}
});
}
// ==================== 弹窗操作 ====================
/**
* 打开用户表单弹窗
* @param id 用户ID编辑时传入
*/
async function handleOpenDialog(id?: string): Promise<void> {
dialog.visible = true;
// 并行加载下拉选项数据
try {
[roleOptions.value, deptOptions.value] = await Promise.all([
RoleAPI.getOptions(),
DeptAPI.getOptions(),
]);
} catch (error) {
ElMessage.error("加载选项数据失败");
console.error("加载选项数据失败:", error);
}
// 编辑:加载用户数据
if (id) {
dialog.title = "修改用户";
UserAPI.getFormData(id).then((data) => {
Object.assign(formData, { ...data });
});
try {
const data = await UserAPI.getFormData(id);
Object.assign(formData, data);
} catch (error) {
ElMessage.error("加载用户数据失败");
console.error("加载用户数据失败:", error);
}
} else {
// 新增:设置默认值
dialog.title = "新增用户";
}
}
// 关闭弹窗
function handleCloseDialog() {
/**
* 关闭用户表单弹窗
*/
function handleCloseDialog(): void {
dialog.visible = false;
userFormRef.value.resetFields();
userFormRef.value.clearValidate();
// 重置表单数据
formData.id = undefined;
formData.status = 1;
}
// 提交用户表单(防抖)
const handleSubmit = useDebounceFn(() => {
userFormRef.value.validate((valid: boolean) => {
if (valid) {
const userId = formData.id;
loading.value = true;
if (userId) {
UserAPI.update(userId, formData)
.then(() => {
ElMessage.success("修改用户成功");
handleCloseDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
} else {
UserAPI.create(formData)
.then(() => {
ElMessage.success("新增用户成功");
handleCloseDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
}
/**
* 提交用户表单(防抖)
*/
const handleSubmit = useDebounceFn(async () => {
const valid = await userFormRef.value.validate().catch(() => false);
if (!valid) return;
const userId = formData.id;
loading.value = true;
try {
if (userId) {
await UserAPI.update(userId, formData);
ElMessage.success("修改用户成功");
} else {
await UserAPI.create(formData);
ElMessage.success("新增用户成功");
}
});
handleCloseDialog();
handleResetQuery();
} catch (error) {
ElMessage.error(userId ? "修改用户失败" : "新增用户失败");
console.error("提交用户表单失败:", error);
} finally {
loading.value = false;
}
}, 1000);
/**
* 检查是否删除当前登录用户
* @param singleId 单个删除的用户ID
* @param selectedIds 批量删除的用户ID数组
* @param currentUserInfo 当前用户信息
* @returns 是否包含当前用户
*/
function isDeletingCurrentUser(
singleId?: number,
selectedIds: number[] = [],
currentUserInfo?: any
): boolean {
if (!currentUserInfo?.userId) return false;
// 单个删除检查
if (singleId && singleId.toString() === currentUserInfo.userId) {
return true;
}
// 批量删除检查
if (!singleId && selectedIds.length > 0) {
return selectedIds.map(String).includes(currentUserInfo.userId);
}
return false;
}
/**
* 删除用户
*
* @param id 用户ID
* @param id 用户ID单个删除时传入
*/
function handleDelete(id?: number) {
const userIds = [id || selectIds.value].join(",");
function handleDelete(id?: number): void {
const userIds = id ? String(id) : selectedIds.value.join(",");
if (!userIds) {
ElMessage.warning("请勾选删除项");
return;
}
// 安全检查:防止删除当前登录用户
const currentUserInfo = userStore.userInfo;
if (isDeletingCurrentUser(id, selectIds.value, currentUserInfo)) {
if (isCurrentUserInDeleteList(id)) {
ElMessage.error("不能删除当前登录用户");
return;
}
ElMessageBox.confirm("确认删除用户?", "警告", {
ElMessageBox.confirm("确认删除选中的用户吗?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(
() => {
})
.then(async () => {
loading.value = true;
UserAPI.deleteByIds(userIds)
.then(() => {
ElMessage.success("删除成功");
handleResetQuery();
})
.finally(() => (loading.value = false));
},
() => {
ElMessage.info("已取消删除");
}
);
try {
await UserAPI.deleteByIds(userIds);
ElMessage.success("删除成功");
handleResetQuery();
} catch (error) {
ElMessage.error("删除失败");
console.error("删除用户失败:", error);
} finally {
loading.value = false;
}
})
.catch(() => {
// 用户取消操作,无需处理
});
}
// 打开导入弹窗
function handleOpenImportDialog() {
/**
* 检查删除列表中是否包含当前登录用户
* @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);
}
// ==================== 导入导出 ====================
/**
* 打开导入弹窗
*/
function handleOpenImportDialog(): void {
importDialogVisible.value = true;
}
// 导出用户
function handleExport() {
UserAPI.export(queryParams).then((response: any) => {
/**
* 导出用户列表
*/
async function handleExport(): Promise<void> {
try {
const response = await UserAPI.export(queryParams);
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const contentDisposition = response.headers["content-disposition"];
const fileName = decodeURI(contentDisposition.split(";")[1].split("=")[1]);
const fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
// 创建下载链接
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
// 触发下载
document.body.appendChild(downloadLink);
downloadLink.click();
// 清理
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
});
ElMessage.success("导出成功");
} catch (error) {
ElMessage.error("导出失败");
console.error("导出用户列表失败:", error);
}
}
onMounted(() => {
// 检查是否有自动搜索参数
const autoSearch = route.query.autoSearch as string;
// 如果有自动搜索,由 onAutoSearch 回调处理(会调用 handleQuery
// 如果有 AI 操作,先加载数据,然后由 useAiAction 处理操作(操作完成后会刷新)
// 如果都没有,正常加载数据
if (autoSearch !== "true") {
// 延迟一下,确保 useAiAction 先初始化
nextTick(() => {
handleQuery();
});
}
// 注意autoSearch === "true" 时onAutoSearch 回调会调用 handleQuery所以这里不需要再调用
});
// 初始化数据加载由 useAiAction 的 onInit 回调统一处理
// 无需手动在 onMounted 中调用 handleQuery()
</script>