Переглянути джерело

Merge branch 'feature/ZUNYIYIKED-27' of wanglj/labsop_h5 into master

徐凯 6 місяців тому
батько
коміт
6fcdadbe2f

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "await-to-js": "^3.0.0",
     "axios": "^1.8.2",
     "cropperjs": "1.5.13",
+    "dayjs": "^1.11.13",
     "downloadjs": "^1.4.7",
     "element-plus": "^2.9.8",
     "moment": "^2.29.4",

+ 3 - 0
pnpm-lock.yaml

@@ -16,6 +16,9 @@ dependencies:
   cropperjs:
     specifier: 1.5.13
     version: 1.5.13
+  dayjs:
+    specifier: ^1.11.13
+    version: 1.11.13
   downloadjs:
     specifier: ^1.4.7
     version: 1.4.7

+ 53 - 49
src/api/platform/animal/index.ts

@@ -10,53 +10,57 @@ import request from '/@/utils/micro_request.js';
 const basePath = import.meta.env.VITE_PLATFORM_API;
 export function usePlatAnimalCageApplicationApi() {
 	return {
-		// 创建
-		create(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'Create', params);
-		},
-		// 列表
-		getList(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetApplicationList', params);
-		},
-		// 详情
-		getEntityById(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetApplicationDetail', params);
-		},
-		// 更新
-		updateById(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'UpdateApplication', params);
-		},
-		// 动物类型列表
-		getAnimalTypeList(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimals', 'GetVendorAnimalCategoires', params);
-		},
-		// 获取课题名称列表
-		getProjectGroup(params?: Object) {
-			return request.postRequest(basePath, 'PlatProjProjectGroup', 'GetProjectGroup', params);
-		},
-		// 笼位退还
-		releaseCage(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'ReleaseCage', params);
-		},
-		// 笼位退还列表
-		getCageReleaseList(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetCageReleaseApplications', params);
-		},
-		// 获取动物管理三方token
-		getVendorToken(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimals', 'GetVendorToken', params);
-		},
-		// 导出笼位退还列表
-		getCageReleaseApplicationsExport(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetCageReleaseApplicationsExport', params);
-		},
-		// 导出列表
-		getApplicationListExport(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetApplicationListExport', params);
-		},
-		//笼位退还申请详情
-		getApplicationReleaseDetail(params?: Object) {
-			return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetCageReleaseApplicationDetail', params);
-		},
-	};
+    // 创建
+    create(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'Create', params)
+    },
+    // 列表
+    getList(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetApplicationList', params)
+    },
+    // 详情
+    getEntityById(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetApplicationDetail', params)
+    },
+    // 更新
+    updateById(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'UpdateApplication', params)
+    },
+    // 动物类型列表
+    getAnimalTypeList(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimals', 'GetVendorAnimalCategoires', params)
+    },
+    // 获取课题名称列表
+    getProjectGroup(params?: Object) {
+      return request.postRequest(basePath, 'PlatProjProjectGroup', 'GetProjectGroup', params)
+    },
+    // 笼位退还
+    releaseCage(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'ReleaseCage', params)
+    },
+    // 笼位退还列表
+    getCageReleaseList(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetCageReleaseApplications', params)
+    },
+    // 获取动物管理三方token
+    getVendorToken(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimals', 'GetVendorToken', params)
+    },
+    // 导出笼位退还列表
+    getCageReleaseApplicationsExport(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetCageReleaseApplicationsExport', params)
+    },
+    // 导出列表
+    getApplicationListExport(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetApplicationListExport', params)
+    },
+    //笼位退还申请详情
+    getApplicationReleaseDetail(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetCageReleaseApplicationDetail', params)
+    },
+    //获取我的历史笼位列表
+    getMyCageHistoryList(params?: Object) {
+      return request.postRequest(basePath, 'PlatAnimalCageApplication', 'GetCageReleaseApplications', params)
+    }
+  }
 }

+ 57 - 33
src/constants/pageConstants.ts

@@ -1,46 +1,70 @@
 // 笼位申请审批状态
 export enum ApproveStatus {
-	WAIT_SUBMIT = 10, // 待提交
-	APPROVING = 20, // 审核中
-	PASS = 30, // 通过
-	REVOKE = 35, // 撤销
-	REFUSE = 40, // 拒绝
+  WAIT_SUBMIT = 10, // 待提交
+  APPROVING = 20, // 审核中
+  PASS = 30, // 通过
+  REVOKE = 35, // 撤销
+  REFUSE = 40 // 拒绝
 }
 
 // 笼位退还审批状态
 export enum ReturnStatus {
-	WAIT = 10,
-	ON_THE_WAY = 20,
-	COMPLETE = 30,
+  WAIT = 10,
+  ON_THE_WAY = 20,
+  COMPLETE = 30
 }
 
 // 动物级别
 export const LeavelList = [
-	{ name: '普通级', id: 1 },
-	{ name: 'SPF级', id: 3 },
-	{ name: '无菌级', id: 4 },
-];
+  { name: '普通级', id: 1 },
+  { name: 'SPF级', id: 3 },
+  { name: '无菌级', id: 4 }
+]
 
 // 笼位申请审批状态列表
 export const ApproveStatusList = [
-	{
-		name: '待提交',
-		id: ApproveStatus.WAIT_SUBMIT,
-	},
-	{
-		name: '审批中',
-		id: ApproveStatus.APPROVING,
-	},
-	{
-		name: '通过',
-		id: ApproveStatus.PASS,
-	},
-	{
-		name: '撤回',
-		id: ApproveStatus.REVOKE,
-	},
-	{
-		name: '拒绝',
-		id: ApproveStatus.REFUSE,
-	},
-];
+  {
+    name: '待提交',
+    id: ApproveStatus.WAIT_SUBMIT
+  },
+  {
+    name: '审批中',
+    id: ApproveStatus.APPROVING
+  },
+  {
+    name: '通过',
+    id: ApproveStatus.PASS
+  },
+  {
+    name: '撤回',
+    id: ApproveStatus.REVOKE
+  },
+  {
+    name: '拒绝',
+    id: ApproveStatus.REFUSE
+  }
+]
+
+export enum ProcurementChannels {
+  PURCHASED_BY_OTHERS = '10',
+  PURCHASED_BY_MYSELF = '20'
+}
+
+export enum UploadFileType {
+  LICENSE_NUMBER = 'licenseNumberFile',
+  ANIMAL_TEST_DATE = 'animalTestDateFile',
+  ENV_TEST_DATE = 'envTestDateFile',
+  CAGE_APPOINT_FILE = 'cageAppointFile',
+  ETHICS_CHECK_FILE = 'ethicsCheckFile',
+  ETHICS_ADVICE_FILE = 'ethicsAdviceFile'
+}
+
+export enum FeedingSpecial {
+  HAVE_FEEDING_SPECIAL = '10',
+  NO_FEEDING_SPECIAL = '20'
+}
+
+export enum MyCageType {
+  MINE_CAGE = 'mineCage',
+  MY_CAGE_HISTORY = 'myCageHistory'
+}

