Files
qingcheng-Web/src/view/userList/userList.vue

697 lines
14 KiB
Vue
Raw Normal View History

2025-06-18 09:26:56 +08:00
<template>
<!-- 搜索框 -->
<div class="search-box">
<div class="search-container">
<a-form layout="inline">
<a-space>
<a-form-item label="手机号">
<a-input-search
style="width: 300px;"
placeholder="请输入手机号"
enter-button
@search="handlePhoneSearch"
v-model:value="searchPhone"
type="text"
class="custom-search"
:maxlength="11"
@input="handleSearchPhoneInput"
@keydown="handleKeyDown"
/>
</a-form-item>
<a-button class="custom-button" @click="reset">重置搜索</a-button>
</a-space>
</a-form>
</div>
</div>
<!-- 数据-->
<a-table
:columns="columns"
:data-source="tableData"
:scroll="{ x: 1400, y: 550 }"
:loading="loading"
:pagination="pagination"
bordered
rowKey="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'userAvatar'">
<a-avatar :src="downLoadImage + record.userAvatar" />
</template>
<template v-if="column.key === 'operation'">
<a-space :size="16">
<a class="action-btn" @click="showDrawer(record)">详情</a>
</a-space>
</template>
</template>
</a-table>
<!-- 抽屉-->
<a-drawer
v-model:open="open"
title="用户详细信息"
placement="right"
width="600"
>
<div v-if="selectedUser" class="user-detail">
<div class="header">
<a-avatar :size="128" :src="downLoadImage+selectedUser.userAvatar" class="avatar" />
</div>
<a-form
v-if="isEditMode"
:model="editForm"
ref="editFormRef"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<!-- 可编辑表单 -->
<a-form-item label="账户" name="userAccount">
</a-form-item>
2025-06-18 09:26:56 +08:00
<a-form-item label="昵称" name="nickName">
2025-06-18 09:26:56 +08:00
</a-form-item>
<a-form-item label="头像" name="userAvatar" required>
2025-06-18 09:26:56 +08:00
</a-form-item>
<a-form-item label="手机号" name="phoneNumber">
2025-06-18 09:26:56 +08:00
</a-form-item>
<a-form-item label="账户">
2025-06-18 09:26:56 +08:00
</a-form-item>
<a-form-item label="密码" name="userPassword" required>
2025-06-18 09:26:56 +08:00
</a-form-item>
2025-06-18 09:26:56 +08:00
</a-form>
<div v-else class="view-mode">
<!-- 基本信息卡片 -->
<a-card title="基本信息" class="info-card">
<div class="info-item">
<user-outlined class="info-icon" />
<div class="info-content">
<span class="info-label">用户ID</span>
<span class="info-value">{{ selectedUser.id }}</span>
</div>
</div>
<div class="info-item">
<idcard-outlined class="info-icon" />
<div class="info-content">
<span class="info-label">账户信息</span>
<div class="account-detail">
<span>{{ selectedUser.userAccount }}</span>
<a-tag class="role-tag">
{{ editForm.userRole}}
</a-tag>
</div>
</div>
</div>
<div class="info-item">
<smile-outlined class="info-icon" />
<div class="info-content">
<span class="info-label">昵称</span>
<span class="info-value highlight">{{ selectedUser.nickName }}</span>
</div>
</div>
</a-card>
<!-- 联系信息卡片 -->
<a-card title="联系信息" class="info-card">
<div class="info-item">
<phone-outlined class="info-icon" />
<div class="info-content">
<span class="info-label">手机号码</span>
</div>
</div>
</a-card>
</div>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
2025-06-18 09:26:56 +08:00
import myAxios from "../../api/myAxios.ts";
import { message } from "ant-design-vue";
2025-06-18 09:26:56 +08:00
import {downLoadImage} from '../../api/ImageUrl.ts'
2025-06-18 09:26:56 +08:00
import {
UserOutlined,
IdcardOutlined,
SmileOutlined,
PhoneOutlined,
} from '@ant-design/icons-vue';
const handleSearchPhoneInput = (e: any) => {
let value = e.target.value;
// 只允许输入数字并截取前11位
const newValue = value.replace(/[^0-9]/g, '').slice(0, 11);
if (newValue !== value) {
searchPhone.value = newValue;
}
};
// 新增 onKeyDown 事件处理函数
const handleKeyDown = (e: KeyboardEvent) => {
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
const allowedKeys = [
'Backspace',
'Delete',
'ArrowLeft',
'ArrowRight',
'Tab',
];
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
if (!/[0-9]/.test(e.key) && !allowedKeys.includes(e.key)) {
e.preventDefault();
}
};
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
const loading = ref(false);
const searchParams = ref({
current: 1,
pageSize: 10,
sortField: "id",
sortOrder: "ascend",
userRole:"user",
2025-06-18 09:26:56 +08:00
phoneNumber:null
});
//用户表
const columns = [
{
title: '用户ID',
dataIndex: 'id',
width: 50,
key: 'id',
fixed: 'left',
align: 'center',
2025-07-02 09:43:17 +08:00
sorter: true,
sortDirections: ['ascend', 'descend']
2025-06-18 09:26:56 +08:00
},
{
title: '头像',
dataIndex: 'userAvatar',
key: 'userAvatar',
width: 50,
fixed: 'left',
align: 'center'
},
{
title: '账号',
dataIndex: 'userAccount',
width: 90,
key: 'userAccount',
fixed: 'left',
align: 'center'
},
{
title: '用户昵称',
dataIndex: 'nickName',
width: 70,
key: 'nickName',
align: 'center'
},
{
title: '身份',
dataIndex: 'userRole',
key: 'userRole',
width: 50,
align: 'center'
},
{
title: '手机号',
dataIndex: 'phoneNumber',
key: 'phoneNumber',
width: 80,
align: 'center',
2025-07-02 09:43:17 +08:00
sorter: true,
sortDirections: ['ascend', 'descend']
2025-06-18 09:26:56 +08:00
},
{
title: '邀请码',
dataIndex: 'invitationCode',
key: 'invitationCode',
width: 70,
align: 'center',
2025-07-02 09:43:17 +08:00
sorter: true,
sortDirections: ['ascend', 'descend']
2025-06-18 09:26:56 +08:00
},
{
title: '上级ID',
dataIndex: 'parentUserId',
key: 'parentUserId',
width: 40,
align: 'center',
2025-07-02 09:43:17 +08:00
sorter: true,
sortDirections: ['ascend', 'descend']
2025-06-18 09:26:56 +08:00
},
{
title: '操作',
key: 'operation',
fixed: 'right',
width: 90,
align: 'center'
}
];
// 分页配置
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total}`,
pageSizeOptions: ['10', '20', '50', '100']
});
2025-07-02 09:43:17 +08:00
const editFormRef = ref();
2025-06-18 09:26:56 +08:00
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
const getUserList = async () => {
loading.value = true;
try {
const storedToken = localStorage.getItem('token');
if (!storedToken) throw new Error('未找到登录信息');
const res: any = await myAxios.post("/userInfo/page",
{...searchParams.value },
{ headers: { Authorization: storedToken } }
);
console.log(res)
if (res.code === 1 && res.data) {
tableData.value = res.data.records.map((item: any) => {
if (item.parentUserId === -1) {
item.parentUserId = '无';
}
return {
...item,
superUserList: item.superHostList? item.superHostList.join(', ') : '无'
};
});
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
pagination.value.total = res.data.total;
pagination.value.current = res.data.current;
pagination.value.pageSize = res.data.size;
}
} finally {
loading.value = false;
}
};
onMounted(getUserList);
// ID查询方法
interface User {
id: number;
nickName: string;
userAvatar: string;
phoneNumber: string;
userAccount: string;
userPassword: string;
invitationCode: string;
userRole:string;
parentUserId:number
}
2025-07-02 09:43:17 +08:00
const tableData = ref<User[]>([]);
2025-06-18 09:26:56 +08:00
2025-07-02 09:43:17 +08:00
const searchPhone = ref("");
2025-06-18 09:26:56 +08:00
const handlePhoneSearch = async () => {
if (!searchPhone.value) {
message.warning('请输入手机号');
return;
}
// 验证手机号格式
if (!/^1[3-9]\d{9}$/.test(searchPhone.value)) {
message.warning('请输入有效的11位手机号');
return;
}
loading.value = true;
try {
const storedToken = localStorage.getItem('token');
if (!storedToken) throw new Error('未找到登录信息');
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
const res: { code: number; data: any; message: any } = await myAxios.post(
"/userInfo/page",
{
current: 1,
pageSize: 10,
userRole:'user',
2025-06-18 09:26:56 +08:00
phoneNumber: searchPhone.value
},
{ headers: { Authorization: storedToken } }
);
if (res.code === 1 && res.data) {
tableData.value = res.data.records.map((item: any) => {
if (item.parentUserId === -1) {
item.parentUserId = '无';
}
return {
...item,
superUserList: item.superHostList? item.superHostList.join(', ') : '无'
};
});
// 更新分页信息
pagination.value.total = res.data.total;
pagination.value.current = res.data.current;
pagination.value.pageSize = res.data.size;
} else {
message.error(res.message || '查询失败');
}
} catch (error) {
console.error('查询失败:', error);
message.error('查询操作失败,请检查网络');
} finally {
loading.value = false;
}
};
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
const reset = () => {
2025-07-02 09:43:17 +08:00
searchPhone.value = "";
2025-06-18 09:26:56 +08:00
searchParams.value = {
current: 1,
pageSize: 10,
sortField: "id",
sortOrder: "ascend",
userRole: "user",
2025-06-18 09:26:56 +08:00
phoneNumber: null
};
2025-07-02 09:43:17 +08:00
getUserList();
2025-06-18 09:26:56 +08:00
};
// 操作用户
const open = ref<boolean>(false);
const selectedUser = ref<any>();
2025-06-18 09:26:56 +08:00
const isEditMode = ref(false);
const editForm = ref({
id: 0,
nickName: '',
userAvatar: '',
phoneNumber: '',
userAccount: '',
userPassword: '',
invitationCode: '',
userRole: '',
parentUserId: 0,
SuperUserList: ''
});
const showDrawer = (record: any) => {
selectedUser.value = record;
editForm.value = {
id: record.id,
nickName: record.nickName,
userAvatar: record.userAvatar||'',
phoneNumber: record.phoneNumber,
userAccount: record.userAccount,
userPassword: record.userPassword,
invitationCode: record.invitationCode,
userRole: record.userRole,
parentUserId: record.parentUserId,
SuperUserList: Array.isArray(record.superHostList)
? record.superHostList.join(',')
: record.SuperUserList || ''
};
open.value = true;
isEditMode.value = false;
};
const handleTableChange = (pag: any, _: any, sorter: any) => {
2025-06-18 09:26:56 +08:00
let sortField = "id";
let sortOrder = "ascend";
2025-06-18 09:26:56 +08:00
if (sorter.field) {
sortField = sorter.field;
sortOrder = sorter.order;
2025-06-18 09:26:56 +08:00
}
searchParams.value = {
...searchParams.value,
current: pag.current,
pageSize: pag.pageSize,
sortField: sortField,
sortOrder: sortOrder
2025-06-18 09:26:56 +08:00
};
2025-07-02 09:43:17 +08:00
pagination.value.current = pag.current;
pagination.value.pageSize = pag.pageSize;
2025-06-18 09:26:56 +08:00
getUserList();
2025-06-18 09:26:56 +08:00
};
</script>
<style scoped>
.action-btn {
padding: 0 8px;
}
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
:deep(.ant-divider-vertical) {
border-color: rgba(0, 0, 0, 0.15);
height: 1.2em;
margin: 0 4px;
}
.user-detail {
padding: 20px;
}
.avatar {
display: block;
margin: 0 auto 20px;
}
:deep(.ant-descriptions-item-label) {
font-weight: 600;
width: 120px;
}
.header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.avatar {
margin-bottom: 20px;
}
.custom-button {
width: 120px;
}
.view-mode {
display: flex;
flex-direction: column;
gap: 16px;
}
.info-card {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
margin-bottom: 16px;
}
.info-card :deep(.ant-card-head) {
background: #fafafa;
border-bottom: 1px solid #e8e8e8;
}
.info-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.info-item:last-child {
border-bottom: none;
}
.info-icon {
font-size: 18px;
color: #1890ff;
margin-right: 16px;
width: 24px;
text-align: center;
}
.info-content {
flex: 1;
}
.info-label {
display: block;
color: #666;
font-size: 12px;
margin-bottom: 4px;
}
.info-value {
font-size: 14px;
color: #333;
font-weight: 500;
}
.highlight {
color: #1890ff;
}
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
.role-tag {
margin-left: 8px;
font-size: 12px;
}
:root {
--role-user: #87d068;
--role-admin: #f50;
--role-boss: #722ed1;
}
.role-tag[color="user"] {
background: var(--role-user);
color: white;
}
.role-tag[color="admin"] {
background: var(--role-admin);
color: white;
}
.role-tag[color="boss"] {
background: var(--role-boss);
color: white;
}
.search-box {
margin-bottom: 10px;
}
.password-strength span {
color: #999;
font-size: 12px;
position: relative;
padding-left: 18px;
}
.password-strength span::before {
content: '';
width: 12px;
height: 12px;
border-radius: 50%;
background: #eee;
position: absolute;
left: 0;
top: 2px;
}
.avatar-uploader :deep(.ant-upload) {
width: 100%;
height: 100%;
overflow: hidden;
}
.avatar-uploader img {
width: 100%;
height: 100%;
display: block;
}
.custom-button {
background-color: #ffa940;
border-color: #ffa940;
color: #fff;
}
.custom-button:hover,
.custom-button:focus {
background-color: #ffa940;
border-color: #ffa940;
color: #fff;
}
.custom-button[disabled] {
background-color: #ffa940;
border-color: #ffa940;
opacity: 0.6;
color: #fff;
}
.custom-search :deep(.ant-input-search-button) {
background-color: #ffa940;
border-color: #ffa940;
}
.custom-search :deep(.ant-input-search-button:hover),
.custom-search :deep(.ant-input-search-button:focus) {
background-color: #fa8c16;
border-color: #fa8c16;
}
2025-07-02 09:43:17 +08:00
2025-06-18 09:26:56 +08:00
.custom-search :deep(.ant-input) {
border-right-color: #ffa940;
}
:deep(.ant-checkbox-wrapper-disabled) {
display: none;
}
</style>