gaoluyang
2026-06-22 4ee8bafa6b4742b72628511e0b14fbfeec7737e6
src/views/index.vue
@@ -113,11 +113,11 @@
          <div class="panel-title-row">
            <div class="panel-title">生产订单进度</div>
            <el-radio-group v-model="orderFilter" size="small">
              <el-radio-button label="all">全部({{ orderProgressMeta.total }})</el-radio-button>
              <el-radio-button label="waiting">待开始({{ orderProgressMeta.waitingCount }})</el-radio-button>
              <el-radio-button label="inProgress">进行中({{ orderProgressMeta.inProgressCount }})</el-radio-button>
              <el-radio-button label="completed">已完成({{ orderProgressMeta.completedCount }})</el-radio-button>
              <el-radio-button label="paused">已暂停({{ orderProgressMeta.pausedCount }})</el-radio-button>
              <el-radio-button :value="'all'">全部({{ orderProgressMeta.total }})</el-radio-button>
              <el-radio-button :value="'waiting'">待开始({{ orderProgressMeta.waitingCount }})</el-radio-button>
              <el-radio-button :value="'inProgress'">进行中({{ orderProgressMeta.inProgressCount }})</el-radio-button>
              <el-radio-button :value="'completed'">已完成({{ orderProgressMeta.completedCount }})</el-radio-button>
              <el-radio-button :value="'paused'">已暂停({{ orderProgressMeta.pausedCount }})</el-radio-button>
            </el-radio-group>
          </div>
          <el-table :data="filteredOrders" stripe>
@@ -182,32 +182,6 @@
          </ul>
        </div>
        <div v-if="visiblePanels.quality" class="cockpit-panel quality-panel">
          <div class="panel-title-row">
            <div class="panel-title">质量统计</div>
            <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo">
              <el-radio-button :value="1">周</el-radio-button>
              <el-radio-button :value="2">月</el-radio-button>
              <el-radio-button :value="3">季度</el-radio-button>
            </el-radio-group>
          </div>
          <div class="quality-cards">
            <div class="quality-card one">原材料已检数量 <span>{{ qualityStatisticsObject.supplierNum }}件</span></div>
            <div class="quality-card two">过程检验数量 <span>{{ qualityStatisticsObject.processNum }}件</span></div>
            <div class="quality-card three">出厂已检数量 <span>{{ qualityStatisticsObject.factoryNum }}件</span></div>
          </div>
          <Echarts
              :options="chartBaseOptions"
              :chartStyle="chartStyle"
              :grid="grid"
              :legend="barLegend"
              :series="barSeries1"
              :tooltip="tooltip"
              :xAxis="xAxis1"
              :yAxis="yAxis1"
              style="height: 270px"
          />
        </div>
      </div>
      <div v-if="hasRightPanels" class="right-column">
@@ -294,21 +268,34 @@
          </ul>
        </div>
        <div v-if="visiblePanels.receipt" class="cockpit-panel receipt-panel">
          <div class="panel-title">回款与开票分析</div>
          <Echarts
              :options="chartBaseOptions"
              :chartStyle="chartStyle"
              :grid="grid"
              :legend="lineLegend"
              :series="lineSeries"
              :tooltip="tooltipLine"
              :xAxis="xAxis2"
              :yAxis="yAxis2"
              style="height: 300px"
          />
        </div>
      </div>
      <!-- 质量统计 - 占满整行 -->
      <div v-if="visiblePanels.quality" class="cockpit-panel quality-panel full-width">
        <div class="panel-title-row">
          <div class="panel-title">质量统计</div>
          <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo">
            <el-radio-button :value="1">周</el-radio-button>
            <el-radio-button :value="2">月</el-radio-button>
            <el-radio-button :value="3">季度</el-radio-button>
          </el-radio-group>
        </div>
        <div class="quality-cards">
          <div class="quality-card one">原材料已检数量 <span>{{ qualityStatisticsObject.supplierNum }}吨</span></div>
          <div class="quality-card two">过程检验数量 <span>{{ qualityStatisticsObject.processNum }}吨</span></div>
          <div class="quality-card three">出厂已检数量 <span>{{ qualityStatisticsObject.factoryNum }}吨</span></div>
        </div>
        <Echarts
            :options="chartBaseOptions"
            :chartStyle="chartStyle"
            :grid="grid"
            :legend="barLegend"
            :series="barSeries1"
            :tooltip="tooltip"
            :xAxis="xAxis1"
            :yAxis="yAxis1"
            style="height: 270px"
        />
      </div>
    </section>
