完善用户管理和登录的表单校验
This commit is contained in:
parent
6cbd8f93e6
commit
0336f21f83
|
@ -10,12 +10,10 @@
|
|||
<div class="input-box">
|
||||
|
||||
<input class="password" :type="type" placeholder="密码" v-model="userPassword"/>
|
||||
<div @click="showPwd">
|
||||
<!-- 隐藏图标 -->
|
||||
<img src="../assets/login/hidePassword.png" v-show="showPassword === false" alt=""/>
|
||||
<!-- 显示图标 -->
|
||||
<img src="../assets/login/showPassword.png" v-show="showPassword === true" alt=""/>
|
||||
</div>
|
||||
<!-- <div @click="showPwd">-->
|
||||
<!-- <img src="../assets/login/hidePassword.png" v-show="!showPassword" alt=""/>-->
|
||||
<!-- <img src="../assets/login/showPassword.png" v-show="showPassword" alt=""/>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<div class="error-messages" v-if="userPassword && passwordErrors.length">
|
||||
<!-- <div class="error-messages" v-if="false">-->
|
||||
|
@ -45,7 +43,7 @@ const store = userStore()
|
|||
const router = useRouter()
|
||||
|
||||
//默认闭眼图标
|
||||
let showPassword = ref(false)
|
||||
// let showPassword = ref(false)
|
||||
//登录密码隐藏
|
||||
let type = ref('password')
|
||||
const userAccount = ref('');
|
||||
|
@ -137,14 +135,14 @@ const onLogin = async () => {
|
|||
/**
|
||||
* 显示密码
|
||||
*/
|
||||
const showPwd = () => {
|
||||
showPassword.value = !showPassword.value
|
||||
if (showPassword.value == false) {
|
||||
type.value = 'password'
|
||||
} else {
|
||||
type.value = 'text'
|
||||
}
|
||||
}
|
||||
// const showPwd = () => {
|
||||
// showPassword.value = !showPassword.value
|
||||
// if (showPassword.value == false) {
|
||||
// type.value = 'password'
|
||||
// } else {
|
||||
// type.value = 'text'
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -41,7 +41,21 @@
|
|||
</a-form-item>
|
||||
|
||||
<a-form-item label="头像" name="userAvatar">
|
||||
<a-input v-model:value="formData.userAvatar" />
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="picture-card"
|
||||
class="avatar-uploader"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="handleUpload"
|
||||
>
|
||||
<img v-if="formData.userAvatar" :src="previewImage" alt="avatar" style="width: 100%"/>
|
||||
<div v-else>
|
||||
<loading-outlined v-if="uploadLoading"></loading-outlined>
|
||||
<plus-outlined v-else></plus-outlined>
|
||||
<div class="ant-upload-text">上传头像</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="手机号" name="phoneNumber">
|
||||
|
@ -161,24 +175,83 @@
|
|||
<a-form
|
||||
v-if="isEditMode"
|
||||
:model="editForm"
|
||||
:rules="editFormRules"
|
||||
ref="editFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<!-- 可编辑表单 -->
|
||||
<a-form-item label="昵称">
|
||||
<a-input v-model:value="editForm.nickName" />
|
||||
<a-form-item label="昵称" name="nickName">
|
||||
<a-input
|
||||
v-model:value="editForm.nickName"
|
||||
:maxlength="6"
|
||||
@input="handleEditNicknameInput"
|
||||
@keypress="handleMaxLength(6)"
|
||||
>
|
||||
<template #suffix>
|
||||
<span v-if="editForm.nickName.length > 0" class="input-tip">
|
||||
{{ editForm.nickName.length }}/6
|
||||
<span v-if="editForm.nickName.length > 6" class="error-tip">(超过最大长度)</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="头像URL">
|
||||
<a-input v-model:value="editForm.userAvatar" />
|
||||
|
||||
<a-form-item label="头像" name="userAvatar">
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="picture-card"
|
||||
class="avatar-uploader"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="handleEditUpload"
|
||||
>
|
||||
<img v-if="editForm.userAvatar" :src="editPreviewImage" alt="avatar" style="width: 100%"/>
|
||||
<div v-else>
|
||||
<loading-outlined v-if="editUploadLoading"></loading-outlined>
|
||||
<plus-outlined v-else></plus-outlined>
|
||||
<div class="ant-upload-text">上传头像</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号">
|
||||
<a-input v-model:value="editForm.phoneNumber" />
|
||||
|
||||
|
||||
<a-form-item label="手机号" name="phoneNumber">
|
||||
<a-input
|
||||
v-model:value="editForm.phoneNumber"
|
||||
:maxlength="11"
|
||||
@input="handleEditPhoneInput"
|
||||
>
|
||||
<template #suffix>
|
||||
<span v-if="editForm.phoneNumber.length > 0" class="input-tip">
|
||||
{{ editForm.phoneNumber.length }}/11
|
||||
<span v-if="editForm.phoneNumber.length > 11" class="error-tip">(超过最大长度)</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="账户">
|
||||
<a-input v-model:value="editForm.userAccount" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input v-model:value="editForm.userPassword" />
|
||||
<a-form-item label="密码" name="userPassword">
|
||||
<a-input
|
||||
v-model:value="editForm.userPassword"
|
||||
type="password"
|
||||
:maxlength="10"
|
||||
@input="handleEditPasswordInput"
|
||||
@keypress="handleMaxLength(10)"
|
||||
>
|
||||
<template #suffix>
|
||||
<span v-if="editForm.userPassword.length > 0" class="input-tip">
|
||||
{{ editForm.userPassword.length }}/10
|
||||
<div class="password-strength">
|
||||
<span :class="{ 'strength-ok': editHasLower }">小写字母</span>
|
||||
<span :class="{ 'strength-ok': editHasUpper }">大写字母</span>
|
||||
<span :class="{ 'strength-ok': editHasNumber }">数字</span>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="邀请码">
|
||||
<a-input v-model:value="editForm.invitationCode" />
|
||||
|
@ -191,7 +264,7 @@
|
|||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="上级用户ID">
|
||||
<a-input-number v-model:value="editForm.parentUserId" />
|
||||
<a-input-number v-model:value="editForm.parentUserId" :min="0" />
|
||||
</a-form-item>
|
||||
<a-form-item label="上级用户列表">
|
||||
<a-input
|
||||
|
@ -263,12 +336,94 @@
|
|||
import { onMounted, ref,computed } from "vue";
|
||||
import myAxios from "../../api/myAxios.ts";
|
||||
import { message } from "ant-design-vue";
|
||||
import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
|
||||
import type { UploadProps } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
UserOutlined,
|
||||
IdcardOutlined,
|
||||
SmileOutlined,
|
||||
PhoneOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
const uploadLoading = ref(false);
|
||||
const previewImage = ref('');
|
||||
// 上传前校验
|
||||
const beforeUpload: UploadProps['beforeUpload'] = file => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件!');
|
||||
return false;
|
||||
}
|
||||
const isLt5M = file.size / 1024 / 1024 < 5;
|
||||
if (!isLt5M) {
|
||||
message.error('图片大小不能超过5MB!');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 自定义上传处理
|
||||
const handleUpload = async ({ file }: { file: File }) => {
|
||||
const form = new FormData();
|
||||
form.append('file', file); // 只保留file字段在formData中
|
||||
|
||||
try {
|
||||
uploadLoading.value = true;
|
||||
const storedToken = localStorage.getItem('token');
|
||||
|
||||
// 根据接口文档修改请求地址和参数
|
||||
const res:any = await myAxios.post('/file/upload?biz=avatar', form, {
|
||||
headers: {
|
||||
Authorization: storedToken,
|
||||
// 保留由浏览器自动生成的内容类型
|
||||
}
|
||||
});
|
||||
console.log(res)
|
||||
// 根据响应结构调整(假设返回data直接是URL)
|
||||
if (res.code === 1) { // 注意文档中响应状态码是200
|
||||
formData.value.userAvatar = res.data;
|
||||
previewImage.value = URL.createObjectURL(file);
|
||||
message.success('上传成功');
|
||||
} else {
|
||||
message.error(res.message || '上传失败');
|
||||
}
|
||||
} catch (error:any) {
|
||||
console.error('上传失败详情:', error);
|
||||
message.error(`上传失败:${error.response?.data?.error || error.message}`);
|
||||
} finally {
|
||||
uploadLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 编辑表单实时处理函数
|
||||
const handleEditNicknameInput = (e: any) => {
|
||||
// 限制最大长度并实时提示
|
||||
const trimmedValue = e.target.value.trim().slice(0, 6);
|
||||
editForm.value.nickName = trimmedValue;
|
||||
// 触发表单校验
|
||||
editFormRef.value?.validateField('nickName');
|
||||
};
|
||||
|
||||
const handleEditPhoneInput = (e: any) => {
|
||||
// 过滤非数字字符并限制长度
|
||||
const numericValue = e.target.value.replace(/\D/g, '').slice(0, 11);
|
||||
editForm.value.phoneNumber = numericValue;
|
||||
// 触发手机号格式校验
|
||||
editFormRef.value?.validateField('phoneNumber');
|
||||
};
|
||||
|
||||
const handleEditPasswordInput = (e: any) => {
|
||||
// 限制密码长度
|
||||
editForm.value.userPassword = e.target.value.slice(0, 10);
|
||||
};
|
||||
|
||||
// 通用最大长度处理函数
|
||||
const handleMaxLength = (max: number) => (e: KeyboardEvent) => {
|
||||
if (e.target instanceof HTMLInputElement && e.target.value.length >= max) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 新增角色颜色映射
|
||||
// const roleColorMap = {
|
||||
|
@ -487,7 +642,59 @@ const pagination = ref({
|
|||
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 editHasLower = computed(() => /[a-z]/.test(editForm.value.userPassword))
|
||||
const editHasUpper = computed(() => /[A-Z]/.test(editForm.value.userPassword))
|
||||
const editHasNumber = computed(() => /\d/.test(editForm.value.userPassword))
|
||||
|
||||
const editFormRef = ref(); // 添加表单引用
|
||||
|
||||
const handleTableChange = (pag: any, sorter: any) => {
|
||||
searchParams.value = {
|
||||
|
@ -675,12 +882,46 @@ const editForm = ref({
|
|||
// };
|
||||
|
||||
// 修改showDrawer方法
|
||||
|
||||
// 编辑状态下的头像上传状态
|
||||
const editUploadLoading = ref(false);
|
||||
const editPreviewImage = ref('');
|
||||
|
||||
// 编辑时的自定义上传处理
|
||||
const handleEditUpload = async ({ file }: { file: File }) => {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
|
||||
try {
|
||||
editUploadLoading.value = true;
|
||||
const storedToken = localStorage.getItem('token');
|
||||
|
||||
const res: any = await myAxios.post('/file/upload?biz=avatar', form, {
|
||||
headers: {
|
||||
Authorization: storedToken,
|
||||
}
|
||||
});
|
||||
|
||||
if (res.code === 1) {
|
||||
editForm.value.userAvatar = res.data;
|
||||
editPreviewImage.value = URL.createObjectURL(file);
|
||||
message.success('头像更新成功');
|
||||
} else {
|
||||
message.error(res.message || '头像上传失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('编辑头像上传失败:', error);
|
||||
message.error(`上传失败:${error.response?.data?.error || error.message}`);
|
||||
} finally {
|
||||
editUploadLoading.value = false;
|
||||
}
|
||||
};
|
||||
const showDrawer = (record: any) => {
|
||||
selectedUser.value = record;
|
||||
editForm.value = {
|
||||
id: record.id,
|
||||
nickName: record.nickName,
|
||||
userAvatar: record.userAvatar,
|
||||
userAvatar: record.userAvatar||'',
|
||||
phoneNumber: record.phoneNumber,
|
||||
userAccount: record.userAccount,
|
||||
userPassword: record.userPassword,
|
||||
|
@ -704,13 +945,44 @@ const handleSave = async () => {
|
|||
const storedToken = localStorage.getItem('token');
|
||||
if (!storedToken) throw new Error('未找到登录信息');
|
||||
|
||||
// 构建符合接口要求的请求体
|
||||
const payload = {
|
||||
...editForm.value,
|
||||
SuperUserList: editForm.value.SuperUserList.split(',').map(Number)
|
||||
// 处理空值转换
|
||||
const processValue = (value: any) => {
|
||||
if (value === "" || value === "无") return null; // 将空字符串和"无"转为null
|
||||
return value;
|
||||
};
|
||||
|
||||
const res:any = await myAxios.post('/userInfo/update', payload, {
|
||||
// 处理数字类型字段
|
||||
const processNumber = (value: any) => {
|
||||
const num = Number(value);
|
||||
return isNaN(num) ? null : num;
|
||||
};
|
||||
|
||||
// 构建请求体
|
||||
const payload = {
|
||||
id: editForm.value.id,
|
||||
nickName: processValue(editForm.value.nickName),
|
||||
userAvatar: processValue(editForm.value.userAvatar),
|
||||
phoneNumber: processValue(editForm.value.phoneNumber),
|
||||
userAccount: editForm.value.userAccount, // 保持禁用状态的值
|
||||
userPassword: editForm.value.userPassword || undefined, // 密码为空时不修改
|
||||
invitationCode: processValue(editForm.value.invitationCode),
|
||||
userRole: editForm.value.userRole || 'user',
|
||||
parentUserId: processNumber(editForm.value.parentUserId),
|
||||
SuperUserList: editForm.value.SuperUserList
|
||||
? editForm.value.SuperUserList.split(',')
|
||||
.map(item => processNumber(item.trim()))
|
||||
.filter(num => num !== null)
|
||||
: []
|
||||
};
|
||||
|
||||
// 清理undefined字段
|
||||
const cleanPayload = Object.fromEntries(
|
||||
Object.entries(payload).filter(([_, v]) => v !== undefined)
|
||||
);
|
||||
|
||||
console.log("最终请求体:", JSON.stringify(cleanPayload, null, 2));
|
||||
|
||||
const res:any = await myAxios.post('/userInfo/update', cleanPayload, {
|
||||
headers: {
|
||||
Authorization: storedToken,
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -719,20 +991,16 @@ const handleSave = async () => {
|
|||
|
||||
if (res.code === 1) {
|
||||
message.success('更新成功');
|
||||
// 更新本地数据
|
||||
Object.assign(selectedUser.value, editForm.value);
|
||||
// 刷新表格数据
|
||||
await getUserList();
|
||||
isEditMode.value = false;
|
||||
} else {
|
||||
message.error(res.message || '更新失败');
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error:any) {
|
||||
console.error('更新失败:', error);
|
||||
message.error('更新失败,请检查网络');
|
||||
message.error(error.response?.data?.message || '更新失败,请检查网络');
|
||||
}
|
||||
};
|
||||
|
||||
//新增用户
|
||||
const openUser = ref<boolean>(false);
|
||||
const formRef = ref();
|
||||
|
@ -993,4 +1261,56 @@ const reset = () => {
|
|||
.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;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user