gaoluyang
2026-06-16 c4d25912d11ab9059f8165c25a161634bb9b5e97
src/pages/oa/ApproveManage/approve-list/index.vue
@@ -1,109 +1,94 @@
<!--
  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>
@@ -119,71 +104,52 @@
</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;
@@ -204,31 +170,22 @@
        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";
@@ -238,9 +195,7 @@
        }
      })
      .catch(() => {
        if (page.current === 1) {
          list.value = [];
        }
        if (page.current === 1) list.value = [];
        pageStatus.value = "loadmore";
        uni.showToast({ title: "查询失败", icon: "none" });
      });
@@ -254,17 +209,16 @@
  };
  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 => {
@@ -272,52 +226,57 @@
      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>