|
|
@@ -0,0 +1,348 @@
|
|
|
+<template>
|
|
|
+ <div class="entry-container">
|
|
|
+ <div class="search-wrap" ref="searchWrapRef">
|
|
|
+ <el-form :model="state.queryParams" ref="queryRef">
|
|
|
+ <el-form-item prop="serialNo">
|
|
|
+ <el-select v-model="state.queryParams.approveStatus" style="width: 100%" placeholder="审批状态" clearable @change="search">
|
|
|
+ <el-option v-for="item in ApproveStatusList" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="serialNo">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="dateTime"
|
|
|
+ type="daterange"
|
|
|
+ style="width: 100%"
|
|
|
+ start-placeholder="开始时间"
|
|
|
+ end-placeholder="结束时间"
|
|
|
+ clearable
|
|
|
+ @change="search"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="serialNo">
|
|
|
+ <el-input v-model="state.queryParams.userName" style="width: 100%" placeholder="申请人" clearable @change="search" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div style="text-align: right">
|
|
|
+ <el-button @click="handleExport" style="height: 25px" type="primary">导出</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <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.list" :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="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>
|
|
|
+
|
|
|
+ <ApplicationModal ref="cageApplicationModalRef" @refresh="handleRefresh" />
|
|
|
+ <DetailModal :showDialog="showDetailDialog" :isReturnCageList="false" ref="detailModalRef" @close="() => (showDetailDialog = false)" />
|
|
|
+ <ReturnCageDialog ref="returnCageDialogRef" :currentRefundableItemNumber="currentRefundableItemNumber" :getTableData="handleRefresh" />
|
|
|
+
|
|
|
+ <van-floating-bubble v-model:offset="offset" icon="plus" @click="handleApplication" axis="y" />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref, reactive, onMounted, defineAsyncComponent } from 'vue'
|
|
|
+ import to from 'await-to-js'
|
|
|
+ import dayjs from 'dayjs'
|
|
|
+ import { storeToRefs } from 'pinia'
|
|
|
+ import { useRouter, useRoute } from 'vue-router'
|
|
|
+
|
|
|
+ import { formatDate } from '/@/utils/formatTime'
|
|
|
+ import { useUserInfo } from '/@/stores/userInfo'
|
|
|
+ import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal'
|
|
|
+ import { ApproveStatus, ReturnStatus, LeavelList, ApproveStatusList } from '/@/constants/pageConstants'
|
|
|
+
|
|
|
+ const ApplicationModal = defineAsyncComponent(() => import('/@/view/animal/application/components/Application.vue'))
|
|
|
+ const DetailModal = defineAsyncComponent(() => import('/@/view/animal/application/components/Detail.vue'))
|
|
|
+ const ReturnCageDialog = defineAsyncComponent(() => import('/@/view/animal/application/components/ReturnCageDialog.vue'))
|
|
|
+
|
|
|
+ const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi()
|
|
|
+ const stores = useUserInfo()
|
|
|
+ const { userInfos } = storeToRefs(stores)
|
|
|
+
|
|
|
+ const router = useRouter()
|
|
|
+ const route = useRoute()
|
|
|
+
|
|
|
+ const cageApplicationModalRef = ref()
|
|
|
+ const detailModalRef = ref()
|
|
|
+ const returnCageDialogRef = ref()
|
|
|
+ const showDetailDialog = ref<boolean>(false)
|
|
|
+ const dateTime = ref<any>([])
|
|
|
+ const offset = ref({ x: -80, y: 450 })
|
|
|
+ const state = reactive({
|
|
|
+ queryParams: {
|
|
|
+ categoryId: null,
|
|
|
+ categoryName: '',
|
|
|
+ level: null,
|
|
|
+ projectGroupName: '',
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 20,
|
|
|
+ userName: '',
|
|
|
+ startDate: '',
|
|
|
+ endDate: '',
|
|
|
+ approveStatus: ''
|
|
|
+ },
|
|
|
+ finished: false,
|
|
|
+ loading: true,
|
|
|
+ list: [] as any[]
|
|
|
+ })
|
|
|
+ const currentRefundableItemNumber = ref<number>(0)
|
|
|
+
|
|
|
+ const resetQueryParams = () => {
|
|
|
+ state.queryParams = {
|
|
|
+ categoryId: null,
|
|
|
+ categoryName: '',
|
|
|
+ level: null,
|
|
|
+ projectGroupName: '',
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 20,
|
|
|
+ userName: '',
|
|
|
+ startDate: '',
|
|
|
+ endDate: '',
|
|
|
+ approveStatus: ''
|
|
|
+ }
|
|
|
+ ;(state.finished = false), (state.loading = true), (state.list = [] as any[])
|
|
|
+ }
|
|
|
+
|
|
|
+ const setListPayload = (isExport?: boolean) => {
|
|
|
+ const payload = {
|
|
|
+ ...state.queryParams,
|
|
|
+ pageSize: isExport ? 99999 : state.queryParams.pageSize
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dateTime.value && dateTime.value[0]) {
|
|
|
+ payload.startDate = dayjs(dateTime.value[0]).format('YYYY-MM-DD')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dateTime.value && dateTime.value[1]) {
|
|
|
+ payload.endDate = dayjs(dateTime.value[1]).format('YYYY-MM-DD')
|
|
|
+ }
|
|
|
+
|
|
|
+ Object.entries(payload).forEach(([key, value]) => {
|
|
|
+ if (value === '' || value === null) {
|
|
|
+ delete payload[key as keyof typeof payload]
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return payload
|
|
|
+ }
|
|
|
+
|
|
|
+ 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}日`
|
|
|
+ }
|
|
|
+
|
|
|
+ const formatApproveStatus = (status: number) => {
|
|
|
+ return ApproveStatusList.find((item) => item.id === status)?.name || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ const onLoad = async (isSearch?: boolean) => {
|
|
|
+ const [err, res]: ToResponse = await to(
|
|
|
+ platAnimalCageApplicationApi.getList({
|
|
|
+ ...setListPayload(),
|
|
|
+ pageNum: isSearch ? 1 : state.queryParams.pageNum
|
|
|
+ })
|
|
|
+ )
|
|
|
+ if (err) return
|
|
|
+ const list = res?.data?.list || []
|
|
|
+ state.loading = false
|
|
|
+
|
|
|
+ if (!isSearch) {
|
|
|
+ for (const item of list) {
|
|
|
+ state.list.push(item)
|
|
|
+ }
|
|
|
+ state.queryParams.pageNum++
|
|
|
+ if (list.length < state.queryParams.pageSize) {
|
|
|
+ state.finished = true
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ state.list = list
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleCheckDetail = (row: any) => {
|
|
|
+ detailModalRef.value.initForm(row.id)
|
|
|
+ showDetailDialog.value = true
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleRefundable = (row: any) => {
|
|
|
+ currentRefundableItemNumber.value = row.number
|
|
|
+ returnCageDialogRef.value.handleOpenRefundableDialog(row.id)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleApplication = () => {
|
|
|
+ cageApplicationModalRef.value.openDialog()
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleRefresh = () => {
|
|
|
+ resetQueryParams()
|
|
|
+ onLoad()
|
|
|
+ }
|
|
|
+
|
|
|
+ const search = () => {
|
|
|
+ onLoad(true)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleExport = async () => {
|
|
|
+ const [err, res]: ToResponse = await to(platAnimalCageApplicationApi.getApplicationListExport({ ...setListPayload(true), base64Enable: 1 }))
|
|
|
+
|
|
|
+ if (err) return
|
|
|
+
|
|
|
+ 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.style.display = 'none'
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ document.body.removeChild(link)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ const type = route.query.type
|
|
|
+ if (type) {
|
|
|
+ state.queryParams.approveStatus = type as string
|
|
|
+ }
|
|
|
+ onLoad()
|
|
|
+ })
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+ .entry-container {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ .search-wrap {
|
|
|
+ background: #fff;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ padding: 15px;
|
|
|
+ }
|
|
|
+ .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>
|