zhangwencui
2026-06-15 61a2db936dcbc3d5aebe8ff850bcaab76e3b3785
src/pages/oa/ApproveManage/approve-list/index.vue
@@ -1,18 +1,282 @@
<!--
  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>