+ 10 - 0
src/hooks/useUserInfos.ts

@@ -0,0 +1,10 @@
+import { storeToRefs } from 'pinia'
+
+import { useUserInfo } from '/@/stores/userInfo'
+
+export function useUserInfos() {
+  const stores = useUserInfo()
+  const { userInfos } = storeToRefs(stores)
+
+  return { userInfos }
+}

+ 1 - 0
src/layout/animal.vue

@@ -9,6 +9,7 @@
 <template>
   <router-view></router-view>
   <van-tabbar route :placeholder="true" v-if="route.path !== '/entry/add'">
+    <van-tabbar-item replace to="/my-cage" icon="send-gift-o">我的笼位</van-tabbar-item>
     <van-tabbar-item replace to="/animal-application" icon="send-gift-o">笼位申请</van-tabbar-item>
     <van-tabbar-item replace to="/animal-return" icon="send-gift-o">笼位退还</van-tabbar-item>
   </van-tabbar>

+ 8 - 0
src/router.ts

@@ -238,6 +238,14 @@ const routes = [
     redirect: '/login',
     component: () => import('/@/layout/animal.vue'),
     children: [
+      {
+        name: 'myCage',
+        path: '/my-cage',
+        component: () => import('/@/view/animal/myCage/index.vue'),
+        meta: {
+          title: '我的笼位'
+        }
+      },
       {
         name: 'animalApplication',
         path: '/animal-application',

+ 9 - 0
src/utils/formatTime.ts

@@ -176,3 +176,12 @@ export function formatAxis(param: Date): string {
   else if (hour < 22) return '晚上好';
   else return '夜里好';
 }
+
+export const formatToChineseDate = (dateStr: string) => {
+  const date = new Date(dateStr)
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+
+  return `${year}年${month}月${day}日`
+}

+ 496 - 191
src/view/animal/application/components/Application.vue

@@ -1,198 +1,503 @@
 <template>
-	<div class="application-dialog-container">
-		<el-dialog :title="state.dialog.title" @close="onCancel" :close-on-click-modal="false" v-model="state.dialog.isShowDialog" width="100%">
-			<el-form ref="expertDialogFormRef" :model="state.form" :rules="rules" size="default" label-width="140px" label-position="top">
-				<h4 class="mb8 mt8">基本信息</h4>
-				<el-row :gutter="20">
-					<el-col :span="12">
-						<el-form-item label="课题名称" prop="projectGroupId">
-							<el-select 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">
-						<el-form-item label="姓名" prop="group">
-							<el-input v-model="userInfos.userName" disabled />
-						</el-form-item>
-					</el-col>
-				</el-row>
-
-				<h4 class="mb8 mt10">实验动物笼位预约信息</h4>
-				<el-row class="mt10" :gutter="20">
-					<el-col :span="12">
-						<el-form-item label="笼位数量" prop="number">
-							<el-input-number v-model="state.form.number" style="width: 100%" :min="1" />
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="选择时间" prop="startDate">
-							<el-date-picker 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">
-						<el-form-item label="品种品系" prop="categoryId">
-							<el-select 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">
-						<el-form-item label="级别" prop="level">
-							<el-select 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-row>
-			</el-form>
-
-			<template #footer>
-				<span 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>
+  <div class="application-dialog-container">
+    <el-dialog :title="state.dialog.title" @close="onCancel" :close-on-click-modal="false" v-model="state.dialog.isShowDialog" width="100%">
+      <el-form ref="expertDialogFormRef" :model="state.form" :rules="rules" size="default" label-width="140px" label-position="top">
+        <h4 class="mb8 mt8">基本信息</h4>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="课题名称" prop="projectGroupId">
+              <el-select 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">
+            <el-form-item label="姓名" prop="group">
+              <el-input v-model="userInfos.userName" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <h4 class="mb8 mt10">实验动物笼位预约信息</h4>
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="笼位数量" prop="number">
+              <el-input-number v-model="state.form.number" style="width: 100%" :min="1" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="选择时间" prop="startDate">
+              <el-date-picker 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">
+            <el-form-item label="品种品系" prop="categoryId">
+              <el-select 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">
+            <el-form-item label="级别" prop="level">
+              <el-select 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-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="数量(雄性+雌性)" prop="maleNumber">
+              <div style="width: 100%; display: flex; justify-content: space-between">
+                <el-input-number placeholder="雄性数量" v-model="state.form.maleNumber" :min="0" />
+                +
+                <el-input-number placeholder="雌性数量" v-model="state.form.famaleNumber" :min="0" />
+              </div>
+              <div style="width: 100%; margin-top: 10px">
+                <el-input placeholder="总数" v-model="animalNumber" />
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="体重" prop="weight">
+              <el-input-number v-model="state.form.weight" style="width: 100%" :min="0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="周龄" prop="age">
+              <el-input-number v-model="state.form.age" style="width: 100%" :min="0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="饲养总天数" prop="feedingDay">
+              <el-input-number v-model="state.form.feedingDay" style="width: 100%" :min="0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <h4 class="mb8 mt20">采购渠道</h4>
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="采购渠道" prop="buyFrom">
+              <el-radio-group 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-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="动物到达时间" prop="comeTime">
+              <el-date-picker v-model="state.form.comeTime" type="date" placeholder="请选择到达时间" clearable style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="外购来源单位" prop="comeFromUnit">
+              <el-input v-model="state.form.comeFromUnit" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="生产许可证" prop="licenseNumberFile">
+              <el-upload
+                v-model:file-list="licenseNumberFileList"
+                class="upload-demo"
+                :action="uploadUrl"
+                :limit="1"
+                style="width: 100%"
+                :before-upload="beforeAvatarFileUpload"
+                :on-remove="() => handleRemove(UploadFileType.LICENSE_NUMBER)"
+                :on-success="(res: any) => handleSuccess(res, UploadFileType.LICENSE_NUMBER)"
+              >
+                <el-button 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="24">
+            <el-form-item label="近三个月动物质量检测证明" prop="animalTestDateFile">
+              <el-upload
+                v-model:file-list="animalTestDateFileList"
+                class="upload-demo"
+                :action="uploadUrl"
+                :limit="1"
+                style="width: 100%"
+                :before-upload="beforeAvatarFileUpload"
+                :on-remove="() => handleRemove(UploadFileType.ANIMAL_TEST_DATE)"
+                :on-success="(res: any) => handleSuccess(res, UploadFileType.ANIMAL_TEST_DATE)"
+              >
+                <el-button 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="24">
+            <el-form-item label="近三个月饲养环境检测证明" prop="envTestDateFile">
+              <el-upload
+                v-model:file-list="envTestDateFileList"
+                class="upload-demo"
+                :action="uploadUrl"
+                :limit="1"
+                style="width: 100%"
+                :before-upload="beforeAvatarFileUpload"
+                :on-remove="() => handleRemove(UploadFileType.ENV_TEST_DATE)"
+                :on-success="(res: any) => handleSuccess(res, UploadFileType.ENV_TEST_DATE)"
+              >
+                <el-button type="primary">点击上传</el-button>
+                <div class="el-upload__tip ml10">支持格式:jpg png pdf等,单个文件不超过20MB</div>
+              </el-upload>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <h4 class="mb8 mt20">特殊要求和附件</h4>
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="是否有特殊饲养要求" prop="hasFeedingSpecial">
+              <el-radio-group 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-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24" v-if="state.form.hasFeedingSpecial === FeedingSpecial.HAVE_FEEDING_SPECIAL">
+            <el-form-item label="特殊饲养要求" prop="feedingSpecialDesc">
+              <el-input placeholder="输入特殊饲养要求,如每天更换垫料等" v-model="state.form.feedingSpecialDesc" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <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-remove="() => handleRemove(UploadFileType.CAGE_APPOINT_FILE)"
+                :on-success="(res: any) => handleSuccess(res, UploadFileType.CAGE_APPOINT_FILE)"
+              >
+                <el-button 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="24">
+            <el-form-item label="伦理审查表" prop="ethicsCheckFile">
+              <el-upload
+                v-model:file-list="ethicsCheckFileList"
+                class="upload-demo"
+                :action="uploadUrl"
+                :limit="1"
+                style="width: 100%"
+                :before-upload="beforeAvatarFileUpload"
+                :on-remove="() => handleRemove(UploadFileType.ETHICS_CHECK_FILE)"
+                :on-success="(res: any) => handleSuccess(res, UploadFileType.ETHICS_CHECK_FILE)"
+              >
+                <el-button 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="24">
+            <el-form-item label="伦理意见表" prop="ethicsAdviceFile">
+              <el-upload
+                v-model:file-list="ethicsAdviceFileList"
+                class="upload-demo"
+                :action="uploadUrl"
+                :limit="1"
+                style="width: 100%"
+                :before-upload="beforeAvatarFileUpload"
+                :on-remove="() => handleRemove(UploadFileType.ETHICS_ADVICE_FILE)"
+                :on-success="(res: any) => handleSuccess(res, UploadFileType.ETHICS_ADVICE_FILE)"
+              >
+                <el-button 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-checkbox style="margin-top: 30px; margin-bottom: 30px" v-model="safePromise">
+          <div class="safePromise">
+            本人(以上所述课题的负责人)谨此声明:本项目所包含的实验动物、实验方法、实验材料及试剂无放射性、感染性和化学毒性,所有参与实验人员在实验过程中自愿遵守遵义医科大学附属医院实验动物房的管理制度和操作流程,愿意根据其规定的付费方式向遵义医科大学附属医院实验动物房支付所有的费用。
+          </div>
+        </el-checkbox>
+      </el-form>
+
+      <template #footer>
+        <span 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 } from 'vue';
-import to from 'await-to-js';
-import { ElMessage } from 'element-plus';
-
-import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
-import { LeavelList } from '/@/constants/pageConstants';
-import { deepClone } from '/@/utils/other';
-import { useUserInfo } from '/@/stores/userInfo';
-import { storeToRefs } from 'pinia';
-import dayjs from 'dayjs';
-
-const stores = useUserInfo();
-const { userInfos } = storeToRefs(stores);
-
-// 定义子组件向父组件传值/事件
-const emit = defineEmits(['refresh']);
-
-const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi();
-
-const expertDialogFormRef = ref();
-const projectGroupList = ref<any[]>([]);
-const projects = ref<any[]>([]);
-
-const rules = {
-	projectGroupId: { required: true, message: '不能为空', trigger: 'change' },
-	categoryId: { required: true, message: '不能为空', trigger: 'change' },
-	number: { required: true, message: '不能为空', trigger: 'change' },
-	startDate: { required: true, message: '不能为空', trigger: 'change' },
-};
-
-const animalTypeList = ref<any[]>([]);
-const state = reactive({
-	form: {
-		projectGroupName: '',
-		projectGroupId: null,
-		categoryName: '',
-		categoryId: null,
-		level: null,
-		number: 1,
-		startDate: '',
-	},
-	safePromise: false,
-	safeRead: false,
-	dialog: {
-		isShowDialog: false,
-		type: '',
-		title: '',
-		submitTxt: '',
-	},
-});
-
-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 openDialog = async (type: 'add' | 'edit') => {
-	getDicts();
-	state.dialog.type = type;
-	state.dialog.isShowDialog = true;
-};
-
-// 关闭弹窗
-const closeDialog = () => {
-	expertDialogFormRef.value.resetFields();
-	state.dialog.isShowDialog = false;
-};
-// 取消
-const onCancel = () => {
-	closeDialog();
-};
-
-// 提交
-const onSubmit = async () => {
-	expertDialogFormRef.value.validate(async (valid: boolean) => {
-		if (!valid) return;
-		const params = {
-			...deepClone(state.form),
-			categoryId: state.form.categoryId ? Number(state.form.categoryId) : null,
-			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('YYYY-MM-DD'),
-		};
-
-		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,
-});
+  import { reactive, ref, computed } from 'vue'
+  import to from 'await-to-js'
+  import { ElMessage } from 'element-plus'
+  import { UploadFile } from 'element-plus/es/components'
+  import dayjs from 'dayjs'
+  import { storeToRefs } from 'pinia'
+
+  import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
+  import { LeavelList } from '/@/constants/pageConstants'
+  import { deepClone } from '/@/utils/other'
+  import { useUserInfo } from '/@/stores/userInfo'
+  import { ProcurementChannels, FeedingSpecial } from '/@/constants/pageConstants'
+
+  const uploadUrl = (import.meta as any).env.VITE_UPLOAD
+
+  const stores = useUserInfo()
+  const { userInfos } = storeToRefs(stores)
+
+  // 定义子组件向父组件传值/事件
+  const emit = defineEmits(['refresh'])
+
+  const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi()
+
+  const expertDialogFormRef = ref()
+  const projectGroupList = ref<any[]>([])
+  const projects = ref<any[]>([])
+
+  const rules = {
+    projectGroupId: { required: true, message: '不能为空', trigger: 'change' },
+    categoryId: { required: true, message: '不能为空', trigger: 'change' },
+    number: { required: true, message: '不能为空', trigger: 'change' },
+    startDate: { required: true, message: '不能为空', trigger: 'change' }
+  }
+
+  const animalNumber = computed(() => {
+    const maleNumber = state.form.maleNumber || 0
+    const famaleNumber = state.form.famaleNumber || 0
+    return maleNumber + famaleNumber
+  })
+
+  const licenseNumberFileList = ref<UploadFile[]>([])
+  const animalTestDateFileList = ref<UploadFile[]>([])
+  const envTestDateFileList = ref<UploadFile[]>([])
+  const cageAppointFileList = ref<UploadFile[]>([])
+  const ethicsCheckFileList = ref<UploadFile[]>([])
+  const ethicsAdviceFileList = ref<UploadFile[]>([])
+
+  const safePromise = ref<boolean>(false)
+  const animalTypeList = ref<any[]>([])
+  const state = reactive({
+    form: {
+      projectGroupName: '',
+      projectGroupId: null,
+      categoryName: '',
+      categoryId: null,
+      level: null,
+      number: 1,
+      startDate: '',
+      maleNumber: 0,
+      famaleNumber: 0,
+      weight: 0,
+      age: 0,
+      feedingDay: 0,
+      buyFrom: ProcurementChannels.PURCHASED_BY_OTHERS,
+      comeTime: '',
+      comeFromUnit: '',
+      licenseNumberFile: '',
+      animalTestDateFile: '',
+      envTestDateFile: '',
+      hasFeedingSpecial: FeedingSpecial.HAVE_FEEDING_SPECIAL,
+      feedingSpecialDesc: '',
+      cageAppointFile: '',
+      ethicsCheckFile: '',
+      ethicsAdviceFile: ''
+    },
+    safePromise: false,
+    safeRead: false,
+    dialog: {
+      isShowDialog: false,
+      type: '',
+      title: '',
+      submitTxt: ''
+    }
+  })
+
+  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 openDialog = async (type: 'add' | 'edit') => {
+    getDicts()
+    state.dialog.type = type
+    state.dialog.isShowDialog = true
+  }
+
+  // 关闭弹窗
+  const closeDialog = () => {
+    expertDialogFormRef.value.resetFields()
+    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.ENV_TEST_DATE) {
+      envTestDateFileList.value = []
+      state.form.envTestDateFile = ''
+    } 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) => {
+    if (type === UploadFileType.LICENSE_NUMBER) {
+      state.form.licenseNumberFile = res?.Data || ''
+    } else if (type === UploadFileType.ANIMAL_TEST_DATE) {
+      state.form.animalTestDateFile = res?.Data || ''
+    } else if (type === UploadFileType.ENV_TEST_DATE) {
+      state.form.envTestDateFile = res?.Data || ''
+    } else if (type === UploadFileType.CAGE_APPOINT_FILE) {
+      state.form.cageAppointFile = res?.Data || ''
+    } else if (type === UploadFileType.ETHICS_CHECK_FILE) {
+      state.form.ethicsCheckFile = res?.Data || ''
+    } else if (type === UploadFileType.ETHICS_ADVICE_FILE) {
+      state.form.ethicsAdviceFile = res?.Data || ''
+    }
+  }
+
+  // 提交
+  const onSubmit = async () => {
+    expertDialogFormRef.value.validate(async (valid: boolean) => {
+      if (!valid) return
+
+      if (!safePromise.value) {
+        ElMessage.error('请阅读并勾选安全承诺!')
+        return
+      }
+
+      const params = {
+        ...deepClone(state.form),
+        categoryId: state.form.categoryId ? Number(state.form.categoryId) : null,
+        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('YYYY-MM-DD')
+      }
+
+      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;
-}
+  .application-dialog-container {
+    .el-select {
+      width: 100%;
+    }
+    .el-dialog {
+      max-height: 80%;
+      overflow: auto;
+    }
+    .safePromise {
+      white-space: pre-wrap;
+    }
+  }
+  h4 {
+    font-size: 18px;
+  }
+  ul {
+    padding-left: 20px;
+  }
+  .text {
+    p {
+      text-indent: 2em;
+    }
+  }
+  .el-upload + .el-button {
+    vertical-align: top;
+  }
 </style>

+ 255 - 118
src/view/animal/application/components/Detail.vue

@@ -1,125 +1,262 @@
 <template>
-	<div class="facilities-dialog-container">
-		<el-dialog title="详情" @close="closeDialog" :close-on-click-modal="false" v-model="props.showDialog" width="100%">
-			<el-form ref="expertDialogFormRef" disabled :model="state.form" size="default" label-width="140px" label-position="top">
-				<el-row :gutter="35">
-					<el-col :span="12" class="mb20">
-						<el-form-item label="申请人姓名" prop="memberName">
-							<el-input v-model="state.form.userName" disabled placeholder="请输入申请人姓名"></el-input>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12" class="mb20">
-						<el-form-item label="课题名称" prop="memberType">
-							<el-input v-model="state.form.projectGroupName" disabled placeholder="请输入课题名称"></el-input>
-						</el-form-item>
-					</el-col>
-				</el-row>
-
-				<el-row :gutter="35">
-					<el-col :span="12" class="mb20">
-						<el-form-item label="申请时间" prop="memberPhone">
-							<el-input v-model="state.form.createdTime" disabled placeholder="请输入申请时间"></el-input>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12" class="mb20">
-						<el-form-item label="申请状态" prop="mentorName">
-							<el-input v-model="state.form.approveStatus" disabled placeholder="请输入申请状态"></el-input>
-						</el-form-item>
-					</el-col>
-				</el-row>
-
-				<el-row :gutter="35">
-					<el-col :span="12" class="mb20">
-						<el-form-item :label="props.isReturnCageList ? '退还笼位(个)' : '申请笼位(个)'" prop="mentorObj">
-							<el-input v-if="!props.isReturnCageList" v-model="state.form.number" disabled placeholder="请输入申请笼位"></el-input>
-							<el-input v-else v-model="state.form.returnNumber" disabled placeholder="请输入申请笼位"></el-input>
-						</el-form-item>
-					</el-col>
-
-					<el-col :span="12" class="mb20">
-						<el-form-item label="动物类型" prop="mentorDeptName">
-							<el-input v-model="state.form.categoryName" disabled placeholder="请输入动物类型"></el-input>
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
-		</el-dialog>
-	</div>
+  <div class="facilities-dialog-container">
+    <el-dialog title="详情" @close="closeDialog" :close-on-click-modal="false" v-model="props.showDialog" width="100%">
+      <el-form ref="expertDialogFormRef" disabled :model="state.form" size="default" label-width="140px" label-position="top">
+        <el-row :gutter="35">
+          <el-col :span="12" class="mb20">
+            <el-form-item label="申请人姓名" prop="memberName">
+              <el-input v-model="state.form.userName" disabled placeholder="请输入申请人姓名"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" class="mb20">
+            <el-form-item label="课题名称" prop="memberType">
+              <el-input v-model="state.form.projectGroupName" disabled placeholder="请输入课题名称"></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="35">
+          <el-col :span="12" class="mb20">
+            <el-form-item label="申请时间" prop="memberPhone">
+              <el-input v-model="state.form.createdTime" disabled placeholder="请输入申请时间"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" class="mb20">
+            <el-form-item label="申请状态" prop="mentorName">
+              <el-input v-model="state.form.approveStatus" disabled placeholder="请输入申请状态"></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="35">
+          <el-col :span="12" class="mb20">
+            <el-form-item :label="props.isReturnCageList ? '退还笼位(个)' : '申请笼位(个)'" prop="mentorObj">
+              <el-input v-if="!props.isReturnCageList" v-model="state.form.number" disabled placeholder="请输入申请笼位"></el-input>
+              <el-input v-else v-model="state.form.returnNumber" disabled placeholder="请输入申请笼位"></el-input>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12" class="mb20">
+            <el-form-item label="动物类型" prop="mentorDeptName">
+              <el-input v-model="state.form.categoryName" disabled placeholder="请输入动物类型"></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="数量(雄性+雌性)" prop="maleNumber">
+              <div style="width: 100%; display: flex; justify-content: space-between">
+                <el-input-number disabled placeholder="雄性数量" v-model="state.form.maleNumber" :min="0" />
+                +
+                <el-input-number disabled placeholder="雌性数量" v-model="state.form.famaleNumber" :min="0" />
+              </div>
+              <div style="width: 100%; margin-top: 10px">
+                <el-input disabled placeholder="总数" v-model="animalNumber" />
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="体重" prop="weight">
+              <el-input-number disabled v-model="state.form.weight" style="width: 100%" :min="0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="周龄" prop="age">
+              <el-input-number disabled v-model="state.form.age" style="width: 100%" :min="0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="饲养总天数" prop="feedingDay">
+              <el-input-number disabled v-model="state.form.feedingDay" style="width: 100%" :min="0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <h4 class="mb8 mt20">采购渠道</h4>
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="采购渠道" prop="buyFrom">
+              <el-radio-group disabled 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-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="动物到达时间" prop="comeTime">
+              <el-date-picker disabled v-model="state.form.comeTime" type="date" placeholder="请选择到达时间" clearable style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row class="mt10" :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="外购来源单位" prop="comeFromUnit">
+              <el-input disabled v-model="state.form.comeFromUnit" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row v-if="state.form.licenseNumberFile" class="mt10" :gutter="20">
+          <el-col :span="24" class="mb20">
+            <el-form-item label="生产许可证" prop="licenseNumberFile">
+              <el-link type="primary">{{ state.form.licenseNumberFile }}</el-link>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row v-if="state.form.animalTestDateFile" class="mt10" :gutter="20">
+          <el-col :span="24" class="mb20">
+            <el-form-item label="近三个月动物质量检测证明" prop="animalTestDateFile">
+              <el-link type="primary">{{ state.form.animalTestDateFile }}</el-link>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row v-if="state.form.envTestDateFile" class="mt10" :gutter="20">
+          <el-col :span="24" class="mb20">
+            <el-form-item label="近三个月饲养环境检测证明" prop="envTestDateFile">
+              <el-link type="primary">{{ state.form.envTestDateFile }}</el-link>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row v-if="state.form.cageAppointFile" class="mt10" :gutter="20">
+          <el-col :span="24" class="mb20">
+            <el-form-item label="笼位预约表" prop="cageAppointFile">
+              <el-link type="primary">{{ state.form.cageAppointFile }}</el-link>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row v-if="state.form.ethicsCheckFile" class="mt10" :gutter="20">
+          <el-col :span="24" class="mb20">
+            <el-form-item label="伦理审查表" prop="ethicsCheckFile">
+              <el-link type="primary">{{ state.form.ethicsCheckFile }}</el-link>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row v-if="state.form.ethicsAdviceFile" class="mt10" :gutter="20">
+          <el-col :span="24" class="mb20">
+            <el-form-item label="伦理意见表" prop="ethicsAdviceFile">
+              <el-link type="primary">{{ state.form.ethicsAdviceFile }}</el-link>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-dialog>
+  </div>
 </template>
 
 <script setup lang="ts" name="systemProDialog">
-import to from 'await-to-js';
-import { nextTick, reactive, ref } from 'vue';
-import dayjs from 'dayjs';
-
-import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
-import { ApproveStatusList } from '/@/constants/pageConstants';
-
-// 定义子组件向父组件传值/事件
-const props = defineProps({
-	code: { type: String, default: '' },
-	showDialog: { type: Boolean, default: false },
-	isReturnCageList: { type: Boolean, default: false },
-});
-const emit = defineEmits(['close']);
-
-const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi();
-
-const expertDialogFormRef = ref();
-
-const state = reactive({
-	form: {
-		userName: '',
-		number: 0,
-		approveStatus: '',
-		categoryName: '',
-		projectGroupName: '',
-		createdTime: '',
-		returnNumber: 0,
-	},
-	disabled: false,
-});
-
-// 打开弹窗
-const initForm = async (code: string) => {
-	const [err, res]: ToResponse = await to(platAnimalCageApplicationApi.getEntityById({ id: parseInt(code) }));
-	if (err) return;
-	await nextTick();
-	state.form = {
-		...res?.data,
-		approveStatus: ApproveStatusList.find((item) => item.id == res?.data?.approveStatus)?.name,
-		createdTime: dayjs(res?.data?.createdTime).format('YYYY-MM-DD'),
-	};
-};
-
-const closeDialog = () => {
-	emit('close');
-	expertDialogFormRef.value.resetFields();
-};
-
-// 暴露变量
-defineExpose({
-	initForm,
-});
+  import to from 'await-to-js'
+  import { nextTick, reactive, ref, computed } from 'vue'
+  import dayjs from 'dayjs'
+
+  import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
+  import { ApproveStatusList, ProcurementChannels, FeedingSpecial } from '/@/constants/pageConstants'
+  
+  const animalNumber = computed(() => {
+    const maleNumber = state.form.maleNumber || 0
+    const famaleNumber = state.form.famaleNumber || 0
+    return maleNumber + famaleNumber
+  })
+
+  // 定义子组件向父组件传值/事件
+  const props = defineProps({
+    code: { type: String, default: '' },
+    showDialog: { type: Boolean, default: false },
+    isReturnCageList: { type: Boolean, default: false }
+  })
+  const emit = defineEmits(['close'])
+
+  const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi()
+
+  const expertDialogFormRef = ref()
+
+  const state = reactive({
+    form: {
+      userName: '',
+      number: 0,
+      approveStatus: '',
+      categoryName: '',
+      projectGroupName: '',
+      createdTime: '',
+      returnNumber: 0,
+      maleNumber: 0,
+      famaleNumber: 0,
+      weight: 0,
+      age: 0,
+      feedingDay: 0,
+      buyFrom: ProcurementChannels.PURCHASED_BY_OTHERS,
+      comeTime: '',
+      comeFromUnit: '',
+      licenseNumberFile: '',
+      animalTestDateFile: '',
+      envTestDateFile: '',
+      hasFeedingSpecial: FeedingSpecial.HAVE_FEEDING_SPECIAL,
+      feedingSpecialDesc: '',
+      cageAppointFile: '',
+      ethicsCheckFile: '',
+      ethicsAdviceFile: ''
+    },
+    disabled: false
+  })
+
+  // 打开弹窗
+  const initForm = async (code: string) => {
+    const [err, res]: ToResponse = await to(platAnimalCageApplicationApi.getEntityById({ id: parseInt(code) }))
+    if (err) return
+    await nextTick()
+    state.form = {
+      ...res?.data,
+      approveStatus: ApproveStatusList.find((item) => item.id == res?.data?.approveStatus)?.name,
+      createdTime: dayjs(res?.data?.createdTime).format('YYYY-MM-DD')
+    }
+  }
+
+  const closeDialog = () => {
+    emit('close')
+    expertDialogFormRef.value.resetFields()
+  }
+
+  // 暴露变量
+  defineExpose({
+    initForm
+  })
 </script>
 <style lang="scss" scoped>
-:deep(.disUoloadSty .el-upload--picture-card) {
-	display: none; /* 上传按钮隐藏 */
-}
-:deep(.el-descriptions__label.el-descriptions__cell.is-bordered-label) {
-	width: 120px;
-}
-.el-upload-list--picture-card .el-upload-list__item-thumbnail {
-	height: 120px;
-}
-.fileName {
-	position: absolute;
-	width: 100%;
-	left: 0;
-	bottom: 0;
-	text-align: center;
-	font-size: 11px;
-	line-height: 1;
-	margin: 0;
-}
+  :deep(.disUoloadSty .el-upload--picture-card) {
+    display: none; /* 上传按钮隐藏 */
+  }
+  :deep(.el-descriptions__label.el-descriptions__cell.is-bordered-label) {
+    width: 120px;
+  }
+  .el-upload-list--picture-card .el-upload-list__item-thumbnail {
+    height: 120px;
+  }
+  .fileName {
+    position: absolute;
+    width: 100%;
+    left: 0;
+    bottom: 0;
+    text-align: center;
+    font-size: 11px;
+    line-height: 1;
+    margin: 0;
+  }
 </style>

+ 8 - 3
src/view/animal/application/index.vue

@@ -19,7 +19,10 @@
           />
         </el-form-item>
         <el-form-item prop="serialNo">
-          <el-input v-model="state.queryParams.userName" style="width: 100%" placeholder="申请人" clearable @change="search" />
+          <el-select v-model="state.queryParams.isMyself" placeholder="申请人" clearable @change="search">
+            <el-option label="我申请的" :value="1"></el-option>
+            <el-option label="全部" :value="0"></el-option>
+          </el-select>
         </el-form-item>
       </el-form>
       <div style="text-align: right">
@@ -162,7 +165,8 @@
       userName: '',
       startDate: '',
       endDate: '',
-      approveStatus: ''
+      approveStatus: '',
+      isMyself: 0
     },
     finished: false,
     loading: true,
@@ -181,7 +185,8 @@
       userName: '',
       startDate: '',
       endDate: '',
-      approveStatus: ''
+      approveStatus: '',
+      isMyself: 0
     }
     ;(state.finished = false), (state.loading = true), (state.list = [] as any[])
   }

+ 256 - 0
src/view/animal/myCage/index.vue

@@ -0,0 +1,256 @@
+<template>
+  <div class="cage-container">
+    <van-tabs v-model:active="activeStatus" @change="changeType">
+      <van-tab title="我的笼位" :name="MyCageType.MINE_CAGE"></van-tab>
+      <van-tab title="历史笼位" :name="MyCageType.MY_CAGE_HISTORY"></van-tab>
+    </van-tabs>
+
+    <div class="list-container">
+      <van-list v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad">
+        <van-cell v-for="item in state.pageList" :key="item" @click="handleCheckDetail(item)">
+          <template #default>
+            <div class="list">
+              <header class="flex justify-between">
+                <strong class="title">{{ `${item.userName}的笼位申请` }}</strong>
+                <van-tag v-if="item.approveStatus == ApproveStatus.WAIT_SUBMIT" type="warning">待提交</van-tag>
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.APPROVING" type="primary">审核中</van-tag>
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.PASS" type="success">通过</van-tag>
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.REVOKE" type="success">撤销</van-tag>
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.REFUSE" type="danger">拒绝</van-tag>
+              </header>
+              <p class="inst-title">
+                <span>课题名称</span>
+                <span class="title ml8">{{ item.projectGroupName }}</span>
+              </p>
+              <p class="inst-title">
+                <span>申请人</span>
+                <span class="title ml8">
+                  {{ item.userName }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>申请时间</span>
+                <span class="title ml8">
+                  {{ formatToChineseDate(item.createdTime) }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>开始时间</span>
+                <span class="title ml8">
+                  {{ formatToChineseDate(item.startDate) }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>申请笼位(个)</span>
+                <span class="title ml8">
+                  {{ item.number }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>退还笼位(个)</span>
+                <span class="title ml8">
+                  {{ item.returnNumber }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>申请状态</span>
+                <span class="title ml8">
+                  {{ formatApproveStatus(Number(item.approveStatus)) }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>动物类型</span>
+                <span class="title ml8">
+                  {{ item.categoryName }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>级别</span>
+                <span class="title ml8">
+                  {{ LeavelList.find((leaveItem) => leaveItem.id === item.level)?.name || '' }}
+                </span>
+              </p>
+              <footer class="flex justify-between mt16">
+                <span class="title">
+                  <el-button
+                    v-if="
+                      item.approveStatus === ApproveStatus.PASS.toString() &&
+                      item.returnStatus !== ReturnStatus.COMPLETE.toString() &&
+                      userInfos.id === item.userId
+                    "
+                    style="height: 25px"
+                    type="primary"
+                    @click.stop="handleRefundable(item)"
+                    >退还</el-button
+                  >
+                </span>
+                <span class="time">{{ formatDate(new Date(item.createdTime), 'YYYY-mm-dd') }}</span>
+              </footer>
+            </div>
+          </template>
+        </van-cell>
+      </van-list>
+    </div>
+
+    <DetailModal :showDialog="showDetailDialog" :isReturnCageList="false" ref="detailModalRef" @close="() => (showDetailDialog = false)" />
+    <ReturnCageDialog ref="returnCageDialogRef" :currentRefundableItemNumber="currentRefundableItemNumber" :getTableData="handleRefresh" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { reactive, ref, onMounted, defineAsyncComponent, ComponentPublicInstance } from 'vue'
+  import to from 'await-to-js'
+
+  import { formatDate, formatToChineseDate } from '/@/utils/formatTime'
+  import { ApproveStatus, ReturnStatus, LeavelList, ApproveStatusList, MyCageType } from '/@/constants/pageConstants'
+  import { useUserInfos } from '/@/hooks/useUserInfos'
+  import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
+
+  interface ReturnCageDialogInstance extends ComponentPublicInstance {
+    handleOpenRefundableDialog: (id: number) => void
+  }
+
+  interface DetailModalInstance extends ComponentPublicInstance {
+    initForm: (id: number) => void
+  }
+
+  const DetailModal = defineAsyncComponent(() => import('/@/view/animal/application/components/Detail.vue'))
+  const ReturnCageDialog = defineAsyncComponent(() => import('/@/view/animal/application/components/ReturnCageDialog.vue'))
+
+  const { userInfos } = useUserInfos()
+  const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi()
+
+  const returnCageDialogRef = ref<ReturnCageDialogInstance>()
+  const detailModalRef = ref<DetailModalInstance>()
+
+  const showDetailDialog = ref<boolean>(false)
+  const activeStatus = ref<MyCageType>(MyCageType.MINE_CAGE)
+  const currentRefundableItemNumber = ref<number>(0)
+
+  const state = reactive({
+    pageList: [],
+    loading: false,
+    finished: false,
+    queryParams: {
+      pageNum: 1,
+      pageSize: 25,
+      isMyself: 1
+    }
+  })
+
+  const changeType = () => {
+    state.queryParams.pageNum = 1
+    state.pageList = []
+    onLoad()
+  }
+
+  const formatApproveStatus = (status: number) => {
+    return ApproveStatusList.find((item) => item.id === status)?.name || ''
+  }
+
+  const handleRefundable = (row: any) => {
+    currentRefundableItemNumber.value = row.number
+    returnCageDialogRef.value.handleOpenRefundableDialog(row.id)
+  }
+
+  const handleRefresh = () => {
+    resetQueryParams()
+    onLoad()
+  }
+
+  const resetQueryParams = () => {
+    ;(state.pageList = []),
+      (state.loading = false),
+      (state.finished = false),
+      (state.queryParams = {
+        pageNum: 1,
+        pageSize: 25,
+        isMyself: 1
+      })
+  }
+
+  const onLoad = async (isSearch?: boolean) => {
+    state.loading = true
+    const apiRequest =
+      activeStatus.value === MyCageType.MINE_CAGE
+        ? platAnimalCageApplicationApi.getList(state.queryParams)
+        : platAnimalCageApplicationApi.getMyCageHistoryList(state.queryParams)
+
+    const [err, res]: ToResponse = await to(apiRequest)
+
+    state.loading = false
+
+    if (err) return
+    
+    const list = res?.data?.list || []
+
+    if (!isSearch) {
+      for (const item of list) {
+        state.pageList.push(item)
+      }
+      state.queryParams.pageNum++
+      if (list.length < state.queryParams.pageSize) {
+        state.finished = true
+      }
+    } else {
+      state.pageList = list
+    }
+  }
+
+  const handleCheckDetail = (row: any) => {
+    detailModalRef.value.initForm(row.id)
+    showDetailDialog.value = true
+  }
+
+  onMounted(() => {
+    onLoad()
+  })
+</script>
+
+<style lang="scss" scoped>
+  .cage-container {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    .list-container {
+      overflow-y: auto;
+      padding: 10px;
+      border-radius: 4px;
+      flex: 1;
+    }
+    .van-list {
+      .van-cell {
+        background-color: #fff;
+        + .van-cell {
+          margin-top: 10px;
+        }
+        header,
+        footer {
+          color: #333;
+        }
+        .title {
+          flex: 1;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          text-align: left;
+        }
+        .inst-title {
+          color: #333;
+          text-align: left;
+          flex: 1;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          margin-top: 4px;
+          span:first-child {
+            color: rgb(120, 120, 120);
+          }
+        }
+        .time {
+          color: #f69a4d;
+        }
+      }
+    }
+  }
+</style>

+ 1 - 1
src/view/animal/return/index.vue

@@ -222,7 +222,7 @@
     if (res && res.data && typeof res.data === 'string') {
       const link = document.createElement('a')
       link.href = `data:application/octet-stream;base64,${res.data}`
-      link.download = `笼位申请_${dayjs(new Date()).format('YYYY-MM-DD')}.xlsx`
+      link.download = `笼位退还_${dayjs(new Date()).format('YYYY-MM-DD')}.xlsx`
       link.style.display = 'none'
       document.body.appendChild(link)
       link.click()

+ 1 - 1
src/view/home/index.vue

@@ -43,7 +43,7 @@
           <img src="../../assets/img/培训考试.png" alt="" />
           <p>培训考试</p>
         </li>
-        <li @click="onRouterPush('/animal-application')" v-auth="'h5-home-animal'">
+        <li @click="onRouterPush('/my-cage')" v-auth="'h5-home-animal'">
           <img src="../../assets/img/动物笼位.png" alt="" />
           <p>动物笼位</p>
         </li>