1022 lines
28 KiB
Vue
1022 lines
28 KiB
Vue
<template>
|
||
<view class="page-container">
|
||
<!-- 时间维度切换 -->
|
||
<view class="time-tab-bar">
|
||
<view
|
||
v-for="item in timeTabs"
|
||
:key="item.value"
|
||
class="time-tab-item"
|
||
:class="{ 'time-tab-active': activeTab === item.value }"
|
||
@click="handleTabChange(item.value)"
|
||
>
|
||
{{ item.label }}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 日期选择区 -->
|
||
<view class="date-selector">
|
||
<!-- 日模式 -->
|
||
<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>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 年模式 -->
|
||
<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>
|
||
</picker>
|
||
<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>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 停机汇总 -->
|
||
<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>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 停机趋势图(月/年视图) -->
|
||
<view class="chart-section" v-if="activeTab !== 'day' && trendChartData.categories && trendChartData.categories.length > 0">
|
||
<view class="section-header">
|
||
<text class="section-title">停机趋势</text>
|
||
</view>
|
||
<view class="chart-wrapper trend-chart">
|
||
<qiun-data-charts type="mix" :chartData="trendChartData" :opts="trendChartOpts"/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 停机分布 - 班组 -->
|
||
<view class="chart-section">
|
||
<view class="section-header">
|
||
<text class="section-title">班组停机分布</text>
|
||
</view>
|
||
<view class="pie-chart-single" v-if="crewPieData.series[0].data.length > 0">
|
||
<qiun-data-charts type="pie" :chartData="crewPieData" :opts="pieChartOpts"/>
|
||
</view>
|
||
<view class="empty-chart" v-else>
|
||
<text class="empty-icon">📊</text>
|
||
<text class="empty-text">此时间段未发生停机</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 停机分布 - 类型 -->
|
||
<view class="chart-section">
|
||
<view class="section-header">
|
||
<text class="section-title">停机类型分布</text>
|
||
</view>
|
||
<view class="pie-chart-single" v-if="typePieData.series[0].data.length > 0">
|
||
<qiun-data-charts type="pie" :chartData="typePieData" :opts="pieChartOpts"/>
|
||
</view>
|
||
<view class="empty-chart" v-else>
|
||
<text class="empty-icon">📊</text>
|
||
<text class="empty-text">此时间段未发生停机</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 停机详细列表(日视图) -->
|
||
<view class="detail-section" v-if="activeTab === 'day'">
|
||
<view class="section-header">
|
||
<text class="section-title">停机详情</text>
|
||
</view>
|
||
<view class="detail-list">
|
||
<view class="detail-item" v-for="(item, index) in tableData" :key="index">
|
||
<view class="detail-header">
|
||
<text class="detail-time">{{ item.time }}</text>
|
||
<text class="detail-duration">{{ item.duration }}</text>
|
||
</view>
|
||
<view class="detail-info">
|
||
<text class="detail-label">机组:</text>
|
||
<text class="detail-text">{{ item.machine }}</text>
|
||
</view>
|
||
<view class="detail-info">
|
||
<text class="detail-label">备注:</text>
|
||
<text class="detail-text">{{ item.remark }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="tableData.length === 0" class="empty-state">
|
||
<text class="empty-text">暂无停机记录</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { listStoppage } from '@/api/pocket/plantState'
|
||
|
||
// 2. 独立工具函数(避免data初始化时调用this.methods的问题)
|
||
/**
|
||
* 获取默认日期(根据视图类型)
|
||
* @param {string} type - 视图类型:day/month/year
|
||
* @returns {string} 格式化后的日期
|
||
*/
|
||
function getDefaultDate(type = "day") {
|
||
const date = new Date();
|
||
const year = date.getFullYear();
|
||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||
const day = date.getDate().toString().padStart(2, "0");
|
||
|
||
switch (type) {
|
||
case "day":
|
||
return `${year}-${month}-${day}`;
|
||
case "month":
|
||
return `${year}-${month}`;
|
||
case "year":
|
||
return `${year}`;
|
||
default:
|
||
return `${year}-${month}-${day}`;
|
||
}
|
||
}
|
||
|
||
// 获取上个月的年月
|
||
function getLastMonth() {
|
||
const date = new Date();
|
||
date.setMonth(date.getMonth() - 1); // 减1个月
|
||
const year = date.getFullYear();
|
||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||
return `${year}-${month}`;
|
||
}
|
||
|
||
/**
|
||
* 格式化日期
|
||
* @param {Date} date - 日期对象
|
||
* @param {string} type - 视图类型:day/month/year
|
||
* @returns {string} 格式化后的日期
|
||
*/
|
||
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");
|
||
|
||
switch (type) {
|
||
case "day":
|
||
return `${year}-${month}-${day}`;
|
||
case "month":
|
||
return `${year}-${month}`;
|
||
case "year":
|
||
return `${year}`;
|
||
default:
|
||
return `${year}-${month}-${day}`;
|
||
}
|
||
}
|
||
|
||
export default {
|
||
// 4. 响应式数据(替代 Vue3 的 ref)
|
||
data() {
|
||
return {
|
||
// 激活的视图类型(日/月/年)
|
||
activeTab: "day",
|
||
// 开始日期/月份/年份
|
||
startDate: getDefaultDate(),
|
||
// 结束日期/月份/年份(默认)
|
||
endDate: getDefaultDate(),
|
||
// 视图切换选项
|
||
timeTabs: [
|
||
{ label: "日", value: "day" },
|
||
{ label: "月", value: "month" },
|
||
{ label: "年", value: "year" }
|
||
],
|
||
// 汇总数据
|
||
summaryData: [
|
||
{ label: "停机时间", value: 0, unit: "min" },
|
||
{ label: "停机次数", value: 0, unit: "次" },
|
||
{ label: "作业率", value: 0, unit: "%" }
|
||
],
|
||
// 停机趋势图(月/年视图)
|
||
trendChartData: {
|
||
categories: [],
|
||
series: []
|
||
},
|
||
trendChartOpts: {
|
||
color: ["#0066cc", "#f56c6c"],
|
||
padding: [15, 15, 0, 15],
|
||
enableScroll: false,
|
||
legend: { position: "top" },
|
||
dataLabel: false, // 隐藏数据标签,避免数字叠在一起
|
||
xAxis: {
|
||
disableGrid: true,
|
||
rotateLabel: true,
|
||
rotateAngle: 60,
|
||
labelCount: 10,
|
||
fontSize: 10
|
||
},
|
||
yAxis: {
|
||
gridType: "dash",
|
||
dashLength: 4,
|
||
gridColor: "#e4e7ed",
|
||
data: [
|
||
{ position: "left", title: "停机时间(min)" },
|
||
{ position: "right", title: "作业率(%)" }
|
||
]
|
||
},
|
||
extra: {
|
||
mix: {
|
||
column: {
|
||
width: 40,
|
||
categoryGap: 10
|
||
}
|
||
},
|
||
tooltip: {
|
||
showBox: true,
|
||
showArrow: true,
|
||
showCategory: true
|
||
}
|
||
}
|
||
},
|
||
// 饼图配置
|
||
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
|
||
},
|
||
extra: {
|
||
pie: {
|
||
activeOpacity: 0.5,
|
||
activeRadius: 10,
|
||
labelWidth: 15,
|
||
border: false,
|
||
ringWidth: 0,
|
||
offsetAngle: 0,
|
||
disablePieStroke: true
|
||
}
|
||
}
|
||
},
|
||
// 班组停机分布
|
||
crewPieData: {
|
||
series: [{ data: [] }]
|
||
},
|
||
// 停机类型分布
|
||
typePieData: {
|
||
series: [{ data: [] }]
|
||
},
|
||
// 停机详细列表
|
||
tableData: []
|
||
};
|
||
},
|
||
// 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 "";
|
||
}
|
||
}
|
||
},
|
||
// 6. 方法定义(所有交互逻辑放这里)
|
||
methods: {
|
||
// 切换视图(日/月/年)
|
||
handleTabChange(tab) {
|
||
this.activeTab = tab;
|
||
|
||
// 重置日期
|
||
if (tab === "day") {
|
||
// 日视图:首尾日期相同(今天)
|
||
const today = getDefaultDate();
|
||
this.startDate = today;
|
||
this.endDate = today;
|
||
} else if (tab === "month") {
|
||
// 月视图:上个月到这个月
|
||
this.startDate = getLastMonth();
|
||
this.endDate = getDefaultDate("month");
|
||
} else {
|
||
// 年视图:今年
|
||
const currentYear = getDefaultDate("year");
|
||
this.startDate = currentYear;
|
||
this.endDate = currentYear;
|
||
}
|
||
|
||
// 切换视图时重新加载数据
|
||
this.loadStoppageData();
|
||
},
|
||
// 日模式:日期选择器变更
|
||
handleDateChange(e) {
|
||
this.startDate = e.detail.value;
|
||
this.endDate = e.detail.value; // 日模式首尾日期一致
|
||
this.loadStoppageData();
|
||
},
|
||
// 月模式:开始月份变更
|
||
handleStartMonthChange(e) {
|
||
this.startDate = e.detail.value;
|
||
// 自动调整结束月份:不超过开始月份+1年
|
||
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;
|
||
}
|
||
this.loadStoppageData();
|
||
},
|
||
// 月模式:结束月份变更
|
||
handleEndMonthChange(e) {
|
||
this.endDate = e.detail.value;
|
||
this.loadStoppageData();
|
||
},
|
||
// 年模式:开始年份变更
|
||
handleStartYearChange(e) {
|
||
this.startDate = e.detail.value;
|
||
this.loadStoppageData();
|
||
},
|
||
// 年模式:结束年份变更
|
||
handleEndYearChange(e) {
|
||
this.endDate = e.detail.value;
|
||
this.loadStoppageData();
|
||
},
|
||
// 加载停机数据
|
||
loadStoppageData() {
|
||
uni.showLoading({ title: '加载中' })
|
||
|
||
// 转换为完整日期格式
|
||
const start = this.formatFullDate(this.startDate, true)
|
||
let end = this.formatFullDate(this.endDate, false)
|
||
|
||
// 如果是月视图,检查结束日期是否是当月
|
||
if (this.activeTab === 'month' && this.endDate && this.endDate.length === 7) {
|
||
const today = new Date()
|
||
const todayYear = today.getFullYear()
|
||
const todayMonth = today.getMonth() + 1
|
||
const [endYear, endMonth] = this.endDate.split('-').map(Number)
|
||
|
||
// 如果是当月,使用今天的日期作为结束日期
|
||
if (endYear === todayYear && endMonth === todayMonth) {
|
||
const todayDay = today.getDate()
|
||
end = `${this.endDate}-${String(todayDay).padStart(2, '0')}`
|
||
}
|
||
// 如果不是当月,end 已经是该月的最后一天(由 formatFullDate 处理)
|
||
}
|
||
|
||
const queryParams = {
|
||
pageNum: 1,
|
||
pageSize: 9999,
|
||
startDate: start,
|
||
endDate: end
|
||
}
|
||
|
||
console.log('停机查询参数:', queryParams)
|
||
|
||
listStoppage(queryParams).then(response => {
|
||
uni.hideLoading()
|
||
|
||
console.log('停机统计响应:', response)
|
||
|
||
if (response.code === 200 && response.rows && response.rows.length > 0) {
|
||
// 处理停机数据
|
||
console.log('停机数据:', response.rows)
|
||
|
||
// 更新表格数据(日视图)
|
||
this.tableData = response.rows.map(item => ({
|
||
time: this.formatDateTime(item.startDate) + ' - ' + this.formatDateTime(item.endDate),
|
||
duration: this.secondsToMinutes(item.duration) + 'min',
|
||
remark: item.remark || '-',
|
||
machine: item.unit || '-'
|
||
}))
|
||
|
||
// 计算汇总数据(秒转分钟)
|
||
const totalDurationSeconds = response.rows.reduce((sum, item) => sum + (Number(item.duration) || 0), 0)
|
||
const totalDurationMinutes = this.secondsToMinutes(totalDurationSeconds)
|
||
const totalCount = response.rows.length
|
||
|
||
// 计算作业率(需要知道总可用时间)
|
||
const totalAvailableMinutes = this.getTotalAvailableMinutes()
|
||
const workRate = this.calculateWorkRate(totalDurationMinutes, totalAvailableMinutes)
|
||
|
||
this.summaryData = [
|
||
{ label: '停机时间', value: totalDurationMinutes, unit: 'min' },
|
||
{ label: '停机次数', value: totalCount, unit: '次' },
|
||
{ label: '作业率', value: workRate, unit: '%' }
|
||
]
|
||
|
||
// 更新饼图数据(按班组统计)
|
||
const crewMap = {}
|
||
const typeMap = {}
|
||
|
||
response.rows.forEach(item => {
|
||
const crew = item.crew || '未知班组'
|
||
const type = item.stopType || '未知类型'
|
||
const durationMinutes = this.secondsToMinutes(item.duration)
|
||
crewMap[crew] = (crewMap[crew] || 0) + durationMinutes
|
||
typeMap[type] = (typeMap[type] || 0) + durationMinutes
|
||
})
|
||
|
||
this.crewPieData = {
|
||
series: [{
|
||
data: Object.keys(crewMap).map(crew => ({
|
||
name: crew,
|
||
value: crewMap[crew]
|
||
}))
|
||
}]
|
||
}
|
||
|
||
this.typePieData = {
|
||
series: [{
|
||
data: Object.keys(typeMap).map(type => ({
|
||
name: type,
|
||
value: typeMap[type]
|
||
}))
|
||
}]
|
||
}
|
||
|
||
// 如果是月/年视图,构建趋势图
|
||
if (this.activeTab !== 'day') {
|
||
if (response.rows.length > 0) {
|
||
this.buildTrendChart(response.rows)
|
||
} else {
|
||
// 没有数据时也要清空趋势图
|
||
this.trendChartData = {
|
||
categories: [],
|
||
series: []
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 没有数据时使用默认值
|
||
console.log('暂无停机数据')
|
||
// 清空数据
|
||
this.tableData = []
|
||
this.summaryData = [
|
||
{ label: '停机时间', value: 0, unit: 'min' },
|
||
{ label: '停机次数', value: 0, unit: '次' },
|
||
{ label: '作业率', value: 100, unit: '%' }
|
||
]
|
||
this.crewPieData = { series: [{ data: [] }] }
|
||
this.typePieData = { series: [{ data: [] }] }
|
||
// 清空趋势图
|
||
this.trendChartData = {
|
||
categories: [],
|
||
series: []
|
||
}
|
||
}
|
||
}).catch(error => {
|
||
uni.hideLoading()
|
||
console.error('加载停机数据失败:', error)
|
||
uni.showToast({
|
||
title: '加载失败',
|
||
icon: 'none'
|
||
})
|
||
})
|
||
},
|
||
// 格式化日期时间
|
||
formatDateTime(dateStr) {
|
||
if (!dateStr) return '-'
|
||
const date = new Date(dateStr)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hours = String(date.getHours()).padStart(2, '0')
|
||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||
},
|
||
// 获取总可用时间(分钟)
|
||
getTotalAvailableMinutes() {
|
||
const start = new Date(this.formatFullDate(this.startDate, true))
|
||
const end = new Date(this.formatFullDate(this.endDate, false))
|
||
|
||
// 计算天数差
|
||
const diffTime = end - start
|
||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1 // 包含结束日期
|
||
|
||
// 每天1440分钟(24小时)
|
||
return diffDays * 1440
|
||
},
|
||
|
||
// 计算作业率
|
||
calculateWorkRate(stopDuration, totalAvailableMinutes) {
|
||
if (!totalAvailableMinutes || totalAvailableMinutes === 0) {
|
||
return 100
|
||
}
|
||
const workRate = ((totalAvailableMinutes - stopDuration) / totalAvailableMinutes) * 100
|
||
return Math.max(0, Math.min(100, workRate)).toFixed(2)
|
||
},
|
||
|
||
// 构建趋势图(月/年视图)
|
||
buildTrendChart(stoppageData) {
|
||
if (!stoppageData || stoppageData.length === 0) {
|
||
console.log('无法构建趋势图:数据为空')
|
||
this.trendChartData = {
|
||
categories: [],
|
||
series: []
|
||
}
|
||
return
|
||
}
|
||
|
||
// 根据视图类型决定分组方式
|
||
const dateMap = {}
|
||
const isYearView = this.activeTab === 'year'
|
||
|
||
stoppageData.forEach(item => {
|
||
if (!item.startDate) return
|
||
|
||
let key
|
||
if (isYearView) {
|
||
// 年视图:按月份分组
|
||
key = this.formatDateByMonth(item.startDate)
|
||
} else {
|
||
// 月视图:按日期分组
|
||
key = this.formatDate(item.startDate)
|
||
}
|
||
|
||
if (!dateMap[key]) {
|
||
dateMap[key] = { duration: 0, count: 0 }
|
||
}
|
||
const durationMinutes = this.secondsToMinutes(item.duration)
|
||
dateMap[key].duration += durationMinutes
|
||
dateMap[key].count += 1
|
||
})
|
||
|
||
let categories = []
|
||
if (isYearView) {
|
||
// 年视图:生成完整的12个月份
|
||
categories = Array.from({ length: 12 }, (_, i) => `${i + 1}月`)
|
||
} else {
|
||
// 月视图:生成从开始日期到结束日期的完整日期范围
|
||
const startStr = this.formatFullDate(this.startDate, true)
|
||
let endStr = this.formatFullDate(this.endDate, false)
|
||
|
||
// 检查结束日期是否是当月
|
||
const today = new Date()
|
||
const todayYear = today.getFullYear()
|
||
const todayMonth = today.getMonth() + 1
|
||
|
||
// 解析结束日期(格式:yyyy-MM)
|
||
if (this.endDate && this.endDate.length === 7) {
|
||
const [endYear, endMonth] = this.endDate.split('-').map(Number)
|
||
// 如果是当月,使用今天的日期作为结束日期
|
||
if (endYear === todayYear && endMonth === todayMonth) {
|
||
const todayDay = today.getDate()
|
||
endStr = `${this.endDate}-${String(todayDay).padStart(2, '0')}`
|
||
}
|
||
// 如果不是当月,endStr 已经是该月的最后一天(由 formatFullDate 处理)
|
||
}
|
||
|
||
const start = new Date(startStr)
|
||
const end = new Date(endStr)
|
||
|
||
// 生成所有日期
|
||
const dateList = []
|
||
const currentDate = new Date(start)
|
||
while (currentDate <= end) {
|
||
// 格式化日期为 M/D 格式
|
||
const month = currentDate.getMonth() + 1
|
||
const day = currentDate.getDate()
|
||
const dateKey = `${month}/${day}`
|
||
dateList.push(dateKey)
|
||
// 移动到下一天
|
||
currentDate.setDate(currentDate.getDate() + 1)
|
||
}
|
||
categories = dateList
|
||
}
|
||
|
||
if (categories.length === 0) {
|
||
console.log('无法构建趋势图:无有效日期')
|
||
this.trendChartData = {
|
||
categories: [],
|
||
series: []
|
||
}
|
||
return
|
||
}
|
||
|
||
const durationData = []
|
||
const rateData = []
|
||
|
||
categories.forEach(key => {
|
||
const data = dateMap[key] || { duration: 0, count: 0 }
|
||
durationData.push(data.duration)
|
||
// 计算作业率
|
||
// 年视图:每个月按实际天数计算(根据选择的年份)
|
||
// 月视图:每天1440分钟
|
||
let totalMinutes
|
||
if (isYearView) {
|
||
// 获取选择的年份
|
||
const year = parseInt(this.startDate) || new Date().getFullYear()
|
||
const monthIndex = parseInt(key.replace('月', '')) - 1
|
||
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate()
|
||
totalMinutes = daysInMonth * 1440
|
||
} else {
|
||
totalMinutes = 1440
|
||
}
|
||
const rate = this.calculateWorkRate(data.duration, totalMinutes)
|
||
rateData.push(Number(rate))
|
||
})
|
||
|
||
console.log('趋势图数据构建成功:', { categories, durationData, rateData, isYearView })
|
||
|
||
this.trendChartData = {
|
||
categories: categories,
|
||
series: [
|
||
{
|
||
name: "停机时间",
|
||
type: "column",
|
||
index: 0,
|
||
data: durationData
|
||
},
|
||
{
|
||
name: "作业率",
|
||
type: "line",
|
||
index: 1,
|
||
style: "curve",
|
||
data: rateData
|
||
}
|
||
]
|
||
}
|
||
},
|
||
|
||
// 格式化日期(简短格式)
|
||
formatDate(dateStr) {
|
||
if (!dateStr) return ''
|
||
const date = new Date(dateStr)
|
||
const month = date.getMonth() + 1
|
||
const day = date.getDate()
|
||
return `${month}/${day}`
|
||
},
|
||
|
||
// 格式化日期为月份(用于年视图)
|
||
formatDateByMonth(dateStr) {
|
||
if (!dateStr) return ''
|
||
const date = new Date(dateStr)
|
||
const month = date.getMonth() + 1
|
||
return `${month}月`
|
||
},
|
||
|
||
// 秒转分钟(保留整数)
|
||
secondsToMinutes(seconds) {
|
||
if (!seconds || seconds === 0) return 0
|
||
return Math.round(Number(seconds) / 60)
|
||
},
|
||
|
||
// 格式化为完整日期(用于查询)
|
||
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
|
||
}
|
||
},
|
||
// 生命周期钩子
|
||
mounted() {
|
||
this.loadStoppageData()
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
/* 页面容器 */
|
||
.page-container {
|
||
background: #f5f7fa;
|
||
padding: 24rpx;
|
||
}
|
||
|
||
/* 时间维度切换 */
|
||
.time-tab-bar {
|
||
display: flex;
|
||
background: #fff;
|
||
border-radius: 8rpx;
|
||
padding: 8rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1rpx solid #e4e7ed;
|
||
}
|
||
|
||
.time-tab-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 16rpx 0;
|
||
font-size: 26rpx;
|
||
color: #606266;
|
||
border-radius: 6rpx;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.time-tab-active {
|
||
background: #0066cc;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 日期选择区 */
|
||
.date-selector {
|
||
background: #fff;
|
||
border-radius: 8rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1rpx solid #e4e7ed;
|
||
}
|
||
|
||
.date-range-group {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.date-input {
|
||
flex: 1;
|
||
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;
|
||
}
|
||
|
||
.date-value {
|
||
font-size: 28rpx;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
flex: 1;
|
||
text-align: right;
|
||
}
|
||
|
||
.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,
|
||
.detail-section {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20rpx;
|
||
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;
|
||
}
|
||
|
||
/* 趋势图固定高度 */
|
||
.trend-chart {
|
||
height: 500rpx;
|
||
min-height: 500rpx;
|
||
}
|
||
|
||
/* 单个饼图容器 */
|
||
.pie-chart-single {
|
||
background: #fff;
|
||
border: 1rpx solid #e4e7ed;
|
||
border-radius: 8rpx;
|
||
padding: 32rpx 0;
|
||
min-height: 480rpx;
|
||
}
|
||
|
||
/* 空状态图表占位 */
|
||
.empty-chart {
|
||
background: #fff;
|
||
border: 1rpx solid #e4e7ed;
|
||
border-radius: 8rpx;
|
||
padding: 100rpx 0;
|
||
min-height: 480rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 80rpx;
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #909399;
|
||
}
|
||
|
||
/* 停机详情列表 */
|
||
.detail-list {
|
||
background: #fff;
|
||
border-radius: 8rpx;
|
||
border: 1rpx solid #e4e7ed;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.detail-item {
|
||
padding: 24rpx;
|
||
border-bottom: 1rpx solid #f5f7fa;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
|
||
.detail-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.detail-time {
|
||
font-size: 26rpx;
|
||
color: #606266;
|
||
}
|
||
|
||
.detail-duration {
|
||
font-size: 28rpx;
|
||
color: #0066cc;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.detail-info {
|
||
display: flex;
|
||
align-items: baseline;
|
||
margin-bottom: 8rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.detail-label {
|
||
font-size: 24rpx;
|
||
color: #909399;
|
||
min-width: 120rpx;
|
||
}
|
||
|
||
.detail-text {
|
||
font-size: 26rpx;
|
||
color: #303133;
|
||
flex: 1;
|
||
}
|
||
|
||
/* 空状态 */
|
||
.empty-state {
|
||
padding: 100rpx 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #909399;
|
||
}
|
||
</style>
|