Files
sunProduct/src/views/system/HandleLog/addHandleLog.vue
2026-07-21 17:32:22 +08:00

429 lines
13 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="ResidentMainBox p-2 w-full">
<PageHeader></PageHeader>
<div class="ResidentMainBody">
<el-card>
<template #header>
<div class="title">基本信息</div>
</template>
<div class="addBuildingFormBody">
<div class="formbox">
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col>
<el-form-item label="办事内容" prop="content">
<editor
@update:model-value="handleContent"
:model-value="form.content"
:height="500"
:min-height="300"
:read-only="false"
:file-size="5"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="求助人姓名" prop="seekName">
<el-input v-model.trim="form.seekName" placeholder="请输入求助人姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系方式" prop="phone">
<el-input v-model.trim="form.phone" placeholder="请输入联系方式" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="办事时间" prop="handleTime">
<el-date-picker
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
v-model="form.handleTime"
type="datetime"
placeholder="请选择"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="办事地点" prop="handlePosition">
<el-input v-model.trim="form.handlePosition" placeholder="请输入办事地点" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="附属材料">
<el-upload
class="upload-demo"
:file-list="fileList"
:action="uploadUrl"
:multiple="true"
:auto-upload="false"
ref="uploadRef"
:show-file-list="true"
name="files"
:headers="headerToken()"
:on-remove="handleAvatarRemove"
:on-change="handleProgress"
:before-upload="beforeAvatarUpload"
accept=".doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.gif,.bmp,.webp"
>
<div class="uploadBtn flex flex-items-center">
<el-icon><Upload /></el-icon>
<span>上传文件</span>
</div>
<template #tip>仅支持doc/docx/xls/xlsx/jpg/png/gif等</template>
</el-upload>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="handleBack">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import editor from '@/components/Editor/index.vue';
import { ref } from 'vue';
import PageHeader from '@/components/Pageheader/index.vue';
import { addHandleLog, getHandleLog, updateHandleLog, uploadAttachments } from '@/api/system/HandleLog';
import { UploadFiles, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus';
import { useUserStore } from '@/store/modules/user';
import { ElMessage } from 'element-plus';
import { Upload } from '@element-plus/icons-vue'; // 确保引入图标
const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/handleLog/uploadAttachments';
const route = useRoute();
const router = useRouter();
const formRef = ref();
const form = ref({
'id': null,
'content': '',
'seekName': '',
'phone': '',
'handleTime': '',
handleLogFileList: [],
'handlePosition': ''
});
const rules = {
content: [{ required: true, message: '请输入办事内容', trigger: 'blur' }],
seekName: [{ required: true, message: '请选择求助人', trigger: 'blur' }]
};
const userid = ref();
// ! 富文本 =============================================================================================================
function handleContent(val: string) {
form.value.content = val.trim();
}
// ! 富文本 =============================================================================================================
interface CustomUploadFile extends UploadUserFile {
ossId?: string;
}
const fileList = ref<CustomUploadFile[]>([]);
function headerToken() {
const useStore = useUserStore();
return {
Authorization: 'Bearer ' + useStore.token
};
}
const handleAvatarRemove: UploadProps['onRemove'] = (uploadFile: UploadUserFile, uploadFiles: UploadFiles) => {
fileList.value = uploadFiles;
};
// 定义大小限制常量 (单位: MB)
const MAX_SINGLE_FILE_SIZE = 10 * 1024 * 1024; // 10MB in bytes
const MAX_TOTAL_FILE_SIZE = 20 * 1024 * 1024; // 20MB in bytes
const uploadRef = ref();
// 添加一个锁,防止 on-change 递归调用导致重复提示
const isChecking = ref(false);
const handleProgress: UploadProps['onChange'] = (uploadFile: UploadUserFile, uploadFiles: UploadFiles) => {
// 如果正在校验中,直接返回,避免重复执行
if (!beforeAvatarUpload(uploadFile)) {
uploadRef.value.handleRemove(uploadFile);
return;
}
if (isChecking.value) return;
// 加锁
isChecking.value = true;
// 创建一个新数组来存储校验通过的文件
const validFiles: CustomUploadFile[] = [];
let newFilesTotalSize = 0;
let hasError = false;
let errorMsg = '';
// 1. 遍历所有文件,筛选出符合单文件大小要求的文件,并计算新文件总大小
for (const file of uploadFiles) {
// 只校验新上传的文件 (有 raw 属性)
if (file.raw) {
// 检查单个文件大小
if (file.raw.size > MAX_SINGLE_FILE_SIZE) {
// 如果发现单个文件超标,标记错误,并不将该文件加入 validFiles
if (!hasError) {
errorMsg = `文件 ${file.name} 大小超过 10MB,已自动移除`;
hasError = true;
}
continue;
}
newFilesTotalSize += file.raw.size;
}
// 将非超标文件(包括旧文件和未超标的新文件)暂时加入
console.log(file);
validFiles.push({
...file,
ossId: (file as CustomUploadFile).ossId ?? null
});
}
// 2. 如果存在单文件超标的情况,先处理这部分逻辑
if (hasError) {
ElMessage.error(errorMsg);
// 更新 fileList 为移除了超标文件的列表
fileList.value = validFiles;
return;
}
// 3. 检查新文件总大小是否超过 20MB
if (newFilesTotalSize > MAX_TOTAL_FILE_SIZE) {
ElMessage.error(`所有新上传文件的总大小不能超过 20MB,当前已选新文件总大小约为 ${(newFilesTotalSize / 1024 / 1024).toFixed(2)}MB`);
// 策略:移除最后添加的那个导致超标的文件
// 注意:这里需要找到最后添加的那个文件。uploadFile 参数就是当前触发的文件
// 但如果是一次性选择多个文件,uploadFile 可能是其中一个。
// 更稳妥的方式是:如果总和超标,则移除所有新文件中最后加入的那个,或者提示用户手动减少。
// 这里我们尝试移除最后加入的一个新文件
// 找出所有新文件
const newFiles = validFiles.filter((f) => f.raw);
if (newFiles.length > 0) {
// 移除最后一个新文件
const lastNewFile = newFiles[newFiles.length - 1];
const filteredFiles = validFiles.filter((f) => f.uid !== lastNewFile.uid);
fileList.value = filteredFiles;
} else {
// 理论上不会走到这里,因为如果没有新文件,总大小不会增加
fileList.value = validFiles;
}
return;
}
// 4. 校验全部通过,更新 fileList
// 只有当 validFiles 与当前 fileList 不同时才赋值,避免不必要的触发
if (validFiles.length !== fileList.value.length || validFiles.some((f, i) => f.uid !== fileList.value[i]?.uid)) {
fileList.value = validFiles;
}
// 解锁,允许下一次变更触发校验
// 使用 nextTick 或 setTimeout 确保 DOM 更新后再解锁,防止极快连续操作的问题
setTimeout(() => {
isChecking.value = false;
}, 100);
};
const blockedExts = ['.exe', '.bat', '.cmd', '.msi'];
const allowExts = [
// word
'.doc',
'.docx',
'.dot',
'.dotx',
// excel
'.xls',
'.xlsx',
'.xlsm',
'.xlsb',
// img
'.jpg',
'.jpeg',
'.png',
'.gif',
'.bmp',
'.webp',
// 专业/矢量图
'.psd',
'.ai',
'.svg',
'.tif',
'.tiff'
];
const beforeAvatarUpload = (rawFile: UploadUserFile) => {
const ext = rawFile.name.toLowerCase().slice(rawFile.name.lastIndexOf('.'));
if (blockedExts.includes(ext)) {
ElMessage.warning(`禁止上传 ${ext} 文件`);
return false;
}
if (!allowExts.includes(ext)) {
ElMessage.warning('只能上传word,excel,图片等文件');
return false;
}
return true;
};
// ! 初始 ====================================================================================================
function init() {
console.log('init');
getHandleLog(userid.value).then((res) => {
form.value = {
...form.value,
...res.data
};
fileList.value = res.data.handleLogFileList.map((item) => {
return {
name: item.fileName,
url: item.filePath,
ossId: item.ossId
};
});
});
}
if (route.query.id) {
userid.value = route.query.id;
init();
}
// 提交
const submitForm = async () => {
// 筛选出需要上传的新文件
const newFiles = fileList.value.filter((item) => item.raw);
const oldFiles = fileList.value
.filter((item) => !item.raw)
.map((item) => {
return {
fileName: item.name,
filePath: item.url,
ossId: item.ossId
};
});
const files = [...oldFiles];
console.log('oldFiles', files);
if (newFiles.length > 0) {
const formData = new FormData();
newFiles.forEach((item) => {
if (item.raw) {
formData.append('files', item.raw);
}
});
try {
const res = await uploadAttachments(formData);
if (res.data && res.data.success && res.data.success.length > 0) {
res.data.success.forEach((item) => {
const obj = {
fileName: item.fileName,
filePath: item.url,
ossId: item.ossId
};
files.push(obj);
});
}
} catch (error) {
console.error('上传失败', error);
ElMessage.error('文件上传失败');
return; // 上传失败则终止提交
}
}
form.value.handleLogFileList = files;
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (userid.value) {
updateHandleLog(form.value).then(() => {
ElMessage.success('编辑成功');
handleBack();
});
} else {
addHandleLog(form.value).then(() => {
ElMessage.success('添加成功');
handleBack();
});
}
}
});
};
// 返回
function handleBack() {
router.push({
path: '/repair/handlelog'
});
}
</script>
<style scoped lang="scss">
.ResidentMainBox {
display: flex;
flex-direction: column;
.ResidentMainBody {
margin-top: 20px;
flex: 1;
.formbox {
width: 1000px;
margin: 0 auto;
.upload-demo {
width: 100%;
min-width: 200px;
&:deep(.el-upload) {
width: 100%;
}
&:deep(.el-upload-list) {
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
}
.uploadBtn {
height: 30px;
line-height: 20px;
border-radius: 5px;
background-color: rgba(255, 255, 255, 1);
color: rgba(16, 16, 16, 1);
padding-left: 20px;
font-size: 14px;
border: 1px solid rgba(204, 204, 204, 1);
width: 100%;
border: 1px solid #ccc;
span {
margin-left: 10px;
}
}
}
:deep(.el-date-editor.el-input, .el-date-editor.el-input__wrapper) {
width: 380px;
}
&:deep(div.el-step__head.is-process) {
& > div.el-step__line {
width: 0;
border: 1px dashed gray;
background: transparent;
}
}
&:deep(.el-step.is-vertical .el-step__line) {
top: 20px;
}
&:deep(div.el-step__head.is-finish) {
& > div.el-step__line {
width: 0;
border: 1px solid var(--el-color-primary);
background: transparent;
}
}
}
}
</style>