gaoluyang
7 天以前 f07e539896420f2827c714c92b157654c90b71c2
中兴实强
1.供应商往来、客户网来展示字段修改
已添加4个文件
已修改5个文件
1404 ■■■■ 文件已修改
src/api/procurementManagement/invoiceEntry.js 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/indicatorStats.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 222 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/components/ProductTable.vue 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/components/ShipmentTable.vue 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/detail.vue 387 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/invoiceEntry.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
// é‡‡è´­-来票登记接口
import request from "@/utils/request";
// æŸ¥è¯¢é‡‡è´­åˆåŒå·
export function getProduct(query) {
  return request({
    url: "/purchase/ledger/getProduct",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢id采购合同号
export function getPurchaseNoById(query) {
  return request({
    url: "/purchase/ledger/getPurchaseNoById",
    method: "get",
    params: query,
  });
}
// æ ¹æ®é‡‡è´­åˆåŒå·æŸ¥è¯¢è¯¦ç»†ä¿¡æ¯
export function getInfo(query) {
  return request({
    url: "/purchase/ledger/getInfo",
    method: "get",
    params: query,
  });
}
// ä¸»åˆ—表查询
export function gePurchaseList(query) {
  return request({
    url: "/purchase/ledger/list",
    method: "get",
    params: query,
  });
}
// ä¸»åˆ—表查询
export function getRegistrationById(query) {
  return request({
    url: "/purchase/registration/getRegistrationById",
    method: "get",
    params: query,
  });
}
// æ–°å¢žç¼–辑来票登记
export function addOrUpdateRegistration(query) {
  return request({
    url: "/purchase/registration/addOrUpdateRegistration",
    method: "post",
    data: query,
  });
}
// åˆ é™¤æ¥ç¥¨ç™»è®°
export function delRegistration(query) {
  return request({
    url: "/purchase/registration/delRegistration",
    method: "delete",
    data: query,
  });
}
// ä¸»åˆ—表查询
export function gePurchaseListPage(query) {
  return request({
    url: "/purchase/ledger/listPage",
    method: "get",
    params: query,
  });
}
src/api/salesManagement/indicatorStats.js
@@ -36,3 +36,30 @@
    params: query,
  });
}
// å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€»
export function customerTransactionsSummary(query) {
  return request({
    url: "/metricStatistics/customerTransactionsSummary",
    method: "get",
    params: query,
  });
}
// å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»†
export function customerTransactionsProducts(query) {
  return request({
    url: "/metricStatistics/customerTransactionsProducts",
    method: "get",
    params: query,
  });
}
// å®¢æˆ·å¾€æ¥å‘货明细
export function customerTransactionsShipments(query) {
  return request({
    url: "/metricStatistics/customerTransactionsShipments",
    method: "get",
    params: query,
  });
}
src/router/index.js
@@ -131,6 +131,19 @@
      },
    ],
  },
  {
    path: "/customer-transactions-detail",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "",
        component: () => import("@/views/salesManagement/receiptPaymentLedger/detail.vue"),
        name: "CustomerTransactionsDetail",
        meta: { title: "客户往来详情", activeMenu: "/salesManagement/receiptPaymentLedger" },
      },
    ],
  },
  // è´¢åŠ¡ç®¡ç†æ¨¡å—è·¯ç”±
  // {
  //   path: "/financial",
src/views/procurementManagement/paymentLedger/index.vue
@@ -17,7 +17,7 @@
      <div></div>
    </div>
    <el-row :gutter="20">
      <el-col :span="14">
      <el-col :span="8">
        <div class="table_list">
          <el-table ref="multipleTable"
                    border
@@ -42,19 +42,15 @@
                             prop="contractAmounts"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
            <el-table-column label="付款金额(元)"
                             prop="paymentAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
            <el-table-column label="应付金额(元)"
                             prop="payableAmount"
                             show-overflow-tooltip>
              <template #default="{ row, column }">
                <el-text type="danger">
                  {{ formattedNumber(row, column, row.payableAmount) }}
                </el-text>
              </template>
            </el-table-column>
<!--            <el-table-column label="应付金额(元)"-->
<!--                             prop="payableAmount"-->
<!--                             show-overflow-tooltip>-->
<!--              <template #default="{ row, column }">-->
<!--                <el-text type="danger">-->
<!--                  {{ formattedNumber(row, column, row.payableAmount) }}-->
<!--                </el-text>-->
<!--              </template>-->
<!--            </el-table-column>-->
          </el-table>
          <pagination v-show="total > 0"
                      @pagination="paginationSearch"