@@ -320,7 +307,7 @@
      <div class="process-selection-wrapper">
        <el-checkbox-group v-model="tempProcessIds">
          <div class="process-grid">
            <el-checkbox v-for="item in processOptions" :key="item.id" :label="item.id" border>
            <el-checkbox v-for="item in processOptions" :key="item.id" :value="item.id" border>
              {{ item.name }}
            </el-checkbox>
          </div>
@@ -360,7 +347,6 @@
import useUserStore from "@/store/modules/user.js";
import {
  analysisCustomerContractAmounts,
  getAmountHalfYear,
  getBusiness,
  homeTodos,
  processDataProductionStatistics,
@@ -368,7 +354,6 @@
  productionOverview,
  productionRealtimeBoard,
  qualityInspectionStatistics,
  statisticsReceivablePayable,
  todayProductionPlan,
} from "@/api/viewIndex.js";
import { list } from "@/api/productionManagement/productionProcess";
@@ -434,6 +419,8 @@
  monthSaleHaveMoney: 0,
  monthSaleMoney: 0,
  todayInventoryNum: 0,
  lastMonthSaleMoney: 0,
  lastMonthPurchaseMoney: 0,
});
const qualityStatisticsObject = ref({
@@ -584,74 +571,6 @@
  },
]);
const lineLegend = {
  show: true,
  textStyle: { color: axisTextColor },
  data: ["开票", "回款"],
};
const xAxis2 = ref([
  {
    type: "category",
    data: [],
    axisLine: { lineStyle: { color: axisLineColor } },
    axisLabel: {
      color: axisTextColor,
      interval: 0,
      formatter: (value) => value.replace(/~/g, "\n"),
    },
  },
]);
const yAxis2 = ref([
  {
    type: "value",
    splitLine: { lineStyle: { color: splitLineColor } },
    axisLine: { lineStyle: { color: axisLineColor } },
    axisLabel: { color: axisTextColor },
  },
]);
const tooltipLine = {
  trigger: "axis",
  backgroundColor: "rgba(255, 255, 255, 0.97)",
  borderColor: "rgba(148, 163, 184, 0.26)",
  textStyle: { color: "#334155" },
};
const lineSeries = ref([
  {
    name: "开票",
    type: "line",
    data: [],
    smooth: true,
    itemStyle: { color: "#2563eb" },
    lineStyle: { width: 2, color: "#2563eb" },
    showSymbol: true,
    areaStyle: {
      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
        { offset: 0, color: "rgba(37, 99, 235, 0.24)" },
        { offset: 1, color: "rgba(37, 99, 235, 0.02)" },
      ]),
    },
  },
  {
    name: "回款",
    type: "line",
    data: [],
    smooth: true,
    itemStyle: { color: "#14b8a6" },
    lineStyle: { width: 2, color: "#14b8a6" },
    showSymbol: true,
    areaStyle: {
      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
        { offset: 0, color: "rgba(20, 184, 166, 0.2)" },
        { offset: 1, color: "rgba(20, 184, 166, 0.02)" },
      ]),
    },
  },
]);
const pieTooltip = reactive({
  trigger: "item",
  backgroundColor: "rgba(255, 255, 255, 0.97)",
@@ -784,15 +703,29 @@
  });
});
// 计算环比增长率
const calculateGrowthRate = (current, last) => {
  const currentVal = parseFloat(current) || 0;
  const lastVal = parseFloat(last) || 0;
  if (lastVal === 0) return currentVal > 0 ? 100 : 0;
  return ((currentVal - lastVal) / lastVal * 100).toFixed(1);
};
const dashboardCards = computed(() => [
  {
    key: "sales",
    title: "销售数据",
    desc: "本月销售额(元)",
    value: formatNumber(businessInfo.value.monthSaleMoney),
    subLabel: "未开票金额",
    subValue: formatNumber(businessInfo.value.monthSaleHaveMoney),
    trend: `占比 ${ratioText(businessInfo.value.monthSaleHaveMoney, businessInfo.value.monthSaleMoney)}`,
    subLabel: "上月销售",
    subValue: formatNumber(businessInfo.value.lastMonthSaleMoney),
    trend: (() => {
      const rate = calculateGrowthRate(businessInfo.value.monthSaleMoney, businessInfo.value.lastMonthSaleMoney);
      const num = parseFloat(rate);
      if (num > 0) return `环比 ↑ ${rate}%`;
      if (num < 0) return `环比 ↓ ${Math.abs(num)}%`;
      return "环比持平";
    })(),
    icon: DataLine,
    visible: visibleModules.value.sales,
  },
@@ -801,12 +734,15 @@
    title: "采购数据",
    desc: "本月采购额(元)",
    value: formatNumber(businessInfo.value.monthPurchaseMoney),
    subLabel: "待付款金额",
    subValue: formatNumber(businessInfo.value.monthPurchaseHaveMoney),
    trend: `占比 ${ratioText(
        businessInfo.value.monthPurchaseHaveMoney,
        businessInfo.value.monthPurchaseMoney
    )}`,
    subLabel: "上月采购",
    subValue: formatNumber(businessInfo.value.lastMonthPurchaseMoney),
    trend: (() => {
      const rate = calculateGrowthRate(businessInfo.value.monthPurchaseMoney, businessInfo.value.lastMonthPurchaseMoney);
      const num = parseFloat(rate);
      if (num > 0) return `环比 ↑ ${rate}%`;
      if (num < 0) return `环比 ↓ ${Math.abs(num)}%`;
      return "环比持平";
    })(),
    icon: ShoppingCartFull,
    visible: visibleModules.value.procurement,
  },
@@ -820,17 +756,6 @@
    trend: "库存结构持续优化",
    icon: Box,
    visible: visibleModules.value.inventory,
  },
  {
    key: "production",
    title: "生产总览",
    desc: "累计产出(件)",
    value: formatNumber(productionOverviewData.value.totalOutput),
    subLabel: "累计报废",
    subValue: formatNumber(productionOverviewData.value.totalScrap),
    trend: `良率 ${Number(productionOverviewData.value.yieldRate || 0).toFixed(2)}%`,
    icon: Operation,
    visible: visibleModules.value.production,
  },
].filter((item) => item.visible));
@@ -1070,16 +995,16 @@
  realtime: visibleModules.value.production,
  quick: quickEntries.value.length > 0,
  plan: visibleModules.value.production,
  receipt: visibleModules.value.sales || visibleModules.value.finance,
}));
const hasLeftPanels = computed(
    () => visiblePanels.value.process || visiblePanels.value.order || visiblePanels.value.contract || visiblePanels.value.quality
    () => visiblePanels.value.process || visiblePanels.value.order || visiblePanels.value.contract
);
const hasRightPanels = computed(
    () => visiblePanels.value.todo || visiblePanels.value.realtime || visiblePanels.value.quick || visiblePanels.value.plan || visiblePanels.value.receipt
    () => visiblePanels.value.todo || visiblePanels.value.realtime || visiblePanels.value.quick || visiblePanels.value.plan
);
const hasVisiblePanels = computed(() => hasLeftPanels.value || hasRightPanels.value);
const hasVisiblePanels = computed(() => hasLeftPanels.value || hasRightPanels.value || visiblePanels.value.quality);
const quickEntries = computed(() =>
    quickEntryConfigs
@@ -1305,45 +1230,22 @@
  todoList.value = res.data || [];
};
const statisticsReceivable = async () => {
  const res = await statisticsReceivablePayable({ type: 1 });
  barSeries.value[0].data = [
    { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } },
    { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } },
  ];
};
const qualityStatisticsInfo = async () => {
  const res = await qualityInspectionStatistics({ type: qualityRange.value });
  xAxis1.value[0].data = [];
  barSeries1.value[0].data = [];
  barSeries1.value[1].data = [];
  barSeries1.value[2].data = [];
  (res.data.item || []).forEach((item) => {
  const data = res.data || {};
  (data.item || []).forEach((item) => {
    xAxis1.value[0].data.push(item.date);
    barSeries1.value[0].data.push(item.supplierNum);
    barSeries1.value[1].data.push(item.processNum);
    barSeries1.value[2].data.push(item.factoryNum);
  });
  qualityStatisticsObject.value.supplierNum = res.data.supplierNum;
  qualityStatisticsObject.value.processNum = res.data.processNum;
  qualityStatisticsObject.value.factoryNum = res.data.factoryNum;
};
const getAmountHalfYearNum = async () => {
  const res = await getAmountHalfYear();
  const monthName = [];
  const receiptAmount = [];
  const invoiceAmount = [];
  (res.data || []).forEach((item) => {
    monthName.push(item.month);
    receiptAmount.push(item.receiptAmount);
    invoiceAmount.push(item.invoiceAmount);
  });
  xAxis2.value[0].data = monthName.map((item) => item.replace(/~/g, "\n~"));
  lineSeries.value[0].data = invoiceAmount;
  lineSeries.value[1].data = receiptAmount;
  qualityStatisticsObject.value.supplierNum = data.supplierNum || 0;
  qualityStatisticsObject.value.processNum = data.processNum || 0;
  qualityStatisticsObject.value.factoryNum = data.factoryNum || 0;
};
const getProcessList = async () => {
@@ -1417,10 +1319,6 @@
  }
  if (visiblePanels.value.quality) {
    qualityStatisticsInfo();
  }
  if (visiblePanels.value.receipt) {
    statisticsReceivable();
    getAmountHalfYearNum();
  }
  if (visiblePanels.value.process) {
    getProcessList();
@@ -1552,7 +1450,7 @@
.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
@@ -1744,7 +1642,7 @@
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 230px;
  max-height: 380px;
  overflow-y: auto;
}
@@ -1793,18 +1691,31 @@
}
.main-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: start;
}
.left-column,
.left-column {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.right-column {
  width: 400px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}
.full-width {
  width: 100%;
  flex-basis: 100%;
}
.process-panel {
@@ -2193,7 +2104,7 @@
}
.quick-panel {
  min-height: 0;
  min-height: 280px;
}
.table-progress {
@@ -2235,10 +2146,6 @@
.quality-panel {
  min-height: 0;
}
.receipt-panel {
  min-height: 340px;
}
.plan-list {
@@ -2393,24 +2300,31 @@
}
@media (max-width: 1600px) {
  .main-grid {
    grid-template-columns: minmax(0, 1fr) 380px;
  .right-column {
    width: 380px;
  }
}
@media (max-width: 1366px) {
  .main-grid {
    grid-template-columns: minmax(0, 1fr) 340px;
  .right-column {
    width: 340px;
  }
  .stats-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (max-width: 992px) {
  .stats-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 1200px) {
  .main-grid {
    grid-template-columns: 1fr;
  .left-column,
  .right-column {
    width: 100%;
  }
  .right-column {