zhangwencui
6 小时以前 afacd28d1593ce4698d83e239dc82440c7d945ad
src/views/costAccounting/productionCostAccounting/index.vue
@@ -1,14 +1,17 @@
<template>
  <div class="production-cost-page">
    <el-card class="filter-card" shadow="never">
    <el-card class="filter-card"
             shadow="never">
      <template #header>
        <div class="card-head">
          <div class="card-head-left">
            <el-icon class="card-icon ui-icon"><DataLine /></el-icon>
            <el-icon class="card-icon ui-icon">
              <DataLine />
            </el-icon>
            <span class="card-title">生产成本核算</span>
            <span class="subtle">成本 = Σ 投入量 × 对应单价</span>
          </div>
          <div class="card-head-right">
          <!-- <div class="card-head-right">
            <el-radio-group
              v-model="statisticsType"
              size="small"
@@ -17,15 +20,15 @@
              <el-radio-button label="day">按日</el-radio-button>
              <el-radio-button label="month">按月</el-radio-button>
            </el-radio-group>
          </div>
          </div> -->
        </div>
      </template>
      <div class="filter-layout">
        <el-form :model="searchForm" :inline="true" class="filter-form">
        <el-form :model="searchForm"
                 :inline="true"
                 class="filter-form">
          <el-form-item label="时间范围">
            <el-date-picker
              v-if="statisticsType === 'day'"
            <el-date-picker v-if="statisticsType === 'day'"
              v-model="searchForm.dateRange"
              type="daterange"
              range-separator="至"
@@ -33,10 +36,8 @@
              end-placeholder="结束日期"
              value-format="YYYY-MM-DD"
              class="w-260"
              @change="handleQuery"
            />
            <el-date-picker
              v-else
                            @change="handleQuery" />
            <el-date-picker v-else
              v-model="searchForm.monthRange"
              type="monthrange"
              range-separator="至"
@@ -44,57 +45,54 @@
              end-placeholder="结束月份"
              value-format="YYYY-MM"
              class="w-260"
              @change="handleQuery"
            />
                            @change="handleQuery" />
          </el-form-item>
          <el-form-item label="产品类别">
            <el-select
              v-model="searchForm.category"
            <el-select v-model="searchForm.dictCode"
              clearable
              filterable
              placeholder="全部类别"
              class="w-180"
              @change="handleQuery"
            >
              <el-option
                v-for="item in categoryOptions"
                :key="item"
                :label="item"
                :value="item"
              />
                       @change="handleQuery">
              <el-option v-for="item in categoryOptions"
                         :key="item.dictCode"
                         :label="item.dictLabel"
                         :value="item.dictCode" />
            </el-select>
          </el-form-item>
          <el-form-item label="生产订单">
            <el-select
              v-model="searchForm.orderNo"
            <el-select v-model="searchForm.productOrderId"
              clearable
              filterable
              placeholder="全部订单"
              class="w-180"
              @change="handleQuery"
            >
              <el-option
                v-for="item in orderOptions"
                :key="item"
                :label="item"
                :value="item"
              />
                       @change="handleQuery">
              <el-option v-for="order in orderList"
                         :key="order.id"
                         :label="`${order.npsNo}`"
                         :value="order.id" />
            </el-select>
          </el-form-item>
        </el-form>
        <div class="filter-actions">
          <el-button class="lux-btn" type="primary" @click="handleQuery">
          <el-button class="lux-btn"
                     type="primary"
                     @click="handleQuery">
            刷新
          </el-button>
          <el-button class="lux-btn" @click="handleReset">重置</el-button>
          <el-button class="lux-btn" type="success" plain @click="handleExport">
          <el-button class="lux-btn"
                     @click="handleReset">重置</el-button>
          <el-button class="lux-btn"
                     type="success"
                     plain
                     @click="handleExport">
            导出
          </el-button>
        </div>
      </div>
    </el-card>
    <el-card class="panel-card" shadow="never">
    <el-card class="panel-card"
             shadow="never">
      <div class="kpi-strip">
        <div class="kpi-item kpi-total">
          <div class="kpi-label">总生产成本</div>
