| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773 |
- <template>
- <div class="equipment-abnormal">
- <!-- 左侧面板 -->
- <div class="left-panel">
- <!-- 搜索 -->
- <div class="panel-card search-card">
- <div class="panel-title"><el-icon><Search /></el-icon> 快速查询</div>
- <el-input v-model="searchKey" placeholder="设备名称/编号/地址" clearable prefix-icon="Search" size="default" />
- <el-select v-model="levelFilter" placeholder="异常等级" clearable style="width:100%;margin-top:10px">
- <el-option label="严重" value="严重" />
- <el-option label="一般" value="一般" />
- <el-option label="轻微" value="轻微" />
- </el-select>
- <el-select v-model="typeFilter" placeholder="设备类型" clearable style="width:100%;margin-top:10px">
- <el-option label="阀门" value="阀门" />
- <el-option label="调压器" value="调压器" />
- <el-option label="流量计" value="流量计" />
- <el-option label="窨井盖" value="窨井盖" />
- <el-option label="终端节点" value="终端节点" />
- <el-option label="检查井" value="检查井" />
- </el-select>
- <el-select v-model="statusFilter" placeholder="处理状态" clearable style="width:100%;margin-top:10px">
- <el-option label="待处理" value="待处理" />
- <el-option label="处理中" value="处理中" />
- <el-option label="已修复" value="已修复" />
- </el-select>
- <div style="display:flex;gap:8px;margin-top:10px">
- <el-button type="primary" size="small" @click="handleQuery" style="flex:1">查询</el-button>
- <el-button size="small" @click="handleReset" style="flex:1">重置</el-button>
- </div>
- </div>
- <!-- 异常等级分布 -->
- <div class="panel-card">
- <div class="panel-title"><el-icon><PieChart /></el-icon> 异常等级分布</div>
- <div class="level-list">
- <div class="level-item critical">
- <span class="level-dot"></span>
- <span class="level-label">严重</span>
- <span class="level-count">{{ stats.critical }}</span>
- </div>
- <div class="level-item warning">
- <span class="level-dot"></span>
- <span class="level-label">一般</span>
- <span class="level-count">{{ stats.normal }}</span>
- </div>
- <div class="level-item minor">
- <span class="level-dot"></span>
- <span class="level-label">轻微</span>
- <span class="level-count">{{ stats.minor }}</span>
- </div>
- </div>
- </div>
- <!-- 设备类型统计 -->
- <div class="panel-card">
- <div class="panel-title"><el-icon><Histogram /></el-icon> 设备类型统计</div>
- <div class="type-list">
- <div class="type-item" v-for="item in deviceTypeStats" :key="item.name">
- <span class="type-name">{{ item.name }}</span>
- <span class="type-bar-bg"><span class="type-bar" :style="{width: (stats.total > 0 ? item.count/stats.total*100 : 0)+'%', background: item.color}"></span></span>
- <span class="type-count">{{ item.count }}</span>
- </div>
- </div>
- </div>
- </div>
- <!-- 右侧内容区 -->
- <div class="right-panel">
- <!-- 统计卡片 -->
- <div class="stats-row">
- <div class="stat-card stat-total">
- <div class="stat-icon"><el-icon size="22"><WarningFilled /></el-icon></div>
- <div class="stat-info">
- <div class="stat-label">异常设备总数</div>
- <div class="stat-value">{{ stats.total }} <small>台</small></div>
- </div>
- </div>
- <div class="stat-card stat-today">
- <div class="stat-icon"><el-icon size="22"><Clock /></el-icon></div>
- <div class="stat-info">
- <div class="stat-label">今日新增</div>
- <div class="stat-value">{{ stats.today }} <small>台</small></div>
- </div>
- </div>
- <div class="stat-card stat-rate">
- <div class="stat-icon"><el-icon size="22"><TrendCharts /></el-icon></div>
- <div class="stat-info">
- <div class="stat-label">异常占比</div>
- <div class="stat-value">{{ stats.rate }} <small>%</small></div>
- </div>
- </div>
- <div class="stat-card stat-processed">
- <div class="stat-icon"><el-icon size="22"><CircleCheckFilled /></el-icon></div>
- <div class="stat-info">
- <div class="stat-label">已修复</div>
- <div class="stat-value">{{ stats.processed }} <small>台</small></div>
- </div>
- </div>
- <div class="stat-card stat-pending">
- <div class="stat-icon"><el-icon size="22"><Warning /></el-icon></div>
- <div class="stat-info">
- <div class="stat-label">待处理</div>
- <div class="stat-value">{{ stats.pending }} <small>台</small></div>
- </div>
- </div>
- <div class="stat-card stat-process-rate">
- <div class="stat-icon"><el-icon size="22"><DataAnalysis /></el-icon></div>
- <div class="stat-info">
- <div class="stat-label">修复率</div>
- <div class="stat-value">{{ stats.processRate }} <small>%</small></div>
- </div>
- </div>
- </div>
- <!-- 图表区域 - 上方: 趋势图(全宽) -->
- <div class="chart-card chart-trend">
- <div class="chart-header">
- <span class="chart-title">设备异常趋势图</span>
- <span class="chart-subtitle">近15天运行数据</span>
- <div class="chart-legend-custom">
- <span class="legend-tag tag-total">异常总数</span>
- <span class="legend-tag tag-new">新增异常</span>
- <span class="legend-tag tag-fixed">已修复</span>
- </div>
- </div>
- <div id="trendChart" class="chart-body chart-body-large" style="height:340px"></div>
- </div>
- <!-- 图表区域 - 下方: 两个图表并排 -->
- <div class="charts-row">
- <div class="chart-card chart-half">
- <div class="chart-header">
- <span class="chart-title">异常类型分布</span>
- </div>
- <div id="typeChart" class="chart-body" style="height:290px"></div>
- </div>
- <div class="chart-card chart-half">
- <div class="chart-header">
- <span class="chart-title">设备异常等级与处理状态</span>
- </div>
- <div id="levelChart" class="chart-body" style="height:290px"></div>
- </div>
- </div>
- <!-- 异常设备列表 -->
- <div class="table-card">
- <div class="table-header">
- <span class="table-title">异常设备列表</span>
- <span class="table-count">共 {{ tableTotal }} 条记录</span>
- </div>
- <el-table :data="paginatedData" border stripe v-loading="loading" max-height="460" size="small"
- @sort-change="handleSortChange" :default-sort="{ prop: 'alarmTime', order: 'descending' }">
- <el-table-column prop="id" label="序号" width="56" align="center" sortable="custom" />
- <el-table-column prop="deviceCode" label="设备编号" min-width="140" show-overflow-tooltip />
- <el-table-column prop="deviceName" label="设备名称" min-width="150" show-overflow-tooltip />
- <el-table-column prop="deviceType" label="设备类型" width="100" />
- <el-table-column prop="level" label="异常等级" width="100" align="center" sortable="custom">
- <template #default="{ row }">
- <el-tag :type="levelTagType(row.level)" size="small">{{ row.level }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="alarmDesc" label="异常描述" min-width="220" show-overflow-tooltip />
- <el-table-column prop="address" label="安装地址" min-width="200" show-overflow-tooltip />
- <el-table-column prop="alarmTime" label="异常时间" width="168" sortable="custom" />
- <el-table-column prop="processStatus" label="处理状态" width="100" align="center" sortable="custom">
- <template #default="{ row }">
- <el-tag :type="row.processStatus === '已修复' ? 'success' : row.processStatus === '处理中' ? 'warning' : 'danger'" size="small">
- {{ row.processStatus }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="150" fixed="right" align="center">
- <template #default="{ row }">
- <el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
- <el-button v-if="row.processStatus === '待处理'" type="warning" link size="small" @click="handleProcess(row)">处理</el-button>
- <el-button v-if="row.processStatus === '处理中'" type="success" link size="small" @click="handleComplete(row)">完成</el-button>
- </template>
- </el-table-column>
- </el-table>
- <div class="table-footer">
- <el-pagination
- v-model:current-page="currentPage"
- v-model:page-size="pageSize"
- :total="tableTotal"
- layout="total, prev, pager, next"
- :page-sizes="[10, 20]"
- small
- background
- />
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
- import * as echarts from 'echarts'
- import { Clock, CircleCheckFilled, DataAnalysis, Histogram, PieChart, Search, TrendCharts, Warning, WarningFilled } from '@element-plus/icons-vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { getEquipmentAbnormalList, getEquipmentAbnormalStats } from '@/api/pipeNetwork/gasMonitor'
- const loading = ref(false)
- const searchKey = ref('')
- const levelFilter = ref('')
- const typeFilter = ref('')
- const statusFilter = ref('')
- const currentPage = ref(1)
- const pageSize = ref(10)
- const sortProp = ref('alarmTime')
- const sortOrder = ref('descending')
- let trendChart = null
- let typeChart = null
- let levelChart = null
- // ==================== 统计数据(从API加载) ====================
- const stats = ref({
- total: 0, today: 0, rate: 0, processed: 0, pending: 0,
- processRate: 0, critical: 0, normal: 0, minor: 0
- })
- const deviceTypeStats = ref([])
- async function loadStats() {
- try {
- const res = await getEquipmentAbnormalStats()
- if (res.data) {
- const d = res.data
- stats.value = {
- total: d.total || 0, today: d.today || 0, rate: d.rate || 0,
- processed: d.processed || 0, pending: d.pending || 0,
- processRate: d.processRate || 0, critical: d.critical || 0,
- normal: d.normal || 0, minor: d.minor || 0
- }
- // 设备类型统计
- const colors = ['#e63946', '#f77f00', '#2d6a4f', '#7f4f24', '#6c757d', '#b08968']
- const typeMap = d.deviceTypeStats || {}
- let idx = 0
- deviceTypeStats.value = Object.entries(typeMap).map(([name, count]) => ({
- name, count, color: colors[idx++ % colors.length]
- }))
- // 图表数据
- chartData.value = {
- trend: d.trend || { dates: [], totalData: [], newData: [], fixedData: [] },
- typeDistribution: d.typeDistribution || { categories: [], values: [] },
- levelStatus: d.levelStatus || { levels: [], pending: [], processing: [], fixed: [] }
- }
- }
- } catch (e) { console.error(e) }
- }
- // ==================== 图表数据(从API动态加载) ====================
- const chartData = ref({
- trend: { dates: [], totalData: [], newData: [], fixedData: [] },
- typeDistribution: { categories: [], values: [] },
- levelStatus: { levels: [], pending: [], processing: [], fixed: [] }
- })
- // ==================== 表格数据(从API加载) ====================
- const tableData = ref([])
- const tableTotal = ref(0)
- async function loadList() {
- loading.value = true
- try {
- const res = await getEquipmentAbnormalList({
- pageNum: currentPage.value,
- pageSize: pageSize.value,
- searchKey: searchKey.value || undefined,
- level: levelFilter.value || undefined,
- type: typeFilter.value || undefined,
- status: statusFilter.value || undefined
- })
- if (res.data) {
- tableData.value = (res.data.rows || []).map((r, i) => ({
- ...r,
- id: String((currentPage.value - 1) * pageSize.value + i + 1),
- level: r.levelText || r.faultLevel,
- processStatus: r.processStatus || '待处理',
- alarmTime: r.alarmTime || '',
- alarmDesc: r.alarmDesc || '',
- deviceCode: r.deviceCode || '',
- deviceName: r.deviceName || '',
- deviceType: r.deviceType || '',
- address: r.address || ''
- }))
- tableTotal.value = res.data.total || 0
- }
- } catch (e) { console.error(e) } finally { loading.value = false }
- }
- const paginatedData = computed(() => {
- const list = [...tableData.value]
- if (sortProp.value) {
- list.sort((a, b) => {
- const va = a[sortProp.value] || ''
- const vb = b[sortProp.value] || ''
- const cmp = typeof va === 'string' ? va.localeCompare(vb) : va - vb
- return sortOrder.value === 'descending' ? -cmp : cmp
- })
- }
- return list
- })
- // 排序切换
- function handleSortChange({ prop, order }) {
- sortProp.value = prop
- sortOrder.value = order || 'descending'
- }
- // 分页切换时重载
- watch(currentPage, () => loadList())
- watch(pageSize, () => { currentPage.value = 1; loadList() })
- // ==================== 工具函数 ====================
- function levelTagType(level) {
- if (level === '严重') return 'danger'
- if (level === '一般') return 'warning'
- return 'info'
- }
- function handleQuery() {
- currentPage.value = 1
- loadList()
- }
- function handleReset() {
- searchKey.value = ''
- levelFilter.value = ''
- typeFilter.value = ''
- statusFilter.value = ''
- currentPage.value = 1
- loadList()
- }
- function handleDetail(row) {
- ElMessageBox.alert(
- `<div style="line-height:2.2;font-size:14px;">
- <p><b>设备编号:</b>${row.deviceCode}</p>
- <p><b>设备名称:</b>${row.deviceName}</p>
- <p><b>设备类型:</b>${row.deviceType}</p>
- <p><b>异常等级:</b><span style="color:${row.level==='严重'?'#e63946':row.level==='一般'?'#f77f00':'#909399'}">${row.level}</span></p>
- <p><b>异常描述:</b>${row.alarmDesc}</p>
- <p><b>安装地址:</b>${row.address}</p>
- <p><b>异常时间:</b>${row.alarmTime}</p>
- <p><b>处理状态:</b>${row.processStatus}</p>
- </div>`,
- '设备异常详情',
- { dangerouslyUseHTMLString: true, confirmButtonText: '关闭', customClass: 'detail-dialog' }
- )
- }
- function handleProcess(row) {
- ElMessageBox.confirm(`确认开始处理设备「${row.deviceName}」的异常?系统将自动派发工单至运维班组。`, '处理确认', {
- confirmButtonText: '确认处理',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- row.processStatus = '处理中'
- ElMessage.success(`已派发工单,设备「${row.deviceName}」处理中`)
- }).catch(() => {})
- }
- function handleComplete(row) {
- ElMessageBox.confirm(`确认设备「${row.deviceName}」的异常已修复完成?`, '修复确认', {
- confirmButtonText: '确认修复',
- cancelButtonText: '取消',
- type: 'success'
- }).then(() => {
- row.processStatus = '已修复'
- ElMessage.success(`设备「${row.deviceName}」异常已修复`)
- }).catch(() => {})
- }
- // ==================== ECharts ====================
- function initTrendChart() {
- const dom = document.getElementById('trendChart')
- if (!dom) return
- if (trendChart) trendChart.dispose()
- trendChart = echarts.init(dom)
- const t = chartData.value.trend
- trendChart.setOption({
- tooltip: {
- trigger: 'axis',
- backgroundColor: 'rgba(255,255,255,0.96)',
- borderColor: '#e0e4ea',
- textStyle: { color: '#2c3e50', fontSize: 13 }
- },
- legend: {
- data: ['异常总数', '新增异常', '已修复'],
- top: 0,
- textStyle: { color: '#606a78', fontSize: 12 }
- },
- grid: { left: 52, right: 44, top: 44, bottom: 32 },
- xAxis: {
- type: 'category',
- data: t.dates || [],
- boundaryGap: false,
- axisLabel: { color: '#9098a6', fontSize: 12 },
- axisLine: { lineStyle: { color: '#d0d5dd' } }
- },
- yAxis: {
- type: 'value',
- name: '数量(台)',
- nameTextStyle: { color: '#9098a6', fontSize: 12 },
- axisLabel: { color: '#9098a6', fontSize: 12 },
- splitLine: { lineStyle: { color: '#f0f2f6', type: 'dashed' } }
- },
- series: [
- {
- name: '异常总数', type: 'line',
- data: t.totalData || [], smooth: true, symbol: 'circle', symbolSize: 6,
- lineStyle: { width: 3, color: '#e63946' },
- itemStyle: { color: '#e63946', borderColor: '#fff', borderWidth: 2 },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: 'rgba(230,57,70,0.15)' },
- { offset: 1, color: 'rgba(230,57,70,0.0)' }
- ])
- }
- },
- {
- name: '新增异常', type: 'line',
- data: t.newData || [], smooth: true, symbol: 'emptyCircle', symbolSize: 6,
- lineStyle: { width: 2.5, color: '#f77f00' },
- itemStyle: { color: '#f77f00', borderColor: '#f77f00', borderWidth: 2 }
- },
- {
- name: '已修复', type: 'line',
- data: t.fixedData || [], smooth: true, symbol: 'diamond', symbolSize: 7,
- lineStyle: { width: 2.5, color: '#2a9d8f', type: 'dashed' },
- itemStyle: { color: '#2a9d8f', borderColor: '#fff', borderWidth: 2 }
- }
- ]
- })
- }
- function initTypeChart() {
- const dom = document.getElementById('typeChart')
- if (!dom) return
- if (typeChart) typeChart.dispose()
- typeChart = echarts.init(dom)
- const d = chartData.value.typeDistribution
- const colors = ['#457b9d', '#e76f51', '#e63946']
- typeChart.setOption({
- tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
- grid: { left: 46, right: 20, top: 16, bottom: 32 },
- xAxis: {
- type: 'category',
- data: d.categories || [],
- axisLabel: { color: '#9098a6', fontSize: 11, rotate: 15 },
- axisLine: { lineStyle: { color: '#d0d5dd' } }
- },
- yAxis: {
- type: 'value', name: '数量(台)',
- axisLabel: { color: '#9098a6', fontSize: 11 },
- splitLine: { lineStyle: { color: '#f0f2f6', type: 'dashed' } }
- },
- series: [{
- type: 'bar',
- data: (d.values || []).map((v, i) => ({ value: v, itemStyle: { color: colors[i] || '#6c757d', borderRadius: [4, 4, 0, 0] } })),
- barWidth: 28,
- label: { show: true, position: 'top', color: '#4e5969', fontSize: 12, fontWeight: 600 }
- }]
- })
- }
- function initLevelChart() {
- const dom = document.getElementById('levelChart')
- if (!dom) return
- if (levelChart) levelChart.dispose()
- levelChart = echarts.init(dom)
- const d = chartData.value.levelStatus
- levelChart.setOption({
- tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
- legend: {
- data: ['待处理', '处理中', '已修复'],
- top: 0,
- textStyle: { color: '#606a78', fontSize: 12 }
- },
- grid: { left: 46, right: 20, top: 44, bottom: 28 },
- xAxis: {
- type: 'category',
- data: d.levels || [],
- axisLabel: { color: '#9098a6', fontSize: 12 },
- axisLine: { lineStyle: { color: '#d0d5dd' } }
- },
- yAxis: {
- type: 'value', name: '数量(台)',
- axisLabel: { color: '#9098a6', fontSize: 11 },
- splitLine: { lineStyle: { color: '#f0f2f6', type: 'dashed' } },
- nameTextStyle: { color: '#9098a6', fontSize: 11 }
- },
- series: [
- {
- name: '待处理', type: 'bar',
- data: d.pending || [], barWidth: 30, barGap: '20%',
- itemStyle: { color: '#e63946', borderRadius: [4, 4, 0, 0] }
- },
- {
- name: '处理中', type: 'bar',
- data: d.processing || [], barWidth: 30,
- itemStyle: { color: '#f77f00', borderRadius: [4, 4, 0, 0] }
- },
- {
- name: '已修复', type: 'bar',
- data: d.fixed || [], barWidth: 30,
- itemStyle: { color: '#2a9d8f', borderRadius: [4, 4, 0, 0] }
- }
- ]
- })
- }
- function initCharts() {
- try { initTrendChart() } catch (e) { console.warn('趋势图初始化失败:', e) }
- try { initTypeChart() } catch (e) { console.warn('类型分布图初始化失败:', e) }
- try { initLevelChart() } catch (e) { console.warn('等级统计图初始化失败:', e) }
- }
- function resizeCharts() {
- trendChart?.resize()
- typeChart?.resize()
- levelChart?.resize()
- }
- onMounted(async () => {
- await nextTick()
- // 先加载数据,再初始化图表
- await Promise.all([loadStats(), loadList()])
- await nextTick()
- initCharts()
- window.addEventListener('resize', resizeCharts)
- })
- onBeforeUnmount(() => {
- window.removeEventListener('resize', resizeCharts)
- trendChart?.dispose()
- typeChart?.dispose()
- levelChart?.dispose()
- })
- </script>
- <style scoped>
- .equipment-abnormal {
- display: flex;
- gap: 16px;
- padding: 16px;
- background: #f0f2f6;
- min-height: calc(100vh - 84px);
- }
- /* ========== 左侧面板 ========== */
- .left-panel {
- width: 280px;
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- gap: 14px;
- }
- .panel-card {
- background: #fff;
- border-radius: 12px;
- padding: 16px;
- box-shadow: 0 2px 12px rgba(15, 35, 95, 0.04);
- }
- .panel-title {
- font-size: 14px;
- font-weight: 700;
- color: #1f2d3d;
- margin-bottom: 14px;
- display: flex;
- align-items: center;
- gap: 8px;
- padding-bottom: 10px;
- border-bottom: 1px solid #f0f2f6;
- }
- .panel-title .el-icon { color: #1890ff; }
- /* 等级列表 */
- .level-list { display: flex; flex-direction: column; gap: 8px; }
- .level-item {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 8px 12px;
- border-radius: 8px;
- background: #f8fafc;
- cursor: pointer;
- transition: all 0.2s;
- }
- .level-item:hover { background: #eef2f8; transform: translateX(4px); }
- .level-dot { width: 10px; height: 10px; border-radius: 50%; }
- .level-item.critical .level-dot { background: #e63946; box-shadow: 0 0 6px rgba(230,57,70,0.4); }
- .level-item.warning .level-dot { background: #f77f00; box-shadow: 0 0 6px rgba(247,127,0,0.4); }
- .level-item.minor .level-dot { background: #457b9d; box-shadow: 0 0 6px rgba(69,123,157,0.4); }
- .level-label { flex: 1; font-size: 13px; color: #4e5969; }
- .level-count { font-size: 18px; font-weight: 700; color: #1f2d3d; }
- /* 设备类型列表 */
- .type-list { display: flex; flex-direction: column; gap: 10px; }
- .type-item { display: flex; align-items: center; gap: 10px; }
- .type-name { width: 56px; font-size: 12px; color: #606a78; text-align: right; flex-shrink: 0; }
- .type-bar-bg { flex: 1; height: 8px; background: #f0f2f6; border-radius: 4px; overflow: hidden; }
- .type-bar { height: 100%; border-radius: 4px; transition: width 0.6s ease; }
- .type-count { width: 24px; font-size: 12px; font-weight: 600; color: #1f2d3d; text-align: right; flex-shrink: 0; }
- /* ========== 右侧内容区 ========== */
- .right-panel {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 16px;
- min-width: 0;
- }
- /* 统计卡片 */
- .stats-row {
- display: grid;
- grid-template-columns: repeat(6, 1fr);
- gap: 12px;
- }
- .stat-card {
- background: #fff;
- border-radius: 12px;
- padding: 14px 16px;
- box-shadow: 0 2px 12px rgba(15, 35, 95, 0.04);
- display: flex;
- align-items: center;
- gap: 14px;
- cursor: pointer;
- transition: all 0.25s;
- }
- .stat-card:hover {
- transform: translateY(-2px);
- box-shadow: 0 6px 20px rgba(15, 35, 95, 0.1);
- }
- .stat-icon {
- width: 44px; height: 44px;
- border-radius: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .stat-total .stat-icon { background: #fef0f0; color: #e63946; }
- .stat-today .stat-icon { background: #fff3e6; color: #f77f00; }
- .stat-rate .stat-icon { background: #fef5e7; color: #e76f51; }
- .stat-processed .stat-icon { background: #e8f8f5; color: #2a9d8f; }
- .stat-pending .stat-icon { background: #eaf2f8; color: #457b9d; }
- .stat-process-rate .stat-icon { background: #e8f5e9; color: #2d6a4f; }
- .stat-label { font-size: 12px; color: #9098a6; margin-bottom: 4px; }
- .stat-value { font-size: 24px; font-weight: 700; color: #1f2d3d; line-height: 1.1; }
- .stat-value small { font-size: 12px; color: #9098a6; font-weight: 400; }
- /* 图表卡片 */
- .chart-card {
- background: #fff;
- border-radius: 12px;
- padding: 16px 20px;
- box-shadow: 0 2px 12px rgba(15, 35, 95, 0.04);
- display: flex;
- flex-direction: column;
- }
- .chart-trend { /* 全宽 */ }
- .charts-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 14px;
- }
- .chart-half { flex: 1; }
- .chart-header {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 6px;
- flex-wrap: wrap;
- }
- .chart-title {
- font-size: 15px;
- font-weight: 700;
- color: #1f2d3d;
- }
- .chart-subtitle {
- font-size: 12px;
- color: #a0a9b6;
- }
- .chart-legend-custom {
- margin-left: auto;
- display: flex;
- gap: 8px;
- }
- .legend-tag {
- font-size: 11px;
- padding: 2px 10px;
- border-radius: 10px;
- color: #fff;
- }
- .tag-total { background: #e63946; }
- .tag-new { background: #f77f00; }
- .tag-fixed { background: #2a9d8f; }
- .chart-body {
- flex: 1;
- min-height: 280px;
- }
- .chart-body-large {
- min-height: 330px;
- }
- /* 表格卡片 */
- .table-card {
- background: #fff;
- border-radius: 12px;
- padding: 16px 20px;
- box-shadow: 0 2px 12px rgba(15, 35, 95, 0.04);
- }
- .table-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 12px;
- }
- .table-title {
- font-size: 15px;
- font-weight: 700;
- color: #1f2d3d;
- }
- .table-count {
- font-size: 12px;
- color: #9098a6;
- }
- .table-footer {
- display: flex;
- justify-content: flex-end;
- margin-top: 12px;
- }
- /* ========== 响应式 ========== */
- @media (max-width: 1600px) {
- .stats-row { grid-template-columns: repeat(3, 1fr); }
- .left-panel { width: 260px; }
- }
- @media (max-width: 1400px) {
- .equipment-abnormal { flex-direction: column; }
- .left-panel { width: 100%; flex-direction: row; flex-wrap: wrap; }
- .left-panel .panel-card { flex: 1; min-width: 240px; }
- .charts-row { grid-template-columns: 1fr; }
- }
- @media (max-width: 768px) {
- .stats-row { grid-template-columns: repeat(2, 1fr); }
- }
- </style>
|