Files
klp-oa/klp-ui/src/views/lines/acid/components/shutdown-statistic.vue
砂糖 212c2b16eb feat: 新增锌线生产监控模块及相关API和组件
refactor(auth): 增加锌线系统token管理功能
feat(api): 添加锌线停机记录、生产报表和设备快照API
feat(views): 实现锌线实时监控、生产统计和停机统计页面
feat(components): 开发锌线生产报表、停机统计和班组绩效组件
feat(utils): 新增锌线专用请求工具zinc1Request
chore(vue.config): 配置锌线API代理
2026-01-19 13:29:44 +08:00

908 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="page-container">
<div style="display: flex; align-items: center;">
<!-- 时间维度切换 -->
<div class="time-tab-bar">
<div 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 }}
</div>
</div>
<!-- 日期选择区 -->
<div class="date-selector">
<!-- 日模式 -->
<el-date-picker v-if="activeTab === 'day'" v-model="startDate" type="date" value-format="yyyy-MM-dd"
placeholder="选择日期" @change="handleDateChange" class="single-date-picker" />
<!-- 月模式 -->
<div v-else-if="activeTab === 'month'" class="date-range-group">
<el-date-picker v-model="startDate" type="month" value-format="yyyy-MM" placeholder="选择开始月份"
@change="handleStartMonthChange" class="range-date-picker" />
<span class="date-separator"></span>
<el-date-picker v-model="endDate" type="month" value-format="yyyy-MM" placeholder="选择结束月份"
:picker-options="monthPickerOptions" @change="handleEndMonthChange" class="range-date-picker" />
</div>
<!-- 年模式 -->
<div v-else class="date-range-group">
<el-date-picker v-model="startDate" type="year" value-format="yyyy" placeholder="选择开始年份"
@change="handleStartYearChange" class="range-date-picker" />
<span class="date-separator"></span>
<el-date-picker v-model="endDate" type="year" value-format="yyyy" placeholder="选择结束年份"
@change="handleEndYearChange" class="range-date-picker" />
</div>
</div>
</div>
<!-- 停机汇总 -->
<div class="summary-section">
<div class="section-header">
<span class="section-title">停机汇总</span>
<span class="section-date">{{ displayDateRange }}</span>
</div>
<div class="summary-grid">
<div class="summary-card" v-for="(item, index) in summaryData" :key="index">
<span class="summary-label">{{ item.label }}</span>
<div class="summary-value-box">
<span class="summary-value">{{ item.value }}</span>
<span v-if="item.unit" class="summary-unit">{{ item.unit }}</span>
</div>
</div>
</div>
</div>
<el-row :gutter="10">
<el-col :span="12">
<!-- 停机分布 - 班组 -->
<div class="chart-section">
<div class="section-header">
<span class="section-title">班组停机分布</span>
</div>
<div v-if="crewPieData.length > 0" class="pie-chart-single" ref="crewPie" id="crewPie"></div>
<div class="empty-chart" v-else>
<span class="empty-icon">📊</span>
<span class="empty-text">此时间段未发生停机</span>
</div>
</div>
</el-col>
<el-col :span="12">
<!-- 停机分布 - 类型 -->
<div class="chart-section">
<div class="section-header">
<span class="section-title">停机类型分布</span>
</div>
<div v-if="typePieData.length > 0" class="pie-chart-single" ref="typePie" id="typePie"></div>
<div class="empty-chart" v-else>
<span class="empty-icon">📊</span>
<span class="empty-text">此时间段未发生停机</span>
</div>
</div>
</el-col>
</el-row>
<!-- 停机详细列表日视图 -->
<div class="detail-section" v-if="activeTab === 'day'">
<div class="section-header">
<span class="section-title">停机详情</span>
</div>
<div class="detail-list">
<div v-if="tableData.length === 0" class="empty-state">
<span class="empty-text">暂无停机记录</span>
</div>
<el-table v-else :data="tableData">
<el-table-column label="时间范围" prop="time"></el-table-column>
<el-table-column label="持续时间" prop="duration"></el-table-column>
<el-table-column label="机组" prop="machine"></el-table-column>
<el-table-column label="备注" prop="remark" show-overflow-tooltip></el-table-column>
</el-table>
</div>
</div>
<!-- 停机趋势图/年视图 -->
<div class="chart-section" v-else>
<div class="section-header">
<span class="section-title">停机趋势</span>
</div>
<div class="chart-wrapper trend-chart" ref="trendChart" id="trendChart"></div>
</div>
</div>
</template>
<script>
// ✅【完全未改动】原API导入
import { listStoppage } from '@/api/pocket/plantState'
// 引入Echarts5.x 兼容Vue2
import * as echarts from 'echarts'
// ✅【完全未改动】原独立工具函数
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);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
return `${year}-${month}`;
}
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 {
name: 'PlantStoppage',
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: "%" }
],
// Echarts图表实例
trendChart: null,
crewPie: null,
typePie: null,
// 月份选择器限制条件
monthPickerOptions: {},
// 趋势图临时存储X轴数据
trendXData: [],
// ✅【完全未改动】原图表配色
mainColor: ["#0066cc", "#f56c6c"],
pieColor: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff", "#d9ecff"],
crewPieData: [],
typePieData: [],
tableData: [],
// ✅【新增】resize防抖定时器解决窗口缩放卡顿
resizeTimer: null
};
},
// ✅【完全未改动】计算属性
computed: {
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 "";
}
}
},
watch: {
// 监听开始月份,更新结束月份可选范围
startDate(val) {
if (this.activeTab === 'month' && val) {
this.monthPickerOptions = {
disabledDate: (time) => {
const maxDate = new Date(this.maxMonthEnd + '-01')
return time.getTime() > maxDate.getTime()
}
}
}
},
// ✅【新增】监听tab切换自动触发图表自适应
activeTab() {
this.$nextTick(() => {
this.resizeEcharts()
})
}
},
mounted() {
// 初始化Echarts图表
this.initEcharts()
// 加载业务数据
this.loadStoppageData()
// 窗口自适应监听
window.addEventListener('resize', this.resizeEcharts)
},
beforeDestroy() {
// ✅【修复】安全销毁图表实例+清除监听+清除定时器,防止内存泄漏
window.removeEventListener('resize', this.resizeEcharts)
clearTimeout(this.resizeTimer)
if (this.trendChart && !this.trendChart.isDisposed()) {
this.trendChart.dispose()
this.trendChart = null
}
if (this.crewPie && !this.crewPie.isDisposed()) {
this.crewPie.dispose()
this.crewPie = null
}
if (this.typePie && !this.typePie.isDisposed()) {
this.typePie.dispose()
this.typePie = null
}
},
methods: {
// ✅【逻辑未改动】仅适配Element传参
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(val) {
this.startDate = val;
this.endDate = val;
this.loadStoppageData();
},
handleStartMonthChange(val) {
this.startDate = val;
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(val) {
this.endDate = val;
this.loadStoppageData();
},
handleStartYearChange(val) {
this.startDate = val;
this.loadStoppageData();
},
handleEndYearChange(val) {
this.endDate = val;
this.loadStoppageData();
},
// ✅【核心逻辑完全未改动】仅替换uni加载提示为Element + 新增清空图表逻辑
loadStoppageData() {
const loading = this.$loading({
lock: true,
text: '加载中',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
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')}`
}
}
const queryParams = {
pageNum: 1,
pageSize: 9999,
startDate: start,
endDate: end
}
console.log('停机查询参数:', queryParams)
listStoppage(queryParams).then(response => {
loading.close()
console.log('停机统计响应:', response)
if (response.code === 200 && response.rows && response.rows.length > 0) {
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 = Object.keys(crewMap).map(crew => ({ name: crew, value: crewMap[crew] }))
this.typePieData = Object.keys(typeMap).map(type => ({ name: type, value: typeMap[type] }))
// 渲染饼图
this.$nextTick(() => {
this.renderPieChart('crew', this.crewPieData)
this.renderPieChart('type', this.typePieData)
})
if (this.activeTab !== 'day') {
if (response.rows.length > 0) {
this.buildTrendChart(response.rows)
} else {
this.trendXData = []
}
}
} else {
console.log('暂无停机数据')
this.tableData = []
this.summaryData = [
{ label: '停机时间', value: 0, unit: 'min' },
{ label: '停机次数', value: 0, unit: '次' },
{ label: '作业率', value: 100, unit: '%' }
]
this.crewPieData = []
this.typePieData = []
this.trendXData = []
// ✅【修复】清空所有图表数据
this.clearChart(this.trendChart)
this.renderPieChart('crew', [])
this.renderPieChart('type', [])
}
}).catch(error => {
loading.close()
console.error('加载停机数据失败:', error)
this.$message.error('加载失败,请稍后重试')
})
},
// ✅【完全未改动】原所有工具方法
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
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)
},
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 ''
if (dateStr.length === 10) {
return dateStr
}
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')}`
}
}
if (dateStr.length === 4) {
if (isStart) {
return `${dateStr}-01-01`
} else {
return `${dateStr}-12-31`
}
}
return dateStr
},
// ✅【逻辑未改动】仅修改图表渲染方式
buildTrendChart(stoppageData) {
if (!stoppageData || stoppageData.length === 0) {
console.log('无法构建趋势图:数据为空')
this.trendXData = []
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) {
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
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')}`
}
}
const start = new Date(startStr)
const end = new Date(endStr)
const dateList = []
const currentDate = new Date(start)
while (currentDate <= end) {
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.trendXData = []
return
}
this.trendXData = categories
const durationData = []
const rateData = []
categories.forEach(key => {
const data = dateMap[key] || { duration: 0, count: 0 }
durationData.push(data.duration)
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))
})
// 渲染趋势混合图
this.renderTrendChart(categories, durationData, rateData)
},
// ===== ✅【全部修复】Echarts 图表初始化/渲染/自适应 核心方法 =====
// 安全初始化单个图表实例【新增核心方法】
initSingleChart(chartId) {
const dom = document.getElementById(chartId)
console.log('获取dom', dom)
if (!dom) return null
const chartInstance = echarts.init(dom)
return chartInstance
},
// 安全清空图表内容【新增核心方法】
clearChart(chartInstance) {
if (chartInstance && !chartInstance.isDisposed()) {
chartInstance.clear()
}
},
// 初始化图表(懒初始化占位)
initEcharts() {
this.trendChart = this.initSingleChart('trendChart')
this.crewPie = this.initSingleChart('crewPie')
this.typePie = this.initSingleChart('typePie')
},
// 渲染停机趋势图 (柱状+折线 双Y轴)【修复完整版】
renderTrendChart(xData, durData, rateData) {
this.trendChart = this.initSingleChart('trendChart')
if (!this.trendChart) return
if (xData.length === 0) {
this.clearChart(this.trendChart)
return
}
const option = {
color: this.mainColor,
grid: { left: 30, right: 30, top: 40, bottom: 60 },
legend: { top: 0, left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: xData,
axisLine: { show: false },
axisLabel: { rotate: 60, fontSize: 12 }
},
yAxis: [
{ type: 'value', name: '停机时间(min)', position: 'left', splitLine: { type: 'dashed', color: '#e4e7ed' } },
{ type: 'value', name: '作业率(%)', position: 'right', splitLine: { show: false } }
],
series: [
{ name: '停机时间', type: 'bar', data: durData, barWidth: 40 },
{ name: '作业率', type: 'line', yAxisIndex: 1, data: rateData, smooth: true }
]
}
this.trendChart.setOption(option, true)
},
// 渲染饼图 (班组/类型 通用)【修复完整版】
renderPieChart(type, data) {
const chartId = type === 'crew' ? 'crewPie' : 'typePie'
if (type === 'crew') {
this.crewPie = this.initSingleChart(chartId)
} else {
this.typePie = this.initSingleChart(chartId)
}
console.log('渲染饼图数据:', type, data, chartId)
const chart = type === 'crew' ? this.crewPie : this.typePie
console.log('获取到的图表实例:', chart)
if (!chart) return
if (data.length === 0) {
this.clearChart(chart)
return
}
const option = {
color: this.pieColor,
tooltip: { trigger: 'item', formatter: '{b}: {c}min ({d}%)' },
legend: { bottom: 0, left: 'center', textStyle: { fontSize: 12, color: '#666' } },
series: [{
name: type === 'crew' ? '班组停机' : '类型停机',
type: 'pie',
radius: ['40%', '70%'],
data: data,
label: { show: true, fontSize: 12 }
}]
}
chart.setOption(option, true)
},
// 图表自适应 - 防抖+安全校验【修复完整版】
resizeEcharts: function () {
clearTimeout(this.resizeTimer)
this.resizeTimer = setTimeout(() => {
if (this.trendChart && !this.trendChart.isDisposed()) this.trendChart.resize()
if (this.crewPie && !this.crewPie.isDisposed()) this.crewPie.resize()
if (this.typePie && !this.typePie.isDisposed()) this.typePie.resize()
}, 200)
}
}
};
</script>
<style scoped lang="scss">
// ✅ PC端适配rpx转px(1rpx=0.5px) 保留原布局+样式+间距 + 修复图表高度问题
.page-container {
background: #f5f7fa;
padding: 12px;
min-height: calc(100vh - 20px);
}
.time-tab-bar {
display: flex;
width: 240px;
background: #fff;
border-radius: 4px;
padding: 4px;
margin-bottom: 12px;
border: 1px solid #e4e7ed;
}
.time-tab-item {
flex: 1;
text-align: center;
padding: 8px 0;
font-size: 13px;
color: #606266;
border-radius: 3px;
transition: all 0.2s;
cursor: pointer;
}
.time-tab-active {
background: #0066cc;
color: #fff;
font-weight: 500;
}
.date-selector {
background: #fff;
border-radius: 4px;
padding: 12px;
margin-bottom: 12px;
margin-left: 20px;
border: 1px solid #e4e7ed;
}
.single-date-picker {
width: 100%;
}
.date-range-group {
display: flex;
justify-content: space-between;
align-items: stretch;
gap: 0;
width: 100%;
}
.range-date-picker {
flex: 1;
}
.date-separator {
font-size: 14px;
color: #000;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
}
.summary-section,
.chart-section,
.detail-section {
margin-bottom: 12px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
padding-left: 8px;
border-left: 2px solid #0066cc;
}
.section-title {
font-size: 15px;
font-weight: 500;
color: #303133;
}
.section-date {
font-size: 12px;
color: #909399;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.summary-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 14px 10px;
text-align: center;
transition: all 0.2s;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
}
.summary-label {
display: block;
font-size: 12px;
color: #909399;
margin-bottom: 8px;
}
.summary-value-box {
display: flex;
align-items: baseline;
justify-content: center;
}
.summary-value {
font-size: 20px;
font-weight: 600;
color: #0066cc;
line-height: 1;
}
.summary-unit {
font-size: 11px;
color: #909399;
margin-left: 3px;
}
.chart-wrapper {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 12px 8px;
min-height: 225px;
width: 100%;
}
// ✅【修复】趋势图高度强制生效
.trend-chart {
height: 250px !important;
min-height: 250px;
}
// ✅【修复】饼图容器增加固定高度,解决高度塌陷
.pie-chart-single {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 16px 0;
height: 260px !important;
min-height: 240px;
width: 100%;
}
// ✅【修复】空图表容器高度和饼图一致
.empty-chart {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 50px 0;
height: 260px !important;
min-height: 240px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
}
.empty-icon {
font-size: 40px;
opacity: 0.3;
}
.empty-text {
font-size: 14px;
color: #909399;
}
.detail-list {
background: #fff;
border-radius: 4px;
border: 1px solid #e4e7ed;
overflow: hidden;
}
.detail-item {
padding: 12px;
border-bottom: 1px solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.detail-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.detail-time {
font-size: 13px;
color: #606266;
}
.detail-duration {
font-size: 14px;
color: #0066cc;
font-weight: 600;
}
.detail-info {
display: flex;
align-items: baseline;
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
.detail-label {
font-size: 12px;
color: #909399;
min-width: 60px;
}
.detail-text {
font-size: 13px;
color: #303133;
flex: 1;
}
.empty-state {
padding: 50px 0;
text-align: center;
}
</style>