gaoluyang
8 天以前 e449a5408265e4bd1f6c66f5be28a42efac444ee
src/pages/sales/receiptPaymentLedger/detail.vue
@@ -1,176 +1,268 @@
<template>
  <view class="receipt-payment-detail">
    <!-- 使用通用页面头部组件 -->
    <PageHeader title="客户往来详情"
    <PageHeader :title="pageTitle"
                @back="goBack" />
    <!-- 统计信息 -->
    <view class="summary-info"
          v-if="tableData.length > 0">
      <view class="summary-item">
        <text class="summary-label">总记录数</text>
        <text class="summary-value">{{ tableData.length }}</text>
    <!-- 第二级:项目下的客户往来 -->
    <template v-if="level === 'customer'">
      <!-- 项目应收摘要(需求七:应收账款管理重点信息摘要) -->
      <view class="summary-info">
        <view class="summary-item">
          <text class="summary-label">客户数</text>
          <text class="summary-value">{{ customerTotal }}</text>
        </view>
        <view class="summary-item">
          <text class="summary-label">签约总额</text>
          <text class="summary-value">{{ fmtMoney(sumOf("contractAmounts")) }}</text>
        </view>
        <view class="summary-item">
          <text class="summary-label">已回款</text>
          <text class="summary-value highlight">{{ fmtMoney(sumOf("receiptPaymentAmount")) }}</text>
        </view>
        <view class="summary-item">
          <text class="summary-label">应收总额</text>
          <text class="summary-value danger">{{ fmtMoney(sumOf("receiptableAmount")) }}</text>
        </view>
        <view class="summary-item">
          <text class="summary-label">超期合同</text>
          <text class="summary-value danger">{{ overdueCount }} 份</text>
        </view>
      </view>
      <view class="summary-item">
        <text class="summary-label">开票总金额</text>
        <text class="summary-value">{{ formatAmount(invoiceTotal) }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">回款总金额</text>
        <text class="summary-value highlight">{{ formatAmount(receiptTotal) }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">应收总金额</text>
        <text class="summary-value danger">{{ formatAmount(unReceiptTotal) }}</text>
      </view>
    </view>
    <!-- 回款记录明细列表 -->
    <view class="detail-list"
          v-if="tableData.length > 0">
      <view v-for="(item, index) in tableData"
            :key="index"
            class="detail-item">
        <view class="item-header">
          <view class="item-left">
            <view class="record-icon">
              <up-icon name="file-text"
                       size="16"
                       color="#ffffff"></up-icon>
      <view class="detail-list"
            v-if="customerList.length > 0">
        <view v-for="item in customerList"
              :key="item.customerId"
              class="detail-item"
              @click="goContracts(item)">
          <view class="item-header">
            <view class="item-left">
              <view class="record-icon">
                <up-icon name="account"
                         size="16"
                         color="#ffffff" />
              </view>
              <text class="item-index">{{ item.customerName || "未命名客户" }}</text>
            </view>
            <text class="item-index">{{ index + 1 }}</text>
            <up-tag :text="item.overdueCount > 0 ? `${item.overdueCount} 份超期` : '正常履约'"
                    :type="item.overdueCount > 0 ? 'error' : 'success'"
                    size="mini"
                    shape="circle" />
          </view>
          <view class="item-date">{{ item.happenTime }}</view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">开票金额(元)</text>
            <text class="detail-value">{{ formatAmount(item.invoiceTotal) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">回款金额(元)</text>
            <text class="detail-value highlight">{{ formatAmount(item.receiptPaymentAmount) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">应收金额(元)</text>
            <text class="detail-value danger">{{ formatAmount(item.unReceiptPaymentAmount) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">发生日期</text>
            <text class="detail-value">{{ item.receiptPaymentDate }}</text>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">签约总额</text>
              <text class="detail-value">{{ fmtMoney(item.contractAmounts) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">已回款</text>
              <text class="detail-value highlight">{{ fmtMoney(item.receiptPaymentAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">应收金额</text>
              <text class="detail-value danger">{{ fmtMoney(item.receiptableAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">退货金额</text>
              <text class="detail-value">{{ fmtMoney(item.returnAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">回款进度</text>
              <text class="detail-value"
                    :class="{ danger: item.overdueCount > 0 }">
                {{ calcRate(item.receiptPaymentAmount, item.contractAmounts) }}%
              </text>
            </view>
            <view class="detail-row">
              <text class="detail-label">合同数</text>
              <text class="detail-value">{{ item.contractCount || 0 }} 份</text>
            </view>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无回款记录</text>
    </view>
      <view v-else
            class="no-data">
        <text>该项目下暂无客户往来数据</text>
      </view>
    </template>
    <!-- 第三级:客户下的合同明细 -->
    <template v-else>
      <view class="detail-list"
            v-if="contractList.length > 0">
        <view v-for="(item, index) in contractList"
              :key="index"
              class="detail-item">
          <view class="item-header">
            <view class="item-left">
              <view class="record-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff" />
              </view>
              <text class="item-index">{{ item.contractName || item.salesContractNo || "未命名合同" }}</text>
            </view>
            <up-tag :text="contractStatusText(item)"
                    :type="contractStatusType(item)"
                    size="mini"
                    shape="circle" />
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">合同编号</text>
              <text class="detail-value">{{ item.salesContractNo || "-" }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">合同总额</text>
              <text class="detail-value">{{ fmtMoney(item.contractAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">已回款</text>
              <text class="detail-value"
                    :class="{ danger: item.isPaymentTimeout }">
                {{ fmtMoney(item.receiptPaymentAmount) }}
                ({{ calcRate(item.receiptPaymentAmount, item.contractAmount) }}%)
              </text>
            </view>
            <view class="detail-row">
              <text class="detail-label">应收金额</text>
              <text class="detail-value danger">{{ fmtMoney(item.receiptableAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">退货金额</text>
              <text class="detail-value">{{ fmtMoney(item.returnAmount) }}</text>
            </view>
          </view>
        </view>
      </view>
      <view v-else
            class="no-data">
        <text>该客户下暂无合同明细</text>
      </view>
    </template>
  </view>
</template>
<script setup>
  import { ref, computed, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { customerInteractions } from "@/api/salesManagement/receiptPayment.js";
  import { computed, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    customewTransactions,
    customewTransactionsDetails,
  } from "@/api/salesManagement/indicatorStats";
  import { unwrapList } from "@/utils/list";
  // 客户信息
  // level: customer = 项目下的客户汇总;contract = 客户下的合同明细
  const level = ref("customer");
  const projectId = ref("");
  const projectName = ref("");
  const customerId = ref("");
  const customerName = ref("");
  // 表格数据
  const tableData = ref([]);
  const customerList = ref([]);
  const contractList = ref([]);
  const customerTotal = ref(0);
  const invoiceTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.invoiceTotal) || 0);
    }, 0);
  const pageTitle = computed(() => {
    if (level.value === "customer") return projectName.value || "客户往来";
    return customerName.value || "合同明细";
  });
  const receiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.receiptPaymentAmount) || 0);
    }, 0);
  });
  const sumOf = field =>
    customerList.value.reduce((sum, item) => sum + (Number(item[field]) || 0), 0);
  const unReceiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.unReceiptPaymentAmount) || 0);
    }, 0);
  });
  const overdueCount = computed(() =>
    customerList.value.reduce((sum, item) => sum + (Number(item.overdueCount) || 0), 0)
  );
  // 返回上一页
  const goBack = () => {
    uni.removeStorageSync("customerId");
    uni.navigateBack();
  };
  // 获取页面参数
  const getPageParams = () => {
    // 从本地存储获取客户ID
    const storedCustomerId = uni.getStorageSync("customerId");
    if (storedCustomerId) {
      customerId.value = storedCustomerId;
    }
  };
  // 查询列表
  const getList = () => {
    if (!customerId.value) {
      uni.showToast({
        title: "客户信息缺失",
        icon: "error",
  const loadCustomers = () => {
    if (!projectId.value) return;
    uni.showLoading({ title: "加载中...", mask: true });
    customewTransactions({ projectId: projectId.value, current: -1, size: -1 })
      .then(res => {
        customerList.value = unwrapList(res);
        customerTotal.value = Number(res?.data?.total ?? customerList.value.length);
        uni.hideLoading();
      })
      .catch(() => {
        uni.hideLoading();
        uni.showToast({ title: "查询失败", icon: "none" });
      });
      return;
    }
  };
    showLoadingToast("加载中...");
    const param = {
  const loadContracts = () => {
    if (!projectId.value || !customerId.value) return;
    uni.showLoading({ title: "加载中...", mask: true });
    customewTransactionsDetails({
      projectId: projectId.value,
      customerId: customerId.value,
      current: -1,
      size: -1,
    };
    customerInteractions(param)
    })
      .then(res => {
        tableData.value = res.data;
        closeToast();
        contractList.value = unwrapList(res);
        uni.hideLoading();
      })
      .catch(() => {
        closeToast();
        uni.showToast({
          title: "查询失败",
          icon: "error",
        });
        uni.hideLoading();
        uni.showToast({ title: "查询失败", icon: "none" });
      });
  };
  // 格式化金额
  const formatAmount = amount => {
    return amount ? parseFloat(amount).toFixed(2) : "0.00";
  const goContracts = item => {
    customerId.value = item.customerId;
    customerName.value = item.customerName || "";
    level.value = "contract";
    loadContracts();
  };
  // 显示加载提示
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  // 返回:第三级退回第二级,第二级才真正退出页面
  const goBack = () => {
    if (level.value === "contract") {
      level.value = "customer";
      contractList.value = [];
      return;
    }
    uni.navigateBack();
  };
  // 关闭提示
  const closeToast = () => {
    uni.hideLoading();
  const contractStatusText = item => {
    if (item.isPaymentTimeout) return "超期未回";
    const amount = Number(item.contractAmount) || 0;
    if (amount > 0 && (Number(item.receiptPaymentAmount) || 0) >= amount) return "已结清";
    return "履行中";
  };
  onShow(() => {
    // 页面显示时获取参数并刷新列表
    getPageParams();
    getList();
  });
  const contractStatusType = item => {
    if (item.isPaymentTimeout) return "error";
    const amount = Number(item.contractAmount) || 0;
    if (amount > 0 && (Number(item.receiptPaymentAmount) || 0) >= amount) return "success";
    return "primary";
  };
  onMounted(() => {
    // 页面加载时获取参数并刷新列表
    getPageParams();
    getList();
  const fmtMoney = value => {
    const num = Number(value);
    if (!Number.isFinite(num)) return "¥ 0.00";
    return "¥ " + num.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  };
  const calcRate = (paid, amount) => {
    const total = Number(amount);
    if (!Number.isFinite(total) || total === 0) return "0.0";
    return (((Number(paid) || 0) / total) * 100).toFixed(1);
  };
  onLoad(options => {
    projectId.value = options?.projectId || "";
    projectName.value = options?.projectName ? decodeURIComponent(options.projectName) : "";
    if (!projectId.value) {
      uni.showToast({ title: "缺少项目参数", icon: "none" });
      return;
    }
    loadCustomers();
  });
</script>
@@ -249,6 +341,8 @@
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
  }
  .record-icon {
@@ -259,17 +353,16 @@
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }
  .item-index {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
  .item-date {
    font-size: 12px;
    color: #666;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .item-details {