|
|
@@ -0,0 +1,735 @@
|
|
|
+<template>
|
|
|
+ <div class="application-dialog-container">
|
|
|
+ <el-dialog :title="state.dialog.title" @close="onCancel" :close-on-click-modal="false" :destroy-on-close="true"
|
|
|
+ v-model="state.dialog.isShowDialog" width="800px">
|
|
|
+ <el-form ref="expertDialogFormRef" :model="state.form" :rules="rules" size="default" label-width="140px"
|
|
|
+ label-position="top">
|
|
|
+ <LaText size="16" type="important" bold class="mb16">基本信息</LaText>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="课题名称" prop="projectGroupId">
|
|
|
+ <el-select :disabled="state.dialog.type === 'view'" v-model="state.form.projectGroupId" placeholder="请选择">
|
|
|
+ <el-option v-for="item in projects" :key="item.id" :label="item.projectName" :value="item.id" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="姓名" prop="group">
|
|
|
+ <el-input v-if="state.dialog.type === 'add'" v-model="userInfos.nickName" disabled />
|
|
|
+ <el-input v-else v-model="state.form.userName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="部门" prop="deptName">
|
|
|
+ <el-input v-if="state.dialog.type === 'add'" v-model="userInfos.deptName" disabled />
|
|
|
+ <el-input v-else v-model="state.form.deptName" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="联系方式" prop="phone">
|
|
|
+ <el-input v-if="state.dialog.type === 'add'" v-model="userInfos.phone" disabled />
|
|
|
+ <el-input v-else v-model="state.form.phone" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <LaText class="mb16 mt20" size="16" type="important" bold>实验动物笼位预约信息</LaText>
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="笼位数量" prop="number">
|
|
|
+ <el-input-number :disabled="state.dialog.type === 'view'" v-model="state.form.number" style="width: 100%"
|
|
|
+ :min="1" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="开始使用时间" prop="startDate">
|
|
|
+ <el-date-picker :disabled="state.dialog.type === 'view'" v-model="state.form.startDate" type="date"
|
|
|
+ placeholder="请选择开始使用时间" clearable style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="动物类别" prop="categoryId">
|
|
|
+ <el-select :disabled="state.dialog.type === 'view'" v-model="state.form.categoryId" placeholder="请选择">
|
|
|
+ <el-option v-for="item in animalTypeList" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="品种品系" prop="variety">
|
|
|
+ <el-input :disabled="state.dialog.type === 'view'" v-model="state.form.variety" placeholder="请输入品种品系" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="饲养区域" prop="level">
|
|
|
+ <el-select :disabled="state.dialog.type === 'view'" v-model="state.form.level" placeholder="请选择">
|
|
|
+ <el-option v-for="item in LeavelList" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="周龄" prop="age">
|
|
|
+ <div style="display: flex; align-items: center; gap: 8px;">
|
|
|
+ <el-input-number :disabled="state.dialog.type === 'view'" v-model="state.form.age.min"
|
|
|
+ placeholder="最小周龄" style="width: 45%" :min="0" />
|
|
|
+ <span style="color: #999;">至</span>
|
|
|
+ <el-input-number :disabled="state.dialog.type === 'view'" v-model="state.form.age.max"
|
|
|
+ placeholder="最大周龄" style="width: 45%" :min="0" />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="体重" prop="weight">
|
|
|
+ <div style="display: flex; align-items: center; gap: 8px;">
|
|
|
+ <el-input-number :disabled="state.dialog.type === 'view'" v-model="state.form.weight.min"
|
|
|
+ placeholder="最小体重" style="width: 45%" :min="0" :precision="2" />
|
|
|
+ <span style="color: #999;">至</span>
|
|
|
+ <el-input-number :disabled="state.dialog.type === 'view'" v-model="state.form.weight.max"
|
|
|
+ placeholder="最大体重" style="width: 45%" :min="0" :precision="2" />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="饲养总天数" prop="feedingDay">
|
|
|
+ <el-input-number :disabled="state.dialog.type === 'view'" v-model="state.form.feedingDay"
|
|
|
+ style="width: 100%" :min="1" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="6" class="mb16">
|
|
|
+ <el-form-item label="雄性" prop="maleNumber">
|
|
|
+ <el-input-number style="width: 100%" :disabled="state.dialog.type === 'view'" placeholder="雄性数量"
|
|
|
+ v-model="state.form.maleNumber" :min="0" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="6" class="mb16">
|
|
|
+ <el-form-item label="雌性" prop="famaleNumber">
|
|
|
+ <el-input-number style="width: 100%" :disabled="state.dialog.type === 'view'" placeholder="雌性数量"
|
|
|
+ v-model="state.form.famaleNumber" :min="0" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="合计" prop="totalNumber">
|
|
|
+ <el-input-number style="width: 100%" disabled placeholder="合计" v-model="state.form.totalNumber"
|
|
|
+ :min="0" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <LaText class="mb16 mt20" size="16" type="important" bold>采购渠道</LaText>
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="采购渠道" prop="buyFrom">
|
|
|
+ <el-radio-group :disabled="state.dialog.type === 'view'" v-model="state.form.buyFrom">
|
|
|
+ <el-radio :label="ProcurementChannels.PURCHASED_BY_OTHERS" size="large">动物房代购</el-radio>
|
|
|
+ <el-radio :label="ProcurementChannels.PURCHASED_BY_MYSELF" size="large">自行购买</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="动物到达时间" prop="comeTime"
|
|
|
+ :required="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF">
|
|
|
+ <el-date-picker :disabled="state.dialog.type === 'view'" v-model="state.form.comeTime" type="date"
|
|
|
+ placeholder="请选择到达时间" clearable style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row v-if="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF" class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="外购来源单位"
|
|
|
+ :prop="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF ? 'comeFromUnit' : ''">
|
|
|
+ <el-input :disabled="state.dialog.type === 'view'" v-model="state.form.comeFromUnit" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row class="mt10" :gutter="20" v-if="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="生产许可证副本" prop="licenseNumberFile"
|
|
|
+ :required="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF">
|
|
|
+ <el-upload v-model:file-list="licenseNumberFileList" class="upload-demo" :action="uploadUrl" :limit="1"
|
|
|
+ style="width: 100%" :before-upload="beforeAvatarFileUpload" :disabled="state.dialog.type === 'view'"
|
|
|
+ :on-preview="handlePreview" :on-remove="() => handleRemove(UploadFileType.LICENSE_NUMBER)"
|
|
|
+ :on-success="(res: any, file: UploadFile) => handleSuccess(res, UploadFileType.LICENSE_NUMBER, file)">
|
|
|
+ <el-button :disabled="state.dialog.type === 'view'" type="primary">点击上传</el-button>
|
|
|
+ <div class="el-upload__tip ml10">支持格式:jpg png pdf等,单个文件不超过20MB</div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row class="mt10" :gutter="20" v-if="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="近三个月动物质量检测证明" prop="animalTestDateFile"
|
|
|
+ :required="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF">
|
|
|
+ <el-upload v-model:file-list="animalTestDateFileList" class="upload-demo" :action="uploadUrl" :limit="1"
|
|
|
+ style="width: 100%" :before-upload="beforeAvatarFileUpload" :on-preview="handlePreview"
|
|
|
+ :disabled="state.dialog.type === 'view'"
|
|
|
+ :on-remove="() => handleRemove(UploadFileType.ANIMAL_TEST_DATE)"
|
|
|
+ :on-success="(res: any, file: UploadFile) => handleSuccess(res, UploadFileType.ANIMAL_TEST_DATE, file)">
|
|
|
+ <el-button :disabled="state.dialog.type === 'view'" type="primary">点击上传</el-button>
|
|
|
+ <div class="el-upload__tip ml10">支持格式:jpg png pdf等,单个文件不超过20MB</div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row class="mt10" :gutter="20" v-if="state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="基因鉴定报告" prop="geneIdentificationFile">
|
|
|
+ <el-upload v-model:file-list="geneIdentificationFileList" class="upload-demo" :action="uploadUrl"
|
|
|
+ :limit="1" style="width: 100%" :before-upload="beforeAvatarFileUpload" :on-preview="handlePreview"
|
|
|
+ :disabled="state.dialog.type === 'view'"
|
|
|
+ :on-remove="() => handleRemove(UploadFileType.GENE_IDENTIFICATION_FILE)"
|
|
|
+ :on-success="(res: any, file: UploadFile) => handleSuccess(res, UploadFileType.GENE_IDENTIFICATION_FILE, file)">
|
|
|
+ <el-button :disabled="state.dialog.type === 'view'" type="primary">点击上传</el-button>
|
|
|
+ <div class="el-upload__tip ml10">支持格式:jpg png pdf等,单个文件不超过20MB</div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <LaText class="mb16 mt20" size="16" type="important" bold>特殊要求和附件</LaText>
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="是否有特殊饲养要求" prop="hasFeedingSpecial">
|
|
|
+ <el-radio-group :disabled="state.dialog.type === 'view'" v-model="state.form.hasFeedingSpecial">
|
|
|
+ <el-radio :label="FeedingSpecial.HAVE_FEEDING_SPECIAL" size="large">有</el-radio>
|
|
|
+ <el-radio :label="FeedingSpecial.NO_FEEDING_SPECIAL" size="large">无</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12" class="mb16" v-if="state.form.hasFeedingSpecial === FeedingSpecial.HAVE_FEEDING_SPECIAL">
|
|
|
+ <el-form-item label="特殊饲养要求" prop="feedingSpecialDesc" required>
|
|
|
+ <el-input :disabled="state.dialog.type === 'view'" placeholder="输入特殊饲养要求,如每天更换垫料等"
|
|
|
+ v-model="state.form.feedingSpecialDesc" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="笼位预约表" prop="cageAppointFile">
|
|
|
+ <el-upload
|
|
|
+ v-model:file-list="cageAppointFileList"
|
|
|
+ class="upload-demo"
|
|
|
+ :action="uploadUrl"
|
|
|
+ :limit="1"
|
|
|
+ style="width: 100%"
|
|
|
+ :before-upload="beforeAvatarFileUpload"
|
|
|
+ :on-preview="handlePreview"
|
|
|
+ :disabled="state.dialog.type === 'view'"
|
|
|
+ :on-remove="() => handleRemove(UploadFileType.CAGE_APPOINT_FILE)"
|
|
|
+ :on-success="(res: any, file: UploadFile) => handleSuccess(res, UploadFileType.CAGE_APPOINT_FILE, file)"
|
|
|
+ >
|
|
|
+ <el-button :disabled="state.dialog.type === 'view'" type="primary">点击上传</el-button>
|
|
|
+ <div class="el-upload__tip ml10">支持格式:jpg png pdf等,单个文件不超过20MB</div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row> -->
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="实验动物福利伦理审查申请表" prop="ethicsCheckFile" required>
|
|
|
+ <el-upload v-model:file-list="ethicsCheckFileList" class="upload-demo" :action="uploadUrl" :limit="1"
|
|
|
+ style="width: 100%" :before-upload="beforeAvatarFileUpload" :on-preview="handlePreview"
|
|
|
+ :disabled="state.dialog.type === 'view'"
|
|
|
+ :on-remove="() => handleRemove(UploadFileType.ETHICS_CHECK_FILE)"
|
|
|
+ :on-success="(res: any, file: UploadFile) => handleSuccess(res, UploadFileType.ETHICS_CHECK_FILE, file)">
|
|
|
+ <el-button :disabled="state.dialog.type === 'view'" type="primary">点击上传</el-button>
|
|
|
+ <div class="el-upload__tip ml10">支持格式:jpg png pdf等,单个文件不超过20MB</div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="12" class="mb16">
|
|
|
+ <el-form-item label="实验动物福利伦理审查意见表" prop="ethicsAdviceFile" required>
|
|
|
+ <el-upload v-model:file-list="ethicsAdviceFileList" class="upload-demo" :action="uploadUrl" :limit="1"
|
|
|
+ style="width: 100%" :before-upload="beforeAvatarFileUpload" :on-preview="handlePreview"
|
|
|
+ :disabled="state.dialog.type === 'view'"
|
|
|
+ :on-remove="() => handleRemove(UploadFileType.ETHICS_ADVICE_FILE)"
|
|
|
+ :on-success="(res: any, file: UploadFile) => handleSuccess(res, UploadFileType.ETHICS_ADVICE_FILE, file)">
|
|
|
+ <el-button :disabled="state.dialog.type === 'view'" type="primary">点击上传</el-button>
|
|
|
+ <div class="el-upload__tip ml10">支持格式:jpg png pdf等,单个文件不超过20MB</div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row v-if="state.dialog.type === 'view'">
|
|
|
+ <el-col :span="24" class="mb16">
|
|
|
+ <LaText class="mb16 mt20" size="16" type="important" bold>审批流</LaText>
|
|
|
+ <FlowTable :id="state.form.id" :businessCode="state.form.id + ''" defCode="plat_cage_applications" />
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row class="mt10" :gutter="20">
|
|
|
+ <el-col :span="24" class="mt30 mb16">
|
|
|
+ <el-checkbox :disabled="state.dialog.type === 'view'" v-model="safePromiseStatus">
|
|
|
+ {{ SafePromise }}
|
|
|
+ </el-checkbox>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <span v-if="state.dialog.type === 'add'" class="dialog-footer">
|
|
|
+ <el-button type="info" @click="onCancel" size="default">取 消</el-button>
|
|
|
+ <el-button color="#2c78ff" @click="onSubmit()" size="default">提交</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="systemProDialog">
|
|
|
+ import { reactive, ref, computed, defineAsyncComponent, watch } from 'vue';
|
|
|
+ import to from 'await-to-js';
|
|
|
+ import { ElMessage } from 'element-plus';
|
|
|
+ import { storeToRefs } from 'pinia';
|
|
|
+ import dayjs from 'dayjs';
|
|
|
+ import { UploadFile } from 'element-plus/es/components';
|
|
|
+
|
|
|
+ import { usePlatAnimalCageApplicationApi } from 'labsop-api/src/api/platform/animal';
|
|
|
+ import {
|
|
|
+ LeavelList,
|
|
|
+ ProcurementChannels,
|
|
|
+ UploadFileType,
|
|
|
+ FeedingSpecial,
|
|
|
+ SafePromise,
|
|
|
+ DateFormat,
|
|
|
+ } from '/@/constants/pageConstants';
|
|
|
+
|
|
|
+ import { deepClone } from '/@/utils/other';
|
|
|
+ import { useUserInfo } from '/@/stores/userInfo';
|
|
|
+
|
|
|
+ const uploadUrl = (import.meta as any).env.VITE_UPLOAD;
|
|
|
+
|
|
|
+ const stores = useUserInfo();
|
|
|
+ const { userInfos } = storeToRefs(stores);
|
|
|
+
|
|
|
+ const FlowTable = defineAsyncComponent(() => import('/@/components/flow/flow-table.vue'));
|
|
|
+
|
|
|
+ // 定义子组件向父组件传值/事件
|
|
|
+ const emit = defineEmits(['refresh']);
|
|
|
+
|
|
|
+ const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi();
|
|
|
+
|
|
|
+ const expertDialogFormRef = ref();
|
|
|
+ const projectGroupList = ref<any[]>([]);
|
|
|
+ const projects = ref<any[]>([]);
|
|
|
+
|
|
|
+ const animalNumber = computed(() => {
|
|
|
+ const maleNumber = state.form.maleNumber || 0;
|
|
|
+ const famaleNumber = state.form.famaleNumber || 0;
|
|
|
+ return maleNumber + famaleNumber;
|
|
|
+ });
|
|
|
+
|
|
|
+ const animalTypeList = ref<any[]>([]);
|
|
|
+
|
|
|
+ const licenseNumberFileList = ref<UploadFile[]>([]);
|
|
|
+ const animalTestDateFileList = ref<UploadFile[]>([]);
|
|
|
+ const geneIdentificationFileList = ref<UploadFile[]>([]);
|
|
|
+ const cageAppointFileList = ref<UploadFile[]>([]);
|
|
|
+ const ethicsCheckFileList = ref<UploadFile[]>([]);
|
|
|
+ const ethicsAdviceFileList = ref<UploadFile[]>([]);
|
|
|
+
|
|
|
+ const safePromiseStatus = ref<boolean>(false);
|
|
|
+
|
|
|
+ const rules = {
|
|
|
+ projectGroupId: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ categoryName: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ number: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ startDate: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ comeTime: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ // 只有在自行购买时才验证动物到达时间
|
|
|
+ if (state.form.buyFrom === ProcurementChannels.PURCHASED_BY_MYSELF && (!value || value === '')) {
|
|
|
+ callback(new Error('动物到达时间不能为空'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ // maleNumber: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ // weight: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ buyFrom: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ comeFromUnit: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ variety: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ categoryId: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ feedingDay: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+
|
|
|
+ level: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ // feedingSpecialDesc: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ // ethicsCheckFile: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ // ethicsAdviceFile: { required: true, message: '不能为空', trigger: 'change' },
|
|
|
+ licenseNumberFile: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ // 只有在动物房代购时才验证这些字段
|
|
|
+ if (state.form.buyFrom === ProcurementChannels.PURCHASED_BY_OTHERS && (!value || value.length === 0)) {
|
|
|
+ callback(new Error('生产许可证副本不能为空'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ animalTestDateFile: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ // 只有在动物房代购时才验证这些字段
|
|
|
+ if (state.form.buyFrom === ProcurementChannels.PURCHASED_BY_OTHERS && (!value || value.length === 0)) {
|
|
|
+ callback(new Error('动物质检证明不能为空'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ ethicsCheckFile: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ if (!value || value.length === 0) {
|
|
|
+ callback(new Error('实验动物福利伦理审查申请表不能为空'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ ethicsAdviceFile: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ if (!value || value.length === 0) {
|
|
|
+ callback(new Error('实验动物福利伦理审查意见表不能为空'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ feedingSpecialDesc: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ // 只有在有特殊饲养要求时才验证特殊饲养要求描述
|
|
|
+ if (state.form.hasFeedingSpecial === FeedingSpecial.HAVE_FEEDING_SPECIAL && (!value || value.trim() === '')) {
|
|
|
+ callback(new Error('特殊饲养要求不能为空'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ age: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ if (!value || value.min === null || value.max === null) {
|
|
|
+ callback(new Error('周龄范围不能为空'));
|
|
|
+ } else if (value.min > value.max) {
|
|
|
+ callback(new Error('最小周龄不能大于最大周龄'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ weight: {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ if (!value || value.min === null || value.max === null) {
|
|
|
+ callback(new Error('体重范围不能为空'));
|
|
|
+ } else if (value.min > value.max) {
|
|
|
+ callback(new Error('最小体重不能大于最大体重'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'change',
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ const defaultFormFields = {
|
|
|
+ id: 0,
|
|
|
+ projectGroupName: '',
|
|
|
+ projectGroupId: null,
|
|
|
+ categoryName: '',
|
|
|
+ categoryId: null,
|
|
|
+ level: null,
|
|
|
+ number: 1,
|
|
|
+ startDate: '',
|
|
|
+ maleNumber: 0,
|
|
|
+ famaleNumber: 0,
|
|
|
+ totalNumber: 0,
|
|
|
+ weight: { min: 0, max: 0 },
|
|
|
+ age: { min: 0, max: 0 },
|
|
|
+ feedingDay: 0,
|
|
|
+ userName: '',
|
|
|
+ deptId: null,
|
|
|
+ deptName: '',
|
|
|
+ phone: '',
|
|
|
+ buyFrom: ProcurementChannels.PURCHASED_BY_OTHERS,
|
|
|
+ comeTime: '',
|
|
|
+ comeFromUnit: '',
|
|
|
+ licenseNumberFile: [] as { name: string; url: string }[],
|
|
|
+ animalTestDateFile: [] as { name: string; url: string }[],
|
|
|
+ geneIdentificationFile: [] as { name: string; url: string }[],
|
|
|
+ hasFeedingSpecial: FeedingSpecial.HAVE_FEEDING_SPECIAL,
|
|
|
+ feedingSpecialDesc: '',
|
|
|
+ cageAppointFile: [] as { name: string; url: string }[],
|
|
|
+ ethicsCheckFile: [] as { name: string; url: string }[],
|
|
|
+ ethicsAdviceFile: [] as { name: string; url: string }[],
|
|
|
+ };
|
|
|
+
|
|
|
+ const state = reactive({
|
|
|
+ form: defaultFormFields,
|
|
|
+ safePromise: false,
|
|
|
+ safeRead: false,
|
|
|
+ dialog: {
|
|
|
+ isShowDialog: false,
|
|
|
+ type: '',
|
|
|
+ title: '',
|
|
|
+ submitTxt: '',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => [state.form.maleNumber, state.form.famaleNumber],
|
|
|
+ ([maleNumber, famaleNumber]) => {
|
|
|
+ state.form.totalNumber = (maleNumber || 0) + (famaleNumber || 0);
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+ );
|
|
|
+
|
|
|
+ const getDicts = () => {
|
|
|
+ Promise.all([
|
|
|
+ platAnimalCageApplicationApi.getAnimalTypeList({}),
|
|
|
+ platAnimalCageApplicationApi.getProjectGroup({}),
|
|
|
+ ]).then(([animalType, projectGroup]) => {
|
|
|
+ animalTypeList.value = animalType.data;
|
|
|
+ if (projectGroup && projectGroup.data) {
|
|
|
+ projectGroupList.value = projectGroup.data;
|
|
|
+ const currentProject = projectGroup.data[0]?.projects;
|
|
|
+ if (currentProject) {
|
|
|
+ projects.value = currentProject;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const isValidJsonArray = (str: string) => {
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(str);
|
|
|
+ return Array.isArray(parsed);
|
|
|
+ } catch (e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const parseRangeField = (str: string) => {
|
|
|
+ try {
|
|
|
+ if (!str) return { min: 0, max: 0 };
|
|
|
+ // 处理双重转义的情况
|
|
|
+ const cleanStr = str.replace(/\\"/g, '"').replace(/^"|"$/g, '');
|
|
|
+ const parsed = JSON.parse(cleanStr);
|
|
|
+ return {
|
|
|
+ min: parsed.min || 0,
|
|
|
+ max: parsed.max || 0
|
|
|
+ };
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('解析范围字段失败:', str, e);
|
|
|
+ return { min: 0, max: 0 };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 打开弹窗
|
|
|
+ const openDialog = async (type: 'add' | 'view', row?: any) => {
|
|
|
+ await getDicts();
|
|
|
+ state.dialog.type = type;
|
|
|
+ state.dialog.title = '笼位申请';
|
|
|
+
|
|
|
+ if (type === 'view' && row) {
|
|
|
+ // 处理范围字段的JSON字符串
|
|
|
+ const processedRow = {
|
|
|
+ ...row,
|
|
|
+ age: parseRangeField(row.age),
|
|
|
+ weight: parseRangeField(row.weight)
|
|
|
+ };
|
|
|
+ state.form = processedRow;
|
|
|
+ licenseNumberFileList.value = isValidJsonArray(row.licenseNumberFile) ? JSON.parse(row.licenseNumberFile) : [];
|
|
|
+ animalTestDateFileList.value = isValidJsonArray(row.animalTestDateFile) ? JSON.parse(row.animalTestDateFile) : [];
|
|
|
+ geneIdentificationFileList.value = isValidJsonArray(row.geneIdentificationFile)
|
|
|
+ ? JSON.parse(row.geneIdentificationFile)
|
|
|
+ : [];
|
|
|
+ cageAppointFileList.value = isValidJsonArray(row.cageAppointFile) ? JSON.parse(row.cageAppointFile) : [];
|
|
|
+ ethicsCheckFileList.value = isValidJsonArray(row.ethicsCheckFile) ? JSON.parse(row.ethicsCheckFile) : [];
|
|
|
+ ethicsAdviceFileList.value = isValidJsonArray(row.ethicsAdviceFile) ? JSON.parse(row.ethicsAdviceFile) : [];
|
|
|
+ safePromiseStatus.value = true;
|
|
|
+ } else if (type === 'add') {
|
|
|
+ // 回显并写入当前人的部门与联系方式,用于提交
|
|
|
+ state.form.deptId = userInfos.value?.deptId || null;
|
|
|
+ state.form.deptName = userInfos.value?.deptName || '';
|
|
|
+ state.form.phone = userInfos.value?.phone || '';
|
|
|
+ }
|
|
|
+ state.dialog.isShowDialog = true;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 关闭弹窗
|
|
|
+ const closeDialog = () => {
|
|
|
+ state.form = defaultFormFields;
|
|
|
+ licenseNumberFileList.value = [];
|
|
|
+ animalTestDateFileList.value = [];
|
|
|
+ geneIdentificationFileList.value = [];
|
|
|
+ cageAppointFileList.value = [];
|
|
|
+ ethicsCheckFileList.value = [];
|
|
|
+ ethicsAdviceFileList.value = [];
|
|
|
+ safePromiseStatus.value = false;
|
|
|
+ state.dialog.isShowDialog = false;
|
|
|
+ };
|
|
|
+ // 取消
|
|
|
+ const onCancel = () => {
|
|
|
+ closeDialog();
|
|
|
+ };
|
|
|
+
|
|
|
+ const beforeAvatarFileUpload = (file: { size: number }) => {
|
|
|
+ let isLt10m = file.size / 1024 / 1024 / 20 < 1;
|
|
|
+ if (!isLt10m) {
|
|
|
+ ElMessage.error('上传文件大小不能超过 20MB!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleRemove = (type: UploadFileType) => {
|
|
|
+ if (type === UploadFileType.LICENSE_NUMBER) {
|
|
|
+ licenseNumberFileList.value = [];
|
|
|
+ state.form.licenseNumberFile = [];
|
|
|
+ } else if (type === UploadFileType.ANIMAL_TEST_DATE) {
|
|
|
+ animalTestDateFileList.value = [];
|
|
|
+ state.form.animalTestDateFile = [];
|
|
|
+ } else if (type === UploadFileType.GENE_IDENTIFICATION_FILE) {
|
|
|
+ geneIdentificationFileList.value = [];
|
|
|
+ state.form.geneIdentificationFile = [];
|
|
|
+ } else if (type === UploadFileType.CAGE_APPOINT_FILE) {
|
|
|
+ cageAppointFileList.value = [];
|
|
|
+ state.form.cageAppointFile = [];
|
|
|
+ } else if (type === UploadFileType.ETHICS_CHECK_FILE) {
|
|
|
+ ethicsCheckFileList.value = [];
|
|
|
+ state.form.ethicsCheckFile = [];
|
|
|
+ } else if (type === UploadFileType.ETHICS_ADVICE_FILE) {
|
|
|
+ ethicsAdviceFileList.value = [];
|
|
|
+ state.form.ethicsAdviceFile = [];
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSuccess = (res: { Data: string }, type: UploadFileType, file: UploadFile) => {
|
|
|
+ console.log('ressss', res, file);
|
|
|
+ if (type === UploadFileType.LICENSE_NUMBER) {
|
|
|
+ state.form.licenseNumberFile = [{ name: file.name, url: res?.Data }];
|
|
|
+ } else if (type === UploadFileType.ANIMAL_TEST_DATE) {
|
|
|
+ state.form.animalTestDateFile = [{ name: file.name, url: res?.Data }];
|
|
|
+ } else if (type === UploadFileType.GENE_IDENTIFICATION_FILE) {
|
|
|
+ state.form.geneIdentificationFile = [{ name: file.name, url: res?.Data }];
|
|
|
+ } else if (type === UploadFileType.CAGE_APPOINT_FILE) {
|
|
|
+ state.form.cageAppointFile = [{ name: file.name, url: res?.Data }];
|
|
|
+ } else if (type === UploadFileType.ETHICS_CHECK_FILE) {
|
|
|
+ state.form.ethicsCheckFile = [{ name: file.name, url: res?.Data }];
|
|
|
+ } else if (type === UploadFileType.ETHICS_ADVICE_FILE) {
|
|
|
+ state.form.ethicsAdviceFile = [{ name: file.name, url: res?.Data }];
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handlePreview = (file: UploadFile) => {
|
|
|
+ if (file.url) {
|
|
|
+ window.open(file.url, '_blank');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 提交
|
|
|
+ const onSubmit = async () => {
|
|
|
+ expertDialogFormRef.value.validate(async (valid: boolean) => {
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ if (!safePromiseStatus.value) {
|
|
|
+ ElMessage.error('请阅读并勾选安全承诺!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!state.form.maleNumber && !state.form.famaleNumber) {
|
|
|
+ ElMessage.error('请添加雄性或雌性数量!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // json 字符串化
|
|
|
+ state.form.age= JSON.stringify(state.form.age);
|
|
|
+ state.form.weight = JSON.stringify(state.form.weight);
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ ...deepClone(state.form),
|
|
|
+ categoryName: animalTypeList.value.find((item) => item.id == state.form.categoryId)?.name,
|
|
|
+ projectGroupName: projects.value.find((item) => item.id == state.form.projectGroupId)?.projectName,
|
|
|
+ startDate: dayjs(state.form.startDate).format(DateFormat),
|
|
|
+ comeTime: state.form.comeTime ? dayjs(state.form.comeTime).format(DateFormat) : '',
|
|
|
+ // 将范围对象转换为JSON字符串
|
|
|
+ age: JSON.stringify(state.form.age),
|
|
|
+ weight: JSON.stringify(state.form.weight),
|
|
|
+ licenseNumberFile: JSON.stringify(state.form.licenseNumberFile),
|
|
|
+ animalTestDateFile: JSON.stringify(state.form.animalTestDateFile),
|
|
|
+ geneIdentificationFile: JSON.stringify(state.form.geneIdentificationFile),
|
|
|
+ cageAppointFile: JSON.stringify(state.form.cageAppointFile),
|
|
|
+ ethicsCheckFile: JSON.stringify(state.form.ethicsCheckFile),
|
|
|
+ ethicsAdviceFile: JSON.stringify(state.form.ethicsAdviceFile),
|
|
|
+ };
|
|
|
+
|
|
|
+ Object.entries(params).forEach(([key, value]) => {
|
|
|
+ if (value === '' || value === null) {
|
|
|
+ delete params[key as keyof typeof params];
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const post = platAnimalCageApplicationApi.create;
|
|
|
+ const [err]: ToResponse = await to(post(params));
|
|
|
+ if (err) return;
|
|
|
+ ElMessage.success('操作成功');
|
|
|
+ closeDialog();
|
|
|
+ emit('refresh');
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 暴露变量
|
|
|
+ defineExpose({
|
|
|
+ openDialog,
|
|
|
+ });
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+.application-dialog-container {
|
|
|
+ .el-select {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+h4 {
|
|
|
+ font-size: 18px;
|
|
|
+}
|
|
|
+
|
|
|
+ul {
|
|
|
+ padding-left: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.text {
|
|
|
+ p {
|
|
|
+ text-indent: 2em;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.el-upload+.el-button {
|
|
|
+ vertical-align: top;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-checkbox) {
|
|
|
+ white-space: pre-wrap;
|
|
|
+}
|
|
|
+</style>
|