@@ -110,26 +108,37 @@
        </div>
      </div>
    </el-card>
    <el-row :gutter="14" class="summary-row">
    <el-row :gutter="14"
            class="summary-row">
      <el-col :span="12">
        <el-card class="table-card" shadow="never">
        <el-card class="table-card"
                 shadow="never">
          <template #header>
            <div class="panel-head">
              <span class="card-title">按产品类别汇总</span>
            </div>
          </template>
          <el-table :data="categorySummary" stripe class="lux-table" height="260">
            <el-table-column prop="category" label="产品类别" min-width="140" />
            <el-table-column prop="totalQuantity" label="用量" align="right" min-width="120">
          <el-table :data="categorySummary"
                    stripe
                    class="lux-table"
                    height="260">
            <el-table-column prop="name"
                             label="产品类别"
                             min-width="140" />
            <el-table-column prop="quantity"
                             label="用量"
                             align="right"
                             min-width="120">
              <template #default="scope">
                <span class="quantity-cell">
                  <span class="quantity-value">{{ formatNumber(scope.row.totalQuantity, 2) }}</span>
                  <span class="quantity-value">{{ formatNumber(scope.row.quantity, 2) }}</span>
                  <span class="quantity-unit">{{ scope.row.unit || "-" }}</span>
                </span>
              </template>
            </el-table-column>
            <el-table-column prop="totalCost" label="成本(元)" align="right">
            <el-table-column prop="totalCost"
                             label="成本(元)"
                             align="right">
              <template #default="scope">
                <span class="cost-value">¥{{ formatMoney(scope.row.totalCost) }}</span>
              </template>
@@ -138,24 +147,37 @@
        </el-card>
      </el-col>
      <el-col :span="12">
        <el-card class="table-card" shadow="never">
        <el-card class="table-card"
                 shadow="never">
          <template #header>
            <div class="panel-head">
              <span class="card-title">按生产订单汇总</span>
            </div>
          </template>
          <el-table :data="orderSummary" stripe class="lux-table" height="260">
            <el-table-column prop="orderNo" label="生产订单" min-width="150" />
            <el-table-column prop="category" label="产品类别" min-width="120" />
            <el-table-column prop="totalQuantity" label="用量" align="right" min-width="120">
          <el-table :data="orderSummary"
                    stripe
                    class="lux-table"
                    height="260">
            <el-table-column prop="name"
                             label="生产订单"
                             min-width="150" />
            <el-table-column prop="strength"
                             label="产品类别"
                             min-width="120" />
            <el-table-column prop="quantity"
                             label="用量"
                             align="right"
                             min-width="120">
              <template #default="scope">
                <span class="quantity-cell">
                  <span class="quantity-value">{{ formatNumber(scope.row.totalQuantity, 2) }}</span>
                  <span class="quantity-value">{{ formatNumber(scope.row.quantity, 2) }}</span>
                  <span class="quantity-unit">{{ scope.row.unit || "-" }}</span>
                </span>
              </template>
            </el-table-column>
            <el-table-column prop="totalCost" label="总成本(元)" align="right">
            <el-table-column prop="totalCost"
                             label="总成本(元)"
                             align="right">
              <template #default="scope">
                <span class="cost-value">¥{{ formatMoney(scope.row.totalCost) }}</span>
              </template>
