2025-10-27 13:21:43 +08:00
|
|
|
|
<template>
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<view class="page-container">
|
|
|
|
|
|
<!-- 时间维度切换 -->
|
|
|
|
|
|
<view class="time-tab-bar">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="item in timeTabs"
|
2025-10-27 13:21:43 +08:00
|
|
|
|
:key="item.value"
|
2025-10-31 17:18:30 +08:00
|
|
|
|
class="time-tab-item"
|
|
|
|
|
|
:class="{ 'time-tab-active': activeTab === item.value }"
|
2025-10-27 13:21:43 +08:00
|
|
|
|
@click="handleTabChange(item.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ item.label }}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<!-- 日期选择区 -->
|
|
|
|
|
|
<view class="date-selector">
|
2025-10-27 13:21:43 +08:00
|
|
|
|
<!-- 日模式 -->
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<picker v-if="activeTab === 'day'" mode="date" :value="startDate" @change="handleDateChange">
|
|
|
|
|
|
<view class="date-input">
|
|
|
|
|
|
<text class="date-label">日期</text>
|
|
|
|
|
|
<text class="date-value">{{ startDate }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</picker>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 月模式 -->
|
|
|
|
|
|
<view v-else-if="activeTab === 'month'" class="date-range-group">
|
|
|
|
|
|
<picker mode="date" fields="month" :value="startDate" @change="handleStartMonthChange">
|
|
|
|
|
|
<view class="date-input">
|
|
|
|
|
|
<text class="date-label">起</text>
|
|
|
|
|
|
<text class="date-value">{{ startDate }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</picker>
|
|
|
|
|
|
<view class="date-separator">至</view>
|
|
|
|
|
|
<picker mode="date" fields="month" :value="endDate" :start="startDate" :end="maxMonthEnd" @change="handleEndMonthChange">
|
|
|
|
|
|
<view class="date-input">
|
|
|
|
|
|
<text class="date-label">止</text>
|
|
|
|
|
|
<text class="date-value">{{ endDate }}</text>
|
|
|
|
|
|
</view>
|
2025-10-27 13:21:43 +08:00
|
|
|
|
</picker>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<!-- 年模式 -->
|
|
|
|
|
|
<view v-else class="date-range-group">
|
|
|
|
|
|
<picker mode="date" fields="year" :value="startDate" @change="handleStartYearChange">
|
|
|
|
|
|
<view class="date-input">
|
|
|
|
|
|
<text class="date-label">起</text>
|
|
|
|
|
|
<text class="date-value">{{ startDate }}</text>
|
|
|
|
|
|
</view>
|
2025-10-27 13:21:43 +08:00
|
|
|
|
</picker>
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<view class="date-separator">至</view>
|
|
|
|
|
|
<picker mode="date" fields="year" :value="endDate" @change="handleEndYearChange">
|
|
|
|
|
|
<view class="date-input">
|
|
|
|
|
|
<text class="date-label">止</text>
|
|
|
|
|
|
<text class="date-value">{{ endDate }}</text>
|
2025-10-27 13:21:43 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</picker>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<!-- 生产汇总数据 -->
|
|
|
|
|
|
<view class="summary-section">
|
|
|
|
|
|
<view class="section-header">
|
|
|
|
|
|
<text class="section-title">生产汇总</text>
|
|
|
|
|
|
<text class="section-date">{{ displayDateRange }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="summary-grid">
|
|
|
|
|
|
<view class="summary-card" v-for="(item, index) in summaryData" :key="index">
|
|
|
|
|
|
<text class="summary-label">{{ item.label }}</text>
|
|
|
|
|
|
<view class="summary-value-box">
|
|
|
|
|
|
<text class="summary-value">{{ item.value }}</text>
|
|
|
|
|
|
<text v-if="item.unit" class="summary-unit">{{ item.unit }}</text>
|
2025-10-27 13:21:43 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<!-- 产量趋势图 -->
|
|
|
|
|
|
<view class="chart-section">
|
|
|
|
|
|
<view class="section-header">
|
|
|
|
|
|
<text class="section-title">产量趋势</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="chart-wrapper">
|
|
|
|
|
|
<qiun-data-charts type="mix" :chartData="productionChartData" :opts="productionChartOpts"/>
|
|
|
|
|
|
</view>
|
2025-10-27 13:21:43 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<!-- 班组产量对比 -->
|
|
|
|
|
|
<view class="chart-section">
|
|
|
|
|
|
<view class="section-header">
|
|
|
|
|
|
<text class="section-title">班组产量对比</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="chart-wrapper">
|
|
|
|
|
|
<qiun-data-charts type="column" :chartData="crewChartData" :opts="columnChartOpts"/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 规格分布 -->
|
|
|
|
|
|
<view class="chart-section">
|
|
|
|
|
|
<view class="section-header">
|
|
|
|
|
|
<text class="section-title">规格分布</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="pie-charts-row">
|
|
|
|
|
|
<view class="pie-chart-item">
|
|
|
|
|
|
<text class="pie-title">厚度分布</text>
|
|
|
|
|
|
<qiun-data-charts type="pie" :chartData="thicknessPieData" :opts="pieChartOpts"/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="pie-charts-row">
|
|
|
|
|
|
<view class="pie-chart-item">
|
|
|
|
|
|
<text class="pie-title">宽度分布</text>
|
|
|
|
|
|
<qiun-data-charts type="pie" :chartData="widthPieData" :opts="pieChartOpts"/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-10-27 13:21:43 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-10-31 17:18:30 +08:00
|
|
|
|
import {
|
|
|
|
|
|
getProductionSummary,
|
|
|
|
|
|
getCrewProduction,
|
|
|
|
|
|
getThicknessDistribution,
|
|
|
|
|
|
getWidthDistribution
|
|
|
|
|
|
} from '@/api/pocket/plantState'
|
|
|
|
|
|
|
|
|
|
|
|
// 工具函数
|
2025-10-27 13:21:43 +08:00
|
|
|
|
function getDefaultDate(type = "day") {
|
|
|
|
|
|
const date = new Date();
|
|
|
|
|
|
return formatDate(date, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatDate(date, type) {
|
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
|
|
|
|
const day = date.getDate().toString().padStart(2, "0");
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
2025-10-27 13:21:43 +08:00
|
|
|
|
switch (type) {
|
|
|
|
|
|
case "day": return `${year}-${month}-${day}`;
|
|
|
|
|
|
case "month": return `${year}-${month}`;
|
|
|
|
|
|
case "year": return `${year}`;
|
|
|
|
|
|
default: return `${year}-${month}-${day}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
activeTab: "day",
|
|
|
|
|
|
startDate: getDefaultDate(),
|
|
|
|
|
|
endDate: getDefaultDate(),
|
|
|
|
|
|
timeTabs: [
|
2025-10-31 17:18:30 +08:00
|
|
|
|
{ label: "日", value: "day" },
|
|
|
|
|
|
{ label: "月", value: "month" },
|
|
|
|
|
|
{ label: "年", value: "year" }
|
2025-10-27 13:21:43 +08:00
|
|
|
|
],
|
2025-10-31 17:18:30 +08:00
|
|
|
|
// 汇总数据
|
|
|
|
|
|
summaryData: [
|
|
|
|
|
|
{ label: "生产钢卷数", value: 0, unit: "卷" },
|
|
|
|
|
|
{ label: "平均宽度", value: 0, unit: "mm" },
|
|
|
|
|
|
{ label: "平均厚度", value: 0, unit: "mm" },
|
|
|
|
|
|
{ label: "原料总量", value: 0, unit: "t" },
|
|
|
|
|
|
{ label: "成品总量", value: 0, unit: "t" },
|
|
|
|
|
|
{ label: "成材率", value: 0, unit: "%" }
|
2025-10-27 13:21:43 +08:00
|
|
|
|
],
|
2025-10-31 17:18:30 +08:00
|
|
|
|
// 产量趋势图(柱状+折线)
|
|
|
|
|
|
productionChartData: {},
|
|
|
|
|
|
productionChartOpts: {
|
|
|
|
|
|
color: ["#0066cc", "#00b96b"],
|
|
|
|
|
|
padding: [15, 15, 0, 15],
|
2025-10-27 13:21:43 +08:00
|
|
|
|
enableScroll: false,
|
2025-10-31 17:18:30 +08:00
|
|
|
|
legend: { position: "top" },
|
|
|
|
|
|
xAxis: { disableGrid: true },
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
gridType: "dash",
|
|
|
|
|
|
dashLength: 4,
|
|
|
|
|
|
gridColor: "#e4e7ed",
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{ position: "left", title: "产量(t)" },
|
|
|
|
|
|
{ position: "right", title: "成材率(%)" }
|
|
|
|
|
|
]
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
extra: {
|
2025-10-31 17:18:30 +08:00
|
|
|
|
mix: {
|
|
|
|
|
|
column: { width: 20 }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 班组产量对比
|
|
|
|
|
|
crewChartData: {},
|
|
|
|
|
|
columnChartOpts: {
|
|
|
|
|
|
color: ["#0066cc", "#409eff", "#66b1ff"],
|
|
|
|
|
|
padding: [15, 15, 0, 15],
|
2025-10-27 13:21:43 +08:00
|
|
|
|
enableScroll: false,
|
2025-10-31 17:18:30 +08:00
|
|
|
|
legend: {},
|
|
|
|
|
|
xAxis: { disableGrid: true },
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
gridType: "dash",
|
|
|
|
|
|
dashLength: 4,
|
|
|
|
|
|
gridColor: "#e4e7ed",
|
|
|
|
|
|
data: [{ title: "产量(t)" }]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 饼图配置
|
|
|
|
|
|
pieChartOpts: {
|
|
|
|
|
|
color: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff", "#d9ecff"],
|
|
|
|
|
|
padding: [15, 15, 15, 15],
|
|
|
|
|
|
enableScroll: false,
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
position: "bottom",
|
|
|
|
|
|
lineHeight: 16,
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
fontColor: "#666",
|
|
|
|
|
|
margin: 5,
|
|
|
|
|
|
itemGap: 8
|
|
|
|
|
|
},
|
2025-10-27 13:21:43 +08:00
|
|
|
|
extra: {
|
|
|
|
|
|
pie: {
|
|
|
|
|
|
activeOpacity: 0.5,
|
|
|
|
|
|
activeRadius: 10,
|
|
|
|
|
|
labelWidth: 15,
|
|
|
|
|
|
border: false,
|
2025-10-31 17:18:30 +08:00
|
|
|
|
ringWidth: 0,
|
|
|
|
|
|
offsetAngle: 0,
|
|
|
|
|
|
disablePieStroke: true,
|
|
|
|
|
|
linearType: "none",
|
|
|
|
|
|
customRadius: 0
|
|
|
|
|
|
}
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
2025-10-31 17:18:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
thicknessPieData: {
|
|
|
|
|
|
series: [{ data: [] }]
|
|
|
|
|
|
},
|
|
|
|
|
|
widthPieData: {
|
|
|
|
|
|
series: [{ data: [] }]
|
|
|
|
|
|
}
|
2025-10-27 13:21:43 +08:00
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
// 5. 计算属性(替代 Vue3 的 computed 函数)
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
// 月模式:最大结束月份(开始月份+1年)
|
|
|
|
|
|
maxMonthEnd() {
|
|
|
|
|
|
if (!this.startDate) return "";
|
|
|
|
|
|
const date = new Date(this.startDate);
|
|
|
|
|
|
date.setFullYear(date.getFullYear() + 1);
|
|
|
|
|
|
return formatDate(date, "month");
|
|
|
|
|
|
},
|
|
|
|
|
|
// 日期范围展示文本(汇总标题用)
|
|
|
|
|
|
displayDateRange() {
|
|
|
|
|
|
switch (this.activeTab) {
|
|
|
|
|
|
case "day":
|
|
|
|
|
|
return this.startDate;
|
|
|
|
|
|
case "month":
|
|
|
|
|
|
return `${this.startDate} 至 ${this.endDate}`;
|
|
|
|
|
|
case "year":
|
|
|
|
|
|
return `${this.startDate} 至 ${this.endDate}`;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
2025-10-31 17:18:30 +08:00
|
|
|
|
this.loadProductionData();
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
2025-10-31 17:18:30 +08:00
|
|
|
|
// 切换时间维度
|
2025-10-27 13:21:43 +08:00
|
|
|
|
handleTabChange(tab) {
|
|
|
|
|
|
this.activeTab = tab;
|
|
|
|
|
|
const defaultDate = getDefaultDate();
|
|
|
|
|
|
this.startDate = defaultDate;
|
|
|
|
|
|
this.endDate = tab === "day" ? defaultDate : getDefaultDate(tab);
|
2025-10-31 17:18:30 +08:00
|
|
|
|
this.loadProductionData();
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 日期选择变更
|
2025-10-27 13:21:43 +08:00
|
|
|
|
handleDateChange(e) {
|
|
|
|
|
|
this.startDate = e.detail.value;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
this.endDate = e.detail.value;
|
|
|
|
|
|
this.loadProductionData();
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
2025-10-27 13:21:43 +08:00
|
|
|
|
handleStartMonthChange(e) {
|
|
|
|
|
|
this.startDate = e.detail.value;
|
|
|
|
|
|
const maxEndDate = new Date(this.startDate);
|
|
|
|
|
|
maxEndDate.setFullYear(maxEndDate.getFullYear() + 1);
|
|
|
|
|
|
const maxEndStr = formatDate(maxEndDate, "month");
|
|
|
|
|
|
if (new Date(this.endDate) > maxEndDate) {
|
|
|
|
|
|
this.endDate = maxEndStr;
|
|
|
|
|
|
}
|
2025-10-31 17:18:30 +08:00
|
|
|
|
this.loadProductionData();
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
2025-10-27 13:21:43 +08:00
|
|
|
|
handleEndMonthChange(e) {
|
|
|
|
|
|
this.endDate = e.detail.value;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
this.loadProductionData();
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
2025-10-27 13:21:43 +08:00
|
|
|
|
handleStartYearChange(e) {
|
|
|
|
|
|
this.startDate = e.detail.value;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
this.loadProductionData();
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
2025-10-27 13:21:43 +08:00
|
|
|
|
handleEndYearChange(e) {
|
|
|
|
|
|
this.endDate = e.detail.value;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
this.loadProductionData();
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载生产数据
|
|
|
|
|
|
loadProductionData() {
|
|
|
|
|
|
uni.showLoading({ title: '加载中' })
|
|
|
|
|
|
|
|
|
|
|
|
// 并行请求所有数据
|
|
|
|
|
|
Promise.all([
|
|
|
|
|
|
this.loadSummaryData(),
|
|
|
|
|
|
this.loadCrewProductionData(),
|
|
|
|
|
|
this.loadSpecDistribution()
|
|
|
|
|
|
]).finally(() => {
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
})
|
2025-10-27 13:21:43 +08:00
|
|
|
|
},
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载汇总数据
|
|
|
|
|
|
loadSummaryData() {
|
|
|
|
|
|
// 转换为完整日期格式
|
|
|
|
|
|
const start = this.formatFullDate(this.startDate, true)
|
|
|
|
|
|
const end = this.formatFullDate(this.endDate, false)
|
|
|
|
|
|
|
|
|
|
|
|
return getProductionSummary(start, end).then(response => {
|
|
|
|
|
|
if (response.code === 200 && response.data) {
|
|
|
|
|
|
const data = response.data
|
|
|
|
|
|
this.summaryData = [
|
|
|
|
|
|
{ label: "生产钢卷数", value: data.coilCount || 0, unit: "卷" },
|
|
|
|
|
|
{ label: "平均宽度", value: data.avgWidth || 0, unit: "mm" },
|
|
|
|
|
|
{ label: "平均厚度", value: data.avgThick || 0, unit: "mm" },
|
|
|
|
|
|
{ label: "原料总量", value: data.totalEntryWeight || 0, unit: "t" },
|
|
|
|
|
|
{ label: "成品总量", value: data.totalExitWeight || 0, unit: "t" },
|
|
|
|
|
|
{ label: "成材率", value: data.yieldRate || 0, unit: "%" }
|
2025-10-27 13:21:43 +08:00
|
|
|
|
]
|
2025-10-31 17:18:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
|
console.error('加载汇总数据失败:', error)
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 加载班组产量数据
|
|
|
|
|
|
loadCrewProductionData() {
|
|
|
|
|
|
const start = this.formatFullDate(this.startDate, true)
|
|
|
|
|
|
const end = this.formatFullDate(this.endDate, false)
|
|
|
|
|
|
|
|
|
|
|
|
return getCrewProduction(start, end).then(response => {
|
|
|
|
|
|
if (response.code === 200 && response.data && response.data.length > 0) {
|
|
|
|
|
|
const crewData = response.data
|
|
|
|
|
|
|
|
|
|
|
|
// 按班组和班次分组
|
|
|
|
|
|
const categories = []
|
|
|
|
|
|
const crewMap = {}
|
|
|
|
|
|
|
|
|
|
|
|
crewData.forEach(item => {
|
|
|
|
|
|
const label = `${item.crew}-${item.shift}`
|
|
|
|
|
|
categories.push(label)
|
|
|
|
|
|
|
|
|
|
|
|
if (!crewMap[item.shift]) {
|
|
|
|
|
|
crewMap[item.shift] = []
|
|
|
|
|
|
}
|
|
|
|
|
|
crewMap[item.shift].push(Number(item.totalWeight) || 0)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 构建班组产量对比图
|
|
|
|
|
|
const series = Object.keys(crewMap).map(shift => ({
|
|
|
|
|
|
name: shift,
|
|
|
|
|
|
data: crewMap[shift]
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
this.crewChartData = {
|
|
|
|
|
|
categories: categories,
|
|
|
|
|
|
series: series
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
2025-10-31 17:18:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 构建产量趋势图(使用班组数据)
|
|
|
|
|
|
this.productionChartData = {
|
|
|
|
|
|
categories: categories,
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "产量",
|
|
|
|
|
|
type: "column",
|
|
|
|
|
|
index: 0,
|
|
|
|
|
|
data: crewData.map(item => Number(item.totalWeight) || 0)
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "平均厚度",
|
|
|
|
|
|
type: "line",
|
|
|
|
|
|
index: 1,
|
|
|
|
|
|
style: "curve",
|
|
|
|
|
|
data: crewData.map(item => Number(item.avgThick) || 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
|
console.error('加载班组产量失败:', error)
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 加载规格分布
|
|
|
|
|
|
loadSpecDistribution() {
|
|
|
|
|
|
const start = this.formatFullDate(this.startDate, true)
|
|
|
|
|
|
const end = this.formatFullDate(this.endDate, false)
|
|
|
|
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
|
|
getThicknessDistribution(start, end),
|
|
|
|
|
|
getWidthDistribution(start, end)
|
|
|
|
|
|
]).then(([thicknessRes, widthRes]) => {
|
|
|
|
|
|
// 厚度分布
|
|
|
|
|
|
if (thicknessRes.code === 200 && thicknessRes.data) {
|
|
|
|
|
|
this.thicknessPieData = {
|
|
|
|
|
|
series: [{
|
|
|
|
|
|
data: thicknessRes.data.map(item => ({
|
|
|
|
|
|
name: item.category,
|
|
|
|
|
|
value: item.count
|
|
|
|
|
|
}))
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 宽度分布
|
|
|
|
|
|
if (widthRes.code === 200 && widthRes.data) {
|
|
|
|
|
|
this.widthPieData = {
|
|
|
|
|
|
series: [{
|
|
|
|
|
|
data: widthRes.data.map(item => ({
|
|
|
|
|
|
name: item.category,
|
|
|
|
|
|
value: item.count
|
|
|
|
|
|
}))
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
|
console.error('加载规格分布失败:', error)
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化为完整日期(用于查询)
|
|
|
|
|
|
formatFullDate(dateStr, isStart) {
|
|
|
|
|
|
if (!dateStr) return ''
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经是完整日期格式 yyyy-MM-dd,直接返回
|
|
|
|
|
|
if (dateStr.length === 10) {
|
|
|
|
|
|
return dateStr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 月份格式 yyyy-MM
|
|
|
|
|
|
if (dateStr.length === 7) {
|
|
|
|
|
|
if (isStart) {
|
|
|
|
|
|
// 开始日期:该月第一天
|
|
|
|
|
|
return `${dateStr}-01`
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 结束日期:该月最后一天
|
|
|
|
|
|
const [year, month] = dateStr.split('-')
|
|
|
|
|
|
const lastDay = new Date(year, month, 0).getDate()
|
|
|
|
|
|
return `${dateStr}-${String(lastDay).padStart(2, '0')}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 年份格式 yyyy
|
|
|
|
|
|
if (dateStr.length === 4) {
|
|
|
|
|
|
if (isStart) {
|
|
|
|
|
|
// 开始日期:该年第一天
|
|
|
|
|
|
return `${dateStr}-01-01`
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 结束日期:该年最后一天
|
|
|
|
|
|
return `${dateStr}-12-31`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return dateStr
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
/* 页面容器 */
|
|
|
|
|
|
.page-container {
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
padding: 24rpx;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
/* 时间维度切换 */
|
|
|
|
|
|
.time-tab-bar {
|
2025-10-27 13:21:43 +08:00
|
|
|
|
display: flex;
|
2025-10-29 15:38:20 +08:00
|
|
|
|
background: #fff;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
padding: 8rpx;
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
border: 1rpx solid #e4e7ed;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.time-tab-item {
|
2025-10-29 15:38:20 +08:00
|
|
|
|
flex: 1;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
text-align: center;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
padding: 16rpx 0;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
border-radius: 6rpx;
|
|
|
|
|
|
transition: all 0.2s;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.time-tab-active {
|
|
|
|
|
|
background: #0066cc;
|
2025-10-29 15:38:20 +08:00
|
|
|
|
color: #fff;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
font-weight: 500;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
/* 日期选择区 */
|
|
|
|
|
|
.date-selector {
|
2025-10-29 15:38:20 +08:00
|
|
|
|
background: #fff;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
padding: 24rpx;
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
border: 1rpx solid #e4e7ed;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.date-range-group {
|
2025-10-27 13:21:43 +08:00
|
|
|
|
display: flex;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
gap: 0;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.date-input {
|
2025-10-27 13:21:43 +08:00
|
|
|
|
flex: 1;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 24rpx 32rpx;
|
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
|
border: 1rpx solid #e4e7ed;
|
|
|
|
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
|
|
border-radius: 6rpx 0 0 6rpx;
|
|
|
|
|
|
border-right: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
border-radius: 0 6rpx 6rpx 0;
|
|
|
|
|
|
border-left: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.date-label {
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-right: 16rpx;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.date-value {
|
2025-10-29 15:38:20 +08:00
|
|
|
|
font-size: 28rpx;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
color: #303133;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
text-align: right;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.date-separator {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
background: #0066cc;
|
|
|
|
|
|
padding: 24rpx 20rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
min-width: 80rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 区块样式 */
|
|
|
|
|
|
.summary-section,
|
|
|
|
|
|
.chart-section {
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
2025-10-29 15:38:20 +08:00
|
|
|
|
margin-bottom: 20rpx;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
padding-left: 16rpx;
|
|
|
|
|
|
border-left: 4rpx solid #0066cc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-date {
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 汇总卡片网格 */
|
|
|
|
|
|
.summary-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-card {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1rpx solid #e4e7ed;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
padding: 28rpx 20rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value-box {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value {
|
|
|
|
|
|
font-size: 40rpx;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #0066cc;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-unit {
|
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-left: 6rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 图表容器 */
|
|
|
|
|
|
.chart-wrapper {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1rpx solid #e4e7ed;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
padding: 24rpx 16rpx;
|
|
|
|
|
|
min-height: 450rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 饼图行布局 */
|
|
|
|
|
|
.pie-charts-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pie-chart-item {
|
|
|
|
|
|
flex: 1;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
background: #fff;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
border: 1rpx solid #e4e7ed;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
padding: 24rpx 0 32rpx 0;
|
|
|
|
|
|
min-height: 520rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.pie-title {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
|
padding: 0 16rpx;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pie-chart-wrapper {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2025-10-27 13:21:43 +08:00
|
|
|
|
}
|
2025-10-31 17:18:30 +08:00
|
|
|
|
</style>
|