| | |
| | | <!-- |
| | | OA / 审批管理 / 审批列表 |
| | | 路由:/pages/oa/ApproveManage/approve-list/index |
| | | --> |
| | | <template> |
| | | <OaListPage v-if="config" |
| | | :page-key="pageKey" |
| | | :page-config="config" /> |
| | | <view class="oa-approval-page"> |
| | | <PageHeader title="审批列表" |
| | | @back="goBack" /> |
| | | |
| | | <view class="oa-toolbar"> |
| | | <view class="oa-filter-chip active-search"> |
| | | <up-icon name="search" |
| | | size="18" |
| | | color="#666" /> |
| | | <up-input v-model="queryParams.keyword" |
| | | class="chip-input" |
| | | placeholder="审批标题 / 审批编号" |
| | | clearable |
| | | border="none" |
| | | @confirm="handleSearch" /> |
| | | </view> |
| | | <view class="oa-icon-btn" |
| | | @click="handleSearch"> |
| | | <up-icon name="search" |
| | | size="20" |
| | | color="#2979ff" /> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="oa-list-scroll" |
| | | scroll-y |
| | | :show-scrollbar="false" |
| | | :style="{ height: listScrollHeight + 'px' }" |
| | | @scrolltolower="loadMore"> |
| | | <view v-if="list.length" |
| | | class="oa-card-list"> |
| | | <view v-for="item in list" |
| | | :key="item.id" |
| | | class="oa-card" |
| | | @click="openDetail(item)"> |
| | | <view class="oa-card-head"> |
| | | <view class="oa-card-title-wrap"> |
| | | <text class="oa-card-title">{{ item.title || item.instanceNo || "-" }}</text> |
| | | <text v-if="item.templateName" |
| | | class="oa-card-sub">{{ item.templateName }}</text> |
| | | </view> |
| | | <text :class="['oa-status', businessStatusClass(item.status)]"> |
| | | {{ statusText(item.status) }} |
| | | </text> |
| | | </view> |
| | | |
| | | <view class="oa-card-body"> |
| | | <view class="oa-info-grid"> |
| | | <view class="oa-info-row"> |
| | | <text class="oa-info-label">审批编号</text> |
| | | <text class="oa-info-value">{{ item.instanceNo || "-" }}</text> |
| | | </view> |
| | | <view v-if="item.businessName" |
| | | class="oa-info-row"> |
| | | <text class="oa-info-label">业务名称</text> |
| | | <text class="oa-info-value">{{ item.businessName }}</text> |
| | | </view> |
| | | <view class="oa-info-row"> |
| | | <text class="oa-info-label">申请人</text> |
| | | <text class="oa-info-value">{{ item.applicantName || "-" }}</text> |
| | | </view> |
| | | <view class="oa-info-row"> |
| | | <text class="oa-info-label">当前审批人</text> |
| | | <text class="oa-info-value">{{ currentApproverName(item) }}</text> |
| | | </view> |
| | | <view class="oa-info-row"> |
| | | <text class="oa-info-label">申请时间</text> |
| | | <text class="oa-info-value">{{ formatDateTime(item.applyTime) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="canModify(item) || item.isApprove" |
| | | class="oa-card-foot" |
| | | @click.stop> |
| | | <text v-if="canModify(item)" |
| | | class="oa-foot-btn btn-edit" |
| | | @click="goModify(item)">编辑</text> |
| | | <text v-if="item.isApprove" |
| | | class="oa-foot-btn btn-approve" |
| | | @click="handleApprove(item)">审批</text> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="pageStatus" /> |
| | | </view> |
| | | <view v-else |
| | | class="oa-empty"> |
| | | <up-empty mode="list" |
| | | text="暂无审批数据" /> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="fab-button" |
| | | @click="goAdd"> |
| | | <up-icon name="plus" |
| | | size="28" |
| | | color="#ffffff" /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | /** OA - 审批管理 - 审批列表 */ |
| | | import OaListPage from "../../_components/OaListPage.vue"; |
| | | import { useOaPage } from "../../_utils/useOaPage.js"; |
| | | import { onMounted, reactive, ref } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { listApprovalInstancePage } from "@/api/oa/approvalInstance.js"; |
| | | import { OA_NAV } from "@/config/oaPaths.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | import { |
| | | businessStatusClass, |
| | | businessStatusText, |
| | | canModifyInstance, |
| | | stashInstanceRow, |
| | | } from "../../_utils/approveListUtils.js"; |
| | | import { |
| | | inferReimburseModuleKeyFromInstance, |
| | | resolveFinReimbursementIdFromInstance, |
| | | stashReimburseEditFromApprove, |
| | | } from "../../_utils/reimburseApproveBridge.js"; |
| | | |
| | | const pageKey = "ApproveManage/approve-list"; |
| | | const { config } = useOaPage(pageKey); |
| | | const userStore = useUserStore(); |
| | | const queryParams = reactive({ keyword: "" }); |
| | | const list = ref([]); |
| | | const pageStatus = ref("loadmore"); |
| | | const page = reactive({ current: 1, size: 10, total: 0 }); |
| | | const listScrollHeight = ref(400); |
| | | |
| | | function calcListScrollHeight() { |
| | | const sys = uni.getSystemInfoSync(); |
| | | const statusBar = sys.statusBarHeight || 0; |
| | | const navBar = 44; |
| | | const toolbar = 56; |
| | | const fabGap = 16; |
| | | listScrollHeight.value = Math.max( |
| | | 200, |
| | | sys.windowHeight - statusBar - navBar - toolbar - fabGap |
| | | ); |
| | | } |
| | | |
| | | const statusText = status => businessStatusText(status); |
| | | |
| | | const formatDateTime = val => { |
| | | if (!val) return "-"; |
| | | return parseTime(val, "{y}-{m}-{d} {h}:{i}:{s}") || String(val); |
| | | }; |
| | | |
| | | const canModify = item => canModifyInstance(item, userStore); |
| | | |
| | | const currentApproverName = item => { |
| | | const tasks = item?.tasks; |
| | | if (!Array.isArray(tasks) || !tasks.length) return "-"; |
| | | const pending = tasks.find(t => t.taskStatus === "PENDING"); |
| | | if (pending?.approverName) return pending.approverName; |
| | | const names = [...new Set(tasks.map(t => t.approverName).filter(Boolean))]; |
| | | return names.length ? names.join("、") : "-"; |
| | | }; |
| | | |
| | | const buildListParams = () => { |
| | | const keyword = queryParams.keyword?.trim(); |
| | | const dto = {}; |
| | | if (keyword) { |
| | | if (/[\u4e00-\u9fa5]/.test(keyword)) { |
| | | dto.title = keyword; |
| | | } else { |
| | | dto.instanceNo = keyword; |
| | | } |
| | | } |
| | | return { current: page.current, size: page.size, ...dto }; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (pageStatus.value === "loading" || pageStatus.value === "nomore") return; |
| | | pageStatus.value = "loading"; |
| | | listApprovalInstancePage(buildListParams()) |
| | | .then(res => { |
| | | const pageData = res?.data || {}; |
| | | const records = pageData.records || []; |
| | | const total = pageData.total ?? 0; |
| | | if (page.current === 1) { |
| | | list.value = records; |
| | | } else { |
| | | list.value = [...list.value, ...records]; |
| | | } |
| | | page.total = total; |
| | | if (list.value.length >= total || records.length < page.size) { |
| | | pageStatus.value = "nomore"; |
| | | } else { |
| | | pageStatus.value = "loadmore"; |
| | | page.current += 1; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | if (page.current === 1) list.value = []; |
| | | pageStatus.value = "loadmore"; |
| | | uni.showToast({ title: "查询失败", icon: "none" }); |
| | | }); |
| | | }; |
| | | |
| | | const handleSearch = () => { |
| | | page.current = 1; |
| | | pageStatus.value = "loadmore"; |
| | | list.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (pageStatus.value === "loadmore") getList(); |
| | | }; |
| | | |
| | | const goBack = () => uni.navigateBack(); |
| | | const goAdd = () => uni.navigateTo({ url: OA_NAV.approveListTemplateSelect }); |
| | | |
| | | const openDetail = item => { |
| | | if (!item?.id) return; |
| | | stashInstanceRow(item); |
| | | uni.navigateTo({ url: `${OA_NAV.approveListDetail}?id=${item.id}` }); |
| | | }; |
| | | |
| | | const goModify = item => { |
| | | if (!canModify(item)) { |
| | | uni.showToast({ title: "仅进行中的本人申请可编辑", icon: "none" }); |
| | | return; |
| | | } |
| | | const mk = inferReimburseModuleKeyFromInstance(item); |
| | | if (mk) { |
| | | const rid = resolveFinReimbursementIdFromInstance(item); |
| | | if (rid == null) { |
| | | uni.showToast({ title: "无法修改:缺少报销单 ID", icon: "none" }); |
| | | return; |
| | | } |
| | | stashReimburseEditFromApprove(mk, rid); |
| | | uni.navigateTo({ |
| | | url: `${OA_NAV.reimburseForm}?moduleKey=${mk}&mode=edit&reimbursementId=${rid}`, |
| | | }); |
| | | return; |
| | | } |
| | | if (!item?.id) return; |
| | | stashInstanceRow(item); |
| | | uni.navigateTo({ url: `${OA_NAV.approveListApply}?id=${item.id}` }); |
| | | }; |
| | | |
| | | const handleApprove = item => { |
| | | if (!item?.id) return; |
| | | if (!item.isApprove) { |
| | | uni.showToast({ title: "当前审批无需您处理", icon: "none" }); |
| | | return; |
| | | } |
| | | stashInstanceRow(item); |
| | | uni.navigateTo({ url: `${OA_NAV.approveListApprove}?id=${item.id}` }); |
| | | }; |
| | | |
| | | onMounted(() => calcListScrollHeight()); |
| | | |
| | | onShow(() => { |
| | | calcListScrollHeight(); |
| | | handleSearch(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/sales-common.scss"; |
| | | @import "../../_styles/oa-approval-list.scss"; |
| | | |
| | | .active-search { |
| | | padding-right: 4px; |
| | | } |
| | | |
| | | .chip-input { |
| | | flex: 1; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | :deep(.chip-input .u-input__content) { |
| | | background: transparent !important; |
| | | padding: 0 !important; |
| | | } |
| | | </style> |