@@ -164,61 +186,41 @@
        </el-card>
      </el-col>
    </el-row>
    <el-card class="table-card" shadow="never">
    <el-row :gutter="14"
            class="summary-row">
      <el-col :span="12">
        <el-card class="table-card"
                 shadow="never">
      <template #header>
        <div class="panel-head">
          <span class="card-title">多维度汇总明细</span>
          <span class="subtle">{{ timeColumnLabel }} + 产品类别 + 生产订单</span>
              <span class="card-title">产品物料Top10</span>
        </div>
      </template>
      <el-table :data="pagedTableData" stripe class="lux-table">
        <el-table-column prop="timeLabel" :label="timeColumnLabel" min-width="110" />
        <el-table-column prop="category" label="产品类别" min-width="120" />
        <el-table-column prop="orderNo" label="生产订单" min-width="150" />
        <el-table-column prop="totalQuantity" label="用量" align="right" min-width="130">
          <template #default="scope">
            <span class="quantity-cell">
              <span class="quantity-value">{{ formatNumber(scope.row.totalQuantity, 2) }}</span>
              <span class="quantity-unit">{{ scope.row.unit || "-" }}</span>
            </span>
          </template>
        </el-table-column>
        <el-table-column prop="totalCost" label="成本(元)" align="right">
          <template #default="scope">
            <span class="cost-value">¥{{ formatMoney(scope.row.totalCost) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="拆分明细" width="92" fixed="right">
          <template #default="scope">
            <el-button link type="primary" @click="openDetail(scope.row)">查看</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <el-pagination
          v-model:current-page="page.current"
          v-model:page-size="page.size"
          :page-sizes="[10, 20, 50, 100]"
          :total="tableData.length"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
          <div ref="topOrdersChartRef"
               class="chart-container"
               style="height: 300px;"></div>
    </el-card>
    <el-drawer
      v-model="detailVisible"
      </el-col>
      <el-col :span="12">
        <el-card class="table-card"
                 shadow="never">
          <template #header>
            <div class="panel-head">
              <span class="card-title">生产订单Top10</span>
            </div>
          </template>
        </el-card>
      </el-col>
    </el-row>
    <el-drawer v-model="detailVisible"
      :with-header="false"
      class="detail-drawer"
      size="760px"
      :close-on-click-modal="true"
      :close-on-press-escape="true"
      destroy-on-close
    >
      <div v-if="detailRow" class="drawer-head">
               destroy-on-close>
      <div v-if="detailRow"
           class="drawer-head">
        <div class="meta-item">
          <span class="meta-label">{{ timeColumnLabel }}</span>
          <span class="meta-value">{{ detailRow.timeLabel }}</span>
@@ -232,9 +234,16 @@
          <span class="meta-value">{{ detailRow.orderNo }}</span>
        </div>
      </div>
      <el-table :data="detailMaterials" class="lux-table" stripe>
        <el-table-column prop="materialName" label="物料名称" min-width="120" />
        <el-table-column prop="quantity" label="投入量" align="right" min-width="140">
      <el-table :data="detailMaterials"
                class="lux-table"
                stripe>
        <el-table-column prop="materialName"
                         label="物料名称"
                         min-width="120" />
        <el-table-column prop="quantity"
                         label="投入量"
                         align="right"
                         min-width="140">
          <template #default="scope">
            <span class="quantity-cell">
              <span class="quantity-value">{{ formatNumber(scope.row.quantity, 2) }}</span>
@@ -242,12 +251,17 @@
            </span>
          </template>
        </el-table-column>
        <el-table-column prop="unitPrice" label="单价(元)" align="right">
        <el-table-column prop="unitPrice"
                         label="单价(元)"
                         align="right">
          <template #default="scope">
            {{ formatNumber(scope.row.unitPrice, 2) }}
          </template>
        </el-table-column>
        <el-table-column prop="cost" label="成本(元)" align="right" min-width="132">
        <el-table-column prop="cost"
                         label="成本(元)"
                         align="right"
                         min-width="132">
          <template #default="scope">
            <span class="cost-value no-wrap-money">¥{{ formatMoney(scope.row.cost) }}</span>
          </template>
@@ -264,9 +278,18 @@
</template>
<script setup>
import { computed, reactive, ref, watch } from "vue";
  import { computed, reactive, ref, watch, onMounted, nextTick } from "vue";
import { DataLine } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
  import * as echarts from "echarts";
  import { getDicts } from "@/api/system/dict/data.js";
  import { productOrderListPage } from "@/api/productionManagement/productionOrder.js";
  import {
    getProductionCostSummary,
    getProductionCostAggregateByProduct,
    getProductionCostAggregateByOrder,
    getProductionCostTopOrders,
  } from "@/api/costAccounting/productionCost.js";
const statisticsType = ref("day");
@@ -287,57 +310,198 @@
const searchForm = reactive({
  dateRange: getDefaultDateRange(),
  monthRange: getDefaultMonthRange(),
  category: "",
  orderNo: "",
    dictCode: "",
    productOrderId: "",
});
const sourceRecords = ref([
  { date: "2026-03-17", category: "瓷砖", orderNo: "PO-260317-01", materialName: "陶瓷粉", materialType: "原料", quantity: 1200, unit: "kg", unitPrice: 2.8 },
  { date: "2026-03-17", category: "瓷砖", orderNo: "PO-260317-01", materialName: "釉料", materialType: "辅料", quantity: 180, unit: "kg", unitPrice: 8.6 },
  { date: "2026-03-17", category: "水泥", orderNo: "PO-260317-02", materialName: "熟料", materialType: "原料", quantity: 2200, unit: "kg", unitPrice: 1.36 },
  { date: "2026-03-17", category: "水泥", orderNo: "PO-260317-02", materialName: "石膏", materialType: "辅料", quantity: 260, unit: "kg", unitPrice: 0.92 },
  { date: "2026-03-18", category: "砂浆", orderNo: "PO-260318-01", materialName: "机制砂", materialType: "原料", quantity: 1600, unit: "kg", unitPrice: 0.58 },
  { date: "2026-03-18", category: "砂浆", orderNo: "PO-260318-01", materialName: "保水剂", materialType: "辅料", quantity: 65, unit: "kg", unitPrice: 11.4 },
  { date: "2026-03-19", category: "瓷砖", orderNo: "PO-260319-01", materialName: "陶瓷粉", materialType: "原料", quantity: 980, unit: "kg", unitPrice: 2.9 },
  { date: "2026-03-19", category: "瓷砖", orderNo: "PO-260319-01", materialName: "色料", materialType: "辅料", quantity: 42, unit: "kg", unitPrice: 15.8 },
  { date: "2026-03-19", category: "砂浆", orderNo: "PO-260319-03", materialName: "机制砂", materialType: "原料", quantity: 1400, unit: "kg", unitPrice: 0.56 },
  { date: "2026-03-19", category: "砂浆", orderNo: "PO-260319-03", materialName: "减水剂", materialType: "辅料", quantity: 74, unit: "kg", unitPrice: 7.2 },
  { date: "2026-03-20", category: "水泥", orderNo: "PO-260320-02", materialName: "熟料", materialType: "原料", quantity: 2400, unit: "kg", unitPrice: 1.33 },
  { date: "2026-03-20", category: "水泥", orderNo: "PO-260320-02", materialName: "矿粉", materialType: "辅料", quantity: 380, unit: "kg", unitPrice: 1.08 },
    {
      date: "2026-03-17",
      category: "瓷砖",
      orderNo: "PO-260317-01",
      materialName: "陶瓷粉",
      materialType: "原料",
      quantity: 1200,
      unit: "kg",
      unitPrice: 2.8,
    },
    {
      date: "2026-03-17",
      category: "瓷砖",
      orderNo: "PO-260317-01",
      materialName: "釉料",
      materialType: "辅料",
      quantity: 180,
      unit: "kg",
      unitPrice: 8.6,
    },
    {
      date: "2026-03-17",
      category: "水泥",
      orderNo: "PO-260317-02",
      materialName: "熟料",
      materialType: "原料",
      quantity: 2200,
      unit: "kg",
      unitPrice: 1.36,
    },
    {
      date: "2026-03-17",
      category: "水泥",
      orderNo: "PO-260317-02",
      materialName: "石膏",
      materialType: "辅料",
      quantity: 260,
      unit: "kg",
      unitPrice: 0.92,
    },
    {
      date: "2026-03-18",
      category: "砂浆",
      orderNo: "PO-260318-01",
      materialName: "机制砂",
      materialType: "原料",
      quantity: 1600,
      unit: "kg",
      unitPrice: 0.58,
    },
    {
      date: "2026-03-18",
      category: "砂浆",
      orderNo: "PO-260318-01",
      materialName: "保水剂",
      materialType: "辅料",
      quantity: 65,
      unit: "kg",
      unitPrice: 11.4,
    },
    {
      date: "2026-03-19",
      category: "瓷砖",
      orderNo: "PO-260319-01",
      materialName: "陶瓷粉",
      materialType: "原料",
      quantity: 980,
      unit: "kg",
      unitPrice: 2.9,
    },
    {
      date: "2026-03-19",
      category: "瓷砖",
      orderNo: "PO-260319-01",
      materialName: "色料",
      materialType: "辅料",
      quantity: 42,
      unit: "kg",
      unitPrice: 15.8,
    },
    {
      date: "2026-03-19",
      category: "砂浆",
      orderNo: "PO-260319-03",
      materialName: "机制砂",
      materialType: "原料",
      quantity: 1400,
      unit: "kg",
      unitPrice: 0.56,
    },
    {
      date: "2026-03-19",
      category: "砂浆",
      orderNo: "PO-260319-03",
      materialName: "减水剂",
      materialType: "辅料",
      quantity: 74,
      unit: "kg",
      unitPrice: 7.2,
    },
    {
      date: "2026-03-20",
      category: "水泥",
      orderNo: "PO-260320-02",
      materialName: "熟料",
      materialType: "原料",
      quantity: 2400,
      unit: "kg",
      unitPrice: 1.33,
    },
    {
      date: "2026-03-20",
      category: "水泥",
      orderNo: "PO-260320-02",
      materialName: "矿粉",
      materialType: "辅料",
      quantity: 380,
      unit: "kg",
      unitPrice: 1.08,
    },
]);
  const orderList = ref([]);
  // 加载生产订单列表
  const loadOrders = () => {
    productOrderListPage({ pageNum: -1, pageSize: -1 })
      .then(res => {
        orderList.value = res.data.records || [];
      })
      .finally(() => {});
  };
const normalizedRecords = computed(() =>
  sourceRecords.value.map((item) => {
    sourceRecords.value.map(item => {
    const month = item.date.slice(0, 7);
    const cost = Number(item.quantity) * Number(item.unitPrice);
    return { ...item, month, cost };
  })
);
const categoryOptions = computed(() =>
  Array.from(new Set(normalizedRecords.value.map((item) => item.category)))
);
  const categoryOptions = ref([]);
  // 获取产品类型字典
  const getProductTypeOptions = () => {
    getDicts("product_type")
      .then(res => {
        if (res.code === 200) {
          categoryOptions.value = res.data;
        }
      })
      .catch(err => {
        console.error("获取产品类型字典失败:", err);
      });
  };
const orderOptions = computed(() =>
  Array.from(new Set(normalizedRecords.value.map((item) => item.orderNo)))
    Array.from(new Set(normalizedRecords.value.map(item => item.orderNo)))
);
const inRange = (value, range) => {
  if (!Array.isArray(range) || range.length !== 2 || !range[0] || !range[1]) return true;
    if (!Array.isArray(range) || range.length !== 2 || !range[0] || !range[1])
      return true;
  return value >= range[0] && value <= range[1];
};
const getMonthRangeDays = (monthRange) => {
  if (!Array.isArray(monthRange) || monthRange.length !== 2 || !monthRange[0] || !monthRange[1]) {
  const getMonthRangeDays = monthRange => {
    if (
      !Array.isArray(monthRange) ||
      monthRange.length !== 2 ||
      !monthRange[0] ||
      !monthRange[1]
    ) {
    return 0;
  }
  const [startMonth, endMonth] = monthRange;
  const startDate = new Date(`${startMonth}-01T00:00:00`);
  const endDate = new Date(`${endMonth}-01T00:00:00`);
  if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()) || startDate > endDate) {
    if (
      Number.isNaN(startDate.getTime()) ||
      Number.isNaN(endDate.getTime()) ||
      startDate > endDate
    ) {
    return 0;
  }
  const endMonthLastDay = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0);
    const endMonthLastDay = new Date(
      endDate.getFullYear(),
      endDate.getMonth() + 1,
      0
    );
  const diffMs = endMonthLastDay.getTime() - startDate.getTime();
  return Math.floor(diffMs / (24 * 60 * 60 * 1000)) + 1;
};
@@ -346,8 +510,8 @@
  const isDay = statisticsType.value === "day";
  const params = {
    statisticsType: statisticsType.value,
    category: searchForm.category || undefined,
    orderNo: searchForm.orderNo || undefined,
      dictCode: searchForm.dictCode || undefined,
      productOrderId: searchForm.productOrderId || undefined,
  };
  if (isDay) {
@@ -365,18 +529,23 @@
};
const filteredRecords = computed(() =>
  normalizedRecords.value.filter((item) => {
    normalizedRecords.value.filter(item => {
    const hitTime =
      statisticsType.value === "day"
        ? inRange(item.date, searchForm.dateRange)
        : inRange(item.month, searchForm.monthRange);
    const hitCategory = !searchForm.category || item.category === searchForm.category;
    const hitOrder = !searchForm.orderNo || item.orderNo === searchForm.orderNo;
      const hitCategory =
        !searchForm.dictCode || item.dictCode === searchForm.dictCode;
      const hitOrder =
        !searchForm.productOrderId ||
        item.productOrderId === searchForm.productOrderId;
    return hitTime && hitCategory && hitOrder;
  })
);
const timeColumnLabel = computed(() => (statisticsType.value === "day" ? "日期" : "月份"));
  const timeColumnLabel = computed(() =>
    statisticsType.value === "day" ? "日期" : "月份"
  );
const aggregateBy = (list, keyFn) => {
  const map = new Map();
@@ -398,7 +567,7 @@
};
const groupedMap = computed(() =>
  aggregateBy(filteredRecords.value, (item) => {
    aggregateBy(filteredRecords.value, item => {
    const timeKey = statisticsType.value === "day" ? item.date : item.month;
    return `${timeKey}__${item.category}__${item.orderNo}`;
  })
@@ -432,44 +601,19 @@
  return tableData.value.slice(start, start + page.size);
});
const categorySummary = computed(() => {
  const map = aggregateBy(filteredRecords.value, (item) => item.category);
  const rows = [];
  for (const [category, val] of map) {
    rows.push({
      category,
      totalQuantity: val.totalQuantity,
      unit: val.materials[0]?.unit || "",
      totalCost: val.totalCost,
    });
  }
  return rows.sort((a, b) => b.totalCost - a.totalCost);
  const categorySummary = ref([]);
  const orderSummary = ref([]);
  const topOrders = ref([]);
  const overview = ref({
    totalCost: 0,
    orderCount: 0,
    avgCostPerOrder: 0,
});
const orderSummary = computed(() => {
  const map = aggregateBy(filteredRecords.value, (item) => item.orderNo);
  const rows = [];
  for (const [orderNo, val] of map) {
    rows.push({
      orderNo,
      category: val.materials[0]?.category || "-",
      totalQuantity: val.totalQuantity,
      unit: val.materials[0]?.unit || "",
      totalCost: val.totalCost,
    });
  }
  return rows.sort((a, b) => b.totalCost - a.totalCost);
});
const overview = computed(() => {
  const orderCount = new Set(filteredRecords.value.map((item) => item.orderNo)).size;
  const totalCost = filteredRecords.value.reduce((sum, item) => sum + item.cost, 0);
  return {
    totalCost,
    orderCount,
    avgCostPerOrder: orderCount === 0 ? 0 : totalCost / orderCount,
  };
});
  // 图表相关
  const topOrdersChartRef = ref(null);
  let topOrdersChartInstance = null;
const detailVisible = ref(false);
const detailRow = ref(null);
@@ -480,9 +624,87 @@
  detailMaterials.value.reduce((sum, item) => sum + item.cost, 0)
);
const openDetail = (row) => {
  const openDetail = row => {
  detailRow.value = row;
  detailVisible.value = true;
  };
  // 初始化生产订单Top10图表
  const initTopOrdersChart = () => {
    nextTick(() => {
      if (topOrdersChartRef.value) {
        topOrdersChartInstance = echarts.init(topOrdersChartRef.value);
        updateTopOrdersChart();
      }
    });
  };
  // 更新生产订单Top10图表
  const updateTopOrdersChart = () => {
    if (!topOrdersChartInstance) return;
    const data = topOrders.value;
    const xAxisData = data.map(item => item.name);
    const seriesData = data.map(item => item.totalCost);
    const option = {
      tooltip: {
        trigger: "axis",
        axisPointer: {
          type: "shadow",
        },
        backgroundColor: "rgba(255, 255, 255, 0.95)",
        borderColor: "#409EFF",
        borderWidth: 1,
        textStyle: { color: "#303133" },
      },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "15%",
        top: "3%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: xAxisData,
        axisLabel: {
          color: "#606266",
          rotate: 45,
        },
        axisLine: { lineStyle: { color: "#ebeef5" } },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "成本(元)",
        nameTextStyle: { color: "#606266" },
        axisLabel: { color: "#606266" },
        axisLine: { show: false },
        splitLine: { lineStyle: { color: "#f0f2f5" } },
      },
      series: [
        {
          name: "成本",
          type: "bar",
          data: seriesData,
          itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "#409EFF" },
              { offset: 1, color: "#66B1FF" },
            ]),
          },
          barWidth: "60%",
        },
      ],
    };
    topOrdersChartInstance.setOption(option);
  };
  // 窗口大小变化时重新渲染图表
  const handleResize = () => {
    topOrdersChartInstance && topOrdersChartInstance.resize();
};
const handleTypeChange = () => {
@@ -491,31 +713,119 @@
const handleQuery = () => {
  page.current = 1;
  const queryParams = buildQueryParams();
  console.log("[productionCostAccounting] query params:", queryParams);
  ElMessage.success("已按条件完成汇总");
    // 构建API请求参数
    const apiParams = {
      startDate: searchForm.dateRange?.[0],
      endDate: searchForm.dateRange?.[1],
      dictCode: searchForm.dictCode,
      productOrderId: searchForm.productOrderId,
      current: -1,
      size: -1,
    };
    // 调用API获取概览数据
    getProductionCostSummary(apiParams)
      .then(res => {
        if (res.code === 200) {
          const data = res.data;
          overview.value = {
            totalCost: parseFloat(data.totalCost) || 0,
            orderCount: data.orderCount || 0,
            avgCostPerOrder: parseFloat(data.averageOrderCost) || 0,
          };
        } else {
          ElMessage.error(res.message || "获取概览数据失败");
        }
      })
      .catch(err => {
        console.error("获取生产成本汇总数据失败:", err);
        ElMessage.error("系统异常,获取概览数据失败");
      });
    getProductionCostAggregateByOrder;
    // 调用API获取按产品物料汇总数据
    getProductionCostAggregateByProduct(apiParams)
      .then(res => {
        if (res.code === 200) {
          // 按物料名称分组计算
          // 这里简化处理,orderSummary暂时使用相同的数据
          // 实际项目中可能需要调用专门的API获取按订单汇总的数据
          categorySummary.value = res.data.records || [];
        } else {
          ElMessage.error(res.message || "获取物料汇总数据失败");
        }
      })
      .catch(err => {
        console.error("获取按产品物料汇总数据失败:", err);
        ElMessage.error("系统异常,获取物料汇总数据失败");
      });
    getProductionCostAggregateByOrder(apiParams)
      .then(res => {
        if (res.code === 200) {
          // 按物料名称分组计算
          // 这里简化处理,orderSummary暂时使用相同的数据
          // 实际项目中可能需要调用专门的API获取按订单汇总的数据
          orderSummary.value = res.data.records || [];
        } else {
          ElMessage.error(res.message || "获取订单汇总数据失败");
        }
      })
      .catch(err => {
        console.error("获取按订单汇总数据失败:", err);
        ElMessage.error("系统异常,获取订单汇总数据失败");
      });
    // 调用API获取生产订单Top10数据
    getProductionCostTopOrders(apiParams)
      .then(res => {
        if (res.code === 200) {
          topOrders.value = res.data || [];
          updateTopOrdersChart();
        } else {
          ElMessage.error(res.message || "获取生产订单Top10数据失败");
        }
      })
      .catch(err => {
        console.error("获取生产订单Top10数据失败:", err);
        ElMessage.error("系统异常,获取生产订单Top10数据失败");
      });
};
const handleReset = () => {
  searchForm.dateRange = getDefaultDateRange();
  searchForm.monthRange = getDefaultMonthRange();
  searchForm.category = "";
  searchForm.orderNo = "";
    searchForm.dictCode = "";
    searchForm.productOrderId = "";
  handleQuery();
};
const handleSizeChange = (val) => {
  const handleSizeChange = val => {
  page.size = val;
  page.current = 1;
};
const handleCurrentChange = (val) => {
  const handleCurrentChange = val => {
  page.current = val;
};
  onMounted(() => {
    getProductTypeOptions();
    loadOrders();
    handleQuery();
    initTopOrdersChart();
    window.addEventListener("resize", handleResize);
  });
const handleExport = () => {
  const headers = [timeColumnLabel.value, "产品类别", "生产订单", "用量", "单位", "成本(元)"];
  const lines = tableData.value.map((row) =>
    const headers = [
      timeColumnLabel.value,
      "产品类别",
      "生产订单",
      "用量",
      "单位",
      "成本(元)",
    ];
    const lines = tableData.value.map(row =>
    [
      row.timeLabel,
      row.category,
@@ -536,7 +846,7 @@
  ElMessage.success("导出成功");
};
const formatMoney = (v) => {
  const formatMoney = v => {
  const n = Number.parseFloat(v);
  const value = Number.isFinite(n) ? n : 0;
  return value.toLocaleString("zh-CN", {
@@ -671,19 +981,35 @@
}
.kpi-total {
  background: linear-gradient(135deg, rgba(47, 111, 237, 0.1), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(47, 111, 237, 0.1),
      rgba(255, 255, 255, 0.86)
    );
}
.kpi-raw {
  background: linear-gradient(135deg, rgba(22, 163, 74, 0.1), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(22, 163, 74, 0.1),
      rgba(255, 255, 255, 0.86)
    );
}
.kpi-avg {
  background: linear-gradient(135deg, rgba(99, 102, 241, 0.14), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(99, 102, 241, 0.14),
      rgba(255, 255, 255, 0.86)
    );
}
.kpi-order {
  background: linear-gradient(135deg, rgba(100, 116, 139, 0.1), rgba(255, 255, 255, 0.86));
    background: linear-gradient(
      135deg,
      rgba(100, 116, 139, 0.1),
      rgba(255, 255, 255, 0.86)
    );
}
.kpi-label {