yuan
9 天以前 a863e15f2cac29885cd8ca779013c2b18c2398d5
src/views/procurementManagement/procurementReport/index.vue
@@ -25,6 +25,22 @@
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item label="产品规格:">
          <el-select
            v-model="searchForm.specificationModel"
            placeholder="请选择产品规格"
            clearable
            filterable
            style="width: 200px"
          >
            <el-option
              v-for="m in modelOptions"
              :key="m.value"
              :label="m.label"
              :value="m.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch" :loading="loading">
            查询
@@ -48,7 +64,7 @@
          <div class="summary-stats">
            <div class="stat-item">
              <span class="stat-label">采购总额:</span>
              <span class="stat-value">¥{{ businessSummaryStats.totalAmount.toLocaleString() }}</span>
              <span class="stat-value">¥{{ formatStatAmount(businessSummaryStats.totalAmount) }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">商品种类:</span>
@@ -56,7 +72,7 @@
            </div>
            <div class="stat-item">
              <span class="stat-label">退款总额:</span>
              <span class="stat-value">{{ businessSummaryStats.returnAmount }}</span>
              <span class="stat-value">¥{{ formatStatAmount(businessSummaryStats.returnAmount) }}</span>
            </div>
          </div>
        </div>
@@ -92,17 +108,41 @@
// 搜索表单
const searchForm = reactive({
  dateRange: [],
  productCategory: ''
  productCategory: '',
  specificationModel: ''
})
// 产品类别树选项
const productOptions = ref([])
// 产品规格选项(按报表实际出现的规格动态生成)
const modelOptions = ref([])
// 统计数据
const businessSummaryStats = ref({
  totalAmount: 0,
  returnAmount: 0,
  productTypes: 0
})
// 数值展示口径:后端返回 6 位小数(如 211.970000、¥3,420,674.200000),
// 页面按后端实际位数展示即可,不再补齐 6 位——多余的结尾 0 一律去掉。
// maximumFractionDigits 保留 6,是为了兜住前端自己 reduce 求和时的浮点尾数(如 3420674.2000000002)。
const NUM_FMT = { maximumFractionDigits: 6 }
// 数量/金额统一格式化:带千分位,去掉结尾多余的 0;空值按 0 处理
const formatNum = (val, prefix = '') => {
  const num = parseFloat(val)
  if (val === null || val === undefined || val === '' || Number.isNaN(num)) {
    return `${prefix}0`
  }
  return `${prefix}${num.toLocaleString('zh-CN', NUM_FMT)}`
}
// 汇总金额展示
const formatStatAmount = (val) => {
  return formatNum(val)
}
// 表格列配置(根据后端字段定义)
const tableColumns = ref([
@@ -118,32 +158,24 @@
    label: '采购数量',
    prop: 'purchaseNum',
    width: 120,
    formatData: (val) => {
      return val ? parseFloat(val).toLocaleString() : '0'
    }
    formatData: (val) => formatNum(val)
  },
  {
    label: '退货数量',
    prop: 'returnQuantity',
    width: 120,
    formatData: (val) => {
      return val ? parseFloat(val).toLocaleString() : '0'
    }
    formatData: (val) => formatNum(val)
  },
  {
    label: '退货金额',
    prop: 'returnAmount',
    width: 120,
    formatData: (val) => {
      return val ? `¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '¥0.00'
    }
    formatData: (val) => formatNum(val, '¥')
  },
  {
    label: '退款储量',
    prop: 'purchaseAmount',
    formatData: (val) => {
      return val ? `¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '¥0.00'
    }
    formatData: (val) => formatNum(val, '¥')
  },
  {
    label: '采购次数',
@@ -154,9 +186,7 @@
    label: '平均单价',
    prop: 'averagePrice',
    width: 120,
    formatData: (val) => {
      return val ? `¥${parseFloat(val).toFixed(2)}` : '¥0.00'
    }
    formatData: (val) => formatNum(val, '¥')
  },
  {
    label: '供应商名称',
@@ -224,52 +254,55 @@
  try {
    loading.value = true
    const params = {}
    // 时间范围
    if (searchForm.dateRange && searchForm.dateRange.length === 2) {
      params.entryDateStart = searchForm.dateRange[0]
      params.entryDateEnd = searchForm.dateRange[1]
    }
    // 产品类别
    // 产品大类
    if (searchForm.productCategory) {
      const categoryName = findNodeLabelById(productOptions.value, searchForm.productCategory)
      if (categoryName) {
        params.productCategory = categoryName
      }
    }
    // 分页参数
    params.current = page.current
    params.size = page.size
    // 报表按「产品大类+规格+供应商」聚合,数据量小,一次取回后按规格本地过滤
    params.current = 1
    params.size = 1000
    const res = await procurementBusinessSummaryListPage(params)
    if (res && res.data) {
      // 兼容后端可能直接返回数组/或返回分页对象
      businessSummaryData.value = Array.isArray(res.data) ? res.data : (res.data.records || [])
    const payload = res && res.data !== undefined ? res.data : res
    let rows = Array.isArray(payload) ? payload : ((payload && payload.records) || [])
      if (!Array.isArray(res.data)) {
        page.total = Number(res.data.total ?? 0)
        page.current = Number(res.data.current ?? page.current)
        page.size = Number(res.data.size ?? page.size)
      }
      // 计算统计数据
      if (businessSummaryData.value.length > 0) {
        businessSummaryStats.value.totalAmount = businessSummaryData.value.reduce((sum, item) => {
          return sum + (parseFloat(item.purchaseAmount) || 0)
        }, 0)
        businessSummaryStats.value.returnAmount = businessSummaryData.value.reduce((sum, item) => {
          return sum + (parseFloat(item.returnAmount) || 0)
        }, 0)
        businessSummaryStats.value.productTypes = businessSummaryData.value.length
      } else {
        businessSummaryStats.value = {
          totalAmount: 0,
          productTypes: 0
        }
      }
    // 规格下拉选项随当前查询结果动态生成
    const specSet = []
    rows.forEach((item) => {
      const sp = item.specificationModel
      if (sp && specSet.indexOf(sp) === -1) specSet.push(sp)
    })
    modelOptions.value = specSet.sort().map((sp) => ({ label: sp, value: sp }))
    // 产品规格过滤
    if (searchForm.specificationModel) {
      rows = rows.filter((item) => item.specificationModel === searchForm.specificationModel)
    }
    page.total = rows.length
    if (page.current < 1) page.current = 1
    const startIdx = (page.current - 1) * page.size
    businessSummaryData.value = rows.slice(startIdx, startIdx + page.size)
    // 统计按全量过滤结果口径
    businessSummaryStats.value.totalAmount = rows.reduce((sum, item) => {
      return sum + (parseFloat(item.purchaseAmount) || 0)
    }, 0)
    businessSummaryStats.value.returnAmount = rows.reduce((sum, item) => {
      return sum + (parseFloat(item.returnAmount) || 0)
    }, 0)
    businessSummaryStats.value.productTypes = rows.length
  } catch (error) {
    console.error('查询失败:', error)
  } finally {
@@ -287,7 +320,8 @@
const resetSearch = () => {
  Object.assign(searchForm, {
    dateRange: [],
    productCategory: ''
    productCategory: '',
    specificationModel: ''
  })
  page.current = 1
  handleSearch()
@@ -315,7 +349,7 @@
onMounted(() => {
  // 初始化产品类别树
  // 初始化产品类别树、产品规格
  getProductOptions()
  
  // 设置默认时间范围为最近30天
@@ -334,12 +368,6 @@
</script>
<style scoped>
.app-container {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.page-header {
  text-align: center;
  margin-bottom: 20px;