用户接口调用、登录接口调用
This commit is contained in:
67
src/api/request.js
Normal file
67
src/api/request.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import axios from 'axios';
|
||||
import {message as msg} from "ant-design-vue";
|
||||
|
||||
// 创建 Axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: '/api/', // 这里可以设置你的 API 基础地址
|
||||
timeout: 5000 // 请求超时时间
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// 从本地存储中获取 token
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
// 设置请求头中的 Authorization
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
console.log(error); // 打印错误信息
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
const {code, result, message} = response.data;
|
||||
if (code === 0) {
|
||||
return result;
|
||||
} else {
|
||||
msg.error(message).then(r => {
|
||||
msg.destroy()
|
||||
})
|
||||
return Promise.reject(new Error(result));
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.log('err' + error); // 打印错误信息
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 封装 get 请求
|
||||
const get = (url, params = {}) => {
|
||||
return service.get(url, { params });
|
||||
};
|
||||
|
||||
// 封装 post 请求
|
||||
const post = (url, data = {}) => {
|
||||
return service.post(url, data);
|
||||
};
|
||||
|
||||
// 封装上传文件请求
|
||||
const upload = (url, file) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return service.post(url, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export { get, post, upload };
|
||||
10
src/api/request/role.js
Normal file
10
src/api/request/role.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import {get, post} from '../request.js'
|
||||
|
||||
const prefix = 'role'
|
||||
|
||||
export function roleListApi(credentials) {
|
||||
return get(`${prefix}/list`, credentials)
|
||||
}
|
||||
export function roleOptionApi(credentials) {
|
||||
return get(`${prefix}/option`, credentials)
|
||||
}
|
||||
57
src/api/request/user.js
Normal file
57
src/api/request/user.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import {get, post} from '../request.js'
|
||||
|
||||
const prefix = 'user'
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @param credentials
|
||||
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||
*/
|
||||
export function loginApi(credentials) {
|
||||
return post('login', credentials)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param credentials
|
||||
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||
*/
|
||||
export function userListApi(credentials) {
|
||||
return get(`${prefix}/list`, credentials)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
* @param credentials
|
||||
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||
*/
|
||||
export function createUserApi(credentials) {
|
||||
return post(`${prefix}/create`, credentials)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
* @param credentials
|
||||
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||
*/
|
||||
export function updateUserApi(credentials) {
|
||||
return post(`${prefix}/update`, credentials)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
* @param credentials
|
||||
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||
*/
|
||||
export function deleteUserApi(credentials) {
|
||||
return post(`${prefix}/delete`, credentials)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
* @param credentials
|
||||
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||
*/
|
||||
export function resetPasswordApi(credentials) {
|
||||
return post(`${prefix}/reset-password`, credentials)
|
||||
}
|
||||
@@ -35,6 +35,7 @@ onMounted(() => {
|
||||
once: false
|
||||
})
|
||||
})
|
||||
console.log(userStore.user)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -101,9 +102,10 @@ onMounted(() => {
|
||||
个人中心
|
||||
</a>
|
||||
<a
|
||||
v-if="userStore.user?.role === 'admin'"
|
||||
v-if="userStore.user.role_value === 'admin'"
|
||||
href="/admin"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400"
|
||||
target="_blank"
|
||||
>
|
||||
后台管理
|
||||
</a>
|
||||
@@ -191,7 +193,7 @@ onMounted(() => {
|
||||
个人中心
|
||||
</a>
|
||||
<a
|
||||
v-if="userStore.user?.role === 'admin'"
|
||||
v-if="userStore.user?.role_value === 'admin'"
|
||||
href="/admin"
|
||||
class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 hover:text-rose-600 dark:hover:text-rose-400 rounded-xl"
|
||||
@click="mobileMenuVisible = false"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import {loginApi} from "@/api/request/user.js";
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const user = ref(JSON.parse(localStorage.getItem('user') || 'null'))
|
||||
@@ -17,25 +18,21 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const isAuthenticated = computed(() => !!user.value?.token)
|
||||
const isAuthenticated = computed(() => !!user.value?.nick_name)
|
||||
|
||||
function login(credentials) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (credentials.username === 'admin' && credentials.password === 'admin123') {
|
||||
const userData = {
|
||||
username: credentials.username,
|
||||
role: 'admin',
|
||||
token: 'mock_token_' + Math.random().toString(36).slice(2)
|
||||
}
|
||||
localStorage.setItem('user', JSON.stringify(userData))
|
||||
user.value = userData // 添加响应式状态更新
|
||||
localStorage.setItem('token', userData.token)
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error('用户名或密码错误'))
|
||||
}
|
||||
}, 500)
|
||||
loginApi({
|
||||
account: credentials.username,
|
||||
password: credentials.password,
|
||||
remember: credentials.remember,
|
||||
}).then((res) => {
|
||||
const userData = {
|
||||
nick_name: res.nick_name,
|
||||
role_name: res.role_name,
|
||||
role_value: res.role_value,
|
||||
}
|
||||
localStorage.setItem('user', JSON.stringify(userData))
|
||||
localStorage.setItem('token', res.token)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ref, onMounted, computed } from "vue"
|
||||
import AOS from "aos"
|
||||
import "aos/dist/aos.css"
|
||||
import { message, Upload } from "ant-design-vue"
|
||||
import {createUserApi, deleteUserApi, resetPasswordApi, updateUserApi, userListApi} from "@/api/request/user.js";
|
||||
import {roleOptionApi} from "@/api/request/role.js";
|
||||
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
@@ -14,12 +16,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
// 角色数据
|
||||
const roleOptions = [
|
||||
{ id: 1, name: "管理员" },
|
||||
{ id: 2, name: "编辑" },
|
||||
{ id: 3, name: "普通用户" },
|
||||
{ id: 4, name: "访客" },
|
||||
]
|
||||
const roleOptions = ref([])
|
||||
|
||||
// 权限数据
|
||||
const allPermissions = [
|
||||
@@ -33,119 +30,61 @@ const allPermissions = [
|
||||
{ id: 8, name: "营销管理" },
|
||||
]
|
||||
|
||||
const getRoleOption = () => {
|
||||
roleOptionApi().then((res) => {
|
||||
roleOptions.value = res;
|
||||
})
|
||||
}
|
||||
|
||||
getRoleOption();
|
||||
|
||||
// 用户状态选项
|
||||
const statusOptions = [
|
||||
{ value: "启用", label: "启用" },
|
||||
{ value: "禁用", label: "禁用" },
|
||||
{ value: 0, label: "启用" },
|
||||
{ value: 1, label: "禁用" },
|
||||
]
|
||||
|
||||
// 模拟用户数据
|
||||
const dataSource = ref([
|
||||
{
|
||||
id: 1,
|
||||
username: "admin",
|
||||
avatar: "https://randomuser.me/api/portraits/men/32.jpg",
|
||||
email: "admin@example.com",
|
||||
phone: "13800138000",
|
||||
role: roleOptions[0],
|
||||
status: "启用",
|
||||
createdAt: "2024-01-15T08:30:00Z",
|
||||
lastLogin: "2024-03-28T10:15:30Z",
|
||||
permissions: [
|
||||
allPermissions[0],
|
||||
allPermissions[1],
|
||||
allPermissions[2],
|
||||
allPermissions[3],
|
||||
allPermissions[4],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: "editor",
|
||||
avatar: "https://randomuser.me/api/portraits/women/44.jpg",
|
||||
email: "editor@example.com",
|
||||
phone: "13900139000",
|
||||
role: roleOptions[1],
|
||||
status: "启用",
|
||||
createdAt: "2024-02-10T14:20:00Z",
|
||||
lastLogin: "2024-03-27T16:45:12Z",
|
||||
permissions: [allPermissions[1], allPermissions[2]],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: "user1",
|
||||
avatar: "https://randomuser.me/api/portraits/men/22.jpg",
|
||||
email: "user1@example.com",
|
||||
phone: "13700137000",
|
||||
role: roleOptions[2],
|
||||
status: "启用",
|
||||
createdAt: "2024-02-20T09:15:00Z",
|
||||
lastLogin: "2024-03-25T11:30:45Z",
|
||||
permissions: [allPermissions[2]],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
username: "user2",
|
||||
avatar: "https://randomuser.me/api/portraits/women/24.jpg",
|
||||
email: "user2@example.com",
|
||||
phone: "13600136000",
|
||||
role: roleOptions[2],
|
||||
status: "禁用",
|
||||
createdAt: "2024-03-05T16:40:00Z",
|
||||
lastLogin: "2024-03-20T09:10:22Z",
|
||||
permissions: [allPermissions[2]],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
username: "guest",
|
||||
avatar: "https://randomuser.me/api/portraits/men/67.jpg",
|
||||
email: "guest@example.com",
|
||||
phone: "13500135000",
|
||||
role: roleOptions[3],
|
||||
status: "启用",
|
||||
createdAt: "2024-03-10T11:25:00Z",
|
||||
lastLogin: "2024-03-15T14:20:18Z",
|
||||
permissions: [],
|
||||
},
|
||||
])
|
||||
const dataSource = ref([]);
|
||||
|
||||
// 分页
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const totalItems = ref(dataSource.value.length)
|
||||
const pageSize = ref(20)
|
||||
const totalItems = ref(0)
|
||||
const pageCount = ref(0)
|
||||
|
||||
|
||||
const getList = () => {
|
||||
|
||||
userListApi({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
account: account.value,
|
||||
nick_name: nickName.value,
|
||||
email: email.value,
|
||||
role_id: role.value,
|
||||
status: status.value,
|
||||
}).then((res) => {
|
||||
dataSource.value = res.items;
|
||||
currentPage.value = res.page;
|
||||
pageSize.value = res.size;
|
||||
totalItems.value = res.total;
|
||||
pageCount.value = res.page_count;
|
||||
})
|
||||
}
|
||||
|
||||
const paginatedData = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
return dataSource.value.slice(start, end)
|
||||
})
|
||||
|
||||
const changePage = (page) => {
|
||||
currentPage.value = page
|
||||
getList()
|
||||
}
|
||||
|
||||
// 搜索和筛选
|
||||
const searchQuery = ref("")
|
||||
const filterRole = ref("all")
|
||||
const filterStatus = ref("all")
|
||||
|
||||
const filteredData = computed(() => {
|
||||
return dataSource.value.filter(
|
||||
(user) =>
|
||||
(searchQuery.value === "" ||
|
||||
user.username.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
user.phone.includes(searchQuery.value)) &&
|
||||
(filterRole.value === "all" || user.role.id === parseInt(filterRole.value)) &&
|
||||
(filterStatus.value === "all" || user.status === filterStatus.value)
|
||||
)
|
||||
})
|
||||
|
||||
const filteredAndPaginatedData = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
return filteredData.value.slice(start, end)
|
||||
})
|
||||
const account = ref('')
|
||||
const nickName = ref('')
|
||||
const email = ref('')
|
||||
const role = ref('')
|
||||
const status = ref('')
|
||||
|
||||
// 模态框
|
||||
const showModal = ref(false)
|
||||
@@ -170,7 +109,9 @@ const handleUploadChange = (info) => {
|
||||
}
|
||||
if (info.file.status === "done") {
|
||||
// 模拟上传成功后获取图片URL
|
||||
currentItem.value.avatar = "https://randomuser.me/api/portraits/men/32.jpg"
|
||||
// currentItem.value.avatar = "https://randomuser.me/api/portraits/men/32.jpg"
|
||||
// 从avatarList 中随机选取一张图片作为预览
|
||||
currentItem.value.avatar = avatarList[Math.floor(Math.random() * avatarList.length)]
|
||||
uploading.value = false
|
||||
message.success("头像上传成功")
|
||||
}
|
||||
@@ -203,10 +144,11 @@ const openModal = (item = null) => {
|
||||
currentItem.value = item
|
||||
? JSON.parse(JSON.stringify(item)) // 深拷贝避免直接修改原对象
|
||||
: {
|
||||
username: "",
|
||||
nick_name: "",
|
||||
avatar: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
account: "",
|
||||
password: "Spa666@",
|
||||
role: roleOptions[2], // 默认为普通用户
|
||||
status: "启用",
|
||||
permissions: [],
|
||||
@@ -248,75 +190,52 @@ const closeDeleteConfirm = () => {
|
||||
|
||||
const handleDelete = () => {
|
||||
if (itemToDelete.value) {
|
||||
dataSource.value = dataSource.value.filter((item) => item.id !== itemToDelete.value)
|
||||
totalItems.value = dataSource.value.length
|
||||
closeDeleteConfirm()
|
||||
message.success("用户删除成功")
|
||||
deleteUserApi({
|
||||
ids: [itemToDelete.value]
|
||||
}).then(() => {
|
||||
closeDeleteConfirm()
|
||||
message.success("用户删除成功")
|
||||
getList();
|
||||
})
|
||||
} else {
|
||||
// 如果itemToDelete.value为空,则关闭删除确认框
|
||||
showDeleteConfirm.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" })
|
||||
}
|
||||
const avatarList = [
|
||||
'https://randomuser.me/api/portraits/men/32.jpg',
|
||||
'https://randomuser.me/api/portraits/women/33.jpg',
|
||||
'https://randomuser.me/api/portraits/men/34.jpg',
|
||||
'https://randomuser.me/api/portraits/women/35.jpg',
|
||||
]
|
||||
|
||||
const handleSubmit = () => {
|
||||
formLoading.value = true
|
||||
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
if (currentItem.value.id) {
|
||||
// 更新现有用户
|
||||
const index = dataSource.value.findIndex((item) => item.id === currentItem.value.id)
|
||||
if (index !== -1) {
|
||||
dataSource.value[index] = { ...currentItem.value }
|
||||
}
|
||||
message.success("用户更新成功")
|
||||
} else {
|
||||
// 添加新用户
|
||||
const newId = Math.max(...dataSource.value.map((item) => item.id), 0) + 1
|
||||
dataSource.value.push({
|
||||
...currentItem.value,
|
||||
id: newId,
|
||||
createdAt: new Date().toISOString(),
|
||||
lastLogin: null,
|
||||
})
|
||||
totalItems.value = dataSource.value.length
|
||||
message.success("用户创建成功")
|
||||
}
|
||||
if (currentItem.value.id) {
|
||||
|
||||
formLoading.value = false
|
||||
closeModal()
|
||||
}, 800)
|
||||
updateUserApi(currentItem.value).then(() => {
|
||||
message.success("用户更新成功")
|
||||
formLoading.value = false
|
||||
getList()
|
||||
closeModal()
|
||||
})
|
||||
} else {
|
||||
// 添加新用户
|
||||
createUserApi(currentItem.value).then(() => {
|
||||
message.success("用户创建成功")
|
||||
formLoading.value = false
|
||||
getList()
|
||||
closeModal()
|
||||
})
|
||||
}
|
||||
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
// 权限管理
|
||||
const permissionInputValue = ref("")
|
||||
const handleAddPermission = () => {
|
||||
if (permissionInputValue.value) {
|
||||
// 检查是否已存在该权限
|
||||
const existingPermission = allPermissions.find(
|
||||
(permission) => permission.name.toLowerCase() === permissionInputValue.value.toLowerCase()
|
||||
)
|
||||
|
||||
// 如果存在且未添加到当前用户,则添加
|
||||
if (
|
||||
existingPermission &&
|
||||
!currentItem.value.permissions.some((permission) => permission.id === existingPermission.id)
|
||||
) {
|
||||
currentItem.value.permissions.push(existingPermission)
|
||||
}
|
||||
// 如果不存在,创建新权限并添加
|
||||
else if (!existingPermission) {
|
||||
const newPermissionId = Math.max(...allPermissions.map((permission) => permission.id), 0) + 1
|
||||
const newPermission = { id: newPermissionId, name: permissionInputValue.value }
|
||||
allPermissions.push(newPermission)
|
||||
currentItem.value.permissions.push(newPermission)
|
||||
}
|
||||
|
||||
permissionInputValue.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
const removePermission = (index) => {
|
||||
currentItem.value.permissions.splice(index, 1)
|
||||
@@ -333,7 +252,7 @@ const passwordError = ref("")
|
||||
|
||||
const openResetPasswordModal = (user) => {
|
||||
resetPasswordUserId.value = user.id
|
||||
resetPasswordUsername.value = user.username
|
||||
resetPasswordUsername.value = user.nick_name
|
||||
newPassword.value = ""
|
||||
confirmPassword.value = ""
|
||||
passwordError.value = ""
|
||||
@@ -363,12 +282,25 @@ const handleResetPassword = () => {
|
||||
|
||||
resetPasswordLoading.value = true
|
||||
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
resetPasswordApi({
|
||||
id: resetPasswordUserId.value,
|
||||
password: newPassword.value,
|
||||
new_password: confirmPassword.value,
|
||||
}).then(() => {
|
||||
message.success(`用户 ${resetPasswordUsername.value} 的密码已重置`)
|
||||
resetPasswordLoading.value = false
|
||||
closeResetPasswordModal()
|
||||
}, 800)
|
||||
})
|
||||
}
|
||||
getList();
|
||||
|
||||
const reset = () => {
|
||||
account.value = '';
|
||||
nickName.value = '';
|
||||
email.value = '';
|
||||
role.value = '';
|
||||
status.value = '';
|
||||
getList();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -392,10 +324,10 @@ const handleResetPassword = () => {
|
||||
<div class="mb-6 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
v-model="account"
|
||||
type="text"
|
||||
class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="搜索用户名、邮箱或电话"
|
||||
placeholder="搜索账号(手机号)"
|
||||
/>
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -403,26 +335,80 @@ const handleResetPassword = () => {
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="nickName"
|
||||
type="text"
|
||||
class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="搜索用户名"
|
||||
/>
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="email"
|
||||
type="text"
|
||||
class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="搜索邮箱"
|
||||
/>
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<select
|
||||
v-model="filterRole"
|
||||
v-model="role"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white cursor-pointer"
|
||||
>
|
||||
<option value="all">所有角色</option>
|
||||
<option value="">所有角色</option>
|
||||
<option v-for="role in roleOptions" :key="role.id" :value="role.id">{{ role.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<select
|
||||
v-model="filterStatus"
|
||||
v-model="status"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white cursor-pointer"
|
||||
>
|
||||
<option value="all">所有状态</option>
|
||||
<option value="">所有状态</option>
|
||||
<option v-for="status in statusOptions" :key="status.value" :value="status.value">{{ status.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="relative flex">
|
||||
<!-- 查询按钮 -->
|
||||
<button
|
||||
class="w-30 px-5 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-xl shadow-md hover:shadow-lg transition-all duration-300 flex items-center cursor-pointer"
|
||||
@click="getList()"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
 
|
||||
搜索
|
||||
</button>
|
||||
|
||||
<!-- 重置按钮 -->
|
||||
<button
|
||||
class="w-30 px-5 py-2 ml-2 bg-gray-200 hover:bg-gray-300 text-gray-600 rounded-xl shadow-md hover:shadow-lg transition-all duration-300 flex items-center cursor-pointer"
|
||||
@click="reset()"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
@@ -455,38 +441,38 @@ const handleResetPassword = () => {
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr v-for="user in filteredAndPaginatedData" :key="user.id" class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<tr v-for="user in dataSource" :key="user.id" class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="flex-shrink-0 h-10 w-10 rounded-full overflow-hidden">
|
||||
<img :src="user.avatar" :alt="user.username" class="h-full w-full object-cover" />
|
||||
<img :src="user.avatar" :alt="user.nick_name" class="h-full w-full object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white">{{ user.username }}</div>
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white">{{ user.nick_name }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">ID: {{ user.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm text-gray-900 dark:text-white">{{ user.email }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ user.phone }}</div>
|
||||
<div class="text-sm text-gray-900 dark:text-white">{{ user.email || '未绑定邮箱' }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ user.account }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ user.role.name }}
|
||||
{{ user.roles.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span
|
||||
:class="`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
user.status === '启用'
|
||||
user.status === 0
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
||||
: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300'
|
||||
}`"
|
||||
>
|
||||
{{ user.status }}
|
||||
{{ user.status_text }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ user.lastLogin ? formatDate(user.lastLogin) : '从未登录' }}
|
||||
{{ user.last_reset_password_at || '从未登录' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div class="flex justify-end space-x-2">
|
||||
@@ -520,7 +506,7 @@ const handleResetPassword = () => {
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="filteredAndPaginatedData.length === 0">
|
||||
<tr v-if="totalItems === 0">
|
||||
<td colspan="6" class="px-6 py-10 text-center text-gray-500 dark:text-gray-400">
|
||||
没有找到匹配的用户
|
||||
</td>
|
||||
@@ -532,7 +518,7 @@ const handleResetPassword = () => {
|
||||
<!-- 分页 -->
|
||||
<div class="bg-white dark:bg-gray-800 px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<div class="text-sm text-gray-700 dark:text-gray-300">
|
||||
共 {{ filteredData.length }} 条记录
|
||||
共 {{ totalItems }} 条记录
|
||||
</div>
|
||||
<div class="flex space-x-1">
|
||||
<button
|
||||
@@ -544,7 +530,7 @@ const handleResetPassword = () => {
|
||||
上一页
|
||||
</button>
|
||||
<button
|
||||
v-for="page in Math.ceil(filteredData.length / pageSize)"
|
||||
v-for="page in pageCount"
|
||||
:key="page"
|
||||
class="px-3 py-1 border rounded-md text-sm font-medium cursor-pointer"
|
||||
:class="page === currentPage
|
||||
@@ -556,9 +542,9 @@ const handleResetPassword = () => {
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
|
||||
:disabled="currentPage === Math.ceil(filteredData.length / pageSize)"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': currentPage === Math.ceil(filteredData.length / pageSize) }"
|
||||
@click="currentPage < Math.ceil(filteredData.length / pageSize) && changePage(currentPage + 1)"
|
||||
:disabled="currentPage === pageCount"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': currentPage === pageCount }"
|
||||
@click="currentPage < pageCount && changePage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
@@ -600,11 +586,23 @@ const handleResetPassword = () => {
|
||||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||||
<!-- 基本信息 -->
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
|
||||
<!-- 登录账号(电话) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">登录账号(电话)</label>
|
||||
<input
|
||||
v-model="currentItem.account"
|
||||
type="text"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="请输入登录账号(电话)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 用户名 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">用户名</label>
|
||||
<input
|
||||
v-model="currentItem.username"
|
||||
v-model="currentItem.nick_name"
|
||||
type="text"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="请输入用户名"
|
||||
@@ -624,14 +622,15 @@ const handleResetPassword = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 电话 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">电话</label>
|
||||
<!-- 密码 -->
|
||||
<div v-if="!currentItem.id">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">密码</label>
|
||||
<input
|
||||
v-model="currentItem.phone"
|
||||
v-model="currentItem.password"
|
||||
type="text"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="请输入电话"
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -639,10 +638,10 @@ const handleResetPassword = () => {
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">角色</label>
|
||||
<select
|
||||
v-model="currentItem.role"
|
||||
v-model="currentItem.role_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white cursor-pointer"
|
||||
>
|
||||
<option v-for="role in roleOptions" :key="role.id" :value="role">
|
||||
<option v-for="role in roleOptions" :key="role.id" :value="role.id">
|
||||
{{ role.name }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -689,47 +688,6 @@ const handleResetPassword = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 权限 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">权限</label>
|
||||
<div class="flex flex-wrap gap-2 mb-2">
|
||||
<span
|
||||
v-for="(permission, index) in currentItem.permissions"
|
||||
:key="index"
|
||||
class="px-3 py-1.5 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-300 text-sm font-medium rounded-lg flex items-center"
|
||||
>
|
||||
{{ permission.name }}
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1.5 text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-200 cursor-pointer"
|
||||
@click="removePermission(index)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<select
|
||||
v-model="permissionInputValue"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-l-xl focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value="">选择权限</option>
|
||||
<option v-for="permission in allPermissions" :key="permission.id" :value="permission.name">
|
||||
{{ permission.name }}
|
||||
</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-r-xl transition-colors cursor-pointer"
|
||||
@click="handleAddPermission"
|
||||
>
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单按钮 -->
|
||||
|
||||
@@ -13,5 +13,15 @@ export default defineConfig({
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 8888,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:18007/api/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user