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

814 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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"
:rules="editFormRules"
ref="editFormRef"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<!-- 可编辑表单 -->
<a-form-item label="昵称" name="nickName">
</a-form-item>
<a-form-item label="头像" name="userAvatar" required>
</a-form-item>
<a-form-item label="手机号" name="phoneNumber">
</a-form-item>
<a-form-item label="账户" name="userAccount">
</a-form-item>
<a-form-item label="密码" name="userPassword" required>
</a-form-item>
<a-form-item label="身份">
</a-form-item>
<a-form-item :wrapper-col="{ offset: 6 }">
</a-form-item>
</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>
<span class="info-value">{{ selectedUser.phoneNumber || '未绑定' }}</span>
</div>
</div>
</a-card>
</div>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { onMounted, ref} from "vue";
import myAxios from "../../api/myAxios.ts";
import { message } from "ant-design-vue";
import {downLoadImage} from '../../api/ImageUrl.ts'
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) => {
const allowedKeys = [
'Backspace',
'Delete',
'ArrowLeft',
'ArrowRight',
'Tab',
];
if (!/[0-9]/.test(e.key) && !allowedKeys.includes(e.key)) {
e.preventDefault();
}
};
const loading = ref(false);
const searchParams = ref({
current: 1,
pageSize: 10,
sortField: "id",
sortOrder: "ascend",
userRole:"supervisor",
phoneNumber:null
});
//用户表
const columns = [
{
title: '用户ID',
dataIndex: 'id',
width: 50,
key: 'id',
fixed: 'left',
align: 'center',
sorter: true,
sortDirections: ['ascend', 'descend']
},
{
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',
sorter: true,
sortDirections: ['ascend', 'descend']
},
// {
// title: '邀请码',
// dataIndex: 'invitationCode',
// key: 'invitationCode',
// width: 70,
// align: 'center',
// sorter: true,
// sortDirections: ['ascend', 'descend']
// },
// {
// title: '上级ID',
// dataIndex: 'parentUserId',
// key: 'parentUserId',
// width: 40,
// align: 'center',
// sorter: true,
// sortDirections: ['ascend', 'descend']
// },
{
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']
});
//编辑用户表单
const editFormRules = {
nickName: [
{
required: true,
message: '请输入昵称',
trigger: ['input', 'blur']
},
{
min: 1,
max: 6,
message: '昵称长度需为1-6位',
trigger: ['input', 'blur']
}
],
phoneNumber: [
{
required: true,
message: '请输入手机号',
trigger: ['input', 'blur']
},
{
pattern: /^1[3-9]\d{9}$/,
message: '必须是有效的11位手机号',
trigger: ['input', 'blur']
}
],
userPassword: [
{
min: 6,
max: 10,
message: '长度需为6-10位',
trigger: ['input', 'blur']
},
{
validator: (_: any, value: string) => {
if (value && !/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
return Promise.reject('必须包含大小写字母和数字');
}
return Promise.resolve();
},
trigger: ['input', 'blur']
}
]
};
const editFormRef = ref();
// 修改后的分页处理函数
const handleTableChange = (pag: any, _: any, sorter: any) => {
let sortField = "id";
let sortOrder = "ascend";
if (sorter.field) {
sortField = sorter.field;
sortOrder = sorter.order;
}
searchParams.value = {
...searchParams.value,
current: pag.current,
pageSize: pag.pageSize,
sortField: sortField,
sortOrder: sortOrder
};
pagination.value.current = pag.current;
pagination.value.pageSize = pag.pageSize;
getUserList();
};
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(', ') : '无'
};
});
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
}
const tableData = ref<User[]>([]);
const searchPhone = ref("");
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('未找到登录信息');
const res: { code: number; data: any; message: any } = await myAxios.post(
"/userInfo/page",
{
current: 1,
pageSize: 10,
userRole:'supervisor',
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;
}
};
const reset = () => {
searchPhone.value = "";
searchParams.value = {
current: 1,
pageSize: 10,
sortField: "id",
sortOrder: "ascend",
userRole: "supervisor",
phoneNumber: null
};
getUserList();
};
// 操作用户
const open = ref<boolean>(false);
const selectedUser = ref<any>(null);
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;
};
</script>
<style scoped>
.action-btn {
padding: 0 8px;
}
: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;
}
.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;
}
.error-tip {
color: #ff4d4f;
font-size: 12px;
margin-left: 8px;
}
.tip {
color: rgba(0, 0, 0, 0.45);
font-size: 12px;
}
.password-strength {
margin-top: 4px;
display: flex;
gap: 8px;
}
.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;
}
.strength-ok {
color: #52c41a;
}
.strength-ok::before {
background: #52c41a !important;
}
.avatar-uploader :deep(.ant-upload) {
width: 100%;
height: 100%;
overflow: hidden;
}
.ant-upload-text {
margin-top: 8px;
}
.input-tip {
font-size: 0.875rem;
color: #666;
}
.error-tip {
color: #f5222d;
margin-left: 4px;
}
.password-strength {
margin-top: 4px;
font-size: 0.875rem;
}
.strength-ok {
color: #52c41a;
}
.avatar-uploader {
width: 100%;
max-width: 200px;
margin: 0 auto;
}
.avatar-uploader .ant-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .ant-upload:hover {
border-color: #40a9ff;
}
.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;
}
.custom-search :deep(.ant-input) {
border-right-color: #ffa940;
}
:deep(.ant-checkbox-wrapper-disabled) {
display: none;
}
:deep(.custom-search .ant-input:focus),
:deep(.custom-search .ant-input:hover),
:deep(.custom-search .ant-input-focused) {
border-color: #ffa940 !important;
box-shadow: 0 0 0 2px rgba(255, 169, 64, 0.2) !important;
}
</style>