| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- <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>
|