feat(mes/roll): 新增轧辊磨削记录通用查询和报表页面
1. 新增通用查询接口,支持按轧辊ID、产线ID、时间范围筛选磨削记录 2. 重构后端列表接口,支持不传轧辊ID查询全部记录 3. 修复硬度字段类型转换问题,将未倒角转为0数值 4. 新增磨辊报表页面,支持统计分析和图表展示
This commit is contained in:
@@ -26,10 +26,17 @@ public class MesRollGrindController extends BaseController {
|
||||
|
||||
private final IMesRollGrindService iMesRollGrindService;
|
||||
|
||||
/** 查询某轧辊的磨削记录列表 */
|
||||
/** 查询磨削记录列表(不传 rollId 时返回全部,支持时间范围和产线筛选) */
|
||||
@GetMapping("/list")
|
||||
public R<List<MesRollGrindVo>> list(@NotNull(message = "轧辊ID不能为空") @RequestParam Long rollId) {
|
||||
return R.ok(iMesRollGrindService.listByRoll(rollId));
|
||||
public R<List<MesRollGrindVo>> list(
|
||||
@RequestParam(required = false) Long rollId,
|
||||
@RequestParam(required = false) Long lineId,
|
||||
@RequestParam(required = false) String beginTime,
|
||||
@RequestParam(required = false) String endTime) {
|
||||
if (rollId != null) {
|
||||
return R.ok(iMesRollGrindService.listByRoll(rollId));
|
||||
}
|
||||
return R.ok(iMesRollGrindService.listByQuery(null, lineId, beginTime, endTime));
|
||||
}
|
||||
|
||||
/** 按年份查询月度汇总 */
|
||||
|
||||
@@ -16,6 +16,12 @@ public interface MesRollGrindMapper extends BaseMapperPlus<MesRollGrindMapper, M
|
||||
/** 查询某轧辊全部磨削记录(按时间升序) */
|
||||
List<MesRollGrindVo> selectByRollId(@Param("rollId") Long rollId);
|
||||
|
||||
/** 通用查询(所有参数可选) */
|
||||
List<MesRollGrindVo> selectList(@Param("rollId") Long rollId,
|
||||
@Param("lineId") Long lineId,
|
||||
@Param("beginTime") String beginTime,
|
||||
@Param("endTime") String endTime);
|
||||
|
||||
/** 按年份统计每月磨削量 { month, grindCount, totalGrindAmount } */
|
||||
List<Map<String, Object>> selectMonthlyStats(@Param("rollId") Long rollId, @Param("year") int year);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ public interface IMesRollGrindService {
|
||||
/** 查询某轧辊全部磨削记录 */
|
||||
List<MesRollGrindVo> listByRoll(Long rollId);
|
||||
|
||||
/** 通用查询(所有参数可选) */
|
||||
List<MesRollGrindVo> listByQuery(Long rollId, Long lineId, String beginTime, String endTime);
|
||||
|
||||
/** 新增磨削记录,同步更新轧辊当前直径和磨削次数 */
|
||||
Long addGrind(MesRollGrindBo bo);
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ public class MesRollGrindServiceImpl implements IMesRollGrindService {
|
||||
return baseMapper.selectByRollId(rollId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MesRollGrindVo> listByQuery(Long rollId, Long lineId, String beginTime, String endTime) {
|
||||
return baseMapper.selectList(rollId, lineId, beginTime, endTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long addGrind(MesRollGrindBo bo) {
|
||||
|
||||
@@ -5,12 +5,29 @@
|
||||
<select id="selectByRollId" resultType="com.klp.mes.roll.domain.vo.MesRollGrindVo">
|
||||
SELECT grind_id, roll_id, roll_no, grind_time, team,
|
||||
dia_before, dia_after, grind_amount, roll_shape,
|
||||
flaw_result, hardness, operator, remark, create_time
|
||||
flaw_result,
|
||||
CASE WHEN hardness = '未倒角' THEN 0 ELSE CAST(hardness AS DECIMAL(10,1)) END AS hardness,
|
||||
operator, remark, create_time
|
||||
FROM mes_roll_grind
|
||||
WHERE del_flag = 0 AND roll_id = #{rollId}
|
||||
ORDER BY grind_time ASC, grind_id ASC
|
||||
</select>
|
||||
|
||||
<select id="selectList" resultType="com.klp.mes.roll.domain.vo.MesRollGrindVo">
|
||||
SELECT grind_id, roll_id, roll_no, grind_time, team,
|
||||
dia_before, dia_after, grind_amount, roll_shape,
|
||||
flaw_result,
|
||||
CASE WHEN hardness = '未倒角' THEN 0 ELSE CAST(hardness AS DECIMAL(10,1)) END AS hardness,
|
||||
operator, remark, create_time
|
||||
FROM mes_roll_grind
|
||||
WHERE del_flag = 0
|
||||
<if test="rollId != null">AND roll_id = #{rollId}</if>
|
||||
<if test="lineId != null">AND line_id = #{lineId}</if>
|
||||
<if test="beginTime != null and beginTime != ''">AND grind_time >= #{beginTime}</if>
|
||||
<if test="endTime != null and endTime != ''">AND grind_time <= #{endTime}</if>
|
||||
ORDER BY grind_time ASC, grind_id ASC
|
||||
</select>
|
||||
|
||||
<select id="selectMonthlyStats" resultType="map">
|
||||
SELECT
|
||||
DATE_FORMAT(grind_time, '%Y-%m') AS month,
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询轧辊磨削记录列表
|
||||
// 查询轧辊磨削记录列表(传 rollId 查单个轧辊,不传则查全部,支持时间范围)
|
||||
export function listRollGrind(rollId) {
|
||||
return request({ url: '/mes/rollGrind/list', method: 'get', params: { rollId } })
|
||||
}
|
||||
|
||||
// 通用磨削记录查询(所有参数可选)
|
||||
export function listRollGrindAll(params) {
|
||||
return request({ url: '/mes/rollGrind/list', method: 'get', params })
|
||||
}
|
||||
|
||||
// 查询月度汇总
|
||||
export function getMonthlyStats(rollId, year) {
|
||||
return request({ url: '/mes/rollGrind/monthlyStats', method: 'get', params: { rollId, year } })
|
||||
|
||||
@@ -177,11 +177,14 @@
|
||||
</el-table-column>
|
||||
|
||||
<!-- 操作者(自动填入,仅展示) -->
|
||||
<el-table-column label="操作者" align="center" width="72">
|
||||
<el-table-column label="操作者" align="center" width="100">
|
||||
<template slot-scope="{row}">
|
||||
<span :class="isEditing(row) ? 'auto-operator' : ''">
|
||||
<el-input v-if="isEditing(row)" v-model="editRow.operator"
|
||||
size="mini" placeholder="选填" />
|
||||
<span v-else class="remark-text">{{ row.operator || '' }}</span>
|
||||
<!-- <span :class="isEditing(row) ? 'auto-operator' : ''">
|
||||
{{ isEditing(row) ? currentUserName : (row.operator || '—') }}
|
||||
</span>
|
||||
</span> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
451
klp-ui/src/views/mes/roll/report/index.vue
Normal file
451
klp-ui/src/views/mes/roll/report/index.vue
Normal file
@@ -0,0 +1,451 @@
|
||||
<template>
|
||||
<div class="app-container roll-report" v-loading="loading">
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-panel mb16">
|
||||
<el-form :inline="true" size="small">
|
||||
<el-form-item label="磨削时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
:default-time="['00:00:00', '23:59:59']"
|
||||
style="width:260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="resetFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 统计汇总 -->
|
||||
<el-row :gutter="16" class="mb16">
|
||||
<el-col :span="6">
|
||||
<div class="summary-box">
|
||||
<div class="summary-label">磨削总次数</div>
|
||||
<div class="summary-value">{{ summary.grindCount }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-box">
|
||||
<div class="summary-label">磨削总量(mm)</div>
|
||||
<div class="summary-value accent">{{ summary.totalGrindAmount }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-box">
|
||||
<div class="summary-label">涉及轧辊数</div>
|
||||
<div class="summary-value">{{ summary.rollCount }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="summary-box">
|
||||
<div class="summary-label">磨辊人数</div>
|
||||
<div class="summary-value">{{ summary.operatorCount }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<el-row :gutter="16" class="mb16">
|
||||
<el-col :span="14">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title"><i class="el-icon-data-line" /> 每日磨削趋势</span>
|
||||
</div>
|
||||
<div ref="trendChartRef" class="chart-container" style="height:340px"></div>
|
||||
<div v-if="!dailyTrend.length" class="chart-empty">暂无数据</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title"><i class="el-icon-pie-chart" /> 辊型磨削分布</span>
|
||||
</div>
|
||||
<div ref="pieChartRef" class="chart-container" style="height:340px"></div>
|
||||
<div v-if="!rollTypeDist.length" class="chart-empty">暂无数据</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 磨辊人统计表 -->
|
||||
<div class="table-card">
|
||||
<div class="card-header">
|
||||
<span class="card-title"><i class="el-icon-user" /> 磨辊人统计</span>
|
||||
<span class="card-subtitle" v-if="operatorStats.length">共 {{ operatorStats.length }} 人</span>
|
||||
</div>
|
||||
<el-table :data="operatorStats" size="small" border stripe style="width:100%"
|
||||
v-if="operatorStats.length" :default-sort="{ prop: 'totalGrindAmount', order: 'descending' }">
|
||||
<el-table-column label="序号" type="index" width="50" align="center" />
|
||||
<el-table-column label="磨辊人" prop="operator" min-width="120" sortable />
|
||||
<el-table-column label="磨削次数" prop="grindCount" width="110" align="center" sortable />
|
||||
<el-table-column label="磨削总量(mm)" prop="totalGrindAmount" width="130" align="right" sortable>
|
||||
<template slot-scope="{row}">{{ row.totalGrindAmount.toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平均磨削量(mm)" prop="avgGrindAmount" width="140" align="right" sortable>
|
||||
<template slot-scope="{row}">{{ row.avgGrindAmount.toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无磨削记录" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import { getRollStats, listRollInfo } from '@/api/mes/roll/rollInfo'
|
||||
import { listRollGrindAll } from '@/api/mes/roll/rollGrind'
|
||||
import rollLineMixin from '../rollLineMixin'
|
||||
|
||||
export default {
|
||||
name: 'RollReport',
|
||||
mixins: [rollLineMixin],
|
||||
data() {
|
||||
const now = new Date()
|
||||
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
const fmt = d => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||
return {
|
||||
loading: false,
|
||||
dateRange: [fmt(sevenDaysAgo), fmt(now)],
|
||||
stats: {},
|
||||
summary: { grindCount: 0, totalGrindAmount: 0, rollCount: 0, operatorCount: 0 },
|
||||
dailyTrend: [],
|
||||
rollTypeDist: [],
|
||||
operatorStats: [],
|
||||
trendChart: null,
|
||||
pieChart: null,
|
||||
_beginTime: fmt(sevenDaysAgo),
|
||||
_endTime: fmt(now)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
lineId() {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initCharts()
|
||||
})
|
||||
window.addEventListener('resize', this.resizeCharts)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.resizeCharts)
|
||||
this.trendChart && this.trendChart.dispose()
|
||||
this.pieChart && this.pieChart.dispose()
|
||||
},
|
||||
methods: {
|
||||
onLineResolved() {
|
||||
this.loadData()
|
||||
},
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const [b, e] = this.dateRange
|
||||
this._beginTime = b || ''
|
||||
this._endTime = e || ''
|
||||
|
||||
// 1. 状态统计
|
||||
if (this.lineId) {
|
||||
const statsRes = await getRollStats(this.lineId)
|
||||
this.stats = statsRes.data || {}
|
||||
}
|
||||
|
||||
// 2. 获取全部轧辊(用于辊型映射)
|
||||
const rollRes = await listRollInfo({ pageNum: 1, pageSize: 99999, lineId: this.lineId || undefined })
|
||||
const rolls = rollRes.rows || []
|
||||
const rollMap = {}
|
||||
rolls.forEach(r => { rollMap[r.rollId] = r })
|
||||
|
||||
// 3. 一次性获取全部磨削记录(后端支持可选参数)
|
||||
const params = { lineId: this.lineId || undefined }
|
||||
if (this._beginTime) params.beginTime = this._beginTime
|
||||
if (this._endTime) params.endTime = this._endTime + ' 23:59:59'
|
||||
const grindRes = await listRollGrindAll(params)
|
||||
const allRecords = grindRes.data || []
|
||||
|
||||
// 4. 汇总计算
|
||||
this.computeSummary(allRecords, rollMap)
|
||||
this.computeDailyTrend(allRecords)
|
||||
this.computeRollTypeDist(allRecords, rollMap)
|
||||
this.computeOperatorStats(allRecords)
|
||||
this.updateCharts()
|
||||
} catch (e) {
|
||||
console.error('加载报表数据失败', e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
computeSummary(records, rollMap) {
|
||||
const rollSet = new Set()
|
||||
const operatorSet = new Set()
|
||||
let totalAmount = 0
|
||||
records.forEach(r => {
|
||||
if (r.rollId) rollSet.add(r.rollId)
|
||||
if (r.operator) operatorSet.add(r.operator)
|
||||
totalAmount += Number(r.grindAmount || 0)
|
||||
})
|
||||
this.summary = {
|
||||
grindCount: records.length,
|
||||
totalGrindAmount: totalAmount.toFixed(2),
|
||||
rollCount: rollSet.size,
|
||||
operatorCount: operatorSet.size
|
||||
}
|
||||
},
|
||||
computeDailyTrend(records) {
|
||||
const dateMap = {}
|
||||
records.forEach(r => {
|
||||
if (!r.grindTime) return
|
||||
const d = r.grindTime.substring(0, 10)
|
||||
if (!dateMap[d]) dateMap[d] = { grindDate: d, grindCount: 0, totalGrindAmount: 0 }
|
||||
dateMap[d].grindCount++
|
||||
dateMap[d].totalGrindAmount += Number(r.grindAmount || 0)
|
||||
})
|
||||
this.dailyTrend = Object.values(dateMap).sort((a, b) => a.grindDate.localeCompare(b.grindDate))
|
||||
},
|
||||
computeRollTypeDist(records, rollMap) {
|
||||
const typeMap = {}
|
||||
records.forEach(r => {
|
||||
const roll = rollMap[r.rollId]
|
||||
const type = roll ? (roll.rollType === 'WR' ? '工作辊' : roll.rollType === 'BR' ? '支撑辊' : roll.rollType || '未知') : '未知'
|
||||
if (!typeMap[type]) typeMap[type] = { name: type, value: 0, grindCount: 0 }
|
||||
typeMap[type].value += Number(r.grindAmount || 0)
|
||||
typeMap[type].grindCount++
|
||||
})
|
||||
this.rollTypeDist = Object.values(typeMap)
|
||||
},
|
||||
computeOperatorStats(records) {
|
||||
const opMap = {}
|
||||
records.forEach(r => {
|
||||
const name = r.operator || '未知'
|
||||
if (!opMap[name]) opMap[name] = { operator: name, grindCount: 0, totalGrindAmount: 0 }
|
||||
opMap[name].grindCount++
|
||||
opMap[name].totalGrindAmount += Number(r.grindAmount || 0)
|
||||
})
|
||||
this.operatorStats = Object.values(opMap).map(item => ({
|
||||
...item,
|
||||
totalGrindAmount: Number(item.totalGrindAmount.toFixed(2)),
|
||||
avgGrindAmount: item.grindCount > 0 ? Number((item.totalGrindAmount / item.grindCount).toFixed(2)) : 0
|
||||
})).sort((a, b) => b.totalGrindAmount - a.totalGrindAmount)
|
||||
},
|
||||
initCharts() {
|
||||
this.initTrendChart()
|
||||
this.initPieChart()
|
||||
},
|
||||
initTrendChart() {
|
||||
const dom = this.$refs.trendChartRef
|
||||
if (!dom) return
|
||||
if (this.trendChart) this.trendChart.dispose()
|
||||
this.trendChart = echarts.init(dom)
|
||||
this.trendChart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'category', data: [], axisLabel: { rotate: 45, fontSize: 11 } },
|
||||
yAxis: [
|
||||
{ type: 'value', name: '磨削次数', minInterval: 1 },
|
||||
{ type: 'value', name: '磨削量(mm)', position: 'right' }
|
||||
],
|
||||
series: [
|
||||
{ name: '磨削次数', type: 'bar', data: [], barMaxWidth: 24 },
|
||||
{ name: '磨削量(mm)', type: 'line', yAxisIndex: 1, smooth: true, data: [] }
|
||||
],
|
||||
color: ['#409eff', '#0a7c42']
|
||||
})
|
||||
},
|
||||
initPieChart() {
|
||||
const dom = this.$refs.pieChartRef
|
||||
if (!dom) return
|
||||
if (this.pieChart) this.pieChart.dispose()
|
||||
this.pieChart = echarts.init(dom)
|
||||
this.pieChart.setOption({
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c}mm ({d}%)' },
|
||||
series: [{
|
||||
type: 'pie', radius: ['30%', '60%'], center: ['50%', '50%'],
|
||||
label: { formatter: '{b}\n{d}%' },
|
||||
data: [],
|
||||
emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } }
|
||||
}],
|
||||
color: ['#409eff', '#e6a23c', '#909399']
|
||||
})
|
||||
},
|
||||
updateCharts() {
|
||||
this.updateTrendChart()
|
||||
this.updatePieChart()
|
||||
},
|
||||
updateTrendChart() {
|
||||
if (!this.trendChart) this.initTrendChart()
|
||||
const dates = this.dailyTrend.map(d => d.grindDate)
|
||||
const counts = this.dailyTrend.map(d => d.grindCount)
|
||||
const amounts = this.dailyTrend.map(d => Number(d.totalGrindAmount.toFixed(2)))
|
||||
this.trendChart.setOption({
|
||||
xAxis: { data: dates },
|
||||
series: [
|
||||
{ data: counts },
|
||||
{ data: amounts }
|
||||
]
|
||||
})
|
||||
this.trendChart.resize()
|
||||
},
|
||||
updatePieChart() {
|
||||
if (!this.pieChart) this.initPieChart()
|
||||
const data = this.rollTypeDist.map(d => ({ name: d.name, value: Number(d.value.toFixed(2)) }))
|
||||
this.pieChart.setOption({ series: [{ data }] })
|
||||
this.pieChart.resize()
|
||||
},
|
||||
resizeCharts() {
|
||||
this.trendChart && this.trendChart.resize()
|
||||
this.pieChart && this.pieChart.resize()
|
||||
},
|
||||
handleQuery() {
|
||||
this.loadData()
|
||||
},
|
||||
resetFilter() {
|
||||
const now = new Date()
|
||||
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
const fmt = d => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||
this.dateRange = [fmt(sevenDaysAgo), fmt(now)]
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.roll-report {
|
||||
background: #f4f5f7;
|
||||
min-height: 100%;
|
||||
}
|
||||
.mb16 { margin-bottom: 16px; }
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-panel {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdee0;
|
||||
border-radius: 4px;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
/* 统计看板 */
|
||||
.stat-panel {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdee0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 18px 20px;
|
||||
gap: 14px;
|
||||
position: relative;
|
||||
}
|
||||
.stat-divider {
|
||||
position: absolute;
|
||||
right: 0; top: 16px; bottom: 16px;
|
||||
width: 1px;
|
||||
background: #e4e6ea;
|
||||
}
|
||||
.stat-icon-wrap {
|
||||
width: 42px; height: 42px;
|
||||
border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.total-icon { background: #edf0f3; color: #3d4b5c; }
|
||||
.online-icon { background: #e8f5ef; color: #0a7c42; }
|
||||
.standby-icon { background: #fdf3e3; color: #8b5c00; }
|
||||
.offline-icon { background: #f0f1f2; color: #5f6368; }
|
||||
.scrapped-icon { background: #fdecea; color: #a61c00; }
|
||||
.stat-body { display: flex; flex-direction: column; }
|
||||
.stat-num {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #1f2329;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.online-num { color: #0a7c42; }
|
||||
.standby-num { color: #8b5c00; }
|
||||
.offline-num { color: #5f6368; }
|
||||
.scrapped-num { color: #a61c00; }
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #8f9099;
|
||||
margin-top: 4px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* 汇总框 */
|
||||
.summary-box {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdee0;
|
||||
border-radius: 4px;
|
||||
padding: 18px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
color: #8f9099;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.summary-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1f2329;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.summary-value.accent { color: #409eff; }
|
||||
|
||||
/* 图表卡片 */
|
||||
.chart-card {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdee0;
|
||||
border-radius: 4px;
|
||||
padding: 16px 20px;
|
||||
position: relative;
|
||||
}
|
||||
.chart-header, .card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.chart-title, .card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #3d4b5c;
|
||||
}
|
||||
.card-subtitle {
|
||||
font-size: 12px;
|
||||
color: #9aa0a6;
|
||||
margin-left: auto;
|
||||
}
|
||||
.chart-container { width: 100%; }
|
||||
.chart-empty {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #c0c4cc;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 表格卡片 */
|
||||
.table-card {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdee0;
|
||||
border-radius: 4px;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user