You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
673 lines
19 KiB
Vue
673 lines
19 KiB
Vue
<template>
|
|
<view class="plan-section">
|
|
<view class="section-header">
|
|
<text class="section-title">{{ t('dashboard.productionPlan') }}</text>
|
|
<text class="section-more" @click="viewMore">
|
|
{{ t('dashboard.viewMore') }}
|
|
</text>
|
|
</view>
|
|
<view class="filter-bar">
|
|
<picker mode="selector" :range="filterRange" range-key="text" :value="filterIndex" @change="onFilterChange">
|
|
<view class="filter-select">
|
|
<text class="filter-text">{{ currentFilterLabel }}</text>
|
|
<text class="filter-arrow">▼</text>
|
|
</view>
|
|
</picker>
|
|
<picker mode="selector" :range="rangeRange" range-key="text" :value="rangeIndex" @change="onRangeChange">
|
|
<view class="filter-select">
|
|
<text class="filter-text">{{ currentRangeLabel }}</text>
|
|
<text class="filter-arrow">▼</text>
|
|
</view>
|
|
</picker>
|
|
</view>
|
|
<view v-if="currentRange === 'custom'" class="filter-date-wrap">
|
|
<view class="date-picker-item">
|
|
<uni-datetime-picker v-model="dateRange.start" type="datetime"
|
|
:placeholder="t('dashboard.startDate')" @change="onStartDateChange" />
|
|
</view>
|
|
<view class="date-picker-item">
|
|
<uni-datetime-picker v-model="dateRange.end" type="datetime"
|
|
:placeholder="t('dashboard.endDate')" @change="onEndDateChange" />
|
|
</view>
|
|
</view>
|
|
<view v-if="currentFilter === 'product'" class="trend-content">
|
|
<view class="trend-stats">
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value">{{ formatNumber(trendData.baogongNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.baogongNum') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value pass">{{ formatNumber(trendData.passNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.passNum') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value fail">{{ formatNumber(trendData.noPassNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.noPassNum') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value rate">{{ formatPercent(trendData.passRate) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.passRate') }}</text>
|
|
</view>
|
|
</view>
|
|
<view v-if="currentRange !== 'custom'" class="trend-chart">
|
|
<text class="chart-title">{{ t('dashboard.baogongNum') }}</text>
|
|
<view class="chart-box">
|
|
<qiun-data-charts type="line" :chartData="baogongChartData" :canvas2d="false"
|
|
:opts="chartOpts" />
|
|
</view>
|
|
</view>
|
|
<view v-if="currentRange !== 'custom'" class="trend-chart">
|
|
<text class="chart-title">{{ t('dashboard.passRate') }}</text>
|
|
<view class="chart-box">
|
|
<qiun-data-charts type="line" :chartData="passRateChartData" :canvas2d="false"
|
|
:opts="passRateChartOpts" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-else class="trend-content">
|
|
<scroll-view scroll-x enable-flex class="trend-stats-scroll">
|
|
<view class="trend-stats">
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value">{{ formatNumber(taskTrendData.totalNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.totalTask') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value">{{ formatNumber(taskTrendData.issuedNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.issuedNum') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value">{{ formatNumber(taskTrendData.partialScheduledNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.partialScheduledNum') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value">{{ formatNumber(taskTrendData.waitingProductionNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.waitingProduction') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value">{{ formatNumber(taskTrendData.producingNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.producing') }}</text>
|
|
</view>
|
|
<view class="trend-stat-card">
|
|
<text class="trend-stat-value">{{ formatNumber(taskTrendData.completedNum) }}</text>
|
|
<text class="trend-stat-label">{{ t('dashboard.completed') }}</text>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
<view v-if="currentRange !== 'custom'" class="trend-chart">
|
|
<text class="chart-title">{{ t('dashboard.totalTask') }}</text>
|
|
<view class="chart-box">
|
|
<qiun-data-charts type="line" :chartData="taskChartData" :canvas2d="false"
|
|
:opts="chartOpts" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import request from '@/utils/request'
|
|
import { formatNumber, formatPercent } from '@/utils/format'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const chartOpts = {
|
|
color: ['#1a3a5c'],
|
|
dataLabel: false,
|
|
dataPointShape: false,
|
|
legend: { show: false },
|
|
xAxis: { disableGrid: true, labelCount: 4 },
|
|
yAxis: { gridType: 'dash', dashLength: 2 },
|
|
extra: { line: { type: 'curve', width: 1, activeType: 'hollow' } }
|
|
}
|
|
|
|
const passRateChartOpts = {
|
|
color: ['#1a3a5c'],
|
|
dataLabel: false,
|
|
dataPointShape: false,
|
|
legend: { show: false },
|
|
xAxis: { disableGrid: true, labelCount: 4 },
|
|
yAxis: { gridType: 'dash', dashLength: 2, data: [{ min: 0, max: 100 }] },
|
|
extra: { line: { type: 'curve', width: 1, activeType: 'hollow' } }
|
|
}
|
|
|
|
const currentFilter = ref('task')
|
|
const currentRange = ref('year')
|
|
const isInitialLoad = ref(true)
|
|
|
|
const filterRange = computed(() => [
|
|
{ text: t('dashboard.filterTask'), value: 'task' },
|
|
{ text: t('dashboard.filterProduct'), value: 'product' }
|
|
])
|
|
|
|
const filterIndex = computed(() => {
|
|
const idx = filterRange.value.findIndex(item => item.value === currentFilter.value)
|
|
return idx >= 0 ? idx : 0
|
|
})
|
|
|
|
const currentFilterLabel = computed(() => {
|
|
return currentFilter.value === 'task' ? t('dashboard.filterTask') : t('dashboard.filterProduct')
|
|
})
|
|
|
|
const rangeRange = computed(() => [
|
|
{ text: t('dashboard.rangeYear'), value: 'year' },
|
|
{ text: t('dashboard.rangeMonth'), value: 'month' },
|
|
{ text: t('dashboard.rangeWeek'), value: 'week' },
|
|
{ text: t('dashboard.rangeToday'), value: 'today' },
|
|
{ text: t('dashboard.rangeCustom'), value: 'custom' }
|
|
])
|
|
|
|
const rangeIndex = computed(() => {
|
|
const idx = rangeRange.value.findIndex(item => item.value === currentRange.value)
|
|
return idx >= 0 ? idx : 0
|
|
})
|
|
|
|
const rangeLabelMap = computed(() => ({
|
|
year: t('dashboard.rangeYear'),
|
|
month: t('dashboard.rangeMonth'),
|
|
week: t('dashboard.rangeWeek'),
|
|
today: t('dashboard.rangeToday'),
|
|
custom: t('dashboard.rangeCustom')
|
|
}))
|
|
|
|
const currentRangeLabel = computed(() => {
|
|
return rangeLabelMap.value[currentRange.value] || t('dashboard.rangeMonth')
|
|
})
|
|
|
|
function getTodayZero() {
|
|
const now = new Date()
|
|
const pad2 = (n) => String(n).padStart(2, '0')
|
|
return `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())} 00:00:00`
|
|
}
|
|
|
|
const dateRange = reactive({ start: getTodayZero(), end: getTodayZero() })
|
|
|
|
const trendData = reactive({
|
|
baogongNum: 0,
|
|
passNum: 0,
|
|
noPassNum: 0,
|
|
passRate: 0
|
|
})
|
|
|
|
const taskTrendData = reactive({
|
|
totalNum: 0,
|
|
issuedNum: 0,
|
|
partialScheduledNum: 0,
|
|
waitingProductionNum: 0,
|
|
producingNum: 0,
|
|
completedNum: 0
|
|
})
|
|
|
|
const baogongChartData = reactive({
|
|
categories: [],
|
|
series: [{ name: t('dashboard.baogongNum'), data: [] }]
|
|
})
|
|
|
|
const passRateChartData = reactive({
|
|
categories: [],
|
|
series: [{ name: t('dashboard.passRate'), data: [] }]
|
|
})
|
|
|
|
const taskChartData = reactive({
|
|
categories: [],
|
|
series: [{ name: t('dashboard.taskTrend'), data: [] }]
|
|
})
|
|
|
|
const weekdayKeys = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']
|
|
|
|
function getDateRange(type) {
|
|
const now = new Date()
|
|
const pad2 = (n) => String(n).padStart(2, '0')
|
|
const fmt = (d) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
|
|
const end = fmt(now)
|
|
|
|
if (type === 'year') {
|
|
const start = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate())
|
|
return { start: fmt(start), end }
|
|
}
|
|
if (type === 'month') {
|
|
const start = new Date(now.getFullYear(), now.getMonth(), 1)
|
|
return { start: fmt(start), end }
|
|
}
|
|
if (type === 'week') {
|
|
const day = now.getDay() || 7
|
|
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day + 1)
|
|
return { start: fmt(start), end }
|
|
}
|
|
if (type === 'today') {
|
|
return { start: end, end }
|
|
}
|
|
return { start: '', end: '' }
|
|
}
|
|
|
|
function transformChartData(dayTrend, rangeType) {
|
|
if (!dayTrend || dayTrend.length === 0) {
|
|
return { categories: [], baogongData: [], passRateData: [] }
|
|
}
|
|
|
|
if (rangeType === 'year') {
|
|
const monthMap = new Map()
|
|
dayTrend.forEach((item) => {
|
|
const m = item.day ? item.day.substring(0, 7) : ''
|
|
if (!m) return
|
|
if (!monthMap.has(m)) {
|
|
monthMap.set(m, { baogongNum: 0, passRateSum: 0, count: 0 })
|
|
}
|
|
const entry = monthMap.get(m)
|
|
entry.baogongNum += item.baogongNum ?? 0
|
|
entry.passRateSum += item.passRate ?? 0
|
|
entry.count += 1
|
|
})
|
|
const categories = []
|
|
const baogongData = []
|
|
const passRateData = []
|
|
monthMap.forEach((val, key) => {
|
|
categories.push(key)
|
|
baogongData.push(val.baogongNum)
|
|
passRateData.push(val.count > 0 ? Math.round((val.passRateSum / val.count) * 100) / 100 : 0)
|
|
})
|
|
return { categories, baogongData, passRateData }
|
|
}
|
|
|
|
if (rangeType === 'week') {
|
|
const categories = dayTrend.map((item) => {
|
|
const d = new Date(item.day)
|
|
const dayIdx = d.getDay()
|
|
return t(`dashboard.${weekdayKeys[dayIdx]}`)
|
|
})
|
|
return {
|
|
categories,
|
|
baogongData: dayTrend.map((item) => item.baogongNum ?? 0),
|
|
passRateData: dayTrend.map((item) => item.passRate ?? 0)
|
|
}
|
|
}
|
|
|
|
if (rangeType === 'today') {
|
|
const categories = dayTrend.map((item) => (item.day || '').substring(5))
|
|
return {
|
|
categories,
|
|
baogongData: dayTrend.map((item) => item.baogongNum ?? 0),
|
|
passRateData: dayTrend.map((item) => item.passRate ?? 0)
|
|
}
|
|
}
|
|
|
|
const categories = dayTrend.map((item) => (item.day || '').substring(5))
|
|
return {
|
|
categories,
|
|
baogongData: dayTrend.map((item) => item.baogongNum ?? 0),
|
|
passRateData: dayTrend.map((item) => item.passRate ?? 0)
|
|
}
|
|
}
|
|
|
|
function onFilterChange(e) {
|
|
const idx = e.detail.value
|
|
const val = filterRange.value[idx]?.value
|
|
if (!val) return
|
|
currentFilter.value = val
|
|
if (val === 'product') {
|
|
loadTrendData()
|
|
} else if (val === 'task') {
|
|
loadTaskTrendData()
|
|
}
|
|
}
|
|
|
|
function onRangeChange(e) {
|
|
const idx = e.detail.value
|
|
const val = rangeRange.value[idx]?.value
|
|
if (!val) return
|
|
currentRange.value = val
|
|
if (val !== 'custom') {
|
|
const range = getDateRange(val)
|
|
dateRange.start = range.start
|
|
dateRange.end = range.end
|
|
if (currentFilter.value === 'product') {
|
|
loadTrendData()
|
|
} else if (currentFilter.value === 'task') {
|
|
loadTaskTrendData()
|
|
}
|
|
} else {
|
|
dateRange.start = getTodayZero()
|
|
dateRange.end = getTodayZero()
|
|
clearChartData()
|
|
}
|
|
}
|
|
|
|
function clearChartData() {
|
|
trendData.baogongNum = 0
|
|
trendData.passNum = 0
|
|
trendData.noPassNum = 0
|
|
trendData.passRate = 0
|
|
baogongChartData.categories = []
|
|
baogongChartData.series = [{ name: t('dashboard.baogongNum'), data: [] }]
|
|
passRateChartData.categories = []
|
|
passRateChartData.series = [{ name: t('dashboard.passRate'), data: [] }]
|
|
|
|
taskTrendData.totalNum = 0
|
|
taskTrendData.issuedNum = 0
|
|
taskTrendData.partialScheduledNum = 0
|
|
taskTrendData.waitingProductionNum = 0
|
|
taskTrendData.producingNum = 0
|
|
taskTrendData.completedNum = 0
|
|
taskChartData.categories = []
|
|
taskChartData.series = [{ name: t('dashboard.taskTrend'), data: [] }]
|
|
}
|
|
|
|
function onStartDateChange(val) {
|
|
dateRange.start = val
|
|
if (dateRange.start && dateRange.end) {
|
|
loadData()
|
|
}
|
|
}
|
|
|
|
function onEndDateChange(val) {
|
|
dateRange.end = val
|
|
if (dateRange.start && dateRange.end) {
|
|
loadData()
|
|
}
|
|
}
|
|
|
|
async function loadTrendData() {
|
|
const trendTypeMap = { year: 1, month: 2, week: 3, today: 4, custom: 5 }
|
|
const params = { trendType: trendTypeMap[currentRange.value] || 2 }
|
|
if (currentRange.value === 'custom') {
|
|
if (dateRange.start) params.beginBaogongTime = dateRange.start
|
|
if (dateRange.end) params.endBaogongTime = dateRange.end
|
|
}
|
|
const res = await request({
|
|
url: '/admin-api/mes/baogong-record/trend',
|
|
method: 'get',
|
|
params,
|
|
showLoading: !isInitialLoad.value
|
|
})
|
|
const data = res?.data || {}
|
|
trendData.baogongNum = data.baogongNum ?? 0
|
|
trendData.passNum = data.passNum ?? 0
|
|
trendData.noPassNum = data.noPassNum ?? 0
|
|
trendData.passRate = data.passRate ?? 0
|
|
|
|
if (currentRange.value === 'custom') {
|
|
baogongChartData.categories = []
|
|
baogongChartData.series = [{ name: t('dashboard.baogongNum'), data: [] }]
|
|
passRateChartData.categories = []
|
|
passRateChartData.series = [{ name: t('dashboard.passRate'), data: [] }]
|
|
return
|
|
}
|
|
|
|
const dayTrend = data.dayTrend || []
|
|
const transformed = transformChartData(dayTrend, currentRange.value)
|
|
baogongChartData.categories = transformed.categories
|
|
baogongChartData.series = [{ name: t('dashboard.baogongNum'), data: transformed.baogongData }]
|
|
passRateChartData.categories = transformed.categories
|
|
passRateChartData.series = [{ name: t('dashboard.passRate'), data: transformed.passRateData }]
|
|
}
|
|
|
|
async function loadTaskTrendData() {
|
|
const trendTypeMap = { year: 1, month: 2, week: 3, today: 4, custom: 5 }
|
|
const params = { trendType: trendTypeMap[currentRange.value] || 2 }
|
|
if (currentRange.value === 'custom') {
|
|
if (dateRange.start) params.beginTime = dateRange.start
|
|
if (dateRange.end) params.endTime = dateRange.end
|
|
}
|
|
const res = await request({
|
|
url: '/admin-api/mes/task/trend',
|
|
method: 'get',
|
|
params,
|
|
showLoading: !isInitialLoad.value
|
|
})
|
|
const data = res?.data || {}
|
|
taskTrendData.totalNum = data.totalNum ?? 0
|
|
taskTrendData.issuedNum = data.issuedNum ?? 0
|
|
taskTrendData.partialScheduledNum = data.partialScheduledNum ?? 0
|
|
taskTrendData.waitingProductionNum = data.waitingProductionNum ?? 0
|
|
taskTrendData.producingNum = data.producingNum ?? 0
|
|
taskTrendData.completedNum = data.completedNum ?? 0
|
|
|
|
if (currentRange.value === 'custom') {
|
|
taskChartData.categories = []
|
|
taskChartData.series = [{ name: t('dashboard.taskTrend'), data: [] }]
|
|
return
|
|
}
|
|
|
|
const dayTrend = data.dayTrend || []
|
|
const categories = []
|
|
const taskData = []
|
|
if (currentRange.value === 'year') {
|
|
const monthMap = new Map()
|
|
dayTrend.forEach((item) => {
|
|
const m = item.day ? item.day.substring(0, 7) : ''
|
|
if (!m) return
|
|
if (!monthMap.has(m)) {
|
|
monthMap.set(m, 0)
|
|
}
|
|
monthMap.set(m, (monthMap.get(m) ?? 0) + (item.count ?? 0))
|
|
})
|
|
monthMap.forEach((val, key) => {
|
|
categories.push(key)
|
|
taskData.push(val)
|
|
})
|
|
} else if (currentRange.value === 'week') {
|
|
dayTrend.forEach((item) => {
|
|
const d = new Date(item.day)
|
|
const dayIdx = d.getDay()
|
|
categories.push(t(`dashboard.${weekdayKeys[dayIdx]}`))
|
|
taskData.push(item.count ?? 0)
|
|
})
|
|
} else {
|
|
dayTrend.forEach((item) => {
|
|
const dayStr = item.day || ''
|
|
categories.push(dayStr.substring(5))
|
|
taskData.push(item.count ?? 0)
|
|
})
|
|
}
|
|
taskChartData.categories = categories
|
|
taskChartData.series = [{ name: t('dashboard.taskTrend'), data: taskData }]
|
|
}
|
|
|
|
function viewMore() {
|
|
if (currentFilter.value === 'task') {
|
|
uni.navigateTo({ url: '/pages_function/pages/taskList/index' })
|
|
} else {
|
|
uni.navigateTo({ url: '/pages_function/pages/planList/index' })
|
|
}
|
|
}
|
|
|
|
function loadData() {
|
|
if (currentFilter.value === 'task') {
|
|
loadTaskTrendData()
|
|
} else {
|
|
loadTrendData()
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await loadData()
|
|
isInitialLoad.value = false
|
|
})
|
|
|
|
defineExpose({ loadData })
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.plan-section {
|
|
background: #ffffff;
|
|
border-radius: 20rpx;
|
|
padding: 28rpx;
|
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24rpx;
|
|
|
|
.section-more {
|
|
font-size: 26rpx;
|
|
color: #666666;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #1a3a5c;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.filter-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.filter-select {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12rpx 24rpx;
|
|
background: #f0f2f5;
|
|
border-radius: 12rpx;
|
|
margin-right: 20rpx;
|
|
|
|
&:active {
|
|
background: #e8ecf0;
|
|
}
|
|
}
|
|
|
|
.filter-text {
|
|
font-size: 26rpx;
|
|
color: #1a3a5c;
|
|
font-weight: 500;
|
|
margin-right: 8rpx;
|
|
}
|
|
|
|
.filter-arrow {
|
|
font-size: 20rpx;
|
|
color: #999999;
|
|
}
|
|
|
|
.filter-date-wrap {
|
|
display: flex;
|
|
gap: 16rpx;
|
|
margin-bottom: 24rpx;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.date-picker-item {
|
|
flex: 1;
|
|
min-width: 200rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
background: #f8fafc;
|
|
border-radius: 12rpx;
|
|
padding: 6rpx 12rpx;
|
|
border: 2rpx solid #e0e6ed;
|
|
transition: border-color 0.3s ease;
|
|
|
|
&:focus-within {
|
|
border-color: #4a90c2;
|
|
background: #ffffff;
|
|
}
|
|
|
|
:deep(.uni-date) {
|
|
flex: 1;
|
|
width: 100%;
|
|
|
|
.uni-date-editor--x {
|
|
border: none;
|
|
background: transparent;
|
|
padding: 0;
|
|
}
|
|
|
|
.uni-date__x-input {
|
|
background: transparent;
|
|
font-size: 24rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.trend-content {
|
|
margin-top: 8rpx;
|
|
}
|
|
|
|
.trend-stats-scroll {
|
|
width: 100%;
|
|
white-space: nowrap;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.trend-stats {
|
|
display: inline-flex;
|
|
flex-wrap: nowrap;
|
|
}
|
|
|
|
.trend-stat-card {
|
|
flex-shrink: 0;
|
|
width: 164rpx;
|
|
background: #f8fafc;
|
|
border-radius: 12rpx;
|
|
padding: 20rpx 12rpx;
|
|
margin: 0 6rpx;
|
|
text-align: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
|
|
&:first-child {
|
|
margin-left: 0;
|
|
}
|
|
|
|
&:last-child {
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
|
|
.trend-stat-value {
|
|
display: block;
|
|
font-size: 36rpx;
|
|
font-weight: bold;
|
|
color: #1a3a5c;
|
|
margin-bottom: 6rpx;
|
|
|
|
&.pass {
|
|
color: #18bc37;
|
|
}
|
|
|
|
&.fail {
|
|
color: #ff4d4f;
|
|
}
|
|
|
|
&.rate {
|
|
color: #4a90c2;
|
|
}
|
|
}
|
|
|
|
.trend-stat-label {
|
|
display: block;
|
|
font-size: 22rpx;
|
|
color: #999999;
|
|
}
|
|
|
|
.trend-chart {
|
|
margin-top: 8rpx;
|
|
}
|
|
|
|
.chart-title {
|
|
display: block;
|
|
font-size: 28rpx;
|
|
font-weight: 500;
|
|
color: #1a3a5c;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.chart-box {
|
|
width: 100%;
|
|
height: 450rpx;
|
|
min-width: 100%;
|
|
border-radius: 12rpx;
|
|
}
|
|
</style>
|