index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. <template>
  2. <div class="cage-container">
  3. <!-- <van-tabs v-model:active="activeStatus" @change="changeType">
  4. <van-tab title="我的笼位" :name="MyCageType.MINE_CAGE"></van-tab>
  5. <van-tab title="历史笼位" :name="MyCageType.MY_CAGE_HISTORY"></van-tab>
  6. </van-tabs> -->
  7. <div class="search-wrap">
  8. <el-form :model="state.queryParams">
  9. <el-form-item prop="approveStatus">
  10. <el-select
  11. v-model="state.queryParams.approveStatus"
  12. style="width: 100%"
  13. placeholder="审批状态"
  14. clearable
  15. @change="search"
  16. >
  17. <el-option
  18. v-for="item in ApproveStatusList"
  19. :key="item.id"
  20. :label="item.name"
  21. :value="item.id"
  22. ></el-option>
  23. </el-select>
  24. </el-form-item>
  25. <el-form-item prop="dateRange">
  26. <el-input
  27. v-model="dateRangeText"
  28. placeholder="请选择申请时间范围"
  29. readonly
  30. style="width: 100%"
  31. @click="showCalendar = true"
  32. >
  33. <template #suffix>
  34. <el-icon
  35. v-if="dateRangeText"
  36. style="cursor: pointer; margin-right: 8px"
  37. @click.stop="clearDateRange"
  38. >
  39. <Close />
  40. </el-icon>
  41. <el-icon
  42. style="cursor: pointer"
  43. @click.stop="showCalendar = true"
  44. >
  45. <Calendar />
  46. </el-icon>
  47. </template>
  48. </el-input>
  49. </el-form-item>
  50. </el-form>
  51. </div>
  52. <div class="list-container">
  53. <van-list v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad">
  54. <van-cell v-for="item in state.pageList" :key="item" @click="handleCheckDetail(item)">
  55. <template #default>
  56. <div class="list">
  57. <header class="flex justify-between">
  58. <strong class="title">{{ `${item.userName}的笼位申请` }}</strong>
  59. <van-tag v-if="item.approveStatus == ApproveStatus.WAIT_SUBMIT" type="warning">待提交</van-tag>
  60. <van-tag v-else-if="item.approveStatus == ApproveStatus.APPROVING" type="primary">审核中</van-tag>
  61. <van-tag v-else-if="item.approveStatus == ApproveStatus.PASS" type="success">通过</van-tag>
  62. <van-tag v-else-if="item.approveStatus == ApproveStatus.REVOKE" type="success">撤销</van-tag>
  63. <van-tag v-else-if="item.approveStatus == ApproveStatus.REFUSE" type="danger">拒绝</van-tag>
  64. </header>
  65. <p class="inst-title">
  66. <span>课题名称</span>
  67. <span class="title ml8">{{ item.projectGroupName }}</span>
  68. </p>
  69. <p class="inst-title">
  70. <span>申请人</span>
  71. <span class="title ml8">
  72. {{ item.userName }}
  73. </span>
  74. </p>
  75. <p class="inst-title">
  76. <span>申请时间</span>
  77. <span class="title ml8">
  78. {{ formatToChineseDate(item.createdTime) }}
  79. </span>
  80. </p>
  81. <p class="inst-title">
  82. <span>开始时间</span>
  83. <span class="title ml8">
  84. {{ formatToChineseDate(item.startDate) }}
  85. </span>
  86. </p>
  87. <p class="inst-title">
  88. <span>申请笼位(个)</span>
  89. <span class="title ml8">
  90. {{ item.number }}
  91. </span>
  92. </p>
  93. <p class="inst-title">
  94. <span>退还笼位(个)</span>
  95. <span class="title ml8">
  96. {{ item.returnNumber }}
  97. </span>
  98. </p>
  99. <p class="inst-title">
  100. <span>申请状态</span>
  101. <span class="title ml8">
  102. {{ formatApproveStatus(Number(item.approveStatus)) }}
  103. </span>
  104. </p>
  105. <p class="inst-title">
  106. <span>动物类别</span>
  107. <span class="title ml8">
  108. {{animalTypeList.find((type) => type.id === item.categoryId)?.name}}
  109. </span>
  110. </p>
  111. <p class="inst-title">
  112. <span>品种品系</span>
  113. <span class="title ml8">
  114. {{ item.variety }}
  115. </span>
  116. </p>
  117. <p class="inst-title">
  118. <span>级别</span>
  119. <span class="title ml8">
  120. {{LeavelList.find((leaveItem) => leaveItem.id === item.level)?.name || ''}}
  121. </span>
  122. </p>
  123. <footer class="flex justify-between mt16">
  124. <span class="title">
  125. <el-button v-if="
  126. item.approveStatus === ApproveStatus.PASS.toString() &&
  127. item.returnStatus !== ReturnStatus.COMPLETE.toString() &&
  128. userInfos.id === item.userId
  129. " style="height: 25px" type="primary" @click.stop="handleRefundable(item)">退还</el-button>
  130. </span>
  131. <span class="time">{{ formatDate(new Date(item.createdTime), 'YYYY-mm-dd') }}</span>
  132. </footer>
  133. </div>
  134. </template>
  135. </van-cell>
  136. </van-list>
  137. </div>
  138. <DetailModal
  139. :showDialog="showDetailDialog"
  140. :isReturnCageList="false"
  141. ref="detailModalRef"
  142. @close="() => (showDetailDialog = false)"
  143. />
  144. <ReturnCageDialog
  145. ref="returnCageDialogRef"
  146. :currentRefundableItemNumber="currentRefundableItemNumber"
  147. :getTableData="handleRefresh"
  148. />
  149. <van-popup
  150. v-model:show="showCalendar"
  151. position="bottom"
  152. :style="{ height: '80vh' }"
  153. round
  154. >
  155. <van-calendar
  156. v-model:show="showCalendar"
  157. type="range"
  158. :min-date="new Date(1900, 0, 1)"
  159. @confirm="onDateRangeConfirm"
  160. />
  161. </van-popup>
  162. </div>
  163. </template>
  164. <script lang="ts" setup>
  165. import { reactive, ref, onMounted, defineAsyncComponent, ComponentPublicInstance, computed } from 'vue'
  166. import to from 'await-to-js'
  167. import dayjs from 'dayjs'
  168. import { Calendar, Close } from '@element-plus/icons-vue'
  169. import { formatDate, formatToChineseDate } from '/@/utils/formatTime'
  170. import { ApproveStatus, ReturnStatus, LeavelList, ApproveStatusList, MyCageType } from '/@/constants/pageConstants'
  171. import { useUserInfos } from '/@/hooks/useUserInfos'
  172. import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
  173. interface ReturnCageDialogInstance extends ComponentPublicInstance {
  174. handleOpenRefundableDialog: (id: number) => void
  175. }
  176. interface DetailModalInstance extends ComponentPublicInstance {
  177. initForm: (id: number) => void
  178. }
  179. const DetailModal = defineAsyncComponent(() => import('/@/view/animal/application/components/Detail.vue'))
  180. const ReturnCageDialog = defineAsyncComponent(() => import('/@/view/animal/application/components/ReturnCageDialog.vue'))
  181. const { userInfos } = useUserInfos()
  182. const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi()
  183. const returnCageDialogRef = ref<ReturnCageDialogInstance>()
  184. const detailModalRef = ref<DetailModalInstance>()
  185. const showDetailDialog = ref<boolean>(false)
  186. const activeStatus = ref<MyCageType>(MyCageType.MINE_CAGE)
  187. const currentRefundableItemNumber = ref<number>(0)
  188. const animalTypeList = ref([])
  189. const dateTime = ref<any>([])
  190. const showCalendar = ref(false)
  191. const selectedDateRange = ref<[Date, Date] | null>(null)
  192. const dateRangeText = computed(() => {
  193. if (selectedDateRange.value && selectedDateRange.value.length === 2) {
  194. const start = dayjs(selectedDateRange.value[0]).format('YYYY-MM-DD')
  195. const end = dayjs(selectedDateRange.value[1]).format('YYYY-MM-DD')
  196. return `${start} 至 ${end}`
  197. }
  198. return ''
  199. })
  200. const state = reactive({
  201. pageList: [] as any[],
  202. loading: false,
  203. finished: false,
  204. queryParams: {
  205. pageNum: 1,
  206. pageSize: 25,
  207. isMyself: 1,
  208. approveStatus: '',
  209. startDate: '',
  210. endDate: ''
  211. }
  212. })
  213. const getDicts = () => {
  214. Promise.all([platAnimalCageApplicationApi.getAnimalTypeList({})]).then(([animalType]) => {
  215. animalTypeList.value = animalType.data
  216. })
  217. }
  218. const changeType = () => {
  219. state.queryParams.pageNum = 1
  220. state.pageList = []
  221. onLoad()
  222. }
  223. const formatApproveStatus = (status: number) => {
  224. return ApproveStatusList.find((item) => item.id === status)?.name || ''
  225. }
  226. const handleRefundable = (row: any) => {
  227. currentRefundableItemNumber.value = row.number
  228. returnCageDialogRef.value.handleOpenRefundableDialog(row.id)
  229. }
  230. const handleRefresh = () => {
  231. resetQueryParams()
  232. onLoad()
  233. }
  234. const resetQueryParams = () => {
  235. state.pageList = []
  236. state.loading = false
  237. state.finished = false
  238. state.queryParams = {
  239. pageNum: 1,
  240. pageSize: 25,
  241. isMyself: 1,
  242. approveStatus: '',
  243. startDate: '',
  244. endDate: ''
  245. }
  246. }
  247. const setListPayload = () => {
  248. const payload: any = {
  249. ...state.queryParams
  250. }
  251. if (dateTime.value && dateTime.value.length === 2 && dateTime.value[0]) {
  252. payload.startDate = dayjs(dateTime.value[0]).format('YYYY-MM-DD')
  253. }
  254. if (dateTime.value && dateTime.value.length === 2 && dateTime.value[1]) {
  255. payload.endDate = dayjs(dateTime.value[1]).format('YYYY-MM-DD')
  256. }
  257. Object.entries(payload).forEach(([key, value]) => {
  258. if (value === '' || value === null) {
  259. delete payload[key as keyof typeof payload]
  260. }
  261. })
  262. return payload
  263. }
  264. const onLoad = async (isSearch?: boolean) => {
  265. state.loading = true
  266. const basePayload = setListPayload()
  267. const payload = {
  268. ...basePayload,
  269. pageNum: isSearch ? 1 : state.queryParams.pageNum
  270. }
  271. const apiRequest =
  272. activeStatus.value === MyCageType.MINE_CAGE
  273. ? platAnimalCageApplicationApi.getList(payload)
  274. : platAnimalCageApplicationApi.getMyCageHistoryList(payload)
  275. const [err, res]: ToResponse = await to(apiRequest)
  276. if (err) return
  277. const list = res?.data?.list || []
  278. state.loading = false
  279. if (!isSearch) {
  280. for (const item of list) {
  281. state.pageList.push(item)
  282. }
  283. state.queryParams.pageNum++
  284. if (list.length < state.queryParams.pageSize) {
  285. state.finished = true
  286. }
  287. } else {
  288. state.pageList = list
  289. state.queryParams.pageNum = 2
  290. state.finished = list.length < state.queryParams.pageSize
  291. }
  292. }
  293. const handleCheckDetail = (row: any) => {
  294. detailModalRef.value.initForm(row.id)
  295. showDetailDialog.value = true
  296. }
  297. const onDateRangeConfirm = (values: Date[]) => {
  298. if (values && values.length === 2) {
  299. selectedDateRange.value = [values[0], values[1]]
  300. dateTime.value = [
  301. dayjs(values[0]).format('YYYY-MM-DD'),
  302. dayjs(values[1]).format('YYYY-MM-DD')
  303. ]
  304. showCalendar.value = false
  305. search()
  306. }
  307. }
  308. const clearDateRange = () => {
  309. selectedDateRange.value = null
  310. dateTime.value = []
  311. state.queryParams.startDate = ''
  312. state.queryParams.endDate = ''
  313. search()
  314. }
  315. const search = () => {
  316. state.pageList = []
  317. state.queryParams.pageNum = 1
  318. state.finished = false
  319. onLoad(true)
  320. }
  321. onMounted(() => {
  322. onLoad()
  323. getDicts()
  324. })
  325. </script>
  326. <style lang="scss" scoped>
  327. .cage-container {
  328. position: relative;
  329. display: flex;
  330. flex-direction: column;
  331. .search-wrap {
  332. background: #fff;
  333. margin-bottom: 10px;
  334. padding: 15px;
  335. }
  336. .list-container {
  337. overflow-y: auto;
  338. padding: 10px;
  339. border-radius: 4px;
  340. flex: 1;
  341. }
  342. .van-list {
  343. .van-cell {
  344. background-color: #fff;
  345. +.van-cell {
  346. margin-top: 10px;
  347. }
  348. header,
  349. footer {
  350. color: #333;
  351. }
  352. .title {
  353. flex: 1;
  354. white-space: nowrap;
  355. overflow: hidden;
  356. text-overflow: ellipsis;
  357. text-align: left;
  358. }
  359. .inst-title {
  360. color: #333;
  361. text-align: left;
  362. flex: 1;
  363. overflow: hidden;
  364. white-space: nowrap;
  365. text-overflow: ellipsis;
  366. margin-top: 4px;
  367. span:first-child {
  368. color: rgb(120, 120, 120);
  369. }
  370. }
  371. .time {
  372. color: #f69a4d;
  373. }
  374. }
  375. }
  376. }
  377. </style>