diff --git a/frontend/.env.development b/frontend/.env.development index 87074712..132807b2 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -5,8 +5,8 @@ VITE_APP_ENV=development VITE_APP_TITLE=fastapi_vue3_admin # 网络请求公用地址 -VITE_API_BASE_URL=http://localhost:8001 -# VITE_API_BASE_URL=http://service.fastapiadmin.com +# VITE_API_BASE_URL=http://localhost:8001 +VITE_API_BASE_URL=http://service.fastapiadmin.com # 代理前缀 VITE_APP_BASE_API=/api/v1 diff --git a/frontend/src/components/Notification/index.vue b/frontend/src/components/Notification/index.vue index ec4e10d4..64a6e6b6 100644 --- a/frontend/src/components/Notification/index.vue +++ b/frontend/src/components/Notification/index.vue @@ -112,9 +112,7 @@ onMounted(() => { featchMyNotice(); }); -onBeforeUnmount(() => { - featchMyNotice(); -}); + diff --git a/frontend/src/enums/api/result.enum.ts b/frontend/src/enums/api/result.enum.ts index 97c50cc8..cded8df8 100644 --- a/frontend/src/enums/api/result.enum.ts +++ b/frontend/src/enums/api/result.enum.ts @@ -20,8 +20,4 @@ export const enum ResultEnum { */ ACCESS_TOKEN_INVALID = 401, - /** - * 拒绝访问 - */ - ACCESS_DENIED = 403, } diff --git a/frontend/src/plugins/permission.ts b/frontend/src/plugins/permission.ts index 3bf0722f..966d01dd 100644 --- a/frontend/src/plugins/permission.ts +++ b/frontend/src/plugins/permission.ts @@ -26,8 +26,6 @@ export function setupPermission() { // 处理已登录用户的路由访问 await handleAuthenticatedUser(to, from, next); } else { - console.log("❌ User not logged in"); - // 未登录用户的处理 if (whiteList.includes(to.path)) { next(); diff --git a/frontend/src/store/modules/permission.store.ts b/frontend/src/store/modules/permission.store.ts index dd8967bb..7b943fa2 100644 --- a/frontend/src/store/modules/permission.store.ts +++ b/frontend/src/store/modules/permission.store.ts @@ -127,11 +127,8 @@ export const usePermissionStore = defineStore("permission", () => { routesLoaded.value = true; - console.log("✅ 动态路由:", (dynamicRoutes)); return dynamicRoutes; - } catch (error) { - console.error("❌ 生成路由失败:", error); - + } catch (error: any) { // 即使失败也要设置状态,避免无限重试 routesLoaded.value = false; diff --git a/frontend/src/store/modules/user.store.ts b/frontend/src/store/modules/user.store.ts index ce803de2..2897b0f5 100644 --- a/frontend/src/store/modules/user.store.ts +++ b/frontend/src/store/modules/user.store.ts @@ -46,34 +46,16 @@ export const useUserStore = defineStore("user", { }, // 登录 - login(LoginFormData: LoginFormData) { - return new Promise((resolve, reject) => { - AuthAPI.login(LoginFormData) - .then((response) => { - // 保存记住我状态和token - this.rememberMe = LoginFormData.remember; - Auth.setTokens(response.data.data.access_token, response.data.data.refresh_token, this.rememberMe); - resolve(); - }) - .catch((error) => { - reject(error); - }); - }); + async login(LoginFormData: LoginFormData) { + const response = await AuthAPI.login(LoginFormData) + this.rememberMe = LoginFormData.remember; + Auth.setTokens(response.data.data.access_token, response.data.data.refresh_token, this.rememberMe); }, // 登出 - logout() { - return new Promise((resolve, reject) => { - AuthAPI.logout({token: Auth.getAccessToken()}) - .then(() => { - // 重置所有系统状态 - this.resetAllState(); - resolve(); - }) - .catch((error) => { - reject(error); - }); - }); + async logout() { + await AuthAPI.logout({token: Auth.getAccessToken()}); + this.resetAllState(); }, // 重置所有状态 @@ -106,7 +88,6 @@ export const useUserStore = defineStore("user", { resolve(); }) .catch((error) => { - console.log(" refreshToken 刷新失败", error); reject(error); }); }); diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts index c8a2b34e..717d08a8 100644 --- a/frontend/src/utils/request.ts +++ b/frontend/src/utils/request.ts @@ -18,8 +18,7 @@ const httpRequest: AxiosInstance = axios.create({ /** * 请求拦截器 - 添加 Authorization 头 */ -httpRequest.interceptors.request.use( - (config: InternalAxiosRequestConfig) => { +httpRequest.interceptors.request.use((config: InternalAxiosRequestConfig) => { const accessToken = Auth.getAccessToken(); // 如果 Authorization 设置为 no-auth,则不携带 Token @@ -32,7 +31,6 @@ httpRequest.interceptors.request.use( return config; }, (error) => { - console.error("请求异常:", error); ElMessage.error(error); return Promise.reject(error); } @@ -41,57 +39,38 @@ httpRequest.interceptors.request.use( /** * 响应拦截器 - 统一处理响应和错误 */ -httpRequest.interceptors.response.use( - (response: AxiosResponse) => { +httpRequest.interceptors.response.use((response: AxiosResponse) => { // 如果响应是二进制流,则直接返回(用于文件下载、Excel 导出等) - if (response?.config?.responseType === "blob") { + if (response.config.responseType === "blob") { return response; } const data = response.data; // 请求成功 - if (data.code === ResultEnum.SUCCESS) { - // 只有不是 get 请求时才显示成功消息 - if (response?.config?.method?.toLowerCase() !== 'get') { - ElMessage.success(data.msg); - } - return response; - } else if (data.code === ResultEnum.ERROR) { + if (data.code != ResultEnum.SUCCESS) { + ElMessage.error(data.msg); + return Promise.reject(response); + } + return response; + }, async (error: AxiosError) => { + const data = error.response?.data; + + if (data?.status_code === ResultEnum.ACCESS_TOKEN_INVALID) { + await redirectToLogin("登录已过期,请重新登录"); + return Promise.reject(new Error(data.msg)); + } else if (data?.code === ResultEnum.ERROR) { ElMessage.error(data.msg || "请求错误"); return Promise.reject(new Error(data.msg || "请求错误")); - } else if (data.code === ResultEnum.EXCEPTION) { + } else if (data?.code === ResultEnum.EXCEPTION) { ElMessage.error(data.msg || "服务异常"); return Promise.reject(new Error(data.msg || "服务异常")); - } else { - ElMessage.error(data.msg || "请求异常"); - return Promise.reject(new Error(data.msg || "请求异常")); - } - }, - async (error: AxiosError) => { - console.error("响应异常 " + error); - - const { response } = error; - - // 网络错误或服务器无响应 - if (!response) { - ElMessage.error("网络连接失败,请检查网络设置"); - return Promise.reject(error); - } - - const { status_code, msg } = response.data as ApiResponse; - - switch (status_code) { - case ResultEnum.ACCESS_TOKEN_INVALID: - // Access Token 过期 - await redirectToLogin("登录已过期,请重新登录"); - return Promise.reject(new Error(msg || "Refresh Token Invalid")); - - default: - return Promise.reject(new Error(msg || "响应错误")); + } else { + ElMessage.error(error.message || "请求异常"); + return Promise.reject(new Error(error.message || "请求异常")); } } -); +); /** * 重定向到登录页面 @@ -110,8 +89,8 @@ async function redirectToLogin(message: string = "请重新登录"): Promise - + - + - + - - - - - - - 文档 - - - - - - - - - - - - - - - - - - - - - - - 视频 - - - - - - - @@ -86,31 +49,16 @@ - + - + - + - - - - - - - - - - - - - - - @@ -287,7 +235,7 @@ 最新动态 - + 完整记录 @@ -362,7 +310,7 @@ const vesionList = ref([ title: "v3.2.1", date: dayjs().format('YYYY-MM-DD HH:mm:ss'), content: "优化性能,修复若干小bug。", - link: "https://gitee.com/youlaiorg/vue3-element-admin/releases", + link: "https://gitee.com/tao__tao/fastapi_vue3_admin/releases", tag: "更新", }, { @@ -370,7 +318,7 @@ const vesionList = ref([ title: "v3.2.0", date: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'), content: "新增用户行为分析功能。", - link: "https://gitee.com/youlaiorg/vue3-element-admin/releases", + link: "https://gitee.com/tao__tao/fastapi_vue3_admin/releases", tag: "新功能", }, { @@ -378,7 +326,7 @@ const vesionList = ref([ title: "v3.1.0", date: dayjs().subtract(3, 'day').format('YYYY-MM-DD HH:mm:ss'), content: "优化权限管理系统。", - link: "https://gitee.com/youlaiorg/vue3-element-admin/releases", + link: "https://gitee.com/tao__tao/fastapi_vue3_admin/releases", tag: "优化", }, ]); diff --git a/frontend/src/views/system/auth/components/Login.vue b/frontend/src/views/system/auth/components/Login.vue index b7710ee1..768b8e2e 100644 --- a/frontend/src/views/system/auth/components/Login.vue +++ b/frontend/src/views/system/auth/components/Login.vue @@ -39,26 +39,16 @@ - + - - - - + + + + + @@ -184,8 +174,6 @@ async function getCaptcha() { const response = await AuthAPI.getCaptcha() loginForm.captcha_key = response.data.data.key; captchaState.img_base = response.data.data.img_base; - } catch (error: any) { - console.error(error.message); } finally { codeLoading.value = false; } @@ -208,7 +196,6 @@ async function handleLoginSubmit() { // 3. 登录成功,让路由守卫处理跳转逻辑 // 解析目标地址,但不直接跳转 const redirect = resolveRedirectTarget(route.query); - console.log("🎉 Login successful, target redirect:", redirect); // 通过替换当前路由触发路由守卫,让守卫处理后续的路由生成和跳转 await router.replace(redirect); @@ -219,7 +206,6 @@ async function handleLoginSubmit() { } catch (error) { // 5. 统一错误处理 getCaptcha(); // 刷新验证码 - console.error(error); } finally { loading.value = false; } diff --git a/frontend/src/views/system/user/components/UserExport.vue b/frontend/src/views/system/user/components/UserExport.vue new file mode 100644 index 00000000..ea94a58b --- /dev/null +++ b/frontend/src/views/system/user/components/UserExport.vue @@ -0,0 +1,381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 确 定 + 取 消 + + + + + + + + diff --git a/frontend/src/views/system/user/components/UserImport.vue b/frontend/src/views/system/user/components/UserImport.vue index eee0fe49..1883f236 100644 --- a/frontend/src/views/system/user/components/UserImport.vue +++ b/frontend/src/views/system/user/components/UserImport.vue @@ -1,30 +1,13 @@ - + + + - + + - + 将文件拖到此处,或 @@ -33,12 +16,7 @@ 格式为*.xlsx / *.xls,文件不超过一个 - + 下载模板 @@ -83,84 +61,7 @@ 关闭 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 确 定 - 取 消 - - - - - - - - - - - - - - - 将文件拖到此处,或 - 点击上传 - - - - *.xlsx / *.xls - - 下载模板 - - - - - - - - - - - - 确 定 - - 取 消 - - - + @@ -170,7 +71,7 @@ import UserAPI from "@/api/system/user"; import { ResultEnum } from "@/enums/api/result.enum"; const emit = defineEmits(["import-success"]); -const visible = defineModel("modelValue", { +const importModalVisible = defineModel("modelValue", { type: Boolean, required: true, default: false, @@ -190,7 +91,7 @@ const importFormData = reactive<{ files: [], }); -watch(visible, (newValue) => { +watch(importModalVisible, (newValue) => { if (newValue) { resultData.value = []; resultVisible.value = false; @@ -238,7 +139,7 @@ const handleUpload = async () => { } try { - const response = await UserAPI.importUser("1", importFormData.files[0].raw as File); + const response = await UserAPI.importUser(importFormData.files[0].raw as File); if (response.data.code === ResultEnum.SUCCESS) { ElMessage.success("导入成功,导入数据:" + response.data.data.count + "条"); emit("import-success"); @@ -269,129 +170,7 @@ const handleCloseResult = () => { // 关闭弹窗 const handleClose = () => { importFormData.files.length = 0; - visible.value = false; + importModalVisible.value = false; }; - -// // 导出 -// function handleExport() { -// const filename = exportsFormData.filename -// ? exportsFormData.filename -// : props.contentConfig.permPrefix || "export"; -// const sheetname = exportsFormData.sheetname ? exportsFormData.sheetname : "sheet"; -// const workbook = new ExcelJS.Workbook(); -// const worksheet = workbook.addWorksheet(sheetname); -// const columns: Partial[] = []; -// cols.value.forEach((col) => { -// if (col.label && col.prop && exportsFormData.fields.includes(col.prop)) { -// columns.push({ header: col.label, key: col.prop }); -// } -// }); -// worksheet.columns = columns; -// if (exportsFormData.origin === ExportsOriginEnum.REMOTE) { -// if (props.contentConfig.exportsAction) { -// props.contentConfig.exportsAction(lastFormData).then((res) => { -// worksheet.addRows(res); -// workbook.xlsx -// .writeBuffer() -// .then((buffer) => { -// saveXlsx(buffer, filename as string); -// }) -// .catch((error) => console.log(error)); -// }); -// } else { -// ElMessage.error("未配置exportsAction"); -// } -// } else { -// worksheet.addRows( -// exportsFormData.origin === ExportsOriginEnum.SELECTED ? selectionData.value : pageData.value -// ); -// workbook.xlsx -// .writeBuffer() -// .then((buffer) => { -// saveXlsx(buffer, filename as string); -// }) -// .catch((error) => console.log(error)); -// } -// } - -// // 下载导入模板 -// function handleDownloadTemplate() { -// const importTemplate = props.contentConfig.importTemplate; -// if (typeof importTemplate === "string") { -// window.open(importTemplate); -// } else if (typeof importTemplate === "function") { -// importTemplate().then((response) => { -// const fileData = response.data; -// const fileName = decodeURI( -// response.headers["content-disposition"].split(";")[1].split("=")[1] -// ); -// saveXlsx(fileData, fileName); -// }); -// } else { -// ElMessage.error("未配置importTemplate"); -// } -// } - -// // 导入 -// function handleImport() { -// const importsAction = props.contentConfig.importsAction; -// if (importsAction === undefined) { -// ElMessage.error("未配置importsAction"); -// return; -// } -// // 获取选择的文件 -// const file = importFormData.files[0].raw as File; -// // 创建Workbook实例 -// const workbook = new ExcelJS.Workbook(); -// // 使用FileReader对象来读取文件内容 -// const fileReader = new FileReader(); -// // 二进制字符串的形式加载文件 -// fileReader.readAsArrayBuffer(file); -// fileReader.onload = (ev) => { -// if (ev.target !== null && ev.target.result !== null) { -// const result = ev.target.result as ArrayBuffer; -// // 从 buffer中加载数据解析 -// workbook.xlsx -// .load(result) -// .then((workbook) => { -// // 解析后的数据 -// const data = []; -// // 获取第一个worksheet内容 -// const worksheet = workbook.getWorksheet(1); -// if (worksheet) { -// // 获取第一行的标题 -// const fields: any[] = []; -// worksheet.getRow(1).eachCell((cell) => { -// fields.push(cell.value); -// }); -// // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行) -// for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) { -// const rowData: IObject = {}; -// const row = worksheet.getRow(rowNumber); -// // 遍历当前行的每个单元格 -// row.eachCell((cell, colNumber) => { -// // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中 -// rowData[fields[colNumber - 1]] = cell.value; -// }); -// // 将当前行的数据对象添加到数组中 -// data.push(rowData); -// } -// } -// if (data.length === 0) { -// ElMessage.error("未解析到数据"); -// return; -// } -// importsAction(data).then(() => { -// ElMessage.success("导入数据成功"); -// handleCloseImportModal(); -// handleRefresh(true); -// }); -// }) -// .catch((error) => console.log(error)); -// } else { -// ElMessage.error("读取文件失败"); -// } -// }; -// } diff --git a/frontend/src/views/system/user/index.vue b/frontend/src/views/system/user/index.vue index fde76282..d8ad470c 100644 --- a/frontend/src/views/system/user/index.vue +++ b/frontend/src/views/system/user/index.vue @@ -87,7 +87,8 @@ - + + @@ -296,6 +297,10 @@ + + + + @@ -318,6 +323,7 @@ import RoleAPI from "@/api/system/role"; import DeptTree from "./components/DeptTree.vue"; import UserImport from "./components/UserImport.vue"; +import UserExport from "./components/UserExport.vue"; const appStore = useAppStore(); @@ -340,6 +346,8 @@ const roleOptions = ref(); const positionOptions = ref(); // 导入弹窗显示状态 const importDialogVisible = ref(false); +// 导出弹窗显示状态 +const exportsDialogVisible = ref(false); // 分页表单 const pageTableData = ref([]); // 详情表单 @@ -744,6 +752,13 @@ function handleExport() { }); } +// 打开导出弹窗 +function handleOpenExportsDialog() { + exportsDialogVisible.value = true; +} + + + onMounted(() => { handleQuery(); });