|
|
@@ -0,0 +1,431 @@
|
|
|
+<template>
|
|
|
+ <div class="accident-create-container">
|
|
|
+ <el-form ref="formRef" :model="formData" :rules="rules" class="form-wrap">
|
|
|
+ <div class="personnel-section">
|
|
|
+ <div v-for="(person, index) in personnelList" :key="index" class="personnel-item">
|
|
|
+ <el-row :gutter="40">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-form-item :prop="`accidentPersonnelInfoList.${index}.carNum`" :rules="carNumRules" label="车牌号:">
|
|
|
+ <el-input
|
|
|
+ v-model="person.carNum"
|
|
|
+ placeholder="请输入违规车辆车牌号码"
|
|
|
+ maxlength="8"
|
|
|
+ show-word-limit
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="10">
|
|
|
+ <el-form-item
|
|
|
+ :prop="`accidentPersonnelInfoList.${index}.accidentPersonnel`"
|
|
|
+ :rules="personRules"
|
|
|
+ label="事故人员:"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="person.accidentPersonnel"
|
|
|
+ placeholder="请输入事故人员姓名"
|
|
|
+ maxlength="20"
|
|
|
+ show-word-limit
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-form-item
|
|
|
+ :prop="`accidentPersonnelInfoList.${index}.phoneNum`"
|
|
|
+ :rules="phoneRules"
|
|
|
+ label="联系方式:"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="person.phoneNum"
|
|
|
+ placeholder="请输入联系方式"
|
|
|
+ maxlength="11"
|
|
|
+ show-word-limit
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="2" class="delete-col">
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ link
|
|
|
+ :icon="Delete"
|
|
|
+ :disabled="personnelList.length === 1"
|
|
|
+ @click="removePersonnel(index)"
|
|
|
+ >删除</el-button
|
|
|
+ >
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ <el-button type="primary" :icon="Plus" :disabled="personnelList.length >= 10" @click="addPersonnel">
|
|
|
+ 新增
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-row :gutter="40" class="basic-info">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="事故地点:" prop="trafficAccidentRecord.accidentLocation">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.trafficAccidentRecord.accidentLocation"
|
|
|
+ placeholder="请输入事故发生地点"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="事故时间:" prop="trafficAccidentRecord.accidentTime">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="formData.trafficAccidentRecord.accidentTime"
|
|
|
+ type="datetime"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ placeholder="请选择事故发生时间"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled-date="disabledDate"
|
|
|
+ :disabled-time="disabledTime"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-form-item label="事故描述:" prop="trafficAccidentRecord.accidentDescription">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.trafficAccidentRecord.accidentDescription"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入事故描述"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="事故图片:" label-width="92.54px">
|
|
|
+ <UploadImages
|
|
|
+ ref="uploadImagesRef"
|
|
|
+ :maxCount="5"
|
|
|
+ :image-list="recordImageList"
|
|
|
+ @upload-success="handleUploadChange"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注:" label-width="92.54px">
|
|
|
+ <el-input v-model="formData.trafficAccidentRecord.remark" placeholder="请输入备注" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="创建人:" label-width="92.54px">
|
|
|
+ <el-input v-model="formData.trafficAccidentRecord.createdByName" placeholder="创建人" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref, reactive, onMounted, computed } from 'vue';
|
|
|
+ import { useRoute, onBeforeRouteLeave } from 'vue-router';
|
|
|
+ import { ElForm } from 'element-plus';
|
|
|
+ import { Delete, Plus } from '@element-plus/icons-vue';
|
|
|
+ import UploadImages from '@/views/disaster/disaster-control/src/components/UploadImages.vue';
|
|
|
+ import { useUserInfoHook } from '@/views/disaster/hooks';
|
|
|
+ import { ImageItem } from '@/types/disaster-control';
|
|
|
+ import { UPLOAD_BIZ_TYPE, uploadFileApi } from '@/api/minio';
|
|
|
+ import { AccidentPersonnelInfoStruct, AddAccidentInfoStruct, getAccidentInfoDetail } from '@/api/traffic-accident';
|
|
|
+ import { msgConfirm } from '@/utils/element-plus/messageBox';
|
|
|
+
|
|
|
+ const { realname } = useUserInfoHook();
|
|
|
+
|
|
|
+ const route = useRoute();
|
|
|
+ const operate = route.query.operate;
|
|
|
+ const id = route.query.id;
|
|
|
+
|
|
|
+ const formRef = ref<InstanceType<typeof ElForm>>();
|
|
|
+
|
|
|
+ const formData = ref<AddAccidentInfoStruct>({
|
|
|
+ trafficAccidentRecord: {
|
|
|
+ accidentImages: '',
|
|
|
+ accidentLocation: '',
|
|
|
+ accidentTime: '',
|
|
|
+ accidentDescription: '',
|
|
|
+ remark: '',
|
|
|
+ createdByName: '',
|
|
|
+ },
|
|
|
+ accidentPersonnelInfoList: [{ carNum: '', accidentPersonnel: '', phoneNum: '' }],
|
|
|
+ });
|
|
|
+
|
|
|
+ const personnelList = ref<AccidentPersonnelInfoStruct[]>([]);
|
|
|
+
|
|
|
+ // 保存初始表单数据用于比较
|
|
|
+ const initialFormData = ref<AddAccidentInfoStruct>({
|
|
|
+ trafficAccidentRecord: {
|
|
|
+ accidentImages: '',
|
|
|
+ accidentLocation: '',
|
|
|
+ accidentTime: '',
|
|
|
+ accidentDescription: '',
|
|
|
+ remark: '',
|
|
|
+ createdByName: '',
|
|
|
+ },
|
|
|
+ accidentPersonnelInfoList: [],
|
|
|
+ });
|
|
|
+ const initialPersonnelList = ref<AccidentPersonnelInfoStruct[]>([]);
|
|
|
+ const initialImagesString = ref<string>('');
|
|
|
+
|
|
|
+ const rules = reactive({
|
|
|
+ 'trafficAccidentRecord.accidentLocation': [{ required: true, message: '请输入事故地点', trigger: 'blur' }],
|
|
|
+ 'trafficAccidentRecord.accidentTime': [{ required: true, message: '请选择事故时间', trigger: 'change' }],
|
|
|
+ 'trafficAccidentRecord.accidentDescription': [{ required: true, message: '请输入事故描述', trigger: 'blur' }],
|
|
|
+ });
|
|
|
+
|
|
|
+ const carNumRules = [
|
|
|
+ {
|
|
|
+ validator: (_: unknown, value: string, callback: (err?: Error) => void) => {
|
|
|
+ if (!value) return callback();
|
|
|
+ if (!/^.{0,8}$/.test(value)) return callback(new Error('车牌号格式错误'));
|
|
|
+ callback();
|
|
|
+ },
|
|
|
+ trigger: ['blur', 'change'],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ const personRules = [
|
|
|
+ { required: true, message: '请输入事故人员姓名', trigger: 'blur' },
|
|
|
+ { min: 1, max: 20, message: '最多20个字符', trigger: 'blur' },
|
|
|
+ ];
|
|
|
+ const phoneRules = [
|
|
|
+ {
|
|
|
+ validator: (_: unknown, value: string, callback: (err?: Error) => void) => {
|
|
|
+ if (!value) return callback();
|
|
|
+ if (!/^1\d{10}$/.test(value)) return callback(new Error('联系方式非11位数字'));
|
|
|
+ callback();
|
|
|
+ },
|
|
|
+ trigger: ['blur', 'change'],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 新增
|
|
|
+ const addPersonnel = () => {
|
|
|
+ if (personnelList.value.length >= 10) return;
|
|
|
+ personnelList.value.push({ carNum: '', accidentPersonnel: '', phoneNum: '' });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 删除
|
|
|
+ const removePersonnel = (index: number) => {
|
|
|
+ if (personnelList.value.length === 1) return;
|
|
|
+ personnelList.value.splice(index, 1);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 禁用未来日期
|
|
|
+ const disabledDate = (time: Date) => {
|
|
|
+ return time.getTime() > Date.now();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 禁用未来时间
|
|
|
+ const disabledTime = (date: Date) => {
|
|
|
+ const now = new Date();
|
|
|
+ if (date.toDateString() === now.toDateString()) {
|
|
|
+ return {
|
|
|
+ disabledHours: () => {
|
|
|
+ const hours: number[] = [];
|
|
|
+ for (let i = now.getHours() + 1; i < 24; i++) {
|
|
|
+ hours.push(i);
|
|
|
+ }
|
|
|
+ return hours;
|
|
|
+ },
|
|
|
+ disabledMinutes: (hour: number) => {
|
|
|
+ if (hour === now.getHours()) {
|
|
|
+ const minutes: number[] = [];
|
|
|
+ for (let i = now.getMinutes() + 1; i < 60; i++) {
|
|
|
+ minutes.push(i);
|
|
|
+ }
|
|
|
+ return minutes;
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+ },
|
|
|
+ disabledSeconds: (hour: number, minute: number) => {
|
|
|
+ if (hour === now.getHours() && minute === now.getMinutes()) {
|
|
|
+ const seconds: number[] = [];
|
|
|
+ for (let i = now.getSeconds() + 1; i < 60; i++) {
|
|
|
+ seconds.push(i);
|
|
|
+ }
|
|
|
+ return seconds;
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {};
|
|
|
+ };
|
|
|
+
|
|
|
+ // 事故图片
|
|
|
+ const uploadImagesRef = ref<InstanceType<typeof UploadImages>>();
|
|
|
+ const uploadImages = ref<ImageItem[]>([]);
|
|
|
+ const imagesString = ref<string>('');
|
|
|
+
|
|
|
+ const recordImageList = computed(() => {
|
|
|
+ if (!formData.value.trafficAccidentRecord.accidentImages) return [];
|
|
|
+ return JSON.parse(formData.value.trafficAccidentRecord.accidentImages);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 格式化事故图片
|
|
|
+ const formatImageList = async (file: File) => {
|
|
|
+ if (!file) return file;
|
|
|
+ const fileName = file.name;
|
|
|
+ const res = await uploadFileApi({ bizType: UPLOAD_BIZ_TYPE.ATTACHMENT, fileName, file });
|
|
|
+ return '"' + res.url + '"';
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleUploadChange = async () => {
|
|
|
+ uploadImages.value = uploadImagesRef.value!.getUploadedImages();
|
|
|
+ const images = await Promise.all(
|
|
|
+ (uploadImages.value || []).map((item) => {
|
|
|
+ if (!item.file && item.url) {
|
|
|
+ return '"' + item.url + '"';
|
|
|
+ } else {
|
|
|
+ return formatImageList(item.file!);
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ imagesString.value = '[' + images.toString() + ']';
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检测表单是否有修改
|
|
|
+ const hasFormChanged = () => {
|
|
|
+ // 比较事故记录数据
|
|
|
+ const currentRecord = formData.value.trafficAccidentRecord;
|
|
|
+ const initialRecord = initialFormData.value.trafficAccidentRecord;
|
|
|
+
|
|
|
+ if (
|
|
|
+ currentRecord.accidentLocation !== initialRecord.accidentLocation ||
|
|
|
+ currentRecord.accidentTime !== initialRecord.accidentTime ||
|
|
|
+ currentRecord.accidentDescription !== initialRecord.accidentDescription ||
|
|
|
+ currentRecord.remark !== initialRecord.remark ||
|
|
|
+ imagesString.value !== initialImagesString.value
|
|
|
+ ) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 比较人员信息数据
|
|
|
+ if (personnelList.value.length !== initialPersonnelList.value.length) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (let i = 0; i < personnelList.value.length; i++) {
|
|
|
+ const current = personnelList.value[i];
|
|
|
+ const initial = initialPersonnelList.value[i];
|
|
|
+ if (
|
|
|
+ current.carNum !== initial.carNum ||
|
|
|
+ current.accidentPersonnel !== initial.accidentPersonnel ||
|
|
|
+ current.phoneNum !== initial.phoneNum
|
|
|
+ ) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 保存初始数据
|
|
|
+ const saveInitialData = () => {
|
|
|
+ initialFormData.value = JSON.parse(JSON.stringify(formData.value));
|
|
|
+ initialPersonnelList.value = JSON.parse(JSON.stringify(personnelList.value));
|
|
|
+ initialImagesString.value = imagesString.value;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 重置表单状态(提交成功后调用)
|
|
|
+ const resetFormState = () => {
|
|
|
+ saveInitialData();
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleValidate = async () => {
|
|
|
+ const form = formRef.value;
|
|
|
+ if (!form) return false;
|
|
|
+ try {
|
|
|
+ await form.validate();
|
|
|
+ return true;
|
|
|
+ } catch (_) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const getFormData = (): AddAccidentInfoStruct => {
|
|
|
+ formData.value.trafficAccidentRecord.accidentImages = imagesString.value;
|
|
|
+ const payload: AddAccidentInfoStruct = {
|
|
|
+ trafficAccidentRecord: formData.value.trafficAccidentRecord,
|
|
|
+ accidentPersonnelInfoList: personnelList.value.map(({ carNum, accidentPersonnel, phoneNum }) => ({
|
|
|
+ carNum,
|
|
|
+ accidentPersonnel,
|
|
|
+ phoneNum,
|
|
|
+ })),
|
|
|
+ } as unknown as AddAccidentInfoStruct;
|
|
|
+ return payload;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 路由守卫 - 离开页面前的确认
|
|
|
+ onBeforeRouteLeave((to, from, next) => {
|
|
|
+ const hasChange = hasFormChanged();
|
|
|
+ if (!hasChange) {
|
|
|
+ next();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ msgConfirm('当前页面存在修改,是否确认离开当前页面?', '提示', {
|
|
|
+ confirmButtonText: '确认',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ customClass: 'customMessageBox--warning',
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ next();
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ next(false);
|
|
|
+ });
|
|
|
+ }, 200);
|
|
|
+ });
|
|
|
+
|
|
|
+ defineExpose({ handleValidate, getFormData, resetFormState });
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ formData.value.trafficAccidentRecord.createdByName = realname;
|
|
|
+ personnelList.value = formData.value.accidentPersonnelInfoList;
|
|
|
+
|
|
|
+ if (operate === 'edit') {
|
|
|
+ getAccidentInfoDetail({ accidentRecordId: Number(id) }).then((res) => {
|
|
|
+ formData.value = res;
|
|
|
+ personnelList.value = res.accidentPersonnelInfoList;
|
|
|
+ imagesString.value = res.trafficAccidentRecord.accidentImages;
|
|
|
+
|
|
|
+ // 数据加载完成后保存初始数据
|
|
|
+ setTimeout(() => {
|
|
|
+ saveInitialData();
|
|
|
+ }, 100);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 创建模式,立即保存初始数据
|
|
|
+ setTimeout(() => {
|
|
|
+ saveInitialData();
|
|
|
+ }, 100);
|
|
|
+ }
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .accident-create-container {
|
|
|
+ .personnel-section {
|
|
|
+ background: #f5f9ff;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 20px 34px;
|
|
|
+ margin-bottom: 30px;
|
|
|
+
|
|
|
+ .personnel-item {
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ :deep(.el-form-item) {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-row {
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .delete-col {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-end;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|