index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <template>
  2. <div class="entry-container">
  3. <div
  4. class="search-wrap"
  5. ref="searchWrapRef"
  6. >
  7. <el-form
  8. :model="state.queryParams"
  9. ref="queryRef"
  10. >
  11. <el-form-item prop="serialNo">
  12. <el-select
  13. v-model="state.queryParams.approveStatus"
  14. style="width: 100%"
  15. placeholder="审批状态"
  16. clearable
  17. @change="search"
  18. >
  19. <el-option
  20. v-for="item in ApproveStatusList"
  21. :key="item.id"
  22. :label="item.name"
  23. :value="item.id"
  24. ></el-option>
  25. </el-select>
  26. </el-form-item>
  27. <el-form-item prop="serialNo">
  28. <el-date-picker
  29. v-model="dateTime"
  30. type="daterange"
  31. style="width: 100%"
  32. start-placeholder="开始时间"
  33. end-placeholder="结束时间"
  34. clearable
  35. @change="search"
  36. />
  37. </el-form-item>
  38. <!-- <el-form-item prop="serialNo">
  39. <el-select
  40. v-model="state.queryParams.isMyself"
  41. placeholder="申请人"
  42. clearable
  43. @change="search"
  44. >
  45. <el-option
  46. label="我申请的"
  47. :value="1"
  48. ></el-option>
  49. <el-option
  50. label="全部"
  51. :value="0"
  52. ></el-option>
  53. </el-select>
  54. </el-form-item> -->
  55. </el-form>
  56. <div style="text-align: right">
  57. <el-button
  58. @click="handleExport"
  59. style="height: 25px"
  60. type="primary"
  61. >
  62. 导出
  63. </el-button>
  64. </div>
  65. </div>
  66. <div class="list-container">
  67. <van-list
  68. v-model:loading="state.loading"
  69. :finished="state.finished"
  70. finished-text="没有更多了"
  71. @load="onLoad"
  72. >
  73. <van-cell
  74. v-for="item in state.list"
  75. :key="item"
  76. @click="handleCheckDetail(item)"
  77. >
  78. <template #default>
  79. <div class="list">
  80. <header class="flex justify-between">
  81. <strong class="title">{{ `${item.userName}的笼位申请` }}</strong>
  82. <van-tag
  83. v-if="item.approveStatus == ApproveStatus.WAIT_SUBMIT"
  84. type="warning"
  85. >
  86. 待提交
  87. </van-tag>
  88. <van-tag
  89. v-else-if="item.approveStatus == ApproveStatus.APPROVING"
  90. type="primary"
  91. >
  92. 审核中
  93. </van-tag>
  94. <van-tag
  95. v-else-if="item.approveStatus == ApproveStatus.PASS"
  96. type="success"
  97. >
  98. 通过
  99. </van-tag>
  100. <van-tag
  101. v-else-if="item.approveStatus == ApproveStatus.REVOKE"
  102. type="success"
  103. >
  104. 撤销
  105. </van-tag>
  106. <van-tag
  107. v-else-if="item.approveStatus == ApproveStatus.REFUSE"
  108. type="danger"
  109. >
  110. 拒绝
  111. </van-tag>
  112. </header>
  113. <p class="inst-title">
  114. <span>课题名称</span>
  115. <span class="title ml8">{{ item.projectGroupName }}</span>
  116. </p>
  117. <p class="inst-title">
  118. <span>申请人</span>
  119. <span class="title ml8">
  120. {{ item.userName }}
  121. </span>
  122. </p>
  123. <p class="inst-title">
  124. <span>申请时间</span>
  125. <span class="title ml8">
  126. {{ formatToChineseDate(item.createdTime) }}
  127. </span>
  128. </p>
  129. <p class="inst-title">
  130. <span>开始时间</span>
  131. <span class="title ml8">
  132. {{ formatToChineseDate(item.startDate) }}
  133. </span>
  134. </p>
  135. <p class="inst-title">
  136. <span>申请笼位(个)</span>
  137. <span class="title ml8">
  138. {{ item.number }}
  139. </span>
  140. </p>
  141. <p class="inst-title">
  142. <span>退还笼位(个)</span>
  143. <span class="title ml8">
  144. {{ item.returnNumber }}
  145. </span>
  146. </p>
  147. <p class="inst-title">
  148. <span>申请状态</span>
  149. <span class="title ml8">
  150. {{ formatApproveStatus(Number(item.approveStatus)) }}
  151. </span>
  152. </p>
  153. <p class="inst-title">
  154. <span>动物类别</span>
  155. <span class="title ml8">
  156. {{ item.categoryName }}
  157. </span>
  158. </p>
  159. <p class="inst-title">
  160. <span>品种品系</span>
  161. <span class="title ml8">
  162. {{ item.variety }}
  163. </span>
  164. </p>
  165. <p class="inst-title">
  166. <span>级别</span>
  167. <span class="title ml8">
  168. {{ LeavelList.find((leaveItem) => leaveItem.id === item.level)?.name || '' }}
  169. </span>
  170. </p>
  171. <footer class="flex justify-between mt16">
  172. <span class="title">
  173. <el-button
  174. v-if="
  175. item.approveStatus === ApproveStatus.PASS.toString() &&
  176. item.returnStatus !== ReturnStatus.COMPLETE.toString() &&
  177. userInfos.id === item.userId
  178. "
  179. style="height: 25px"
  180. type="primary"
  181. @click.stop="handleRefundable(item)"
  182. >
  183. 退还
  184. </el-button>
  185. </span>
  186. <span class="time">{{ formatDate(new Date(item.createdTime), 'YYYY-mm-dd') }}</span>
  187. </footer>
  188. </div>
  189. </template>
  190. </van-cell>
  191. </van-list>
  192. </div>
  193. <ApplicationModal
  194. ref="cageApplicationModalRef"
  195. @refresh="handleRefresh"
  196. />
  197. <DetailModal
  198. :showDialog="showDetailDialog"
  199. :isReturnCageList="false"
  200. ref="detailModalRef"
  201. @close="() => (showDetailDialog = false)"
  202. />
  203. <ReturnCageDialog
  204. ref="returnCageDialogRef"
  205. :currentRefundableItemNumber="currentRefundableItemNumber"
  206. :getTableData="handleRefresh"
  207. />
  208. <van-floating-bubble
  209. v-model:offset="offset"
  210. icon="plus"
  211. @click="handleApplication"
  212. axis="y"
  213. />
  214. </div>
  215. </template>
  216. <script setup lang="ts">
  217. import { ref, reactive, onMounted, defineAsyncComponent } from 'vue'
  218. import to from 'await-to-js'
  219. import dayjs from 'dayjs'
  220. import { useRouter, useRoute } from 'vue-router'
  221. import { formatDate } from '/@/utils/formatTime'
  222. import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
  223. import { ApproveStatus, ReturnStatus, LeavelList, ApproveStatusList } from '/@/constants/pageConstants'
  224. import { useUserInfos } from '/@/hooks/useUserInfos'
  225. const ApplicationModal = defineAsyncComponent(() => import('/@/view/animal/application/components/Application.vue'))
  226. const DetailModal = defineAsyncComponent(() => import('/@/view/animal/application/components/Detail.vue'))
  227. const ReturnCageDialog = defineAsyncComponent(
  228. () => import('/@/view/animal/application/components/ReturnCageDialog.vue'),
  229. )
  230. const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi()
  231. const { userInfos } = useUserInfos()
  232. const router = useRouter()
  233. const route = useRoute()
  234. const cageApplicationModalRef = ref()
  235. const detailModalRef = ref()
  236. const returnCageDialogRef = ref()
  237. const showDetailDialog = ref<boolean>(false)
  238. const dateTime = ref<any>([])
  239. const offset = ref({ x: -80, y: 450 })
  240. const state = reactive({
  241. queryParams: {
  242. categoryId: null,
  243. categoryName: '',
  244. level: null,
  245. projectGroupName: '',
  246. pageNum: 1,
  247. pageSize: 20,
  248. userName: '',
  249. startDate: '',
  250. endDate: '',
  251. approveStatus: '',
  252. isMyself: userInfos.value.userRoles === 'project_group_member' ? 1 : 0,
  253. },
  254. finished: false,
  255. loading: true,
  256. list: [] as any[],
  257. })
  258. const currentRefundableItemNumber = ref<number>(0)
  259. const resetQueryParams = () => {
  260. state.queryParams = {
  261. categoryId: null,
  262. categoryName: '',
  263. level: null,
  264. projectGroupName: '',
  265. pageNum: 1,
  266. pageSize: 20,
  267. userName: '',
  268. startDate: '',
  269. endDate: '',
  270. approveStatus: '',
  271. isMyself: userInfos.value.userRoles === 'project_group_member' ? 1 : 0,
  272. }
  273. ;(state.finished = false), (state.loading = true), (state.list = [] as any[])
  274. }
  275. const setListPayload = (isExport?: boolean) => {
  276. const payload = {
  277. ...state.queryParams,
  278. pageSize: isExport ? 99999 : state.queryParams.pageSize,
  279. }
  280. if (dateTime.value && dateTime.value[0]) {
  281. payload.startDate = dayjs(dateTime.value[0]).format('YYYY-MM-DD')
  282. }
  283. if (dateTime.value && dateTime.value[1]) {
  284. payload.endDate = dayjs(dateTime.value[1]).format('YYYY-MM-DD')
  285. }
  286. Object.entries(payload).forEach(([key, value]) => {
  287. if (value === '' || value === null) {
  288. delete payload[key as keyof typeof payload]
  289. }
  290. })
  291. return payload
  292. }
  293. const formatToChineseDate = (dateStr: string) => {
  294. const date = new Date(dateStr)
  295. const year = date.getFullYear()
  296. const month = String(date.getMonth() + 1).padStart(2, '0')
  297. const day = String(date.getDate()).padStart(2, '0')
  298. return `${year}年${month}月${day}日`
  299. }
  300. const formatApproveStatus = (status: number) => {
  301. return ApproveStatusList.find((item) => item.id === status)?.name || ''
  302. }
  303. const onLoad = async (isSearch?: boolean) => {
  304. const [err, res]: ToResponse = await to(
  305. platAnimalCageApplicationApi.getList({
  306. ...setListPayload(),
  307. pageNum: isSearch ? 1 : state.queryParams.pageNum,
  308. }),
  309. )
  310. if (err) return
  311. if (res && res.data && res.data.list) {
  312. const list = res.data.list || []
  313. state.loading = false
  314. if (!isSearch) {
  315. for (const item of list) {
  316. state.list.push(item)
  317. }
  318. state.queryParams.pageNum++
  319. if (list.length < state.queryParams.pageSize) {
  320. state.finished = true
  321. }
  322. } else {
  323. state.list = list
  324. }
  325. }
  326. }
  327. const handleCheckDetail = (row: any) => {
  328. detailModalRef.value.initForm(row.id)
  329. showDetailDialog.value = true
  330. }
  331. const handleRefundable = (row: any) => {
  332. currentRefundableItemNumber.value = row.number
  333. returnCageDialogRef.value.handleOpenRefundableDialog(row.id)
  334. }
  335. const handleApplication = () => {
  336. cageApplicationModalRef.value.openDialog()
  337. }
  338. const handleRefresh = () => {
  339. resetQueryParams()
  340. onLoad()
  341. }
  342. const search = () => {
  343. onLoad(true)
  344. }
  345. const handleExport = async () => {
  346. const [err, res]: ToResponse = await to(
  347. platAnimalCageApplicationApi.getApplicationListExport({ ...setListPayload(true), base64Enable: 1 }),
  348. )
  349. if (err) return
  350. if (res && res.data && typeof res.data === 'string') {
  351. const link = document.createElement('a')
  352. link.href = `data:application/octet-stream;base64,${res.data}`
  353. link.download = `笼位申请_${dayjs(new Date()).format('YYYY-MM-DD')}.xlsx`
  354. link.style.display = 'none'
  355. document.body.appendChild(link)
  356. link.click()
  357. document.body.removeChild(link)
  358. }
  359. }
  360. onMounted(() => {
  361. const type = route.query.type
  362. if (type) {
  363. state.queryParams.approveStatus = type as string
  364. }
  365. onLoad()
  366. })
  367. </script>
  368. <style lang="scss" scoped>
  369. .entry-container {
  370. position: relative;
  371. display: flex;
  372. flex-direction: column;
  373. .search-wrap {
  374. background: #fff;
  375. margin-bottom: 10px;
  376. padding: 15px;
  377. }
  378. .list-container {
  379. overflow-y: auto;
  380. padding: 10px;
  381. border-radius: 4px;
  382. flex: 1;
  383. }
  384. .van-list {
  385. .van-cell {
  386. background-color: #fff;
  387. + .van-cell {
  388. margin-top: 10px;
  389. }
  390. header,
  391. footer {
  392. color: #333;
  393. }
  394. .title {
  395. flex: 1;
  396. white-space: nowrap;
  397. overflow: hidden;
  398. text-overflow: ellipsis;
  399. text-align: left;
  400. }
  401. .inst-title {
  402. color: #333;
  403. text-align: left;
  404. flex: 1;
  405. overflow: hidden;
  406. white-space: nowrap;
  407. text-overflow: ellipsis;
  408. margin-top: 4px;
  409. span:first-child {
  410. color: rgb(120, 120, 120);
  411. }
  412. }
  413. .time {
  414. color: #f69a4d;
  415. }
  416. }
  417. }
  418. }
  419. </style>