@@ -64,25 +60,54 @@
                      :limit="page.size" />
        </div>
      </el-col>
      <el-col :span="10">
      <el-col :span="16">
        <div class="table_list">
          <PIMTable rowKey="id"
                    :column="tableColumnSon"
                    :tableData="originalTableDataSon"
                    :isSelection="false"
                    :isShowPagination="true"
                    :page="sonPage"
                    :tableLoading="tableLoadingSon"
                    :isShowSummary="isShowSummarySon"
                    :summaryMethod="summarizeMainTable1"
                    height="calc(100vh - 18.5em)"
                    @pagination="sonPaginationSearch">
            <template #payableAmountSlot="{ row }">
              <el-text type="danger">
                {{ parseFloat(row.payableAmount).toFixed(2) }}
              </el-text>
            </template>
          </PIMTable>
          <div class="table-header" v-if="selectedSupplierName">
            <span class="supplier-title">供应商:{{ selectedSupplierName }}</span>
          </div>
          <el-table border
                    v-loading="tableLoadingSon"
                    :data="originalTableDataSon"
                    :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
                    height="calc(100vh - 20em)"
                    style="width: 100%"
                    tooltip-effect="dark"
                    :show-summary="isShowSummarySon"
                    :summary-method="summarizeMainTable1">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="合同签订日期"
                             prop="executionDate"
                             show-overflow-tooltip/>
            <el-table-column label="采购合同号"
                             prop="purchaseContractNumber"
                             show-overflow-tooltip/>
            <el-table-column label="项目名称"
                             prop="projectName"
                             show-overflow-tooltip/>
            <el-table-column label="合同金额(元)"
                             prop="contractAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber" />
<!--            <el-table-column label="收货状态"-->
<!--                             prop="status"-->
<!--                             show-overflow-tooltip-->
<!--                             width="100">-->
<!--              <template #default="{ row }">-->
<!--                <el-tag :type="getReceiptStatusType(row.status)" size="small">-->
<!--                  {{ receiptStatusText[row.status] || '未知状态' }}-->
<!--                </el-tag>-->
<!--              </template>-->
<!--            </el-table-column>-->
          </el-table>
          <pagination v-show="sonPage.total > 0"
                      @pagination="sonPaginationSearch"
                      :total="sonPage.total"
                      :layout="sonPage.layout"
                      :page="sonPage.current"
                      :limit="sonPage.size" />
        </div>
      </el-col>
    </el-row>
@@ -90,12 +115,12 @@
</template>
<script setup>
  import { ref, toRefs } from "vue";
  import { ref, toRefs, reactive, getCurrentInstance } from "vue";
  import { Search } from "@element-plus/icons-vue";
  import {
    paymentLedgerList,
    paymentRecordList,
  } from "@/api/procurementManagement/paymentLedger.js";
  import { gePurchaseListPage } from "@/api/procurementManagement/invoiceEntry.js";
  import Pagination from "../../../components/PIMTable/Pagination.vue";
  const tableData = ref([]);
@@ -119,48 +144,13 @@
  const isShowSummary = ref(true);
  const { searchForm } = toRefs(data);
  const currentSupplierId = ref("");
  const selectedSupplierName = ref("");
  const rowClick = row => {
    currentSupplierId.value = row.supplierId;
    selectedSupplierName.value = row.supplierName;
    sonPage.current = 1;
    getPaymenRecordtList(row.supplierId);
    getPurchaseList(row.supplierName);
  };
  // å­æ¨¡å—
  const tableColumnSon = ref([
    {
      label: "合同签订日期",
      prop: "executionDate",
      width: 110,
    },
    {
      label: "采购合同号",
      prop: "purchaseContractNumber",
      width: 150,
    },
    {
      label: "合同金额(元)",
      prop: "contractAmount",
      width: 200,
      formatData: params => {
        return params ? parseFloat(params).toFixed(2) : 0;
      },
    },
    {
      label: "付款金额(元)",
      prop: "paymentAmount",
      width: 200,
      formatData: params => {
        return params ? parseFloat(params).toFixed(2) : 0;
      },
    },
    {
      label: "应付金额(元)",
      dataType: "slot",
      width: 200,
      prop: "payableAmount",
      slot: "payableAmountSlot",
    },
  ]);
  const tableDataSon = ref([]);
  const originalTableDataSon = ref([]);
  const tableLoadingSon = ref(false);
  const isShowSummarySon = ref(true);
