| | |
| | | <!-- |
| | | OA / 审批管理 / 审批列表 |
| | | 路由:/pages/oa/ApproveManage/approve-list/index |
| | | --> |
| | | <template> |
| | | <view class="approve-list-page sales-account"> |
| | | <view class="oa-approval-page"> |
| | | <PageHeader title="审批列表" |
| | | @back="goBack" /> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input v-model="queryParams.keyword" |
| | | class="search-text" |
| | | placeholder="审批标题 / 审批编号" |
| | | clearable |
| | | @confirm="handleSearch" /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleSearch"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999" /> |
| | | </view> |
| | | |
| | | <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="list-scroll" |
| | | <scroll-view class="oa-list-scroll" |
| | | scroll-y |
| | | :show-scrollbar="false" |
| | | :style="{ height: listScrollHeight + 'px' }" |
| | | @scrolltolower="loadMore"> |
| | | <view v-if="list.length" |
| | | class="ledger-list"> |
| | | class="oa-card-list"> |
| | | <view v-for="item in list" |
| | | :key="item.id" |
| | | class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="file-text" |
| | | size="16" |
| | | color="#ffffff" /> |
| | | 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> |
| | | <text class="item-id">{{ item.title || item.instanceNo || "-" }}</text> |
| | | </view> |
| | | <u-tag :type="statusTagType(item.status)" |
| | | :text="statusText(item.status)" /> |
| | | </view> |
| | | <up-divider /> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">审批编号</text> |
| | | <text class="detail-value">{{ item.instanceNo || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">模板名称</text> |
| | | <text class="detail-value">{{ item.templateName || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">业务名称</text> |
| | | <text class="detail-value">{{ item.businessName || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">申请人</text> |
| | | <text class="detail-value">{{ item.applicantName || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">当前级别</text> |
| | | <text class="detail-value">{{ formatLevel(item.currentLevel) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">当前审批人</text> |
| | | <text class="detail-value">{{ currentApproverName(item) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">申请时间</text> |
| | | <text class="detail-value">{{ formatDateTime(item.applyTime) }}</text> |
| | | </view> |
| | | <view v-if="item.finishTime" |
| | | class="detail-row"> |
| | | <text class="detail-label">完成时间</text> |
| | | <text class="detail-value">{{ formatDateTime(item.finishTime) }}</text> |
| | | <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="action-buttons"> |
| | | <up-button v-if="canModify(item)" |
| | | class="action-btn" |
| | | size="small" |
| | | type="warning" |
| | | plain |
| | | @click.stop="goModify(item)"> |
| | | 编辑 |
| | | </up-button> |
| | | <up-button v-if="item.isApprove" |
| | | class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | @click.stop="handleApprove(item)"> |
| | | 审批 |
| | | </up-button> |
| | | 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="empty-wrap"> |
| | | class="oa-empty"> |
| | | <up-empty mode="list" |
| | | text="暂无审批数据" /> |
| | | </view> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref } from "vue"; |
| | | 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 EDIT_STORAGE_KEY = "oa_approve_instance_edit_row"; |
| | | const userStore = useUserStore(); |
| | | |
| | | const queryParams = reactive({ |
| | | keyword: "", |
| | | }); |
| | | |
| | | const queryParams = reactive({ keyword: "" }); |
| | | const list = ref([]); |
| | | const pageStatus = ref("loadmore"); |
| | | const page = reactive({ current: 1, size: 10, total: 0 }); |
| | | const listScrollHeight = ref(400); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | 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 STATUS_TEXT = { |
| | | PENDING: "进行中", |
| | | APPROVED: "已通过", |
| | | REJECTED: "已驳回", |
| | | }; |
| | | |
| | | const STATUS_TAG = { |
| | | PENDING: "warning", |
| | | APPROVED: "success", |
| | | REJECTED: "error", |
| | | }; |
| | | |
| | | const statusText = status => STATUS_TEXT[status] || status || "-"; |
| | | |
| | | const statusTagType = status => STATUS_TAG[status] || "info"; |
| | | |
| | | const formatLevel = level => { |
| | | if (level == null || level === "") return "-"; |
| | | return `第 ${level} 级`; |
| | | }; |
| | | const statusText = status => businessStatusText(status); |
| | | |
| | | const formatDateTime = val => { |
| | | if (!val) return "-"; |
| | | return parseTime(val, "{y}-{m}-{d} {h}:{i}:{s}") || String(val); |
| | | }; |
| | | |
| | | /** 是否本人发起的审批(兼容列表未返回 applicantId) */ |
| | | const isOwnApplication = item => { |
| | | const uid = userStore.id; |
| | | if (item?.applicantId != null && uid != null && uid !== "") { |
| | | return String(item.applicantId) === String(uid); |
| | | } |
| | | const loginName = userStore.nickName || userStore.name; |
| | | if (loginName && item?.applicantName) { |
| | | return String(item.applicantName).trim() === String(loginName).trim(); |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | /** 仅「进行中」且本人发起时可编辑(已通过/已驳回不显示编辑) */ |
| | | const canModify = item => item?.status === "PENDING" && isOwnApplication(item); |
| | | const canModify = item => canModifyInstance(item, userStore); |
| | | |
| | | const currentApproverName = item => { |
| | | const tasks = item?.tasks; |
| | |
| | | dto.instanceNo = keyword; |
| | | } |
| | | } |
| | | return { |
| | | page: { |
| | | current: page.current, |
| | | size: page.size, |
| | | }, |
| | | approvalInstanceDto: dto, |
| | | }; |
| | | 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"; |
| | |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | if (page.current === 1) { |
| | | list.value = []; |
| | | } |
| | | if (page.current === 1) list.value = []; |
| | | pageStatus.value = "loadmore"; |
| | | uni.showToast({ title: "查询失败", icon: "none" }); |
| | | }); |
| | |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (pageStatus.value === "loadmore") { |
| | | getList(); |
| | | } |
| | | if (pageStatus.value === "loadmore") getList(); |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | const goBack = () => uni.navigateBack(); |
| | | const goAdd = () => uni.navigateTo({ url: OA_NAV.approveListTemplateSelect }); |
| | | |
| | | 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 => { |
| | |
| | | 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; |
| | | uni.setStorageSync(EDIT_STORAGE_KEY, item); |
| | | uni.navigateTo({ |
| | | url: `${OA_NAV.approveListApply}?id=${item.id}`, |
| | | }); |
| | | stashInstanceRow(item); |
| | | uni.navigateTo({ url: `${OA_NAV.approveListApply}?id=${item.id}` }); |
| | | }; |
| | | |
| | | const handleApprove = item => { |
| | | if (!item?.id) return; |
| | | uni.showToast({ title: "审批详情页待对接", icon: "none" }); |
| | | 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"; |
| | | |
| | | .approve-list-page { |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-height: 100vh; |
| | | .active-search { |
| | | padding-right: 4px; |
| | | } |
| | | |
| | | .list-scroll { |
| | | .chip-input { |
| | | flex: 1; |
| | | height: 0; |
| | | padding-bottom: calc(80px + env(safe-area-inset-bottom)); |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .empty-wrap { |
| | | padding: 48px 20px; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | margin-top: 12px; |
| | | padding-top: 12px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .action-btn { |
| | | min-width: 72px; |
| | | :deep(.chip-input .u-input__content) { |
| | | background: transparent !important; |
| | | padding: 0 !important; |
| | | } |
| | | </style> |