From fb5ad57dc158c085df59656aa661185c18b375d6 Mon Sep 17 00:00:00 2001 From: wzclm <2855471171@qq.com> Date: Thu, 20 Feb 2025 21:41:57 +0800 Subject: [PATCH] Implement comprehensive user management functionality with API integration --- .env | 5 +- src/api/user/index.js | 52 ++++ src/stores/user.js | 27 +- src/utils/format.js | 68 +++++ src/utils/request.js | 42 +-- src/views/report/analysis/index.vue | 32 -- src/views/system/users/index.vue | 457 ++++++++++++++++++++++++++-- 7 files changed, 591 insertions(+), 92 deletions(-) create mode 100644 src/utils/format.js diff --git a/.env b/.env index ba65ba3..e03f911 100644 --- a/.env +++ b/.env @@ -19,4 +19,7 @@ VITE_BUILD_GZIP=false VITE_BUILD_BROTLI=false # 是否删除 console -VITE_DROP_CONSOLE=true \ No newline at end of file +VITE_DROP_CONSOLE=true + +# 后端 API 的基础 URL +VITE_API_BASE_URL=http://localhost:3000 \ No newline at end of file diff --git a/src/api/user/index.js b/src/api/user/index.js index e69de29..b0ba226 100644 --- a/src/api/user/index.js +++ b/src/api/user/index.js @@ -0,0 +1,52 @@ +import request from '@/utils/request' + +/** + * 获取用户列表 + * @returns {Promise} 返回用户列表数据 + */ +export function getUserList() { + return request.get('/api/users') +} + +/** + * 新增用户 + * @param {Object} data - 用户数据 + * @param {string} data.username - 用户名(必选) + * @param {string} data.password - 密码(必选) + * @param {string} data.role_id - 角色ID(必选) + * @param {string} [data.real_name] - 真实姓名(可选) + * @param {string} [data.email] - 邮箱(可选) + * @param {string} [data.phone] - 手机号(可选) + * @param {string} [data.status] - 状态(可选) + * @param {string} [data.expire_time] - 过期时间(可选) + * @returns {Promise} 返回创建结果 + */ +export function createUser(data) { + return request.post('/api/users', data) +} + +/** + * 编辑用户 + * @param {string|number} id - 用户ID + * @param {Object} data - 用户数据 + * @param {string} [data.real_name] - 真实姓名(可选) + * @param {string} [data.email] - 邮箱(可选) + * @param {string} [data.phone] - 手机号(可选) + * @param {string} [data.role_id] - 角色ID(可选) + * @param {string} [data.status] - 状态(可选) + * @param {string} [data.expire_time] - 过期时间(可选) + * @param {string} [data.password] - 新密码(可选) + * @returns {Promise} 返回更新结果 + */ +export function updateUser(id, data) { + return request.put(`/api/users/${id}`, data) +} + +/** + * 删除用户 + * @param {string|number} id - 用户ID + * @returns {Promise} 返回删除结果 + */ +export function deleteUser(id) { + return request.delete(`/api/users/${id}`) +} diff --git a/src/stores/user.js b/src/stores/user.js index d57c099..3eb95d1 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -1,11 +1,30 @@ import { defineStore } from 'pinia' import { login as loginApi, logout as logoutApi } from '@/api/login' +// 安全的 JSON 解析函数 +const safeJSONParse = (str, defaultValue = null) => { + if (!str || str === 'undefined' || str === 'null') { + return defaultValue + } + try { + return JSON.parse(str) + } catch (error) { + console.warn('JSON解析错误,使用默认值:', error) + return defaultValue + } +} + export const useUserStore = defineStore('user', { - state: () => ({ - token: localStorage.getItem('token'), - userInfo: JSON.parse(localStorage.getItem('userInfo')) || null - }), + state: () => { + // 从 localStorage 获取数据 + const token = localStorage.getItem('token') + const userInfoStr = localStorage.getItem('userInfo') + + return { + token: token || null, + userInfo: safeJSONParse(userInfoStr) + } + }, actions: { // 登录 diff --git a/src/utils/format.js b/src/utils/format.js new file mode 100644 index 0000000..f4ae7c8 --- /dev/null +++ b/src/utils/format.js @@ -0,0 +1,68 @@ +/** + * 格式化日期时间 + * @param {string|number|Date} time 需要格式化的时间 + * @param {string} [format='YYYY-MM-DD HH:mm:ss'] 格式化的格式 + * @returns {string} 格式化后的时间字符串 + */ +export function formatDateTime(time, format = 'YYYY-MM-DD HH:mm:ss') { + if (!time) return ''; + + const date = new Date(time); + + if (isNaN(date.getTime())) return ''; + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + + return format + .replace('YYYY', year) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hours) + .replace('mm', minutes) + .replace('ss', seconds); +} + +/** + * 格式化日期 + * @param {string|number|Date} time 需要格式化的时间 + * @returns {string} 格式化后的日期字符串 YYYY-MM-DD + */ +export function formatDate(time) { + return formatDateTime(time, 'YYYY-MM-DD'); +} + +/** + * 格式化时间为相对时间 + * @param {string|number|Date} time 需要格式化的时间 + * @returns {string} 相对时间描述 + */ +export function formatRelativeTime(time) { + if (!time) return ''; + + const date = new Date(time); + if (isNaN(date.getTime())) return ''; + + const now = new Date(); + const diff = now - date; + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 30) { + return formatDateTime(time); + } else if (days > 0) { + return `${days}天前`; + } else if (hours > 0) { + return `${hours}小时前`; + } else if (minutes > 0) { + return `${minutes}分钟前`; + } else { + return '刚刚'; + } +} \ No newline at end of file diff --git a/src/utils/request.js b/src/utils/request.js index ff482c2..7152e57 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -38,31 +38,9 @@ service.interceptors.response.use( (response) => { const res = response.data - // 模拟API响应 - if (response.config.url.includes('/api/users/')) { - // 模拟登录接口 - if (response.config.url.includes('/login')) { - return { - token: 'demo-token', - userInfo: { - id: 1, - username: 'admin', - role: '管理员', - email: 'admin@example.com', - status: true - } - } - } - - // 模拟退出登录接口 - if (response.config.url.includes('/logout')) { - return { message: '退出成功' } - } - } - // 如果响应成功 - if (res.code === 200 || res.code === undefined) { - return res.data || res + if (res.success) { + return res } // 处理特定错误码 @@ -74,7 +52,8 @@ service.interceptors.response.use( ElMessage.error('没有权限访问该资源') break case 500: - ElMessage.error('服务器错误,请稍后重试') + console.error('服务器错误详情:', res) + ElMessage.error(res.message || '服务器错误,请稍后重试') break default: ElMessage.error(res.message || '请求失败') @@ -83,7 +62,18 @@ service.interceptors.response.use( return Promise.reject(new Error(res.message || '请求失败')) }, (error) => { - console.error('响应错误:', error) + console.error('响应错误详情:', { + status: error.response?.status, + statusText: error.response?.statusText, + data: error.response?.data, + headers: error.response?.headers, + config: { + url: error.config?.url, + method: error.config?.method, + headers: error.config?.headers, + params: error.config?.params + } + }) // 处理网络错误 if (!error.response) { diff --git a/src/views/report/analysis/index.vue b/src/views/report/analysis/index.vue index 187eb09..cd5802d 100644 --- a/src/views/report/analysis/index.vue +++ b/src/views/report/analysis/index.vue @@ -4,38 +4,6 @@ import * as echarts from "echarts"; import { ElMessageBox, ElMessage } from "element-plus"; import { Plus } from '@element-plus/icons-vue'; -interface AnalysisReport { - id: number; - title: string; - type: "species" | "environment"; // 分析类型:物种/环境 - timeRange: { - // 分析时间范围 - start: string; - end: string; - }; - dataSource: { - // 数据来源 - type: string; - points: string[]; // 监测点位 - }[]; - analysis: { - summary: string; // 分析总结 - trends: { - // 趋势分析 - indicator: string; // 指标 - trend: string; // 变化趋势 - data: any[]; // 数据 - }[]; - abnormal: { - // 异常分析 - type: string; - description: string; - level: string; - }[]; - }; - recommendations: string[]; // 建议措施 -} - // 示例数据 const tableData = ref([ { diff --git a/src/views/system/users/index.vue b/src/views/system/users/index.vue index f48f96e..71e6e86 100644 --- a/src/views/system/users/index.vue +++ b/src/views/system/users/index.vue @@ -1,32 +1,260 @@ @@ -96,6 +457,25 @@ const handleDelete = (row) => { } } +.search-form { + margin-bottom: 20px; + + .el-form-item { + margin-bottom: 16px; + + :deep(.el-input), + :deep(.el-select) { + width: 240px; + } + } +} + +.pagination-container { + margin-top: 20px; + display: flex; + justify-content: flex-end; +} + :deep(.el-table) { th.el-table__cell { background-color: #fafafa; @@ -111,4 +491,23 @@ const handleDelete = (row) => { .el-tag { border-radius: 2px; } + +.user-form { + .el-form-item { + margin-bottom: 20px; + } +} + +.dialog-footer { + text-align: right; + padding-top: 20px; +} + +.w-full { + width: 100%; +} + +:deep(.el-dialog__body) { + padding: 20px 40px; +}