yyb
2026-05-19 9d89dedf542a7b9f8e2549c44723771133f79ef2
src/pages/oa/ApproveManage/approve-list/index.vue
@@ -3,16 +3,297 @@
  路由:/pages/oa/ApproveManage/approve-list/index
-->
<template>
  <OaListPage v-if="config"
              :page-key="pageKey"
              :page-config="config" />
  <view class="approve-list-page sales-account">
    <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>
    </view>
    <scroll-view class="list-scroll"
                 scroll-y
                 :show-scrollbar="false"
                 @scrolltolower="loadMore">
      <view v-if="list.length"
            class="ledger-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" />
              </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">{{ item.applyTime || "-" }}</text>
            </view>
            <view v-if="item.finishTime"
                  class="detail-row">
              <text class="detail-label">完成时间</text>
              <text class="detail-value">{{ item.finishTime }}</text>
            </view>
          </view>
          <view v-if="canEdit(item) || item.isApprove"
                class="action-buttons">
            <up-button v-if="canEdit(item)"
                       class="action-btn"
                       size="small"
                       @click.stop="goEdit(item)">
              编辑
            </up-button>
            <up-button v-if="item.isApprove"
                       class="action-btn"
                       size="small"
                       type="primary"
                       @click.stop="handleApprove(item)">
              审批
            </up-button>
          </view>
        </view>
        <up-loadmore :status="pageStatus" />
      </view>
      <view v-else
            class="empty-wrap">
        <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 { 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";
  const pageKey = "ApproveManage/approve-list";
  const { config } = useOaPage(pageKey);
  const EDIT_STORAGE_KEY = "oa_approve_instance_edit_row";
  const userStore = useUserStore();
  const queryParams = reactive({
    keyword: "",
  });
  const list = ref([]);
  const pageStatus = ref("loadmore");
  const page = reactive({
    current: 1,
    size: 10,
    total: 0,
  });
  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 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 {
      page: {
        current: page.current,
        size: page.size,
      },
      approvalInstanceDto: 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 canEdit = item =>
    item?.status === "PENDING" &&
    String(item.applicantId) === String(userStore.id);
  const goEdit = item => {
    if (!item?.id) return;
    uni.setStorageSync(EDIT_STORAGE_KEY, item);
    uni.navigateTo({
      url: `${OA_NAV.approveListApply}?id=${item.id}`,
    });
  };
  const handleApprove = item => {
    if (!item?.id) return;
    uni.showToast({ title: "审批详情页待对接", icon: "none" });
  };
  onShow(() => {
    handleSearch();
  });
</script>
<style scoped lang="scss">
  @import "@/styles/sales-common.scss";
  .approve-list-page {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
  }
  .list-scroll {
    flex: 1;
    height: 0;
    padding-bottom: calc(80px + env(safe-area-inset-bottom));
  }
  .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;
  }
</style>