backend/src/views/apps/babyhealth/users/components/userEdit.vue

522 lines
14 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>
<el-drawer v-model="visible" :title="dialogTitle" width="500px">
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<div class="form-title">账号信息</div>
<!-- 账号 -->
<el-form-item label="账号">
<el-input v-model="form.account" :disabled="!isAdd" placeholder="请输入账号" />
</el-form-item>
<!-- 密码 -->
<el-form-item label="密码" prop="password" v-if="isAdd">
<el-input v-model="form.password" type="password" autocomplete="new-password" show-password
:placeholder="isAdd ? '请输入密码至少6位' : '留空则不修改密码'" />
</el-form-item>
<!-- 确认密码 -->
<el-form-item label="确认密码" prop="confirmPassword" v-if="isAdd">
<el-input v-model="form.confirmPassword" type="password" autocomplete="new-password" show-password
:placeholder="isAdd ? '请再次输入密码' : '留空则不修改密码'" />
</el-form-item>
<el-divider></el-divider>
<div class="form-title">个人信息</div>
<!-- 头像 -->
<el-form-item label="头像">
<el-upload class="avatar-uploader" :show-file-list="false" :action="uploadUrl" :headers="uploadHeaders"
:on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<img v-if="form.avatar" :src="getAvatarUrl(form.avatar)" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<!-- 裁剪对话框 -->
<CropImage ref="cropImageRef" @confirm="handleCropConfirm" />
<!-- 姓名 -->
<el-form-item label="姓名">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
<!-- 性别 -->
<el-form-item label="性别">
<el-radio-group v-model="form.sex" placeholder="请选择性别">
<el-radio-button label="未知" :value="0" />
<el-radio-button label="男" :value="1" />
<el-radio-button label="女" :value="2" />
</el-radio-group>
</el-form-item>
<!-- 生日 -->
<el-form-item label="生日">
<el-date-picker v-model="form.birth" type="date" placeholder="请选择日期" style="width: 100%"
value-format="YYYY-MM-DD" />
</el-form-item>
<!-- 电话 -->
<el-form-item label="电话">
<el-input v-model="form.phone" placeholder="请输入电话" />
</el-form-item>
<!-- 邮箱 -->
<el-form-item label="邮箱">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
<!-- 状态 -->
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-form>
<!-- 对话框脚部 -->
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit">保存</el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { ElMessage } from "element-plus";
import { Plus } from '@element-plus/icons-vue';
import type { UploadProps, UploadRequestOptions } from 'element-plus';
import { createUser, updateUser, getUserDetail } from "@/api/babyhealth";
import { uploadAvatar } from '@/api/file';
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
isEdit: {
type: Boolean,
default: false,
},
statusDict: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(["update:modelValue", "submit", "close"]);
const visible = ref(false);
const formRef = ref<any>(null);
const isAdd = ref(false);
const form = ref<any>({
id: null,
account: "",
password: "",
confirmPassword: "",
name: "",
sex: 0,
birth: "",
phone: "",
email: "",
status: 1,
avatar: "",
});
const dialogTitle = computed(() => {
return isAdd.value ? "添加用户" : "编辑用户";
});
// 密码验证规则
const validatePassword = (rule: any, value: any, callback: any) => {
if (isAdd.value) {
// 新增用户时,密码必填
if (!value) {
callback(new Error("请输入密码"));
return;
}
if (value.length < 6) {
callback(new Error("密码长度不能少于6位"));
return;
}
} else {
// 编辑用户时,如果填写了密码,则必须符合规则
if (value && value.length < 6) {
callback(new Error("密码长度不能少于6位"));
return;
}
}
callback();
};
// 确认密码验证规则
const validateConfirmPassword = (rule: any, value: any, callback: any) => {
if (isAdd.value) {
// 新增用户时,确认密码必填
if (!value) {
callback(new Error("请再次输入密码"));
return;
}
if (value !== form.value.password) {
callback(new Error("两次输入的密码不一致"));
return;
}
} else {
// 编辑用户时,如果填写了密码,则确认密码必须一致
if (form.value.password && value !== form.value.password) {
callback(new Error("两次输入的密码不一致"));
return;
}
// 如果填写了确认密码但没填密码,提示错误
if (value && !form.value.password) {
callback(new Error("请先输入密码"));
return;
}
}
callback();
};
// 表单验证规则
const rules = {
account: [
{ required: true, message: "请输入账号", trigger: "blur" },
{ min: 3, max: 20, message: "账号长度在 3 到 20 个字符", trigger: "blur" },
],
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
password: [{ validator: validatePassword, trigger: "blur" }],
confirmPassword: [{ validator: validateConfirmPassword, trigger: "blur" }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: "blur" }],
};
// 监听 modelValue
watch(
() => props.modelValue,
(newVal) => {
visible.value = newVal;
}
);
// 监听 visible 变化
watch(visible, (newVal) => {
if (!newVal) {
emit("update:modelValue", false);
}
});
// 监听 statusDict 变化,用于调试
watch(
() => props.statusDict,
(newVal) => { },
{ immediate: true, deep: true }
);
// 上传配置
const uploadUrl = import.meta.env.VITE_API_BASE_URL + "/admin/upload";
const uploadHeaders = {
Authorization: "Bearer " + localStorage.getItem("token"),
};
// 拼接头像完整URL
const getAvatarUrl = (url: string) => {
if (!url) return '';
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
return `${import.meta.env.VITE_API_BASE_URL}${url}`;
};
// 头像上传前校验 - 只校验格式,打开裁剪对话框
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
const isImage = rawFile.type.startsWith('image/');
const isLt2M = rawFile.size / 1024 / 1024 < 2;
if (!isImage) {
ElMessage.error('只能上传图片文件!');
return false;
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB!');
return false;
}
return true;
};
// 上传文件到服务器(裁剪后调用)
const uploadFile = async (file: File) => {
const formData = new FormData();
formData.append('file', file);
try {
// 使用uploadAvatar接口上传文件
const response = await uploadAvatar(formData, { cate: 'user_avatar' });
if (response.code === 200 || response.code === 201) {
// 更新用户头像
form.value.avatar = response.data.url || response.data.path || '';
ElMessage.success("头像上传成功");
} else {
ElMessage.error(response.msg || "上传失败");
}
} catch (error) {
console.error('上传失败:', error);
ElMessage.error("上传失败,请重试");
}
};
// 裁剪确认后的处理
const handleCropConfirm = async (file: File) => {
await uploadFile(file);
};
// 头像上传成功
const handleAvatarSuccess = (response: any) => {
if (response.code === 200 || response.code === 201) {
form.value.avatar = response.data.url || response.data.path || '';
ElMessage.success('头像上传成功');
} else {
ElMessage.error(response.msg || '上传失败');
}
};
const loadUserData = async (user: any) => {
try {
// 处理两种调用方式:传递用户对象或用户 ID
const userId = typeof user === "number" ? user : user?.id || user?.userId;
if (!userId) {
throw new Error("未提供有效的用户 ID");
}
const res = await getUserDetail(userId);
const data = res.data || res;
// 确保 sex 和 status 都是数字类型
const sexValue =
data.sex !== undefined && data.sex !== null
? Number(data.sex)
: 1;
const statusValue =
data.status !== undefined && data.status !== null
? Number(data.status)
: 1;
form.value = {
id: data.id,
account: data.account,
password: "",
confirmPassword: "",
name: data.name,
sex: sexValue,
birth: data.birth,
phone: data.phone,
email: data.email,
status: statusValue,
avatar: data.avatar || "",
};
} catch (e: any) {
console.error("Failed to load user data:", e);
const errorMsg = e?.response?.data?.message || e?.message || "加载用户失败";
ElMessage.error(errorMsg);
throw e;
}
};
const handleCancel = () => {
visible.value = false;
};
const handleSubmit = async () => {
// 表单验证
if (!formRef.value) {
return;
}
try {
await formRef.value.validate();
} catch (error) {
ElMessage.warning("请检查表单填写是否正确");
return;
}
// 验证密码一致性
if (isAdd.value) {
// 新增用户时,密码必填
if (!form.value.password) {
ElMessage.error("请输入密码");
return;
}
if (form.value.password !== form.value.confirmPassword) {
ElMessage.error("两次输入的密码不一致");
return;
}
} else {
// 编辑用户时,如果填写了密码,则必须填写确认密码且一致
if (form.value.password) {
if (form.value.password !== form.value.confirmPassword) {
ElMessage.error("两次输入的密码不一致");
return;
}
}
}
try {
if (isAdd.value) {
// 新增用户
const submitData: any = {
account: form.value.account,
password: form.value.password || '',
name: form.value.name,
sex: form.value.sex,
birth: form.value.birth,
phone: form.value.phone,
email: form.value.email,
status: form.value.status,
avatar: form.value.avatar,
};
await createUser(submitData);
ElMessage.success("添加成功");
} else {
// 编辑用户
if (!form.value.id || form.value.id === 0) {
ElMessage.error("用户ID不能为空");
return;
}
const submitData: any = {
id: form.value.id,
account: form.value.account,
name: form.value.name,
sex: form.value.sex,
birth: form.value.birth,
phone: form.value.phone,
email: form.value.email,
status: form.value.status,
avatar: form.value.avatar,
};
// 只有在填写了密码时才添加到提交数据中
if (form.value.password) {
submitData.password = form.value.password;
}
await updateUser(form.value.id, submitData);
ElMessage.success("更新成功");
}
visible.value = false;
emit("submit");
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "操作失败";
ElMessage.error(errorMsg);
}
};
// 暴露方法给父组件
defineExpose({
loadUserData,
openAdd: () => {
isAdd.value = true;
form.value = {
id: 0,
account: "",
password: "",
confirmPassword: "",
name: "",
sex: 0,
birth: "",
phone: "",
email: "",
status: 1,
avatar: "",
};
visible.value = true;
// 清除表单验证
if (formRef.value) {
formRef.value.clearValidate();
}
},
openEdit: (user: any) => {
isAdd.value = false;
visible.value = true;
// 清除表单验证
if (formRef.value) {
formRef.value.clearValidate();
}
// 异步加载用户详细信息
loadUserData(user);
},
open: (user?: any) => {
if (user) {
isAdd.value = false;
loadUserData(user);
} else {
isAdd.value = true;
form.value = {
id: 0,
account: "",
password: "",
confirmPassword: "",
name: "",
sex: 0,
birth: "",
phone: "",
email: "",
status: 1,
avatar: "",
};
}
visible.value = true;
// 清除表单验证
if (formRef.value) {
formRef.value.clearValidate();
}
},
});
</script>
<style lang="less" scoped>
.form-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 20px;
}
.avatar-uploader {
.avatar {
width: 100px;
height: 100px;
border-radius: 6px;
object-fit: cover;
display: block;
}
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
&:hover {
border-color: var(--el-color-primary);
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>