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 @@
@@ -35,14 +263,50 @@ const handleDelete = (row) => {
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 重置
+
+
+
+
-
-
+
+
@@ -51,19 +315,116 @@ const handleDelete = (row) => {
-
+
+
+ {{ formatDateTime(row.updated_at) }}
+
+
-
+
编辑
-
+
删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 启用
+ 禁用
+
+
+
+
+
+
+
+
+
+
@@ -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;
+}