backend/src/views/apps/babyhealth/babys/components/edit.vue

427 lines
11 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-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑宝贝' : '添加宝贝'"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="dialog-content">
<!-- 头像上传区域 -->
<div class="avatar-section">
<el-upload
:show-file-list="false"
:before-upload="beforeAvatarUpload"
:http-request="uploadFile"
class="avatar-uploader"
>
<el-image
v-if="formData.avatar"
:src="getEnvUrl(formData.avatar)"
class="avatar-preview"
fit="cover"
:preview-src-list="[getEnvUrl(formData.avatar)]"
:initial-index="0"
/>
<div v-else class="avatar-placeholder">
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<span class="avatar-tip">点击上传头像</span>
</div>
</el-upload>
</div>
<!-- 表单区域 -->
<el-form ref="editFormRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="小名" prop="nickname">
<el-input v-model="formData.nickname" placeholder="请输入小名" clearable />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-radio-group v-model="formData.sex">
<el-radio :value="1">男</el-radio>
<el-radio :value="2">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="出生日期" prop="birth">
<el-date-picker
v-model="formData.birth"
type="date"
placeholder="选择出生日期"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="身高" prop="height">
<el-input
v-model="formData.height"
:min="0"
:max="200"
:precision="1"
:step="0.5"
controls-position="right"
style="width: 100%"
>
<template #append>CM</template>
</el-input>
</el-form-item>
<el-form-item label="体重" prop="weight">
<el-input
v-model="formData.weight"
:min="0"
:max="50"
:precision="2"
:step="0.01"
controls-position="right"
style="width: 100%"
>
<template #append>KG</template>
</el-input>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :value="1">正常</el-radio>
<el-radio :value="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import { createBaby, editBaby } from '@/api/babyhealth';
import { uploadAvatar } from '@/api/file';
interface BabyFormData {
id: number;
name: string;
nickname: string;
sex: number;
birth: string;
height: number;
weight: number;
avatar: string;
status: number;
}
const props = defineProps<{
visible: boolean;
editData?: BabyFormData;
}>();
const emit = defineEmits<{
'update:visible': [value: boolean];
success: [];
}>();
const dialogVisible = ref(false);
const loading = ref(false);
const editFormRef = ref<FormInstance>();
const fileList = ref<any[]>([]);
const formData = reactive<BabyFormData>({
id: 0,
name: '',
nickname: '',
sex: 1,
birth: '',
height: 0,
weight: 0,
avatar: '',
status: 1
});
const isEdit = ref(false);
const formRules: FormRules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
sex: [{ required: true, message: '请选择性别', trigger: 'change' }],
birth: [{ required: true, message: '请选择出生日期', trigger: 'change' }]
};
// 拼接接口路径
const getEnvUrl = (path: string) => {
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
return `${API_BASE_URL}${path}`;
};
// 重置表单
const resetForm = () => {
Object.assign(formData, {
id: 0,
name: '',
nickname: '',
sex: 1,
birth: '',
height: 0,
weight: 0,
avatar: '',
status: 1
});
fileList.value = [];
};
// 关闭对话框
const handleClose = () => {
dialogVisible.value = false;
resetForm();
};
// 监听 visible 变化
watch(() => props.visible, (val) => {
dialogVisible.value = val;
});
// 监听 dialogVisible 变化
watch(dialogVisible, (val) => {
emit('update:visible', val);
});
// 监听 editData 变化
watch(() => props.editData, (data) => {
if (data) {
isEdit.value = true;
Object.assign(formData, data);
if (data.avatar) {
fileList.value = [
{
name: 'avatar',
url: data.avatar
}
];
} else {
fileList.value = [];
}
} else {
isEdit.value = false;
resetForm();
}
}, { immediate: true });
// 头像上传前校验
const beforeAvatarUpload = (file: any) => {
const isImage = file.type.startsWith('image/');
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isImage) {
ElMessage.error('仅支持图片格式');
return false;
}
if (!isLt5M) {
ElMessage.error('图片大小不能超过5MB');
return false;
}
return true;
};
// 上传文件到服务器
const uploadFile = async (options: any) => {
const { file } = options;
const uploadFormData = new FormData();
uploadFormData.append('file', file);
try {
// 使用uploadAvatar接口上传文件
const response = await uploadAvatar(uploadFormData, { cate: 'baby_avatar' });
if (response.code === 200 || response.code === 201) {
// 更新宝贝头像
const avatarUrl = response.data.url || response.data.path || '';
formData.avatar = avatarUrl;
if (response.code === 201) {
ElMessage.info("文件已存在,使用已有图片");
} else {
ElMessage.success("头像上传成功");
}
} else {
ElMessage.error(response.msg || "上传失败");
}
} catch (error) {
console.error('上传失败:', error);
ElMessage.error("上传失败,请重试");
}
};
// 上传相关
const handleUploadChange = (file: any) => {
if (file.raw) {
const isImage = file.raw.type.startsWith('image/');
const isLt5M = file.raw.size / 1024 / 1024 < 5;
if (!isImage || !isLt5M) {
fileList.value = [];
ElMessage.error(isImage ? '图片大小不能超过5MB' : '仅支持图片格式');
return;
}
}
};
const handleRemove = (file: any) => {
fileList.value = [];
formData.avatar = '';
};
// 提交表单
const handleSubmit = async () => {
if (!editFormRef.value) return;
await editFormRef.value.validate(async (valid) => {
if (valid) {
loading.value = true;
try {
// 如果有新上传的图片,先上传
if (fileList.value.length > 0 && fileList.value[0].raw) {
const uploadFormData = new FormData();
uploadFormData.append('file', fileList.value[0].raw);
uploadFormData.append('cate', 'baby');
const uploadRes = await uploadAvatar(uploadFormData);
if ((uploadRes.code === 200 || uploadRes.code === 201) && uploadRes.data?.url) {
formData.avatar = uploadRes.data.url;
} else {
ElMessage.error('图片上传失败');
return;
}
}
let res;
if (formData.id) {
// 编辑 - 使用普通对象PUT请求
const submitData: Record<string, any> = {};
Object.keys(formData).forEach(key => {
if (key !== 'id' && formData[key as keyof BabyFormData] !== undefined && formData[key as keyof BabyFormData] !== null) {
submitData[key] = formData[key as keyof BabyFormData];
}
});
res = await editBaby(formData.id, submitData);
} else {
// 新增 - 使用 FormData
const submitData = new FormData();
Object.keys(formData).forEach(key => {
if (key !== 'id' && formData[key as keyof BabyFormData] !== undefined && formData[key as keyof BabyFormData] !== null) {
submitData.append(key, String(formData[key as keyof BabyFormData]));
}
});
res = await createBaby(submitData);
}
if (res.code === 200) {
ElMessage.success(formData.id ? '编辑成功' : '添加成功');
dialogVisible.value = false;
resetForm();
emit('success'); // 通知父组件刷新列表
} else {
ElMessage.error(res.msg || (formData.id ? '编辑失败' : '添加失败'));
}
} catch (error) {
console.error('提交失败:', error);
ElMessage.error('提交失败');
} finally {
loading.value = false;
}
}
});
};
</script>
<style scoped lang="scss">
.dialog-content {
display: flex;
flex-direction: column;
}
.avatar-section {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 24px;
padding-bottom: 24px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.avatar-preview {
width: 120px;
height: 120px;
border-radius: 8px;
object-fit: cover;
display: block;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
:deep(img) {
width: 100%;
height: 100%;
border-radius: 8px;
}
}
.avatar-uploader {
:deep(.el-upload) {
border: 2px dashed var(--el-border-color);
border-radius: 8px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
width: 120px;
height: 120px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--el-fill-color-light);
&:hover {
border-color: var(--el-color-primary);
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
}
}
}
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 16px;
text-align: center;
.avatar-uploader-icon {
font-size: 32px;
color: var(--el-text-color-placeholder);
margin-bottom: 8px;
}
.avatar-tip {
font-size: 12px;
color: var(--el-text-color-placeholder);
}
}
:deep(.el-upload-list__item-thumbnail) {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>