1、报价系统中,需将付款方式改为选择项(现金、电汇、微信、支付宝等),不能做成手工输入;4、退货单未明确退货规则;5、客户往来信息过于简单,只做总结性信息,无多维度明细信息;
已添加6个文件
已修改10个文件
2498 ■■■■■ 文件已修改
doc/20260618_customer_transactions_optimization.md 788 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260618_return_status_field.md 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260618_shipping_info_for_return.md 365 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/indicatorStats.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/returnOrder.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/deliveryLedger/index.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | 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 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/components/formDia.vue 139 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/index.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260618_customer_transactions_optimization.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,788 @@
# å®¢æˆ·å¾€æ¥å¤šç»´åº¦æ˜Žç»†åŠŸèƒ½å‰ç«¯è”è°ƒæ–‡æ¡£
> ä¼˜åŒ–客户往来功能,新增多维度明细查询接口,支持产品明细和发货明细维度
## æ¶‰åŠé¡µé¢
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - å®¢æˆ·å¾€æ¥åˆ—表页
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - å®¢æˆ·å¾€æ¥è¯¦æƒ…页(新增)
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - äº§å“æ˜Žç»†Tab(新增)
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - å‘货明细Tab(新增)
## API
### 1. å®¢æˆ·å¾€æ¥åˆ—表(原有接口,未变更)
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customewTransactions | å®¢æˆ·å¾€æ¥åˆ—表 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNum | Long | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
| customerName | String | å¦ | å®¢æˆ·åç§°ï¼ˆæ¨¡ç³Šæœç´¢ï¼‰ |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| customerId | Long | å®¢æˆ·ID |
| customerName | String | å®¢æˆ·åç§° |
| contractAmounts | BigDecimal | åˆåŒæ€»é‡‘额 |
| receiptPaymentAmount | BigDecimal | æ”¶æ¬¾é‡‘额 |
| receiptableAmount | BigDecimal | åº”收金额 |
---
### 2. å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€»ï¼ˆæ–°å¢žï¼‰
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customerTransactionsSummary | å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€» |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerId | Long | æ˜¯ | å®¢æˆ·ID |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| customerId | Long | å®¢æˆ·ID |
| customerName | String | å®¢æˆ·åç§° |
| contractAmounts | BigDecimal | åˆåŒæ€»é‡‘额 |
| contractCount | Integer | åˆåŒæ•°é‡ |
| productCount | Integer | äº§å“ç§ç±»æ•° |
| shippedAmounts | BigDecimal | å‘货总金额 |
| shippedQuantity | BigDecimal | å‘货总数量 |
| receivedAmounts | BigDecimal | æ”¶æ¬¾é‡‘额 |
| receivableAmounts | BigDecimal | åº”收金额 |
| returnAmounts | BigDecimal | é€€è´§é‡‘额 |
| unshippedAmounts | BigDecimal | æœªå‘货金额 |
| receivedRate | BigDecimal | æ”¶æ¬¾çއ(%) |
| shippedRate | BigDecimal | å‘货率(%) |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "customerId": 1,
    "customerName": "客户A",
    "contractAmounts": 100000.00,
    "contractCount": 5,
    "productCount": 12,
    "shippedAmounts": 80000.00,
    "shippedQuantity": 500,
    "receivedAmounts": 60000.00,
    "receivableAmounts": 20000.00,
    "returnAmounts": 5000.00,
    "unshippedAmounts": 20000.00,
    "receivedRate": 75.00,
    "shippedRate": 80.00
  }
}
```
---
### 3. å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»†ï¼ˆæ–°å¢žï¼‰
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customerTransactionsProducts | å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»† |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerId | Long | æ˜¯ | å®¢æˆ·ID |
| salesLedgerId | Long | å¦ | é”€å”®å°è´¦ID(可选,用于筛选某合同) |
| pageNum | Long | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| salesLedgerId | Long | é”€å”®å°è´¦ID |
| salesContractNo | String | é”€å”®åˆåŒå· |
| productId | Long | äº§å“ID |
| productName | String | äº§å“åç§° |
| model | String | è§„格型号 |
| unit | String | å•位 |
| contractQuantity | BigDecimal | åˆåŒæ•°é‡ |
| taxInclusiveUnitPrice | BigDecimal | åˆåŒå•ä»·(含税) |
| contractAmount | BigDecimal | åˆåŒé‡‘额 |
| shippedQuantity | BigDecimal | å·²å‘货数量 |
| shippedAmount | BigDecimal | å·²å‘货金额 |
| receivedAmount | BigDecimal | å·²æ”¶æ¬¾é‡‘额 |
| receivableAmount | BigDecimal | åº”收金额 |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "salesLedgerId": 1,
        "salesContractNo": "HT-2026-001",
        "productId": 10,
        "productName": "产品A",
        "model": "规格1",
        "unit": "ä»¶",
        "contractQuantity": 100,
        "taxInclusiveUnitPrice": 50.00,
        "contractAmount": 5000.00,
        "shippedQuantity": 80,
        "shippedAmount": 4000.00,
        "receivedAmount": 3000.00,
        "receivableAmount": 1000.00
      }
    ],
    "total": 25,
    "pageNum": 1,
    "pageSize": 10
  }
}
```
---
### 4. å®¢æˆ·å¾€æ¥å‘货明细(新增)
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customerTransactionsShipments | å®¢æˆ·å¾€æ¥å‘货明细 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerId | Long | æ˜¯ | å®¢æˆ·ID |
| salesLedgerId | Long | å¦ | é”€å”®å°è´¦ID(可选,用于筛选某合同) |
| pageNum | Long | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| salesLedgerId | Long | é”€å”®å°è´¦ID |
| salesContractNo | String | é”€å”®åˆåŒå· |
| shippingId | Long | å‘货单ID |
| shippingNo | String | å‘货单号 |
| productName | String | äº§å“åç§° |
| model | String | è§„格型号 |
| shippingQuantity | BigDecimal | å‘货数量 |
| shippingAmount | BigDecimal | å‘货金额(含税) |
| batchNo | String | å‡ºåº“批号 |
| shippingDate | LocalDate | å‘货日期 |
| approvalStatus | Integer | å®¡æ‰¹çŠ¶æ€(0待审/1已审) |
| receivedAmount | BigDecimal | å·²æ”¶æ¬¾é‡‘额 |
| receivableAmount | BigDecimal | åº”收金额 |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "salesLedgerId": 1,
        "salesContractNo": "HT-2026-001",
        "shippingId": 100,
        "shippingNo": "FH-2026-001",
        "productName": "产品A",
        "model": "规格1",
        "shippingQuantity": 50,
        "shippingAmount": 2500.00,
        "batchNo": "20260618001",
        "shippingDate": "2026-06-18",
        "approvalStatus": 1,
        "receivedAmount": 2000.00,
        "receivableAmount": 500.00
      }
    ],
    "total": 30,
    "pageNum": 1,
    "pageSize": 10
  }
}
```
---
## å‰ç«¯é¡µé¢è®¾è®¡
### 1. å®¢æˆ·å¾€æ¥åˆ—表页(优化)
```html
<template>
  <div class="app-container">
    <!-- æœç´¢æ  -->
    <el-form :model="queryParams" ref="queryForm" :inline="true">
      <el-form-item label="客户名称" prop="customerName">
        <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getList">搜索</el-button>
        <el-button @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <el-table :data="list" v-loading="loading">
      <el-table-column label="客户名称" prop="customerName" />
      <el-table-column label="合同总金额" prop="contractAmounts" align="right">
        <template #default="{ row }">
          {{ formatMoney(row.contractAmounts) }}
        </template>
      </el-table-column>
      <el-table-column label="收款金额" prop="receiptPaymentAmount" align="right">
        <template #default="{ row }">
          {{ formatMoney(row.receiptPaymentAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="应收金额" prop="receiptableAmount" align="right">
        <template #default="{ row }">
          <span :class="{ 'text-danger': row.receiptableAmount > 0 }">
            {{ formatMoney(row.receiptableAmount) }}
          </span>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="150" align="center">
        <template #default="{ row }">
          <el-button type="text" @click="viewDetail(row)">查看明细</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µ -->
    <el-pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
  </div>
</template>
<script>
export default {
  name: 'CustomerTransactions',
  data() {
    return {
      loading: false,
      list: [],
      total: 0,
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        customerName: ''
      }
    }
  },
  created() {
    this.getList()
  },
  methods: {
    getList() {
      this.loading = true
      this.$axios.get('/metricStatistics/customewTransactions', { params: this.queryParams })
        .then(res => {
          this.list = res.data.records
          this.total = res.data.total
        })
        .finally(() => {
          this.loading = false
        })
    },
    resetQuery() {
      this.queryParams.customerName = ''
      this.getList()
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    },
    viewDetail(row) {
      this.$router.push({ path: '/sales/customerTransactions/detail', query: { customerId: row.customerId } })
    }
  }
}
</script>
```
---
### 2. å®¢æˆ·å¾€æ¥è¯¦æƒ…页(新增)
```html
<template>
  <div class="app-container">
    <!-- é¡¶éƒ¨ç»Ÿè®¡å¡ç‰‡ -->
    <el-card class="summary-card">
      <div slot="header">
        <span>{{ summary.customerName }} - å¾€æ¥ç»Ÿè®¡</span>
      </div>
      <el-row :gutter="20">
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">合同总金额</div>
            <div class="stat-value">{{ formatMoney(summary.contractAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">合同数量</div>
            <div class="stat-value">{{ summary.contractCount }}份</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">产品种类</div>
            <div class="stat-value">{{ summary.productCount }}种</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">发货金额</div>
            <div class="stat-value">{{ formatMoney(summary.shippedAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">收款金额</div>
            <div class="stat-value text-success">{{ formatMoney(summary.receivedAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">应收金额</div>
            <div class="stat-value text-danger">{{ formatMoney(summary.receivableAmounts) }}</div>
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="20" style="margin-top: 15px;">
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">退货金额</div>
            <div class="stat-value">{{ formatMoney(summary.returnAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">未发货金额</div>
            <div class="stat-value text-warning">{{ formatMoney(summary.unshippedAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">收款率</div>
            <div class="stat-value">
              <el-progress :percentage="summary.receivedRate || 0" :stroke-width="18" />
            </div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">发货率</div>
            <div class="stat-value">
              <el-progress :percentage="summary.shippedRate || 0" :stroke-width="18" color="#67c23a" />
            </div>
          </div>
        </el-col>
      </el-row>
    </el-card>
    <!-- Tab åˆ‡æ¢ -->
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane label="产品明细" name="products">
        <product-table :customerId="customerId" />
      </el-tab-pane>
      <el-tab-pane label="发货明细" name="shipments">
        <shipment-table :customerId="customerId" />
      </el-tab-pane>
      <el-tab-pane label="合同明细" name="contracts">
        <contract-table :customerId="customerId" />
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script>
import ProductTable from './components/ProductTable.vue'
import ShipmentTable from './components/ShipmentTable.vue'
import ContractTable from './components/ContractTable.vue'
export default {
  name: 'CustomerTransactionsDetail',
  components: { ProductTable, ShipmentTable, ContractTable },
  data() {
    return {
      customerId: null,
      summary: {},
      activeTab: 'products'
    }
  },
  created() {
    this.customerId = this.$route.query.customerId
    if (this.customerId) {
      this.getSummary()
    }
  },
  methods: {
    getSummary() {
      this.$axios.get('/metricStatistics/customerTransactionsSummary', {
        params: { customerId: this.customerId }
      }).then(res => {
        this.summary = res.data
      })
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    },
    handleTabChange(tab) {
      // Tab åˆ‡æ¢æ—¶åˆ·æ–°å­ç»„件数据
    }
  }
}
</script>
<style scoped>
.summary-card {
  margin-bottom: 20px;
}
.stat-item {
  text-align: center;
}
.stat-label {
  font-size: 14px;
  color: #909399;
}
.stat-value {
  font-size: 18px;
  font-weight: bold;
  margin-top: 5px;
}
.text-success {
  color: #67c23a;
}
.text-danger {
  color: #f56c6c;
}
.text-warning {
  color: #e6a23c;
}
</style>
```
---
### 3. äº§å“æ˜Žç»†ç»„ä»¶ (ProductTable.vue)
```html
<template>
  <div>
    <!-- ç­›é€‰ -->
    <el-form :inline="true" size="small">
      <el-form-item label="合同号">
        <el-select v-model="filterData.salesLedgerId" clearable placeholder="全部合同" @change="getList">
          <el-option v-for="item in contractList" :key="item.id" :label="item.salesContractNo" :value="item.id" />
        </el-select>
      </el-form-item>
    </el-form>
    <!-- è¡¨æ ¼ -->
    <el-table :data="list" v-loading="loading" size="small">
      <el-table-column label="合同号" prop="salesContractNo" width="150" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" width="120" />
      <el-table-column label="单位" prop="unit" width="80" />
      <el-table-column label="合同数量" prop="contractQuantity" align="right" width="100" />
      <el-table-column label="合同单价" prop="taxInclusiveUnitPrice" align="right" width="100">
        <template #default="{ row }">
          {{ formatMoney(row.taxInclusiveUnitPrice) }}
        </template>
      </el-table-column>
      <el-table-column label="合同金额" prop="contractAmount" align="right" width="120">
        <template #default="{ row }">
          {{ formatMoney(row.contractAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="已发货数量" prop="shippedQuantity" align="right" width="100" />
      <el-table-column label="已发货金额" prop="shippedAmount" align="right" width="120">
        <template #default="{ row }">
          {{ formatMoney(row.shippedAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="已收款金额" prop="receivedAmount" align="right" width="120">
        <template #default="{ row }">
          <span class="text-success">{{ formatMoney(row.receivedAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="应收金额" prop="receivableAmount" align="right" width="120">
        <template #default="{ row }">
          <span :class="{ 'text-danger': row.receivableAmount > 0 }">
            {{ formatMoney(row.receivableAmount) }}
          </span>
        </template>
      </el-table-column>
      <el-table-column label="发货进度" width="150">
        <template #default="{ row }">
          <el-progress
            :percentage="calcPercent(row.shippedQuantity, row.contractQuantity)"
            :stroke-width="10"
          />
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µ -->
    <el-pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      layout="total, prev, pager, next"
      @pagination="getList"
    />
  </div>
</template>
<script>
export default {
  name: 'ProductTable',
  props: {
    customerId: {
      type: Number,
      required: true
    }
  },
  data() {
    return {
      loading: false,
      list: [],
      total: 0,
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        customerId: null,
        salesLedgerId: null
      },
      filterData: {
        salesLedgerId: null
      },
      contractList: []
    }
  },
  watch: {
    customerId(val) {
      if (val) {
        this.queryParams.customerId = val
        this.getList()
        this.getContractList()
      }
    }
  },
  methods: {
    getList() {
      this.loading = true
      this.queryParams.salesLedgerId = this.filterData.salesLedgerId
      this.$axios.get('/metricStatistics/customerTransactionsProducts', { params: this.queryParams })
        .then(res => {
          this.list = res.data.records
          this.total = res.data.total
        })
        .finally(() => {
          this.loading = false
        })
    },
    getContractList() {
      // èŽ·å–è¯¥å®¢æˆ·çš„åˆåŒåˆ—è¡¨ç”¨äºŽç­›é€‰
      this.$axios.get('/metricStatistics/customewTransactionsDetails', {
        params: { customerId: this.customerId, pageNum: 1, pageSize: 100 }
      }).then(res => {
        this.contractList = res.data.records
      })
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    },
    calcPercent(shipped, total) {
      if (!total || total === 0) return 0
      return Math.round((shipped / total) * 100)
    }
  }
}
</script>
```
---
### 4. å‘货明细组件 (ShipmentTable.vue)
```html
<template>
  <div>
    <!-- ç­›é€‰ -->
    <el-form :inline="true" size="small">
      <el-form-item label="合同号">
        <el-select v-model="filterData.salesLedgerId" clearable placeholder="全部合同" @change="getList">
          <el-option v-for="item in contractList" :key="item.id" :label="item.salesContractNo" :value="item.id" />
        </el-select>
      </el-form-item>
    </el-form>
    <!-- è¡¨æ ¼ -->
    <el-table :data="list" v-loading="loading" size="small">
      <el-table-column label="合同号" prop="salesContractNo" width="150" />
      <el-table-column label="发货单号" prop="shippingNo" width="150" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" width="120" />
      <el-table-column label="发货数量" prop="shippingQuantity" align="right" width="100" />
      <el-table-column label="发货金额" prop="shippingAmount" align="right" width="120">
        <template #default="{ row }">
          {{ formatMoney(row.shippingAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="出库批号" prop="batchNo" width="150" />
      <el-table-column label="发货日期" prop="shippingDate" width="120" />
      <el-table-column label="审批状态" prop="approvalStatus" width="100">
        <template #default="{ row }">
          <el-tag :type="row.approvalStatus === 1 ? 'success' : 'warning'" size="mini">
            {{ row.approvalStatus === 1 ? '已审' : '待审' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="已收款金额" prop="receivedAmount" align="right" width="120">
        <template #default="{ row }">
          <span class="text-success">{{ formatMoney(row.receivedAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="应收金额" prop="receivableAmount" align="right" width="120">
        <template #default="{ row }">
          <span :class="{ 'text-danger': row.receivableAmount > 0 }">
            {{ formatMoney(row.receivableAmount) }}
          </span>
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µ -->
    <el-pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      layout="total, prev, pager, next"
      @pagination="getList"
    />
  </div>
</template>
<script>
export default {
  name: 'ShipmentTable',
  props: {
    customerId: {
      type: Number,
      required: true
    }
  },
  data() {
    return {
      loading: false,
      list: [],
      total: 0,
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        customerId: null,
        salesLedgerId: null
      },
      filterData: {
        salesLedgerId: null
      },
      contractList: []
    }
  },
  watch: {
    customerId(val) {
      if (val) {
        this.queryParams.customerId = val
        this.getList()
        this.getContractList()
      }
    }
  },
  methods: {
    getList() {
      this.loading = true
      this.queryParams.salesLedgerId = this.filterData.salesLedgerId
      this.$axios.get('/metricStatistics/customerTransactionsShipments', { params: this.queryParams })
        .then(res => {
          this.list = res.data.records
          this.total = res.data.total
        })
        .finally(() => {
          this.loading = false
        })
    },
    getContractList() {
      this.$axios.get('/metricStatistics/customewTransactionsDetails', {
        params: { customerId: this.customerId, pageNum: 1, pageSize: 100 }
      }).then(res => {
        this.contractList = res.data.records
      })
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    }
  }
}
</script>
```
---
## æ³¨æ„äº‹é¡¹
1. **路由配置**:需在路由中新增客户往来详情页路由 `/sales/customerTransactions/detail`
2. **组件拆分**:产品明细和发货明细建议拆分为独立组件,便于复用和维护
3. **筛选联动**:产品明细和发货明细支持按合同筛选,合同列表从原有接口获取
4. **数据格式**:金额字段需统一使用 `formatMoney` æ–¹æ³•格式化显示
5. **进度条显示**:产品明细中的发货进度使用 `el-progress` ç»„件直观展示
6. **状态标识**:应收金额大于0时使用红色标识,已收款使用绿色标识
7. **审批状态**:发货明细中的审批状态使用 `el-tag` å±•示,已审为绿色,待审为黄色
---
## æ•°æ®å¯¹æ¯”
### ä¼˜åŒ–前 vs ä¼˜åŒ–后
| ç»´åº¦ | ä¼˜åŒ–前 | ä¼˜åŒ–后 |
|------|--------|--------|
| å®¢æˆ·å¾€æ¥ | åªæœ‰åˆåŒé‡‘额、收款、应收 | æ–°å¢žåˆåŒæ•°ã€äº§å“æ•°ã€å‘货率、收款率等12项指标 |
| æ˜Žç»†ç»´åº¦ | ä»…合同明细 | æ–°å¢žäº§å“æ˜Žç»†ã€å‘货明细 |
| ç­›é€‰èƒ½åŠ› | ä»…按客户名筛选 | æ”¯æŒæŒ‰åˆåŒç­›é€‰äº§å“/发货明细 |
| æ•°æ®è¿½æº¯ | æ— æ³•追溯具体发货 | å¯è¿½æº¯æ¯æ¡å‘货记录的收款情况 |
| è¿›åº¦å±•示 | æ—  | å‘货进度条直观展示 |
doc/20260618_return_status_field.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,201 @@
# é€€è´§çŠ¶æ€å­—æ®µæŽ¥å£å˜æ›´å‰ç«¯è”è°ƒæ–‡æ¡£
> é”€å”®é€€è´§ã€å‘货台账、销售台账接口新增退货状态字段
## æ¶‰åŠé¡µé¢
- é”€å”®ç®¡ç† / é”€å”®é€€è´§ - æ–°å¢ž/编辑退货单页面
- é”€å”®ç®¡ç† / å‘货台账列表页面
- é”€å”®ç®¡ç† / é”€å”®å°è´¦åˆ—表页面
---
## API å˜æ›´
### 1. é”€å”®é€€è´§-发货信息查询
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /shippingInfo/getForReturn | é”€å”®é€€è´§-通过客户名称查询发货信息 |
**新增响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| returnedQuantity | BigDecimal | å·²é€€è´§æ•°é‡ |
| returnStatus | String | é€€è´§çŠ¶æ€ï¼šæ— é€€è´§/部分退货/全部退货 |
**响应示例:**
```json
{
  "code": 200,
  "data": [
    {
      "shippingId": 100,
      "shippingNo": "FH-2026-001",
      "shippingQuantity": 50,
      "returnedQuantity": 10,
      "returnStatus": "部分退货",
      "batchNo": "20260618001",
      "displayLabel": "20260618001-京A12345-2026-06-18"
    }
  ]
}
```
---
### 2. å‘货台账列表
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /shippingInfo/listPage | å‘货信息分页列表 |
**新增响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| returnedQuantity | BigDecimal | å·²é€€è´§æ•°é‡ |
| returnStatus | String | é€€è´§çŠ¶æ€ï¼šæ— é€€è´§/部分退货/全部退货 |
**响应示例:**
```json
{
  "code": 200,
  "data": {
    "records": [
      {
        "id": 100,
        "shippingNo": "FH-2026-001",
        "totalQuantity": 50,
        "returnedQuantity": 0,
        "returnStatus": "无退货"
      }
    ]
  }
}
```
---
### 3. é”€å”®å°è´¦åˆ—表
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /salesLedger/listPage | é”€å”®å°è´¦åˆ†é¡µåˆ—表 |
**新增响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| returnedQuantity | BigDecimal | å·²é€€è´§æ•°é‡ï¼ˆè¯¥é”€å”®åˆåŒä¸‹æ‰€æœ‰å‘货的累计退货数) |
| returnStatus | String | é€€è´§çŠ¶æ€ï¼šæ— é€€è´§/部分退货/全部退货 |
**响应示例:**
```json
{
  "code": 200,
  "data": {
    "records": [
      {
        "id": 1,
        "salesContractNo": "HT-2026-001",
        "contractAmount": 100000,
        "returnedQuantity": 30,
        "returnStatus": "部分退货"
      }
    ]
  }
}
```
---
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. å‘货台账列表页面
**新增表格列:**
```html
<el-table-column label="退货状态" prop="returnStatus" width="100">
  <template #default="{ row }">
    <el-tag
      :type="row.returnStatus === '无退货' ? 'success' : (row.returnStatus === '全部退货' ? 'danger' : 'warning')"
    >
      {{ row.returnStatus }}
    </el-tag>
  </template>
</el-table-column>
<el-table-column label="已退货数量" prop="returnedQuantity" width="120" />
```
### 2. é”€å”®å°è´¦åˆ—表页面
**新增表格列:**
```html
<el-table-column label="退货状态" prop="returnStatus" width="100">
  <template #default="{ row }">
    <el-tag
      :type="row.returnStatus === '无退货' ? 'success' : (row.returnStatus === '全部退货' ? 'danger' : 'warning')"
    >
      {{ row.returnStatus }}
    </el-tag>
  </template>
</el-table-column>
```
### 3. é”€å”®é€€è´§é¡µé¢ - å‘货信息选择
**下拉选项增强显示:**
```html
<el-select v-model="form.shippingId" placeholder="请选择发货信息" filterable>
  <el-option
    v-for="item in shippingList"
    :key="item.shippingId"
    :label="item.displayLabel"
    :value="item.shippingId"
  >
    <div style="display: flex; justify-content: space-between;">
      <span>{{ item.displayLabel }}</span>
      <el-tag
        size="small"
        :type="item.returnStatus === '无退货' ? 'success' : 'warning'"
        style="margin-left: 8px;"
      >
        {{ item.returnStatus }}
      </el-tag>
    </div>
  </el-option>
</el-select>
```
---
## é€€è´§çŠ¶æ€è®¡ç®—é€»è¾‘
| æ¡ä»¶ | çŠ¶æ€ |
|------|------|
| é€€è´§æ•°é‡ = 0 | æ— é€€è´§ |
| é€€è´§æ•°é‡ >= å‘货数量 | å…¨éƒ¨é€€è´§ |
| é€€è´§æ•°é‡ > 0 ä¸” < å‘货数量 | éƒ¨åˆ†é€€è´§ |
---
## æ³¨æ„äº‹é¡¹
1. **销售台账的退货数量**:是按销售合同维度的汇总,即该合同下所有发货单的累计退货数量
2. **发货台账的退货数量**:是按单条发货记录维度的退货数量
3. **前端展示建议**:
   - æ— é€€è´§ï¼šç»¿è‰²æ ‡ç­¾ (`success`)
   - éƒ¨åˆ†é€€è´§ï¼šé»„色标签 (`warning`)
   - å…¨éƒ¨é€€è´§ï¼šçº¢è‰²æ ‡ç­¾ (`danger`)
4. **销售退货选择发货信息时**:前端可以根据 `returnStatus` è¿‡æ»¤æˆ–提示用户,避免选择已全部退货的发货记录
doc/20260618_shipping_info_for_return.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,365 @@
# é”€å”®é€€è´§å‘货信息接口优化前端联调文档
> ä¼˜åŒ–销售退货场景下的发货信息查询接口,新增批次号字段,并提供组合显示标签
## æ¶‰åŠé¡µé¢
- é”€å”®ç®¡ç† / é”€å”®é€€è´§ - æ–°å¢ž/编辑退货单页面
- é”€å”®é€€è´§ - é€‰æ‹©å‘货单下拉框
## API
### æ–°å¢žæŽ¥å£ï¼šé”€å”®é€€è´§-发货信息查询
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /shippingInfo/getForReturn | é”€å”®é€€è´§-通过客户名称查询发货信息(含批次号) |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerName | String | æ˜¯ | å®¢æˆ·åç§° |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| shippingId | Long | å‘货单ID |
| shippingNo | String | å‘货单号 |
| salesContractNo | String | é”€å”®åˆåŒå· |
| customerName | String | å®¢æˆ·åç§° |
| productName | String | äº§å“åç§° |
| model | String | è§„格型号 |
| shippingQuantity | BigDecimal | å‘货数量 |
| batchNo | String | æ‰¹æ¬¡å· |
| shippingCarNumber | String | è½¦ç‰Œå· |
| createTime | LocalDateTime | åˆ›å»ºæ—¶é—´ |
| displayLabel | String | æ˜¾ç¤ºæ ‡ç­¾ï¼ˆæ‰¹æ¬¡å·-车牌号-创建时间) |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": [
    {
      "shippingId": 100,
      "shippingNo": "FH-2026-001",
      "salesContractNo": "HT-2026-001",
      "customerName": "客户A",
      "productName": "产品A",
      "model": "规格1",
      "shippingQuantity": 50,
      "batchNo": "20260618001",
      "shippingCarNumber": "京A12345",
      "createTime": "2026-06-18 10:30:00",
      "displayLabel": "20260618001-京A12345-2026-06-18"
    },
    {
      "shippingId": 101,
      "shippingNo": "FH-2026-002",
      "salesContractNo": "HT-2026-002",
      "customerName": "客户A",
      "productName": "产品B",
      "model": "规格2",
      "shippingQuantity": 30,
      "batchNo": "20260618002",
      "shippingCarNumber": "京B67890",
      "createTime": "2026-06-17 14:20:00",
      "displayLabel": "20260618002-京B67890-2026-06-17"
    }
  ]
}
```
---
## åŽŸæœ‰æŽ¥å£ï¼ˆä¿ç•™ä¸å˜ï¼‰
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /shippingInfo/getByCustomerName | é€šè¿‡å®¢æˆ·åç§°æŸ¥è¯¢å…³è”的发货单号(原有接口) |
---
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é”€å”®é€€è´§é¡µé¢ - å‘货单选择下拉框
**修改前:**
```html
<el-form-item label="发货单号">
  <el-select v-model="form.shippingId" placeholder="请选择发货单号">
    <el-option
      v-for="item in shippingList"
      :key="item.id"
      :label="item.shippingNo"
      :value="item.id"
    />
  </el-select>
</el-form-item>
```
**修改后:**
```html
<el-form-item label="发货单号">
  <el-select
    v-model="form.shippingId"
    placeholder="请选择发货单号"
    filterable
    @change="onShippingChange"
  >
    <el-option
      v-for="item in shippingList"
      :key="item.shippingId"
      :label="item.displayLabel"
      :value="item.shippingId"
    >
      <span style="float: left">{{ item.displayLabel }}</span>
      <span style="float: right; color: #8492a6; font-size: 12px">
        {{ item.productName }} - {{ item.shippingQuantity }}{{ item.model ? '(' + item.model + ')' : '' }}
      </span>
    </el-option>
  </el-select>
</el-form-item>
```
### 2. data æ•°æ®
```js
data() {
  return {
    form: {
      shippingId: null,
      batchNo: '',
      shippingCarNumber: '',
      productName: '',
      model: '',
      shippingQuantity: null,
    },
    shippingList: [],
  }
}
```
### 3. methods æ–¹æ³•
```js
methods: {
  // æŸ¥è¯¢å‘货信息列表
  getShippingList(customerName) {
    if (!customerName) {
      this.shippingList = [];
      return;
    }
    this.$axios.get('/shippingInfo/getForReturn', {
      params: { customerName }
    }).then(res => {
      this.shippingList = res.data || [];
    });
  },
  // å‘货单选择变化
  onShippingChange(shippingId) {
    const selected = this.shippingList.find(item => item.shippingId === shippingId);
    if (selected) {
      // å›žæ˜¾æ•°æ®
      this.form.batchNo = selected.batchNo || '';
      this.form.shippingCarNumber = selected.shippingCarNumber || '';
      this.form.productName = selected.productName || '';
      this.form.model = selected.model || '';
      this.form.shippingQuantity = selected.shippingQuantity || 0;
      this.form.salesContractNo = selected.salesContractNo || '';
      this.form.shippingNo = selected.shippingNo || '';
    }
  },
}
```
### 4. å®Œæ•´ç¤ºä¾‹ï¼ˆé”€å”®é€€è´§è¡¨å•)
```html
<template>
  <div class="app-container">
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
      <!-- å®¢æˆ·é€‰æ‹© -->
      <el-form-item label="客户名称" prop="customerName">
        <el-select
          v-model="form.customerName"
          placeholder="请选择客户"
          filterable
          @change="onCustomerChange"
        >
          <el-option
            v-for="item in customerList"
            :key="item.id"
            :label="item.customerName"
            :value="item.customerName"
          />
        </el-select>
      </el-form-item>
      <!-- å‘货单选择 -->
      <el-form-item label="发货信息" prop="shippingId">
        <el-select
          v-model="form.shippingId"
          placeholder="请选择发货单号"
          filterable
          :disabled="!form.customerName"
          @change="onShippingChange"
        >
          <el-option
            v-for="item in shippingList"
            :key="item.shippingId"
            :label="item.displayLabel"
            :value="item.shippingId"
          >
            <div style="display: flex; justify-content: space-between;">
              <span>{{ item.displayLabel }}</span>
              <span style="color: #909399; font-size: 12px">
                {{ item.productName }}
              </span>
            </div>
          </el-option>
        </el-select>
      </el-form-item>
      <!-- å›žæ˜¾ä¿¡æ¯ï¼ˆåªè¯»ï¼‰ -->
      <el-row :gutter="20">
        <el-col :span="8">
          <el-form-item label="批次号">
            <el-input v-model="form.batchNo" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="车牌号">
            <el-input v-model="form.shippingCarNumber" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="产品名称">
            <el-input v-model="form.productName" disabled />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="8">
          <el-form-item label="规格型号">
            <el-input v-model="form.model" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="发货数量">
            <el-input v-model="form.shippingQuantity" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="销售合同号">
            <el-input v-model="form.salesContractNo" disabled />
          </el-form-item>
        </el-col>
      </el-row>
      <!-- å…¶ä»–退货表单字段... -->
    </el-form>
  </div>
</template>
<script>
export default {
  name: 'SalesReturnForm',
  data() {
    return {
      form: {
        customerName: '',
        shippingId: null,
        batchNo: '',
        shippingCarNumber: '',
        productName: '',
        model: '',
        shippingQuantity: null,
        salesContractNo: '',
        shippingNo: '',
      },
      rules: {
        customerName: [{ required: true, message: '请选择客户', trigger: 'change' }],
        shippingId: [{ required: true, message: '请选择发货信息', trigger: 'change' }],
      },
      customerList: [],
      shippingList: [],
    };
  },
  methods: {
    // å®¢æˆ·é€‰æ‹©å˜åŒ–
    onCustomerChange(customerName) {
      // æ¸…空发货单选择
      this.form.shippingId = null;
      this.clearShippingInfo();
      // æŸ¥è¯¢å‘货信息
      this.getShippingList(customerName);
    },
    // æŸ¥è¯¢å‘货信息列表
    getShippingList(customerName) {
      if (!customerName) {
        this.shippingList = [];
        return;
      }
      this.$axios.get('/shippingInfo/getForReturn', {
        params: { customerName }
      }).then(res => {
        this.shippingList = res.data || [];
      });
    },
    // å‘货单选择变化
    onShippingChange(shippingId) {
      const selected = this.shippingList.find(item => item.shippingId === shippingId);
      if (selected) {
        this.form.batchNo = selected.batchNo || '';
        this.form.shippingCarNumber = selected.shippingCarNumber || '';
        this.form.productName = selected.productName || '';
        this.form.model = selected.model || '';
        this.form.shippingQuantity = selected.shippingQuantity;
        this.form.salesContractNo = selected.salesContractNo || '';
        this.form.shippingNo = selected.shippingNo || '';
      } else {
        this.clearShippingInfo();
      }
    },
    // æ¸…空发货信息
    clearShippingInfo() {
      this.form.batchNo = '';
      this.form.shippingCarNumber = '';
      this.form.productName = '';
      this.form.model = '';
      this.form.shippingQuantity = null;
      this.form.salesContractNo = '';
      this.form.shippingNo = '';
    },
  },
};
</script>
```
---
## æ³¨æ„äº‹é¡¹
1. **新接口 vs åŽŸæŽ¥å£**:
   - `getForReturn`(新增):专门用于销售退货场景,返回批次号和组合显示标签
   - `getByCustomerName`(原有):保持不变,其他场景可继续使用
2. **displayLabel æ ¼å¼**:`批次号-车牌号-创建时间(yyyy-MM-dd)`
   - ç¤ºä¾‹ï¼š`20260618001-京A12345-2026-06-18`
   - å¦‚果某字段为空,会显示空字符串,如 `--2026-06-18`
3. **数据回显**:选择发货单后,批次号、车牌号、产品名称等字段自动填充到表单中,前端只需展示即可
4. **客户切换**:切换客户时,需清空已选的发货单和相关回显数据
5. **下拉筛选**:建议开启 `filterable` å±žæ€§ï¼Œæ–¹ä¾¿ç”¨æˆ·é€šè¿‡æ‰¹æ¬¡å·æˆ–车牌号快速筛选
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/api/salesManagement/returnOrder.js
@@ -71,6 +71,16 @@
    })
}
// é”€å”®é€€è´§-发货信息查询(含批次号)
// /shippingInfo/getForReturn
export function getShippingInfoForReturn(query) {
    return request({
        url: '/shippingInfo/getForReturn',
        method: 'get',
        params: query,
    })
}
// å¤„理
// /returnManagement/handle
export function returnManagementHandle(data) {
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/procurementLedger/index.vue
@@ -351,9 +351,16 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="付款方式">
              <el-input v-model="form.paymentMethod"
                        placeholder="请输入"
                        clearable />
              <el-select v-model="form.paymentMethod"
                         placeholder="请选择"
                         clearable
                         style="width: 100%">
                <el-option label="现金" value="现金" />
                <el-option label="电汇" value="电汇" />
                <el-option label="微信" value="微信" />
                <el-option label="支付宝" value="支付宝" />
                <el-option label="支票" value="支票" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
src/views/salesManagement/deliveryLedger/index.vue
@@ -124,6 +124,31 @@
          </template>
        </el-table-column>
        <el-table-column
          label="退货状态"
          prop="returnStatus"
          align="center"
          width="100"
        >
          <template #default="scope">
            <el-tag
              :type="getReturnStatusType(scope.row.returnStatus)"
              size="small"
            >
              {{ scope.row.returnStatus || '无退货' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="已退货数量"
          prop="returnedQuantity"
          align="center"
          width="100"
        >
          <template #default="scope">
            {{ scope.row.returnedQuantity || 0 }}
          </template>
        </el-table-column>
        <el-table-column
          label="出库单号"
          prop="outboundBatches"
          show-overflow-tooltip
@@ -870,6 +895,16 @@
  return statusStr === "审核中" || statusStr === "1";
};
// èŽ·å–é€€è´§çŠ¶æ€æ ‡ç­¾ç±»åž‹
const getReturnStatusType = (returnStatus) => {
  const statusMap = {
    '无退货': 'success',
    '部分退货': 'warning',
    '全部退货': 'danger',
  };
  return statusMap[returnStatus] || 'info';
};
onMounted(() => {
  getList();
});
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
@@ -42,6 +42,26 @@
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="150" />
            <el-table-column label="收款金额(元)"
                             prop="receiptPaymentAmount"
                             show-overflow-tooltip
                             :formatter="formattedNumber"
                             width="150" />
            <el-table-column label="应收金额(元)"
                             prop="receiptableAmount"
                             show-overflow-tooltip
                             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-->
@@ -145,6 +165,7 @@
<script setup>
  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
  import { useRouter } from "vue-router";
  import {
    customewTransactions,
  } from "@/api/salesManagement/indicatorStats.js";
@@ -152,6 +173,7 @@
  import Pagination from "../../../components/PIMTable/Pagination.vue";
  const { proxy } = getCurrentInstance();
  const router = useRouter();
  const tableData = ref([]);
  const orderRecord = ref([]);
  const tableLoading = ref(false);
@@ -265,6 +287,14 @@
    getOrderList(customerId.value);
  };
  // æŸ¥çœ‹æ˜Žç»†
  const viewDetail = (row) => {
    router.push({
      path: '/customer-transactions-detail',
      query: { customerId: row.customerId }
    });
  };
  // å®¡æ ¸çŠ¶æ€æ ‡ç­¾ç±»åž‹
  const getApprovalStatusType = (status) => {
    const statusMap = {
src/views/salesManagement/returnOrder/components/formDia.vue
@@ -32,14 +32,32 @@
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="关联批号:" prop="shippingId">
                <el-select v-model="form.shippingId" filterable placeholder="请选择关联批号" @change="outboundNoChange">
              <el-form-item label="发货信息:" prop="shippingId">
                <el-select
                  v-model="form.shippingId"
                  filterable
                  placeholder="请选择发货信息"
                  :disabled="!form.customerId"
                  @change="outboundNoChange"
                >
                  <el-option
                    v-for="item in outboundOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                    :key="item.shippingId"
                    :label="item.displayLabel"
                    :value="item.shippingId"
                    :disabled="item.returnStatus === '全部退货'"
                  >
                    <div style="display: flex; justify-content: space-between; align-items: center;">
                      <span>{{ item.displayLabel }}</span>
                      <el-tag
                        size="small"
                        :type="getReturnStatusType(item.returnStatus)"
                        style="margin-left: 8px;"
                      >
                        {{ item.returnStatus || '无退货' }}
                      </el-tag>
                    </div>
                  </el-option>
                </el-select>
              </el-form-item>
            </el-col>
@@ -71,6 +89,39 @@
            <el-col :span="4">
              <el-form-item label="退款总额:" prop="refundAmount">
                <el-input v-model="form.refundAmount" disabled placeholder="自动计算" />
              </el-form-item>
            </el-col>
          </el-row>
          <!-- å‘货信息回显 -->
          <el-row :gutter="30" v-if="form.shippingId">
            <el-col :span="4">
              <el-form-item label="批次号:">
                <el-input v-model="form.batchNo" disabled placeholder="--" />
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="车牌号:">
                <el-input v-model="form.shippingCarNumber" disabled placeholder="--" />
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="产品名称:">
                <el-input v-model="form.productName" disabled placeholder="--" />
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="规格型号:">
                <el-input v-model="form.model" disabled placeholder="--" />
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="发货数量:">
                <el-input v-model="form.shippingQuantity" disabled placeholder="--" />
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="销售合同号:">
                <el-input v-model="form.salesContractNo" disabled placeholder="--" />
              </el-form-item>
            </el-col>
          </el-row>
@@ -172,7 +223,7 @@
<script setup>
import { reactive, ref, toRefs, getCurrentInstance } from "vue";
import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById } from "@/api/salesManagement/returnOrder.js";
import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById, getShippingInfoForReturn } from "@/api/salesManagement/returnOrder.js";
import useUserStore from "@/store/modules/user.js";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { listProject } from "@/api/projectManagement/project.js";
@@ -197,6 +248,14 @@
    status: 0,
    returnReason: "",
    refundAmount: "",
    // å‘货信息回显字段
    batchNo: "",
    shippingCarNumber: "",
    productName: "",
    model: "",
    shippingQuantity: null,
    salesContractNo: "",
    shippingNo: "",
  },
  rules: {
    returnNo: [{
@@ -207,7 +266,7 @@
      }, trigger: "blur"
    }],
    customerId: [{ required: true, message: "请选择客户", trigger: "change" }],
    shippingId: [{ required: true, message: "请选择关联出库单号", trigger: "change" }],
    shippingId: [{ required: true, message: "请选择发货信息", trigger: "change" }],
  }
});
const { form, rules } = toRefs(data);
@@ -501,6 +560,13 @@
      status: 0,
      returnReason: "",
      refundAmount: "",
      batchNo: "",
      shippingCarNumber: "",
      productName: "",
      model: "",
      shippingQuantity: null,
      salesContractNo: "",
      shippingNo: "",
    });
    form.value.maker = userStore.nickName || userStore.name || "";
    form.value.makeTime = new Date().toISOString().replace('T', ' ').split('.')[0]; // Default to now
@@ -584,33 +650,68 @@
  if (clearDownstream) {
    form.value.shippingId = "";
    outboundOptions.value = [];
    clearShippingInfo();
  }
  // Find customer name for getSalesLedger if it requires name
  // Find customer name for API
  const customer = customerNameOptions.value.find(c => c.id === val);
  if (!customer) return;
  // Assuming getSalesLedger takes customerName. If it takes ID, adjust accordingly.
  // Previous code used customerName. Let's try passing customerName.
  getSalesLedger({
    customerName: customer.label,
  // ä½¿ç”¨æ–°æŽ¥å£ getShippingInfoForReturn
  getShippingInfoForReturn({
    customerName: customer.label,
  }).then(res => {
    if(res.code === 200){
      outboundOptions.value = res.data.map(item => ({
        label: item.shippingNo, // Or whatever the outbound number field is
        value: item.id,
      }))
      outboundOptions.value = res.data || [];
    }
  })
};
// æ¸…空发货信息
const clearShippingInfo = () => {
  form.value.batchNo = "";
  form.value.shippingCarNumber = "";
  form.value.productName = "";
  form.value.model = "";
  form.value.shippingQuantity = null;
  form.value.salesContractNo = "";
  form.value.shippingNo = "";
};
// èŽ·å–é€€è´§çŠ¶æ€æ ‡ç­¾ç±»åž‹
const getReturnStatusType = (returnStatus) => {
  const statusMap = {
    '无退货': 'success',
    '部分退货': 'warning',
    '全部退货': 'danger',
  };
  return statusMap[returnStatus] || 'info';
};
const outboundNoChange = async (val, clearTable = true) => {
  // val is shippingId
  // ä»Ž outboundOptions ä¸­æ‰¾åˆ°é€‰ä¸­çš„发货信息
  const selected = outboundOptions.value.find(item => item.shippingId === val);
  if (selected) {
    // å›žæ˜¾æ•°æ®
    form.value.batchNo = selected.batchNo || "";
    form.value.shippingCarNumber = selected.shippingCarNumber || "";
    form.value.productName = selected.productName || "";
    form.value.model = selected.model || "";
    form.value.shippingQuantity = selected.shippingQuantity || 0;
    form.value.salesContractNo = selected.salesContractNo || "";
    form.value.shippingNo = selected.shippingNo || "";
  } else {
    clearShippingInfo();
  }
  // èŽ·å–äº§å“åˆ—è¡¨
  let res = await returnManagementGetByShippingId({ shippingId: val });
  if(res.code === 200){
    // If backend returns project info, set it
    if (res.data.projectId) form.value.projectId = res.data.projectId;
    availableProducts.value = mergeShippingProductLists(res.data);
    if (clearTable) tableData.value = [];
  }
src/views/salesManagement/returnOrder/index.vue
@@ -38,6 +38,11 @@
        <template #status="{ row }">
          <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
        </template>
        <template #returnStatus="{ row }">
          <el-tag :type="getReturnStatusType(row.returnStatus)" size="small">
            {{ row.returnStatus || '无退货' }}
          </el-tag>
        </template>
      </PIMTable>
    </div>
    <form-dia ref="formDia" @close="handleQuery" />
@@ -114,6 +119,7 @@
const defaultColumns = [
  { label: "退货单号", prop: "returnNo", minWidth: 160 },
  { label: "单据状态", prop: "status", minWidth: 90, dataType: "slot", slot: "status" },
  { label: "退货状态", prop: "returnStatus", minWidth: 100, dataType: "slot", slot: "returnStatus" },
  { label: "制单时间", prop: "makeTime", minWidth: 170 },
  { label: "客户名称", prop: "customerName", minWidth: 220 },
  { label: "销售单号", prop: "salesContractNo", minWidth: 160 },
@@ -207,6 +213,15 @@
  return statusMap[status] || "未知";
};
const getReturnStatusType = (returnStatus) => {
  const statusMap = {
    '无退货': 'success',
    '部分退货': 'warning',
    '全部退货': 'danger',
  };
  return statusMap[returnStatus] || 'info';
};
onMounted(() => {
  getList();
});
src/views/salesManagement/salesLedger/index.vue
@@ -248,6 +248,21 @@
          width="200"
          show-overflow-tooltip
        />
        <el-table-column
          label="退货状态"
          prop="returnStatus"
          align="center"
          width="100"
        >
          <template #default="scope">
            <el-tag
              :type="getReturnStatusType(scope.row.returnStatus)"
              size="small"
            >
              {{ scope.row.returnStatus || '无退货' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column fixed="right" label="操作" width="220" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="openForm('view', scope.row)"
@@ -404,12 +419,19 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="付款方式">
              <el-input
              <el-select
                v-model="form.paymentMethod"
                placeholder="请输入"
                placeholder="请选择"
                clearable
                :disabled="operationType === 'view'"
              />
                style="width: 100%"
              >
                <el-option label="现金" value="现金" />
                <el-option label="电汇" value="电汇" />
                <el-option label="微信" value="微信" />
                <el-option label="支付宝" value="支付宝" />
                <el-option label="支票" value="支票" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -3002,6 +3024,17 @@
  let res = await userStore.getInfo();
  currentFactoryName.value = res.user.currentFactoryName;
};
// èŽ·å–é€€è´§çŠ¶æ€æ ‡ç­¾ç±»åž‹
const getReturnStatusType = (returnStatus) => {
  const statusMap = {
    '无退货': 'success',
    '部分退货': 'warning',
    '全部退货': 'danger',
  };
  return statusMap[returnStatus] || 'info';
};
onMounted(() => {
  searchForm.salesContractNo = route.query.salesContractNo;
  getList();
src/views/salesManagement/salesQuotation/index.vue
@@ -148,7 +148,13 @@
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="付款方式" prop="paymentMethod">
                  <el-input v-model="form.paymentMethod" placeholder="请输入付款方式" clearable />
                  <el-select v-model="form.paymentMethod" placeholder="请选择付款方式" clearable style="width: 100%">
                    <el-option label="现金" value="现金" />
                    <el-option label="电汇" value="电汇" />
                    <el-option label="微信" value="微信" />
                    <el-option label="支付宝" value="支付宝" />
                    <el-option label="支票" value="支票" />
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
@@ -346,7 +352,7 @@
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  quotationDate: [{ required: true, message: '请选择报价日期', trigger: 'change' }],
  validDate: [{ required: true, message: '请选择有效期', trigger: 'change' }],
  paymentMethod: [{ required: true, message: '请输入付款方式', trigger: 'blur' }]
  paymentMethod: [{ required: true, message: '请选择付款方式', trigger: 'change' }]
}
const productRowRules = {