@@ -170,32 +160,23 @@
  const summarizeMainTable = param => {
    return proxy.summarizeTable(
      param,
      ["contractAmounts", "paymentAmount", "payableAmount"],
      ["contractAmounts", "payableAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        ticketsNum: { noDecimal: true },
        futureTickets: { noDecimal: true },
      }
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable1 = param => {
    let summarizeTable = proxy.summarizeTable(
    return proxy.summarizeTable(
      param,
      ["contractAmount", "invoiceAmount", "paymentAmount"],
      ["contractAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        ticketsNum: { noDecimal: true },
        futureTickets: { noDecimal: true },
      }
    );
    if (originalTableDataSon.value.length > 0) {
      summarizeTable[summarizeTable.length - 1] =
        originalTableDataSon.value[
          originalTableDataSon.value.length - 1
        ].payableAmount.toFixed(2);
    } else {
      summarizeTable[summarizeTable.length - 1] = 0.0;
    }
    return summarizeTable;
  };
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
@@ -219,16 +200,20 @@
      total.value = result.total || 0;
      if (tableData.value.length > 0) {
        currentSupplierId.value = tableData.value[0].supplierId;
        selectedSupplierName.value = tableData.value[0].supplierName;
        sonPage.current = 1;
        getPaymenRecordtList(tableData.value[0].supplierId);
        getPurchaseList(tableData.value[0].supplierName);
      } else {
        originalTableDataSon.value = [];
        selectedSupplierName.value = "";
      }
    });
  };
  const getPaymenRecordtList = supplierId => {
  const getPurchaseList = supplierName => {
    tableLoadingSon.value = true;
    paymentRecordList({
      supplierId: supplierId,
    gePurchaseListPage({
      supplierName: supplierName,
      current: sonPage.current,
      size: sonPage.size,
    })
@@ -236,9 +221,8 @@
        tableLoadingSon.value = false;
        let result = res.data;
        if (Array.isArray(result)) {
          tableDataSon.value = result;
          originalTableDataSon.value = result;
          sonPage.total = result.length;
          handlePagination({ page: sonPage.current, limit: sonPage.size });
        } else {
          originalTableDataSon.value = result.records || [];
          sonPage.total = result.total || 0;
@@ -248,30 +232,37 @@
        tableLoadingSon.value = false;
      });
  };
  const handlePagination = ({ page, limit }) => {
    console.log(page, limit);
    sonPage.current = page;
    sonPage.size = limit;
    const start = (page - 1) * limit;
    const end = start + limit;
    originalTableDataSon.value = tableDataSon.value.slice(start, end);
  };
  const sonPaginationSearch = pagination => {
    // æŽ¥æ”¶åˆ†é¡µå™¨å‚æ•° { page, limit }
    sonPage.current = pagination.page;
    sonPage.size = pagination.limit;
    getPaymenRecordtList(currentSupplierId.value);
    getPurchaseList(selectedSupplierName.value);
  };
  const formattedNumber = (row, column, cellValue) => {
    if (column.property !== "supplierName") {
    if (cellValue !== undefined && cellValue !== null && !isNaN(cellValue)) {
      return parseFloat(cellValue).toFixed(2);
    } else {
      return cellValue;
    }
    return "0.00";
  };
  // æ”¶è´§çŠ¶æ€æ–‡æœ¬æ˜ å°„
  const receiptStatusText = {
    1: '待收货',
    2: '收货中',
    3: '已收货'
  };
  // æ”¶è´§çŠ¶æ€æ ‡ç­¾ç±»åž‹
  const getReceiptStatusType = (status) => {
    const typeMap = {
      1: 'info',
      2: 'warning',
      3: 'success'
    };
    return typeMap[status] || 'info';
  };
  getList();
</script>
@@ -291,4 +282,17 @@
  .pagination-container {
    margin-top: 0;
  }
  .table-header {
    margin-bottom: 10px;
    padding: 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
    .supplier-title {
      font-weight: bold;
      font-size: 14px;
      color: #303133;
    }
  }
</style>
src/views/salesManagement/receiptPaymentLedger/components/ProductTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,229 @@
<template>
  <div class="product-table-container">
    <el-table
      :data="list"
      v-loading="loading"
      size="small"
      border
      class="custom-table"
      :height="tableHeight"
    >
      <el-table-column label="合同号" prop="salesContractNo" width="160" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="contract-no">{{ row.salesContractNo || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="产品名称" prop="productName" min-width="180" show-overflow-tooltip />
      <el-table-column label="规格型号" prop="model" width="140" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="model-text">{{ row.model || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="单位" prop="unit" width="80" align="center" />
      <el-table-column label="合同数量" prop="contractQuantity" align="right" width="100">
        <template #default="{ row }">
          <span class="quantity">{{ formatNumber(row.contractQuantity) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="合同单价" prop="taxInclusiveUnitPrice" align="right" width="110">
        <template #default="{ row }">
          <span class="price">Â¥{{ formatMoney(row.taxInclusiveUnitPrice) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="合同金额" prop="contractAmount" align="right" width="130">
        <template #default="{ row }">
          <span class="amount">Â¥{{ formatMoney(row.contractAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="已发货数量" prop="shippedQuantity" align="right" width="100">
        <template #default="{ row }">
          <span class="shipped-qty">{{ formatNumber(row.shippedQuantity) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="已发货金额" prop="shippedAmount" align="right" width="130">
        <template #default="{ row }">
          <span class="shipped-amt">Â¥{{ formatMoney(row.shippedAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="发货进度" width="180" align="center">
        <template #default="{ row }">
          <div class="progress-cell">
            <el-progress
              :percentage="calcPercent(row.shippedQuantity, row.contractQuantity)"
              :stroke-width="8"
              :show-text="false"
              :color="getProgressColor(calcPercent(row.shippedQuantity, row.contractQuantity))"
            />
            <span class="progress-text">{{ calcPercent(row.shippedQuantity, row.contractQuantity) }}%</span>
          </div>
        </template>
      </el-table-column>
    </el-table>
    <div class="pagination-wrapper">
      <pagination
        v-show="total > 0"
        :total="total"
        :page="queryParams.pageNum"
        :limit="queryParams.pageSize"
        @pagination="handlePagination"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
import { customerTransactionsProducts } from '@/api/salesManagement/indicatorStats.js'
import Pagination from '@/components/PIMTable/Pagination.vue'
const props = defineProps({
  customerId: {
    type: [Number, String],
    required: true
  }
})
const loading = ref(false)
const list = ref([])
const total = ref(0)
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  customerId: null
})
const tableHeight = computed(() => `calc(100vh - 32em)`)
const getList = () => {
  loading.value = true
  queryParams.value.customerId = props.customerId
  customerTransactionsProducts(queryParams.value)
    .then(res => {
      if (res.code === 200) {
        list.value = res.data?.records || []
        total.value = res.data?.total || 0
      }
    })
    .finally(() => {
      loading.value = false
    })
}
const handlePagination = ({ page, limit }) => {
  queryParams.value.pageNum = page
  queryParams.value.pageSize = limit
  getList()
}
const formatMoney = (value) => {
  if (!value) return '0.00'
  return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
const formatNumber = (value) => {
  if (!value) return '0'
  return Number(value).toLocaleString('zh-CN')
}
const calcPercent = (shipped, total) => {
  if (!total || total === 0) return 0
  return Math.min(100, Math.round((shipped / total) * 100))
}
const getProgressColor = (percent) => {
  if (percent < 30) return '#f56c6c'
  if (percent < 60) return '#e6a23c'
  if (percent < 80) return '#409eff'
  return '#67c23a'
}
watch(() => props.customerId, (val) => {
  if (val) {
    queryParams.value.pageNum = 1
    getList()
  }
}, { immediate: true })
</script>
<style scoped lang="scss">
.product-table-container {
  .custom-table {
    border-radius: 8px;
    overflow: hidden;
    :deep(.el-table__header-wrapper) {
      th {
        background: #f8f9fb !important;
        font-weight: 600;
        color: #303133;
      }
    }
    :deep(.el-table__row) {
      transition: all 0.2s;
      &:hover > td {
        background: #f5f7fa !important;
      }
    }
    .contract-no {
      color: #409eff;
      font-weight: 500;
    }
    .model-text {
      color: #606266;
    }
    .quantity {
      font-weight: 500;
      color: #303133;
    }
    .price {
      color: #606266;
    }
    .amount {
      font-weight: 600;
      color: #303133;
    }
    .shipped-qty {
      color: #67c23a;
      font-weight: 500;
    }
    .shipped-amt {
      color: #67c23a;
      font-weight: 500;
    }
    .progress-cell {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 0 10px;
      .el-progress {
        flex: 1;
      }
      .progress-text {
        font-size: 12px;
        color: #606266;
        min-width: 32px;
        text-align: right;
      }
    }
  }
}
.pagination-wrapper {
  padding: 16px 0 0;
  display: flex;
  justify-content: flex-end;
}
</style>
src/views/salesManagement/receiptPaymentLedger/components/ShipmentTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,197 @@
<template>
  <div class="shipment-table-container">
    <el-table
      :data="list"
      v-loading="loading"
      size="small"
      border
      class="custom-table"
      :height="tableHeight"
    >
      <el-table-column label="合同号" prop="salesContractNo" width="160" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="contract-no">{{ row.salesContractNo || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="发货单号" prop="shippingNo" width="160" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="shipping-no">{{ row.shippingNo || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="产品名称" prop="productName" min-width="160" show-overflow-tooltip />
      <el-table-column label="规格型号" prop="model" width="140" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="model-text">{{ row.model || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="发货数量" prop="shippingQuantity" align="right" width="100">
        <template #default="{ row }">
          <span class="quantity">{{ formatNumber(row.shippingQuantity) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="发货金额" prop="shippingAmount" align="right" width="130">
        <template #default="{ row }">
          <span class="amount">Â¥{{ formatMoney(row.shippingAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="出库批号" prop="batchNo" width="160" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="batch-no">{{ row.batchNo || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="发货日期" prop="shippingDate" width="120" align="center">
        <template #default="{ row }">
          <span class="date-text">{{ row.shippingDate || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="审批状态" prop="approvalStatus" width="100" align="center">
        <template #default="{ row }">
          <el-tag
            :type="row.approvalStatus === 1 ? 'success' : 'warning'"
            size="small"
            effect="light"
            round
          >
            {{ row.approvalStatus === 1 ? '已审' : '待审' }}
          </el-tag>
        </template>
      </el-table-column>
    </el-table>
    <div class="pagination-wrapper">
      <pagination
        v-show="total > 0"
        :total="total"
        :page="queryParams.pageNum"
        :limit="queryParams.pageSize"
        @pagination="handlePagination"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
import { customerTransactionsShipments } from '@/api/salesManagement/indicatorStats.js'
import Pagination from '@/components/PIMTable/Pagination.vue'
const props = defineProps({
  customerId: {
    type: [Number, String],
    required: true
  }
})
const loading = ref(false)
const list = ref([])
const total = ref(0)
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  customerId: null
})
const tableHeight = computed(() => `calc(100vh - 32em)`)
const getList = () => {
  loading.value = true
  queryParams.value.customerId = props.customerId
  customerTransactionsShipments(queryParams.value)
    .then(res => {
      if (res.code === 200) {
        list.value = res.data?.records || []
        total.value = res.data?.total || 0
      }
    })
    .finally(() => {
      loading.value = false
    })
}
const handlePagination = ({ page, limit }) => {
  queryParams.value.pageNum = page
  queryParams.value.pageSize = limit
  getList()
}
const formatMoney = (value) => {
  if (!value) return '0.00'
  return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
const formatNumber = (value) => {
  if (!value) return '0'
  return Number(value).toLocaleString('zh-CN')
}
watch(() => props.customerId, (val) => {
  if (val) {
    queryParams.value.pageNum = 1
    getList()
  }
}, { immediate: true })
</script>
<style scoped lang="scss">
.shipment-table-container {
  .custom-table {
    border-radius: 8px;
    overflow: hidden;
    :deep(.el-table__header-wrapper) {
      th {
        background: #f8f9fb !important;
        font-weight: 600;
        color: #303133;
      }
    }
    :deep(.el-table__row) {
      transition: all 0.2s;
      &:hover > td {
        background: #f5f7fa !important;
      }
    }
    .contract-no {
      color: #409eff;
      font-weight: 500;
    }
    .shipping-no {
      color: #909399;
      font-size: 12px;
    }
    .model-text {
      color: #606266;
    }
    .quantity {
      font-weight: 500;
      color: #303133;
    }
    .amount {
      font-weight: 600;
      color: #67c23a;
    }
    .batch-no {
      color: #606266;
      font-size: 12px;
    }
    .date-text {
      color: #606266;
    }
  }
}
.pagination-wrapper {
  padding: 16px 0 0;
  display: flex;
  justify-content: flex-end;
}
</style>
src/views/salesManagement/receiptPaymentLedger/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,387 @@
<template>
  <div class="customer-detail-page">
    <!-- é¡¶éƒ¨å¯¼èˆªæ  -->
    <div class="page-header">
      <div class="header-left">
        <el-button @click="goBack" :icon="ArrowLeft" circle />
        <div class="customer-info">
          <span class="customer-name">{{ summary.customerName || '客户往来详情' }}</span>
          <span class="customer-tag">客户往来统计</span>
        </div>
      </div>
    </div>
    <!-- ç»Ÿè®¡å¡ç‰‡åŒº -->
    <div class="stats-section" v-loading="summaryLoading">
      <div class="stats-grid">
        <div class="stat-card primary">
          <div class="stat-icon">
            <el-icon><Document /></el-icon>
          </div>
          <div class="stat-content">
            <div class="stat-label">合同总金额</div>
            <div class="stat-value">
              <span class="currency">Â¥</span>
              <span class="number">{{ formatMoney(summary.contractAmounts) }}</span>
            </div>
          </div>
        </div>
        <div class="stat-card success">
          <div class="stat-icon">
            <el-icon><Tickets /></el-icon>
          </div>
          <div class="stat-content">
            <div class="stat-label">合同数量</div>
            <div class="stat-value">
              <span class="number">{{ summary.contractCount || 0 }}</span>
              <span class="unit">份</span>
            </div>
          </div>
        </div>
        <div class="stat-card info">
          <div class="stat-icon">
            <el-icon><Van /></el-icon>
          </div>
          <div class="stat-content">
            <div class="stat-label">发货金额</div>
            <div class="stat-value">
              <span class="currency">Â¥</span>
              <span class="number">{{ formatMoney(summary.shippedAmounts) }}</span>
            </div>
          </div>
        </div>
        <div class="stat-card danger">
          <div class="stat-icon">
            <el-icon><Clock /></el-icon>
          </div>
          <div class="stat-content">
            <div class="stat-label">未发货金额</div>
            <div class="stat-value">
              <span class="currency">Â¥</span>
              <span class="number">{{ formatMoney(summary.unshippedAmounts) }}</span>
            </div>
          </div>
        </div>
        <div class="stat-card progress-card">
          <div class="stat-content-full">
            <div class="progress-header">
              <span class="stat-label">发货进度</span>
              <span class="progress-value">{{ summary.shippedRate || 0 }}%</span>
            </div>
            <el-progress
              :percentage="summary.shippedRate || 0"
              :stroke-width="12"
              :show-text="false"
              :color="getProgressColor(summary.shippedRate)"
            />
            <div class="progress-footer">
              <span>已发货: Â¥{{ formatMoney(summary.shippedAmounts) }}</span>
              <span>总额: Â¥{{ formatMoney(summary.contractAmounts) }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- Tab åˆ‡æ¢ -->
    <div class="table-section">
      <el-tabs v-model="activeTab" class="custom-tabs">
        <el-tab-pane label="产品明细" name="products">
          <ProductTable :customerId="customerId" v-if="customerId && activeTab === 'products'" />
        </el-tab-pane>
        <!-- <el-tab-pane label="发货明细" name="shipments">
          <ShipmentTable :customerId="customerId" v-if="customerId && activeTab === 'shipments'" />
        </el-tab-pane> -->
      </el-tabs>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ArrowLeft, Document, Tickets, Van, Clock } from '@element-plus/icons-vue'
import { customerTransactionsSummary } from '@/api/salesManagement/indicatorStats.js'
import ProductTable from './components/ProductTable.vue'
// import ShipmentTable from './components/ShipmentTable.vue'
const route = useRoute()
const router = useRouter()
const customerId = ref(null)
const summary = ref({})
const summaryLoading = ref(false)
const activeTab = ref('products')
const getSummary = () => {
  if (!customerId.value) return
  summaryLoading.value = true
  customerTransactionsSummary({ customerId: customerId.value })
    .then(res => {
      if (res.code === 200) {
        summary.value = res.data || {}
      }
    })
    .finally(() => {
      summaryLoading.value = false
    })
}
const formatMoney = (value) => {
  if (!value) return '0.00'
  return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
const getProgressColor = (rate) => {
  if (!rate || rate < 30) return '#f56c6c'
  if (rate < 60) return '#e6a23c'
  if (rate < 80) return '#409eff'
  return '#67c23a'
}
const goBack = () => {
  router.push('/salesManagement/receiptPaymentLedger')
}
onMounted(() => {
  customerId.value = route.query.customerId
  if (customerId.value) {
    getSummary()
  }
})
</script>
<style scoped lang="scss">
.customer-detail-page {
  background: #f5f7fa;
  min-height: calc(100vh - 84px);
  padding: 20px;
}
.page-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 12px;
  padding: 20px 24px;
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
  .header-left {
    display: flex;
    align-items: center;
    gap: 16px;
    .el-button {
      background: rgba(255, 255, 255, 0.2);
      border: none;
      color: white;
      &:hover {
        background: rgba(255, 255, 255, 0.3);
      }
    }
    .customer-info {
      display: flex;
      flex-direction: column;
      gap: 4px;
      .customer-name {
        font-size: 20px;
        font-weight: 600;
        color: white;
      }
      .customer-tag {
        font-size: 12px;
        color: rgba(255, 255, 255, 0.8);
        background: rgba(255, 255, 255, 0.2);
        padding: 2px 10px;
        border-radius: 10px;
        width: fit-content;
      }
    }
  }
}
.stats-section {
  margin-bottom: 20px;
}
.stats-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 16px;
  @media (max-width: 1400px) {
    grid-template-columns: repeat(3, 1fr);
  }
  @media (max-width: 1000px) {
    grid-template-columns: repeat(2, 1fr);
  }
}
.stat-card {
  background: white;
  border-radius: 12px;
  padding: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
  }
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  }
  &.primary::before { background: linear-gradient(180deg, #667eea, #764ba2); }
  &.success::before { background: linear-gradient(180deg, #67c23a, #4a9c2d); }
  &.warning::before { background: linear-gradient(180deg, #e6a23c, #c78a2f); }
  &.info::before { background: linear-gradient(180deg, #409eff, #2d7dd2); }
  &.danger::before { background: linear-gradient(180deg, #f56c6c, #c45656); }
  &.progress-card::before { background: linear-gradient(180deg, #909399, #606266); }
  .stat-icon {
    width: 52px;
    height: 52px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    flex-shrink: 0;
  }
  &.primary .stat-icon { background: linear-gradient(135deg, rgba(102, 126, 234, 0.15), rgba(118, 75, 162, 0.15)); color: #667eea; }
  &.success .stat-icon { background: linear-gradient(135deg, rgba(103, 194, 58, 0.15), rgba(74, 156, 45, 0.15)); color: #67c23a; }
  &.warning .stat-icon { background: linear-gradient(135deg, rgba(230, 162, 60, 0.15), rgba(199, 138, 47, 0.15)); color: #e6a23c; }
  &.info .stat-icon { background: linear-gradient(135deg, rgba(64, 158, 255, 0.15), rgba(45, 125, 210, 0.15)); color: #409eff; }
  &.danger .stat-icon { background: linear-gradient(135deg, rgba(245, 108, 108, 0.15), rgba(196, 86, 86, 0.15)); color: #f56c6c; }
  &.progress-card .stat-icon { display: none; }
  .stat-content {
    flex: 1;
    min-width: 0;
  }
  .stat-label {
    font-size: 13px;
    color: #909399;
    margin-bottom: 6px;
  }
  .stat-value {
    .currency {
      font-size: 14px;
      color: #606266;
      margin-right: 2px;
    }
    .number {
      font-size: 22px;
      font-weight: 600;
      color: #303133;
    }
    .unit {
      font-size: 14px;
      color: #909399;
      margin-left: 4px;
    }
  }
  &.progress-card {
    grid-column: span 1;
    @media (max-width: 1600px) {
      grid-column: span 1;
    }
    .stat-content-full {
      width: 100%;
    }
    .progress-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
      .stat-label {
        margin-bottom: 0;
      }
      .progress-value {
        font-size: 18px;
        font-weight: 600;
        color: #303133;
      }
    }
    .progress-footer {
      display: flex;
      justify-content: space-between;
      margin-top: 10px;
      font-size: 12px;
      color: #909399;
    }
  }
}
.table-section {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  .custom-tabs {
    :deep(.el-tabs__header) {
      margin-bottom: 0;
      .el-tabs__nav-wrap::after {
        height: 1px;
      }
      .el-tabs__item {
        font-size: 15px;
        padding: 0 24px;
        height: 44px;
        line-height: 44px;
        &.is-active {
          font-weight: 600;
        }
      }
      .el-tabs__active-bar {
        height: 3px;
        border-radius: 2px;
      }
    }
    :deep(.el-tabs__content) {
      padding-top: 20px;
    }
  }
}
</style>
src/views/salesManagement/receiptPaymentLedger/index.vue
@@ -16,7 +16,8 @@
      </div>
    </div>
    <el-row :gutter="20">
      <el-col :span="12">
      <!-- å·¦ä¾§å®¢æˆ·åˆ—表 -->
      <el-col :span="8">
        <div class="table_list"
             style="width: 100%">
          <el-table :data="tableData"
@@ -40,22 +41,37 @@
                             prop="contractAmounts"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
            <el-table-column label="回款金额(元)"
                             width="150" />
            <el-table-column label="收款金额(元)"
                             prop="receiptPaymentAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
                             width="150" />
            <el-table-column label="应收金额(元)"
                             prop="receiptableAmount"
                             show-overflow-tooltip
                             width="200">
              <template #default="{ row, column }">
                <el-text type="danger">
                  {{ formattedNumber(row, column, row.receiptableAmount) }}
                </el-text>
                             width="150">
              <template #default="{ row }">
                <span :style="{ color: row.receiptableAmount > 0 ? '#f56c6c' : '#606266' }">
                  {{ formattedNumber(null, null, row.receiptableAmount) }}
                </span>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="100" align="center" fixed="right">
              <template #default="{ row }">
                <el-button type="primary" link size="small" @click="viewDetail(row)">查看明细</el-button>
              </template>
            </el-table-column>
<!--            <el-table-column label="应收金额(元)"-->
<!--                             prop="receiptableAmount"-->
<!--                             show-overflow-tooltip-->
<!--                             width="150">-->
<!--              <template #default="{ row, column }">-->
<!--                <el-text type="danger">-->
<!--                  {{ formattedNumber(row, column, row.receiptableAmount) }}-->
<!--                </el-text>-->
<!--              </template>-->
<!--            </el-table-column>-->
          </el-table>
          <pagination v-show="total > 0"
                      :total="total"
@@ -65,81 +81,113 @@
                      @pagination="paginationChange" />
        </div>
      </el-col>
      <el-col :span="12">
      <!-- å³ä¾§è®¢å•信息 -->
      <el-col :span="16">
        <div class="table_list"
             style="width: 100%">
          <el-table :data="receiptRecord"
          <el-table :data="orderRecord"
                    border
                    :row-key="(row) => row.id"
                    show-summary
                    :summary-method="summarizeMainTable1"
                    height="calc(100vh - 18.5em)">
                    v-loading="orderLoading"
                    height="calc(100vh - 20em)">
            <el-table-column align="center"
                             label="序号"
                             type="index"
                             width="60" />
            <el-table-column label="合同签订日期"
                             prop="executionDate"
                             show-overflow-tooltip
                             width="110" />
            <el-table-column label="销售合同号"
            <el-table-column label="销售订单"
                             prop="salesContractNo"
                             show-overflow-tooltip
                             width="200" />
            <el-table-column label="合同金额(元)"
                             prop="contractAmount"
                             width="150" />
            <!-- <el-table-column label="发货订单号"
                             prop="shippingNo"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
            <el-table-column label="回款金额(元)"
                             prop="receiptPaymentAmount"
                             width="150" /> -->
            <el-table-column label="客户名称"
                             prop="customerName"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="200" />
            <el-table-column label="应收金额(元)"
                             prop="receiptableAmount"
                             width="150" />
            <el-table-column label="产品名称"
                             prop="productName"
                             show-overflow-tooltip
                             width="200">
              <template #default="{ row, column }">
                <el-text type="danger">
                  {{ formattedNumber(row, column, row.receiptableAmount) }}
                </el-text>
                             width="150" />
            <el-table-column label="规格型号"
                             prop="specificationModel"
                             show-overflow-tooltip
                             width="120" />
            <el-table-column label="发货时间"
                             prop="shippingDate"
                             show-overflow-tooltip
                             width="110" />
            <el-table-column label="发货数量"
                             prop="totalQuantity"
                             show-overflow-tooltip
                             width="100" />
            <!-- <el-table-column label="发货车牌号"
                             prop="shippingCarNumber"
                             show-overflow-tooltip
                             width="120" />
            <el-table-column label="快递公司"
                             prop="expressCompany"
                             show-overflow-tooltip
                             width="120" />
            <el-table-column label="快递单号"
                             prop="expressNumber"
                             show-overflow-tooltip
                             width="150" /> -->
            <el-table-column label="发货状态"
                             prop="status"
                             align="center"
                             width="100">
              <template #default="{ row }">
                <el-tag :type="getApprovalStatusType(row.status)">
                  {{ getApprovalStatusText(row.status) }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="出库单号"
                             prop="outboundBatches"
                             show-overflow-tooltip
                             width="130" />
          </el-table>
          <pagination v-show="recordTotal > 0"
                      :total="recordTotal"
          <pagination v-show="orderTotal > 0"
                      :total="orderTotal"
                      layout="total, sizes, prev, pager, next, jumper"
                      :page="recordPage.current"
                      :limit="recordPage.size"
                      @pagination="recordPaginationChange" />
                      :page="orderPage.current"
                      :limit="orderPage.size"
                      @pagination="orderPaginationChange" />
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
  import { useRouter } from "vue-router";
  import {
    customewTransactions,
    customewTransactionsDetails,
  } from "@/api/salesManagement/indicatorStats.js";
  import { deliveryLedgerListPage } from "@/api/salesManagement/deliveryLedger.js";
  import Pagination from "../../../components/PIMTable/Pagination.vue";
  const { proxy } = getCurrentInstance();
  const router = useRouter();
  const tableData = ref([]);
  const receiptRecord = ref([]);
  const orderRecord = ref([]);
  const tableLoading = ref(false);
  const orderLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
  });
  const recordPage = reactive({
  const orderPage = reactive({
    current: 1,
    size: 100,
  });
  const total = ref(0);
  const recordTotal = ref(0);
  const orderTotal = ref(0);
  const data = reactive({
    searchForm: {
      searchText: "",
@@ -148,7 +196,7 @@
  });
  const customerId = ref("");
  const { searchForm } = toRefs(data);
  const originReceiptRecord = ref([]);
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
@@ -167,9 +215,12 @@
      tableData.value = res.data.records;
      total.value = res.data.total;
      if (tableData.value.length > 0) {
        recordPage.current = 1;
        orderPage.current = 1;
        customerId.value = tableData.value[0].customerId;
        receiptPaymentList(customerId.value);
        getOrderList(customerId.value);
      } else {
        orderRecord.value = [];
        customerId.value = "";
      }
    });
  };
@@ -180,64 +231,90 @@
  const summarizeMainTable = param => {
    return proxy.summarizeTable(
      param,
      ["invoiceTotal", "receiptPaymentAmount", "unReceiptPaymentAmount"],
      ["contractAmounts", "receiptableAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        ticketsNum: { noDecimal: true },
        futureTickets: { noDecimal: true },
      }
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable1 = param => {
    var summarizeTable = proxy.summarizeTable(
      param,
      ["contractAmount", "receiptPaymentAmount", "receiptableAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
    );
    return summarizeTable;
  };
  const receiptPaymentList = id => {
    const param = {
  // èŽ·å–è®¢å•åˆ—è¡¨ï¼ˆæ ¹æ®å®¢æˆ·ID查询发货台账)
  const getOrderList = async (id) => {
    orderLoading.value = true;
    try {
      // ä½¿ç”¨ deliveryLedgerListPage æŽ¥å£æ ¹æ® customerId æŸ¥è¯¢
      const res = await deliveryLedgerListPage({
      customerId: id,
      current: recordPage.current,
      size: recordPage.size,
    };
    customewTransactionsDetails(param).then(res => {
      if (Array.isArray(res.data)) {
        originReceiptRecord.value = res.data;
        recordTotal.value = res.data.length;
        handlePagination({ page: 1, limit: recordPage.size });
      } else {
        receiptRecord.value = res.data.records;
        recordTotal.value = res.data.total;
      }
        current: 1,
        size: 1000,
    });
      let orders = [];
      if (res.data) {
        if (Array.isArray(res.data)) {
          orders = res.data;
        } else if (res.data.records && Array.isArray(res.data.records)) {
          orders = res.data.records;
        }
      }
      orderTotal.value = orders.length;
      handleOrderPagination({ page: orderPage.current, limit: orderPage.size }, orders);
    } catch (error) {
      console.error('获取订单列表失败:', error);
      orderRecord.value = [];
    } finally {
      orderLoading.value = false;
    }
  };
  // æ±‡æ¬¾è®°å½•列表分页
  const recordPaginationChange = pagination => {
    recordPage.current = pagination.page;
    recordPage.size = pagination.limit;
    receiptPaymentList(customerId.value);
  // è®¢å•列表分页
  const orderPaginationChange = pagination => {
    orderPage.current = pagination.page;
    orderPage.size = pagination.limit;
    getOrderList(customerId.value);
  };
  const handleOrderPagination = ({ page, limit }, allOrders) => {
    const start = (page - 1) * limit;
    const end = start + limit;
    orderRecord.value = allOrders.slice(start, end);
  };
  const rowClickMethod = row => {
    customerId.value = row.customerId;
    receiptPaymentList(customerId.value);
    orderPage.current = 1;
    getOrderList(customerId.value);
  };
  const handlePagination = ({ page, limit }) => {
    recordPage.current = page;
    recordPage.size = limit;
  // æŸ¥çœ‹æ˜Žç»†
  const viewDetail = (row) => {
    router.push({
      path: '/customer-transactions-detail',
      query: { customerId: row.customerId }
    });
  };
    const start = (page - 1) * limit;
    const end = start + limit;
  // å®¡æ ¸çŠ¶æ€æ ‡ç­¾ç±»åž‹
  const getApprovalStatusType = (status) => {
    const statusMap = {
      '已发货': 'success',
      '审核通过': 'success',
      '审核中': 'warning',
      '审核不通过': 'danger',
    };
    return statusMap[status] || 'info';
  };
    receiptRecord.value = originReceiptRecord.value.slice(start, end);
  // å®¡æ ¸çŠ¶æ€æ–‡æœ¬
  const getApprovalStatusText = (status) => {
    const statusMap = {
      '已发货': '已发货',
      '审核通过': '审核通过',
      '审核中': '审核中',
      '审核不通过': '审核不通过',
    };
    return statusMap[status] || status || '--';
  };
  onMounted(() => {
@@ -247,6 +324,7 @@
<style scoped lang="scss">
  .table_list {
    width: 50%;
    width: 100%;
  }
</style>
src/views/salesManagement/salesLedger/index.vue
@@ -2720,9 +2720,9 @@
    );
    deliveryForm.value = {
      shippingCarNumber: "",
      expressCompany: "",
      expressCompany: "顺丰快递",
      expressNumber: "",
      type: "货车",
      type: "快递",
      batchNo: [],
      batchNoList,
    };