Implement comprehensive user management functionality with API integration
This commit is contained in:
parent
bb1acb50c1
commit
fb5ad57dc1
5
.env
5
.env
@ -19,4 +19,7 @@ VITE_BUILD_GZIP=false
|
|||||||
VITE_BUILD_BROTLI=false
|
VITE_BUILD_BROTLI=false
|
||||||
|
|
||||||
# 是否删除 console
|
# 是否删除 console
|
||||||
VITE_DROP_CONSOLE=true
|
VITE_DROP_CONSOLE=true
|
||||||
|
|
||||||
|
# 后端 API 的基础 URL
|
||||||
|
VITE_API_BASE_URL=http://localhost:3000
|
||||||
@ -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}`)
|
||||||
|
}
|
||||||
@ -1,11 +1,30 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { login as loginApi, logout as logoutApi } from '@/api/login'
|
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', {
|
export const useUserStore = defineStore('user', {
|
||||||
state: () => ({
|
state: () => {
|
||||||
token: localStorage.getItem('token'),
|
// 从 localStorage 获取数据
|
||||||
userInfo: JSON.parse(localStorage.getItem('userInfo')) || null
|
const token = localStorage.getItem('token')
|
||||||
}),
|
const userInfoStr = localStorage.getItem('userInfo')
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: token || null,
|
||||||
|
userInfo: safeJSONParse(userInfoStr)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
// 登录
|
// 登录
|
||||||
|
|||||||
68
src/utils/format.js
Normal file
68
src/utils/format.js
Normal file
@ -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 '刚刚';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,31 +38,9 @@ service.interceptors.response.use(
|
|||||||
(response) => {
|
(response) => {
|
||||||
const res = response.data
|
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) {
|
if (res.success) {
|
||||||
return res.data || res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理特定错误码
|
// 处理特定错误码
|
||||||
@ -74,7 +52,8 @@ service.interceptors.response.use(
|
|||||||
ElMessage.error('没有权限访问该资源')
|
ElMessage.error('没有权限访问该资源')
|
||||||
break
|
break
|
||||||
case 500:
|
case 500:
|
||||||
ElMessage.error('服务器错误,请稍后重试')
|
console.error('服务器错误详情:', res)
|
||||||
|
ElMessage.error(res.message || '服务器错误,请稍后重试')
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
ElMessage.error(res.message || '请求失败')
|
ElMessage.error(res.message || '请求失败')
|
||||||
@ -83,7 +62,18 @@ service.interceptors.response.use(
|
|||||||
return Promise.reject(new Error(res.message || '请求失败'))
|
return Promise.reject(new Error(res.message || '请求失败'))
|
||||||
},
|
},
|
||||||
(error) => {
|
(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) {
|
if (!error.response) {
|
||||||
|
|||||||
@ -4,38 +4,6 @@ import * as echarts from "echarts";
|
|||||||
import { ElMessageBox, ElMessage } from "element-plus";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
import { Plus } from '@element-plus/icons-vue';
|
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([
|
const tableData = ref([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,32 +1,260 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref, onMounted, computed } from "vue";
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import { getUserList, createUser, updateUser, deleteUser } from "@/api/user";
|
||||||
|
import { formatDateTime } from "@/utils/format";
|
||||||
|
|
||||||
const tableData = ref([
|
const tableData = ref([]);
|
||||||
{
|
const allData = ref([]); // 保存所有数据
|
||||||
id: 1,
|
const loading = ref(false);
|
||||||
username: "admin",
|
const total = ref(0);
|
||||||
role: "超级管理员",
|
|
||||||
email: "admin@example.com",
|
|
||||||
status: true,
|
|
||||||
createTime: "2024-03-20",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
username: "manager",
|
|
||||||
role: "管理人员",
|
|
||||||
email: "manager@example.com",
|
|
||||||
status: true,
|
|
||||||
createTime: "2024-03-20",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = ref({
|
||||||
|
username: '',
|
||||||
|
role: '',
|
||||||
|
status: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过滤后的数据
|
||||||
|
const filteredData = computed(() => {
|
||||||
|
return allData.value.filter(item => {
|
||||||
|
// 用户名搜索
|
||||||
|
if (queryParams.value.username &&
|
||||||
|
!item.real_name.toLowerCase().includes(queryParams.value.username.toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 角色搜索
|
||||||
|
if (queryParams.value.role &&
|
||||||
|
item.role.name !== queryParams.value.role) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态搜索
|
||||||
|
if (queryParams.value.status !== '' &&
|
||||||
|
item.status !== queryParams.value.status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).sort((a, b) => a.id - b.id); // 按ID升序排序
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取用户列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getUserList();
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
allData.value = res.data.list; // 保存所有数据
|
||||||
|
tableData.value = filteredData.value; // 显示过滤后的数据
|
||||||
|
total.value = filteredData.value.length;
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '获取用户列表失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
ElMessage.error(error.response.data.message || '获取用户列表失败');
|
||||||
|
} else if (error.request) {
|
||||||
|
ElMessage.error('网络错误,请检查网络连接');
|
||||||
|
} else {
|
||||||
|
ElMessage.error(error.message || '获取用户列表失败');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理查询
|
||||||
|
const handleQuery = () => {
|
||||||
|
tableData.value = filteredData.value;
|
||||||
|
total.value = filteredData.value.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置查询
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.value = {
|
||||||
|
username: '',
|
||||||
|
role: '',
|
||||||
|
status: ''
|
||||||
|
};
|
||||||
|
tableData.value = allData.value;
|
||||||
|
total.value = allData.value.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增/编辑对话框
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const dialogTitle = ref('新增用户');
|
||||||
|
const formData = ref({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
role_id: undefined,
|
||||||
|
real_name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
status: 1,
|
||||||
|
expire_time: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单规则
|
||||||
|
const rules = {
|
||||||
|
username: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
|
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
|
{ min: 6, max: 30, message: '长度在 6 到 30 个字符', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
pattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,30}$/,
|
||||||
|
message: '密码必须包含字母和数字',
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
role_id: [
|
||||||
|
{ required: true, message: '请选择角色', trigger: 'change' }
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{
|
||||||
|
pattern: /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/,
|
||||||
|
message: '请输入正确的邮箱格式',
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
phone: [
|
||||||
|
{
|
||||||
|
pattern: /^1[3-9]\d{9}$/,
|
||||||
|
message: '请输入正确的手机号格式',
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const formRef = ref(null);
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const editId = ref(null);
|
||||||
|
|
||||||
|
// 打开新增对话框
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogTitle.value = '新增用户';
|
||||||
|
isEdit.value = false;
|
||||||
|
editId.value = null;
|
||||||
|
formData.value = {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
role_id: undefined,
|
||||||
|
real_name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
status: 1,
|
||||||
|
expire_time: undefined
|
||||||
|
};
|
||||||
|
dialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开编辑对话框
|
||||||
const handleEdit = (row) => {
|
const handleEdit = (row) => {
|
||||||
console.log("编辑用户:", row);
|
dialogTitle.value = '编辑用户';
|
||||||
|
isEdit.value = true;
|
||||||
|
editId.value = row.id;
|
||||||
|
formData.value = {
|
||||||
|
real_name: row.real_name || '',
|
||||||
|
email: row.email || '',
|
||||||
|
phone: row.phone || '',
|
||||||
|
role_id: row.role.id.toString(),
|
||||||
|
status: row.status.toString(),
|
||||||
|
expire_time: row.expire_time || '',
|
||||||
|
password: '' // 编辑时密码为可选
|
||||||
|
};
|
||||||
|
dialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (row) => {
|
// 提交表单
|
||||||
console.log("删除用户:", row);
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await formRef.value.validate();
|
||||||
|
|
||||||
|
const submitData = { ...formData.value };
|
||||||
|
|
||||||
|
// 处理数据格式
|
||||||
|
if (submitData.status) {
|
||||||
|
submitData.status = parseInt(submitData.status); // 转换为数字
|
||||||
|
}
|
||||||
|
if (submitData.role_id) {
|
||||||
|
submitData.role_id = parseInt(submitData.role_id); // 转换为数字
|
||||||
|
}
|
||||||
|
// 如果过期时间为空,则不提交该字段
|
||||||
|
if (!submitData.expire_time) {
|
||||||
|
delete submitData.expire_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('提交的数据:', submitData); // 打印提交的数据
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
// 编辑时,如果密码为空,则不提交密码字段
|
||||||
|
if (!submitData.password) {
|
||||||
|
delete submitData.password;
|
||||||
|
}
|
||||||
|
await updateUser(editId.value, submitData);
|
||||||
|
ElMessage.success('编辑成功');
|
||||||
|
} else {
|
||||||
|
await createUser(submitData);
|
||||||
|
ElMessage.success('添加成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogVisible.value = false;
|
||||||
|
getList(); // 刷新列表
|
||||||
|
} catch (error) {
|
||||||
|
console.error('表单提交错误:', error);
|
||||||
|
if (error.response) {
|
||||||
|
console.error('错误响应数据:', error.response.data);
|
||||||
|
ElMessage.error(error.response.data.message || '提交失败,请检查表单数据');
|
||||||
|
} else if (error.request) {
|
||||||
|
ElMessage.error('网络错误,请检查网络连接');
|
||||||
|
} else {
|
||||||
|
ElMessage.error(error.message || '操作失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'确认删除该用户吗?',
|
||||||
|
'警告',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
|
try {
|
||||||
|
await deleteUser(row.id);
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
getList(); // 刷新列表
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.message || '删除失败');
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消删除
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 角色选项
|
||||||
|
const roleOptions = [
|
||||||
|
{ label: '超级管理员', value: '1' },
|
||||||
|
{ label: '管理人员', value: '2' },
|
||||||
|
{ label: '普通用户', value: '3' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 页面加载时获取列表
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -35,14 +263,50 @@ const handleDelete = (row) => {
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>用户管理</span>
|
<span>用户管理</span>
|
||||||
<el-button type="primary">新增用户</el-button>
|
<el-button type="primary" @click="handleAdd">新增用户</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table :data="tableData" style="width: 100%">
|
<!-- 搜索表单 -->
|
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" class="search-form">
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.username"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="角色" prop="role">
|
||||||
|
<el-select v-model="queryParams.role" placeholder="请选择角色" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in roleOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||||
|
<el-option :value="1" label="启用" />
|
||||||
|
<el-option :value="0" label="禁用" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="tableData"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
<el-table-column prop="username" label="用户名" width="120" />
|
<el-table-column prop="real_name" label="用户名" width="120" />
|
||||||
<el-table-column prop="role" label="角色" width="120" />
|
<el-table-column prop="role.name" label="角色" width="120" />
|
||||||
<el-table-column prop="email" label="邮箱" />
|
<el-table-column prop="email" label="邮箱" />
|
||||||
<el-table-column prop="status" label="状态" width="100">
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@ -51,19 +315,116 @@ const handleDelete = (row) => {
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
<el-table-column prop="updated_at" label="创建时间" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDateTime(row.updated_at) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="180">
|
<el-table-column label="操作" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" size="small" @click="handleEdit(row)">
|
<el-button type="primary" link @click="handleEdit(row)">
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" size="small" @click="handleDelete(row)">
|
<el-button type="danger" link @click="handleDelete(row)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-container">
|
||||||
|
<el-pagination
|
||||||
|
:size="'small'"
|
||||||
|
:total="total"
|
||||||
|
:default-current-page="1"
|
||||||
|
:default-page-size="10"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 新增/编辑对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
width="500px"
|
||||||
|
@close="dialogVisible = false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="isEdit ? {
|
||||||
|
...rules,
|
||||||
|
password: [] // 编辑模式下移除密码验证规则
|
||||||
|
} : rules"
|
||||||
|
label-width="100px"
|
||||||
|
class="user-form"
|
||||||
|
>
|
||||||
|
<el-form-item label="用户名" prop="username" v-if="!isEdit">
|
||||||
|
<el-input v-model="formData.username" placeholder="请输入用户名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="isEdit ? '新密码' : '密码'"
|
||||||
|
prop="password"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="formData.password"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
:placeholder="isEdit ? '不修改请留空' : '请输入密码'"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="角色" prop="role_id">
|
||||||
|
<el-select v-model="formData.role_id" placeholder="请选择角色" class="w-full">
|
||||||
|
<el-option
|
||||||
|
v-for="item in roleOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="真实姓名" prop="real_name">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.real_name"
|
||||||
|
placeholder="请输入真实姓名"
|
||||||
|
maxlength="50"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="邮箱" prop="email">
|
||||||
|
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="手机号" prop="phone">
|
||||||
|
<el-input v-model="formData.phone" placeholder="请输入手机号" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-radio-group v-model="formData.status">
|
||||||
|
<el-radio :value="'1'">启用</el-radio>
|
||||||
|
<el-radio :value="'0'">禁用</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="过期时间" prop="expire_time">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.expire_time"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="请选择过期时间"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -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) {
|
:deep(.el-table) {
|
||||||
th.el-table__cell {
|
th.el-table__cell {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
@ -111,4 +491,23 @@ const handleDelete = (row) => {
|
|||||||
.el-tag {
|
.el-tag {
|
||||||
border-radius: 2px;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user