-
-
-
-
-
{{ panel.title }}
-
{{ panel.total }}
+
+ {{ panel.title }}
-
-
-
-
-
-
+
+ 指标名称
+ {{ panel.rangeLabel }}
+ {{ panel.countLabel }}
+
+
+
+ {{ item.name }}
+ {{ item.range }}
+ {{ item.count }}
+
+
@@ -268,19 +225,14 @@ import {
TrendCharts,
PieChart,
Warning,
- WarningFilled,
- BellFilled,
- Monitor,
- CircleCheck,
- ArrowRight,
Connection,
Cpu,
CircleCloseFilled,
- SuccessFilled
+ SuccessFilled,
+ DCaret
} from '@element-plus/icons-vue'
import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
-import { defaultAttribute } from '@/components/table/defaultAttribute'
import TableStore from '@/utils/tableStore'
import { mainHeight } from '@/utils/layout'
/** 来自设备树:level0工程/1项目/2设备/3监测点;MQTT=治理设备,CLD=监测设备 */
@@ -291,18 +243,24 @@ defineOptions({
})
type RateFilter = 'all' | 'le90' | 'le60'
+type CommTypeFilter = 'all' | '治理设备' | '监测设备'
type RankItem = { name: string; value: number; count: number; lowCount: number }
+type RateSortKey = 'onlineRate' | 'integrityRate'
const pageHeight = mainHeight(20)
const headerRef = ref()
const loading = ref(false)
const rateFilter = ref
('all')
+const commTypeFilter = ref('all')
+/** 合并列表排序:默认按在线率升序 */
+const rateSortKey = ref('onlineRate')
+const rateSortAsc = ref(true)
/** 下钻到项目层级时的工程名 */
const drillEng = ref('')
const pieChart = ref({})
const typeChart = ref({})
-const qualityChart = ref({})
-const qualityView = ref<'chart' | 'table'>('chart')
+const onlineLiquidChart = ref({})
+const integrityLiquidChart = ref({})
const tableStore = new TableStore({
url: '',
@@ -346,71 +304,73 @@ const ledgerCards = computed(() => {
label: '工程',
value: inDrill ? 1 : global['工程'] ?? engSet.size,
unit: '个',
- size: 20,
+ size: 18,
icon: HomeFilled,
color: '#376CF3',
- iconBg: 'rgba(55, 108, 243, 0.14)',
- cardBg: 'linear-gradient(135deg, #bfdbfe 0%, #eff6ff 100%)'
+ iconBg: 'rgba(55, 108, 243, 0.14)'
},
{
label: '项目',
value: inDrill ? projSet.size : global['项目'] ?? projSet.size,
unit: '个',
- size: 20,
+ size: 18,
icon: List,
color: '#0E42D2',
- iconBg: 'rgba(14, 66, 210, 0.12)',
- cardBg: 'linear-gradient(135deg, #c7d2fe 0%, #eef2ff 100%)'
+ iconBg: 'rgba(14, 66, 210, 0.12)'
},
{
label: '设备',
value: inDrill ? devices.length : global['设备'] ?? devices.length,
unit: '台',
- size: 20,
+ size: 18,
icon: Platform,
color: '#0EA5E9',
- iconBg: 'rgba(14, 165, 233, 0.14)',
- cardBg: 'linear-gradient(135deg, #a5f3fc 0%, #ecfeff 100%)'
+ iconBg: 'rgba(14, 165, 233, 0.14)'
},
{
label: '监测点',
value: inDrill ? points.length : global['监测点'] ?? points.length,
unit: '个',
- size: 22,
+ size: 20,
icon: LocationFilled,
color: '#14B8A6',
- iconBg: 'rgba(20, 184, 166, 0.14)',
- cardBg: 'linear-gradient(135deg, #99f6e4 0%, #ecfdf8 100%)'
+ iconBg: 'rgba(20, 184, 166, 0.14)'
}
]
})
+const qualityIndicators = [
+ '频率',
+ '谐波电压相角',
+ '相电压有效值',
+ '电流有效值',
+ '正序电压',
+ '负序电压',
+ '零序电压',
+ '线电压有效值',
+ '功率因数'
+]
+
+/** mock:异常 / 告警按指标汇总(后续接接口) */
const qualityData = ref({
outOfRange: [
- { name: '频率', range: '超出合理范围', count: 12 },
- { name: '相电压有效值', range: '超出合理范围', count: 28 },
- { name: '线电压有效值', range: '超出合理范围', count: 19 },
- { name: '电流有效值', range: '超出合理范围', count: 15 }
+ { name: '频率', range: '42.5Hz ~ 57.5Hz', count: 12 },
+ { name: '谐波电压相角', range: '-360.0 ~ 360.0', count: 8 },
+ { name: '相电压有效值', range: '10.0%U ~ 150.0%U', count: 28 },
+ { name: '电流有效值', range: '1.0 ~ 1250.0', count: 15 },
+ { name: '正序电压', range: '0.0%U ~ 150.0%U', count: 18 },
+ { name: '负序电压', range: '0.0%U ~ 150.0%U', count: 14 },
+ { name: '零序电压', range: '0.0%U ~ 150.0%U', count: 9 },
+ { name: '线电压有效值', range: '10.0%U ~ 150.0%U', count: 19 },
+ { name: '功率因数', range: '-1.0 ~ 1.0', count: 11 }
],
- overLimit: [
- { name: '谐波电压含有率', range: '越限告警', count: 18 },
- { name: '长时闪变', range: '越限告警', count: 7 },
- { name: '电压偏差', range: '越限告警', count: 14 },
- { name: '三相不平衡度', range: '越限告警', count: 6 }
- ]
+ overLimit: qualityIndicators.map((name, idx) => ({
+ name,
+ range: '',
+ count: [6, 4, 18, 9, 10, 7, 5, 14, 8][idx]
+ }))
})
-/** 按日期汇总的异常 / 告警数量(双柱图) */
-const qualityTrend = ref([
- { date: '07-11', outOfRange: 18, overLimit: 9 },
- { date: '07-12', outOfRange: 22, overLimit: 11 },
- { date: '07-13', outOfRange: 15, overLimit: 7 },
- { date: '07-14', outOfRange: 28, overLimit: 14 },
- { date: '07-15', outOfRange: 20, overLimit: 10 },
- { date: '07-16', outOfRange: 25, overLimit: 16 },
- { date: '07-17', outOfRange: 19, overLimit: 12 }
-])
-
const aggregateBy = (list: any[], keyFn: (row: any) => string, valueFn: (row: any) => number): RankItem[] => {
const map = new Map()
list.forEach(row => {
@@ -432,35 +392,21 @@ const aggregateBy = (list: any[], keyFn: (row: any) => string, valueFn: (row: an
.sort((a, b) => a.value - b.value)
}
-const onlineList = computed(() => {
- if (drillEng.value) {
- return aggregateBy(
- scopedDevices.value,
- d => d.projName || '未分组',
- d => d.onlineRate
- )
- }
- return aggregateBy(
- deviceOnlineTable.value,
- d => d.engName || '未分组',
+const onlineList = computed(() =>
+ aggregateBy(
+ scopedDevices.value,
+ d => (drillEng.value ? d.projName || '未分组' : d.engName || '未分组'),
d => d.onlineRate
)
-})
+)
-const integrityList = computed(() => {
- if (drillEng.value) {
- return aggregateBy(
- scopedPoints.value,
- p => p.projName || '未分组',
- p => p.integrity
- )
- }
- return aggregateBy(
- pointIntegrityTable.value,
- p => p.engName || '未分组',
+const integrityList = computed(() =>
+ aggregateBy(
+ scopedPoints.value,
+ p => (drillEng.value ? p.projName || '未分组' : p.engName || '未分组'),
p => p.integrity
)
-})
+)
const matchRateFilter = (v: number) => {
if (rateFilter.value === 'le90') return v <= 90
@@ -468,8 +414,55 @@ const matchRateFilter = (v: number) => {
return true
}
-const displayOnlineList = computed(() => onlineList.value.filter(i => matchRateFilter(i.value)))
-const displayIntegrityList = computed(() => integrityList.value.filter(i => matchRateFilter(i.value)))
+/** 按名称合并在线率 / 完整性;筛选时任一指标达标即展示,另一项仍显示真实值 */
+const displayMergedRateList = computed(() => {
+ const onlineMap = new Map(onlineList.value.map(i => [i.name, i]))
+ const integrityMap = new Map(integrityList.value.map(i => [i.name, i]))
+ const names = new Set([...onlineMap.keys(), ...integrityMap.keys()])
+ const list = [...names]
+ .map(name => {
+ const online = onlineMap.get(name)
+ const integrity = integrityMap.get(name)
+ const onlineRate = online?.value ?? 0
+ const integrityRate = integrity?.value ?? 0
+ return {
+ name,
+ onlineCount: online?.count ?? 0,
+ onlineLow: online?.lowCount ?? 0,
+ onlineRate,
+ integrityCount: integrity?.count ?? 0,
+ integrityLow: integrity?.lowCount ?? 0,
+ integrityRate
+ }
+ })
+ .filter(item => matchRateFilter(item.onlineRate) || matchRateFilter(item.integrityRate))
+
+ const key = rateSortKey.value
+ const asc = rateSortAsc.value ? 1 : -1
+ return list.sort((a, b) => {
+ const av = a[key]
+ const bv = b[key]
+ if (typeof av === 'string' || typeof bv === 'string') {
+ return String(av).localeCompare(String(bv), 'zh-CN') * asc
+ }
+ return ((Number(av) || 0) - (Number(bv) || 0)) * asc
+ })
+})
+
+const toggleRateSort = (key: RateSortKey) => {
+ if (rateSortKey.value === key) {
+ rateSortAsc.value = !rateSortAsc.value
+ } else {
+ rateSortKey.value = key
+ rateSortAsc.value = true
+ }
+}
+
+const rateSortClass = (key: RateSortKey) => ({
+ 'is-active': rateSortKey.value === key,
+ 'is-asc': rateSortKey.value === key && rateSortAsc.value,
+ 'is-desc': rateSortKey.value === key && !rateSortAsc.value
+})
const avgRate = (list: any[], key: string) => {
if (!list.length) return 0
@@ -481,7 +474,10 @@ const totalOnlineRate = computed(() => avgRate(scopedDevices.value, 'onlineRate'
const totalIntegrityRate = computed(() => avgRate(scopedPoints.value, 'integrity'))
const commStatusTable = computed(() => {
- const list = scopedDevices.value
+ const list =
+ commTypeFilter.value === 'all'
+ ? scopedDevices.value
+ : scopedDevices.value.filter(d => d.type === commTypeFilter.value)
const normal = list.filter(i => i.normal).length
const abnormal = list.length - normal
const total = list.length || 1
@@ -494,10 +490,10 @@ const commStatusTable = computed(() => {
icon: SuccessFilled
},
{
- name: '中断',
+ name: '异常',
count: abnormal,
ratio: Number(((abnormal / total) * 100).toFixed(2)),
- color: '#f56c6c',
+ color: '#e26257',
icon: CircleCloseFilled
}
]
@@ -535,20 +531,18 @@ const qualityPanels = computed(() => [
{
key: 'outOfRange',
title: '异常数据',
+ rangeLabel: '合理范围',
countLabel: '异常测点数',
color: '#FF9500',
- bg: 'linear-gradient(135deg, #fff7e8 0%, #fffbf0 100%)',
- icon: WarningFilled,
total: qualityBadge.value.outOfRange,
list: qualityData.value.outOfRange
},
{
key: 'overLimit',
title: '告警数据',
+ rangeLabel: '限值',
countLabel: '告警测点数',
- color: '#F53F3F',
- bg: 'linear-gradient(135deg, #ffecec 0%, #fff5f5 100%)',
- icon: BellFilled,
+ color: '#e26257',
total: qualityBadge.value.overLimit,
list: qualityData.value.overLimit
}
@@ -560,6 +554,12 @@ const rateLevel = (v: number) => {
return 'is-danger'
}
+const rateColorHex = (v: number) => {
+ if (v >= 90) return '#67c23a'
+ if (v >= 60) return '#e6a23c'
+ return '#e26257'
+}
+
const onRankClick = (item: RankItem) => {
if (drillEng.value) return
drillEng.value = item.name
@@ -569,89 +569,153 @@ const backToEng = () => {
drillEng.value = ''
}
-const buildDonutChart = (legend: { name: string; count: number; color: string }[], exportFileName: string) => ({
- title: { show: false },
- tooltip: { trigger: 'item', formatter: '{b}
{c} 台 ({d}%)' },
- legend: { show: false },
- xAxis: { show: false },
- yAxis: { show: false },
- dataZoom: { show: false },
- toolbox: { show: false },
- exportFileName,
- options: {
+const buildLiquidChart = (value: number, title: string, shortName: string) => {
+ const color = rateColorHex(value)
+ const ratio = Math.max(0, Math.min(100, Number(value) || 0)) / 100
+ const pct = Number(value).toFixed(2)
+ return {
+ title: { show: false },
+ legend: { show: false },
+ xAxis: { show: false },
+ yAxis: { show: false },
+ dataZoom: { show: false },
+ toolbox: { show: false },
+ tooltip: { show: false },
+ exportFileName: title,
series: [
{
- type: 'pie',
- radius: ['42%', '70%'],
+ type: 'liquidFill',
+ name: title,
+ data: [ratio],
+ radius: '78%',
center: ['50%', '50%'],
- itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
- label: { show: false },
- labelLine: { show: false },
- data: legend.map(i => ({
- name: i.name,
- value: i.count,
- itemStyle: { color: i.color }
- }))
+ color: [color],
+ backgroundStyle: {
+ color:
+ color === '#67c23a'
+ ? 'rgba(103,194,58,0.08)'
+ : color === '#e6a23c'
+ ? 'rgba(230,162,60,0.08)'
+ : 'rgba(245,108,108,0.08)'
+ },
+ outline: {
+ show: true,
+ borderDistance: 4,
+ itemStyle: {
+ borderColor: color,
+ borderWidth: 2,
+ shadowBlur: 0
+ }
+ },
+ itemStyle: {
+ opacity: 0.85,
+ shadowBlur: 0
+ },
+ label: {
+ formatter: () => `{pct|${pct}%}\n{name|${shortName}}`,
+ rich: {
+ pct: {
+ fontSize: 15,
+ fontWeight: 700,
+ lineHeight: 22
+ },
+ name: {
+ fontSize: 12,
+ fontWeight: 500,
+ lineHeight: 18
+ }
+ }
+ }
}
]
}
+}
+
+const buildLiquidCharts = () => {
+ onlineLiquidChart.value = buildLiquidChart(totalOnlineRate.value, '设备在线率', '在线率')
+ integrityLiquidChart.value = buildLiquidChart(totalIntegrityRate.value, '监测点数据完整性', '完整性')
+}
+
+const buildPieLegend = (formatter?: (name: string) => string) => ({
+ show: true,
+ type: 'scroll',
+ orient: 'vertical',
+ right: 4,
+ top: 'middle',
+ height: '78%',
+ itemWidth: 10,
+ itemHeight: 10,
+ itemGap: 8,
+ pageIconSize: 12,
+ pageButtonItemGap: 6,
+ pageButtonGap: 8,
+ pageButtonPosition: 'end',
+ pageIconColor: '#376CF3',
+ pageIconInactiveColor: '#c0c4cc',
+ pageTextStyle: { color: '#86909c', fontSize: 11 },
+ formatter: formatter || ((name: string) => name),
+ textStyle: {
+ fontSize: 12,
+ width: 140,
+ overflow: 'truncate',
+ ellipsis: '...'
+ },
+ tooltip: { show: true }
})
-const buildPieChart = () => {
+const buildDonutChart = (
+ legend: { name: string; count: number; color: string; ratio?: number }[],
+ exportFileName: string,
+ valueUnit = '台'
+) => {
+ const countMap = Object.fromEntries(legend.map(i => [i.name, i.count]))
+ return {
+ title: { show: false },
+ tooltip: { trigger: 'item', formatter: `{b}
{c} ${valueUnit} ({d}%)` },
+ legend: buildPieLegend(name => `${name} ${countMap[name] ?? 0}${valueUnit}`),
+ xAxis: { show: false },
+ yAxis: { show: false },
+ dataZoom: { show: false },
+ toolbox: { show: false },
+ exportFileName,
+ options: {
+ series: [
+ {
+ type: 'pie',
+ radius: ['51%', '79%'],
+ center: ['34%', '50%'],
+ itemStyle: { borderRadius: 3, borderColor: '#fff', borderWidth: 1 },
+ label: { show: false },
+ labelLine: { show: false },
+ data: legend.map(i => ({
+ name: i.name,
+ value: i.count,
+ itemStyle: { color: i.color }
+ }))
+ }
+ ]
+ }
+ }
+}
+
+const buildCommPieChart = () => {
pieChart.value = buildDonutChart(commStatusTable.value, '通讯状态')
+}
+
+const buildTypePieChart = () => {
typeChart.value = buildDonutChart(typeLegend.value, '设备类型')
}
-const buildQualityChart = () => {
- const list = qualityTrend.value
- qualityChart.value = {
- title: { show: false },
- exportFileName: '数据质量统计',
- color: ['#FF9500', '#F53F3F'],
-
- grid: {
- top: '36px',
- left: '12px',
- right: '16px',
- bottom: '8px',
- containLabel: true
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'shadow' }
- },
- dataZoom: { show: false },
- xAxis: {
- type: 'category',
- data: list.map(i => i.date)
- },
- yAxis: {
- type: 'value',
- name: '测点数',
- minInterval: 1
- },
- series: [
- {
- name: '异常数据',
- type: 'bar',
- barMaxWidth: 18,
- data: list.map(i => i.outOfRange)
- },
- {
- name: '告警数据',
- type: 'bar',
- barMaxWidth: 18,
- data: list.map(i => i.overLimit)
- }
- ]
- }
+const buildPieChart = () => {
+ buildCommPieChart()
+ buildTypePieChart()
}
const loadData = () => {
loading.value = true
setTimeout(() => {
buildPieChart()
- buildQualityChart()
+ buildLiquidCharts()
loading.value = false
}, 120)
}
@@ -664,7 +728,15 @@ const onSelectChange = (_showSelect: any, _height: any, datePickerValue?: any) =
loadData()
}
-watch([drillEng, scopedDevices], () => buildPieChart())
+watch([drillEng, scopedDevices, scopedPoints], () => {
+ buildPieChart()
+ buildLiquidCharts()
+})
+
+/** 通讯类型切换只刷新通讯质量饼图,不影响其它模块 */
+watch(commTypeFilter, () => {
+ buildCommPieChart()
+})
onMounted(() => loadData())
@@ -683,7 +755,7 @@ onMounted(() => loadData())
padding: 10px;
display: grid;
grid-template-columns: minmax(260px, 1fr) minmax(0, 2.6fr);
- gap: 10px;
+ //gap: 4px;
}
.oq-left,
@@ -694,6 +766,8 @@ onMounted(() => loadData())
flex-direction: column;
gap: 10px;
overflow: hidden;
+ /* 给卡片左右阴影留出空间,避免被 overflow 裁切 */
+ // padding: 4px 8px;
}
.oq-left > .oq-panel {
@@ -702,10 +776,10 @@ onMounted(() => loadData())
}
.oq-left > .oq-ledger {
- flex: 0 0 230px;
- height: 230px;
- min-height: 230px;
- max-height: 230px;
+ flex: 0 0 200px;
+ height: 200px;
+ min-height: 200px;
+ max-height: 200px;
}
.oq-right > .oq-rate {
@@ -722,12 +796,12 @@ onMounted(() => loadData())
min-width: 0;
min-height: 0;
background: #fff;
- border-radius: 10px;
+ border-radius: 5px;
border: 1px solid #eef2f7;
- padding: 12px 14px;
+ padding: 10px;
display: flex;
flex-direction: column;
- box-shadow: 0 2px 10px rgba(15, 35, 80, 0.05);
+ box-shadow: 0 2px 8px rgba(15, 35, 80, 0.04);
&__hd {
display: flex;
@@ -739,6 +813,13 @@ onMounted(() => loadData())
color: #1d2129;
margin-bottom: 10px;
flex-shrink: 0;
+ min-width: 0;
+
+ > :deep(.el-tooltip__trigger) {
+ max-width: 55%;
+ min-width: 0;
+ overflow: hidden;
+ }
}
&__title {
@@ -769,64 +850,67 @@ onMounted(() => loadData())
display: inline-flex;
align-items: center;
gap: 4px;
+ max-width: 100%;
+ min-width: 0;
font-size: 12px;
font-weight: 400;
color: #376cf3;
background: rgba(55, 108, 243, 0.08);
padding: 2px 8px;
border-radius: 999px;
- max-width: 80%;
overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ vertical-align: middle;
.el-icon {
font-size: 13px;
flex-shrink: 0;
}
+
+ &__text {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
}
.oq-ledger {
&__grid {
- flex: none;
+ flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
+ align-content: stretch;
}
}
.oq-kpi {
position: relative;
- height: 80px;
+ height: 100%;
+ min-height: 62px;
+ max-height: 70px;
box-sizing: border-box;
- background: var(--card-bg, #fff);
- border-radius: 10px;
- padding: 10px 12px;
+ background: #fff;
+ border: 1px solid #eef2f7;
+ border-radius: 8px;
+ padding: 8px 10px;
display: flex;
align-items: center;
gap: 10px;
overflow: hidden;
- transition: transform 0.18s ease, box-shadow 0.18s ease;
-
- &::before {
- content: '';
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- width: 3px;
- background: var(--c);
- }
+ box-shadow: 0 2px 8px rgba(15, 35, 80, 0.04);
+ transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
&:hover {
transform: translateY(-1px);
+ border-color: #e5eaf3;
box-shadow: 0 4px 12px rgba(15, 35, 80, 0.08);
}
&__icon {
- width: 42px;
- height: 42px;
- border-radius: 10px;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
background: var(--bg);
color: var(--c);
display: flex;
@@ -843,8 +927,9 @@ onMounted(() => loadData())
}
&__label {
- font-size: 13px;
+ font-size: 12px;
color: #6e7681;
+ line-height: 1.2;
}
&__val {
@@ -872,13 +957,42 @@ onMounted(() => loadData())
.oq-rate {
overflow: hidden;
+ &__main {
+ flex: 1;
+ min-height: 0;
+ display: grid;
+ grid-template-columns: 200px minmax(0, 1fr);
+ gap: 10px;
+ overflow: hidden;
+ }
+
+ &__charts {
+ min-width: 0;
+ min-height: 0;
+ display: grid;
+ grid-template-rows: 1fr 1fr;
+ gap: 4px;
+ overflow: hidden;
+ }
+
+ &__ball {
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ overflow: hidden;
+
+ .oq-chart {
+ flex: 1;
+ width: 100%;
+ min-height: 0;
+ }
+ }
+
&__body {
flex: 1;
min-height: 0;
- height: 0;
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
+ display: flex;
+ flex-direction: column;
overflow: hidden;
}
@@ -890,10 +1004,13 @@ onMounted(() => loadData())
display: flex;
flex-direction: column;
border: 1px solid #eef2f7;
- border-radius: 10px;
+ border-radius: 5px;
padding: 10px;
background: #fafbfd;
overflow: hidden;
+ --oq-merge-cols: minmax(0, 1.2fr) 70px 75px 75px 70px 75px 75px;
+ --oq-merge-group: 70px 75px 75px;
+ --oq-merge-group-w: 236px; /* 70 + 8 + 75 + 8 + 75 */
}
&__sub {
@@ -901,7 +1018,6 @@ onMounted(() => loadData())
font-weight: 600;
color: #1d2129;
margin-bottom: 8px;
- padding-right: 118px;
flex-shrink: 0;
display: flex;
align-items: center;
@@ -923,46 +1039,6 @@ onMounted(() => loadData())
margin-left: 2px;
}
}
-
- &__total {
- position: absolute;
- top: 5px;
- right: 10px;
- z-index: 1;
- display: inline-flex;
- align-items: baseline;
- gap: 6px;
- padding: 2px 10px;
- border-radius: 999px;
- background: #fff;
- border: 1px solid #e8edf5;
- box-shadow: 0 1px 3px rgba(15, 35, 80, 0.04);
-
- label {
- font-size: 12px;
- font-weight: 400;
- color: #86909c;
- }
-
- b {
- font-size: 15px;
- font-weight: 700;
- font-variant-numeric: tabular-nums;
- line-height: 1.2;
-
- &.is-ok {
- color: #67c23a;
- }
-
- &.is-warn {
- color: #e6a23c;
- }
-
- &.is-danger {
- color: #f56c6c;
- }
- }
- }
}
.oq-rate-filter {
@@ -1001,21 +1077,126 @@ onMounted(() => loadData())
display: grid;
grid-template-columns: minmax(0, 1fr) 70px 100px 70px;
gap: 8px;
- padding: 0 12px 5px 16px;
- margin-bottom: 2px;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 0 12px 6px 16px;
+ margin: 0;
+ border: 1px solid transparent;
flex-shrink: 0;
+ position: sticky;
+ top: 0;
+ z-index: 2;
+ background: #fafbfd;
font-size: 12px;
font-weight: 600;
color: #4e5969;
line-height: 1.2;
- span {
+ > span {
text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 0;
}
.oq-rank__hd-name {
+ justify-content: flex-start;
text-align: left;
}
+
+ &--merge {
+ display: grid;
+ grid-template-columns: minmax(0, 1.2fr) var(--oq-merge-group-w) var(--oq-merge-group-w);
+ gap: 8px;
+ align-items: stretch;
+
+ .oq-rank__hd-name {
+ align-self: center;
+ }
+ }
+ }
+
+ &__hd-block {
+ display: grid;
+ grid-template-columns: var(--oq-merge-group);
+ grid-template-rows: auto auto;
+ gap: 4px 8px;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 4px 0;
+ border-radius: 6px;
+ min-width: 0;
+
+ > span {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 0;
+ text-align: center;
+ }
+
+ .oq-rank__hd-group {
+ grid-column: 1 / -1;
+ grid-row: 1;
+ background: transparent;
+ padding: 2px 0 4px;
+ border-bottom: 1px solid rgba(78, 89, 105, 0.12);
+ }
+
+ &--online {
+ background: rgba(149, 151, 153, 0.1);
+ padding: 4px 0;
+ }
+
+ &--integrity {
+ background: rgba(149, 151, 153, 0.1);
+ padding: 4px 0;
+ }
+ }
+
+ &__hd-group {
+ font-size: 12px;
+ font-weight: 700;
+ color: #4e5969;
+ justify-content: center;
+ }
+
+ .oq-sort {
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ cursor: pointer;
+ user-select: none;
+ line-height: 1;
+ vertical-align: middle;
+ color: #4e5969;
+
+ &:hover,
+ &.is-active {
+ color: var(--el-color-primary);
+ }
+
+ &__icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+ color: #c0c4cc;
+ transition: color 0.15s ease;
+ flex-shrink: 0;
+ }
+
+ &:hover .oq-sort__icon,
+ &.is-active .oq-sort__icon {
+ color: var(--el-color-primary);
+ cursor: pointer;
+ }
}
&__item {
@@ -1024,6 +1205,7 @@ onMounted(() => loadData())
grid-template-columns: minmax(0, 1fr) 70px 100px 70px;
gap: 8px;
align-items: center;
+ box-sizing: border-box;
min-height: 36px;
padding: 6px 12px 6px 16px;
border-radius: 6px;
@@ -1032,6 +1214,10 @@ onMounted(() => loadData())
overflow: hidden;
transition: background 0.15s ease, border-color 0.15s ease;
+ &--merge {
+ grid-template-columns: var(--oq-merge-cols);
+ }
+
&::before {
content: '';
position: absolute;
@@ -1040,7 +1226,7 @@ onMounted(() => loadData())
bottom: 6px;
width: 4px;
border-radius: 2px;
- background: #67c23a;
+ background: var(--el-color-primary);
}
&.is-ok::before {
@@ -1052,7 +1238,7 @@ onMounted(() => loadData())
}
&.is-danger::before {
- background: #f56c6c;
+ background: #e26257;
}
&.clickable {
@@ -1130,7 +1316,7 @@ onMounted(() => loadData())
}
&.is-danger {
- color: #f56c6c;
+ color: #e26257;
}
}
}
@@ -1141,10 +1327,8 @@ onMounted(() => loadData())
&__main {
flex: 1;
min-height: 0;
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 8px;
- align-items: center;
+ display: flex;
+ align-items: stretch;
}
.oq-chart {
@@ -1160,199 +1344,143 @@ onMounted(() => loadData())
min-height: 100px;
}
-.oq-pie-legend {
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 10px;
- min-width: 108px;
- flex-shrink: 0;
-
- &__item {
- display: flex;
- align-items: center;
- gap: 6px;
- white-space: nowrap;
- font-size: 12px;
- color: #4e5969;
- line-height: 1.2;
-
- > i {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- flex-shrink: 0;
- }
-
- em {
- font-style: normal;
- font-weight: 600;
- color: #1d2129;
- font-variant-numeric: tabular-nums;
- }
- }
-}
-
-.oq-pie-nums {
- display: flex;
- gap: 8px;
- flex-shrink: 0;
- padding-top: 4px;
-}
-
-.oq-pie-num {
- flex: 1;
- display: flex;
- align-items: center;
- gap: 6px;
- background: #f8fafc;
- border: 1px solid #eef2f7;
- border-radius: 8px;
- padding: 8px 10px;
- font-size: 12px;
- color: #64748b;
-
- > i {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- flex-shrink: 0;
- }
-
- .el-icon {
- font-size: 14px;
- flex-shrink: 0;
- }
-
- b {
- margin-left: auto;
- font-size: 16px;
- color: #1d2129;
- }
-
- &__val {
- margin-left: auto;
- display: inline-flex;
- align-items: baseline;
- gap: 2px;
- line-height: 1;
-
- b {
- margin-left: 0;
- font-size: 16px;
- font-weight: 700;
- font-variant-numeric: tabular-nums;
- line-height: 1;
- color: inherit;
- }
-
- em {
- font-style: normal;
- font-size: 12px;
- font-weight: 400;
- color: #86909c;
- line-height: 1;
- }
- }
-
- em {
- font-style: normal;
- color: #94a3b8;
- }
-}
-
.oq-quality {
overflow: hidden;
- &__chart {
- flex: 1;
- min-height: 0;
- }
-
- &__body {
+ &__main {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
+ overflow: hidden;
}
- &__panel {
+ &__list-wrap {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
border: 1px solid #eef2f7;
- border-radius: 10px;
+ border-radius: 5px;
+ padding: 10px;
+ background: #fafbfd;
overflow: hidden;
- background: #fff;
}
- &__panel-hd {
+ &__list-hd {
display: flex;
align-items: center;
- gap: 8px;
- padding: 8px 12px;
- background: var(--bg);
- border-bottom: 1px solid #eef2f7;
+ gap: 6px;
+ margin-bottom: 8px;
flex-shrink: 0;
-
- b {
- font-size: 13px;
- font-weight: 600;
- color: #1d2129;
- }
+ font-size: 13px;
+ font-weight: 600;
+ color: #1d2129;
em {
- margin-left: auto;
font-style: normal;
- font-size: 18px;
- font-weight: 700;
- color: var(--c);
- font-variant-numeric: tabular-nums;
+ font-size: 12px;
+ font-weight: 400;
+ color: #6e7681;
+ background: #eef2f7;
+ padding: 1px 8px;
+ border-radius: 999px;
+ margin-left: 2px;
}
}
- &__panel-icon {
- width: 24px;
- height: 24px;
+ &__list-cols {
+ display: grid;
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) 90px;
+ gap: 8px;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 0 12px 3px 16px;
+ margin: 0 4px 0 0;
+ border: 1px solid transparent;
+ flex-shrink: 0;
+ font-size: 12px;
+ font-weight: 600;
+ color: #4e5969;
+
+ span:nth-child(2),
+ span:last-child {
+ text-align: center;
+ }
+ }
+
+ &__list {
+ flex: 1;
+ min-height: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding-right: 4px;
+ scrollbar-gutter: stable;
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: var(--el-color-primary);
+ border-radius: 3px;
+ }
+ }
+
+ &__list-item {
+ position: relative;
+ display: grid;
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) 90px;
+ gap: 8px;
+ align-items: center;
+ box-sizing: border-box;
+ min-height: 36px;
+ padding: 6px 12px 6px 16px;
border-radius: 6px;
background: #fff;
- color: var(--c);
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
+ border: 1px solid #eef2f7;
- .el-icon {
- font-size: 14px;
+ &::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 6px;
+ bottom: 6px;
+ width: 4px;
+ border-radius: 2px;
+ background: var(--c);
}
}
-}
-.oq-table-wrap {
- flex: 1;
- min-height: 0;
- overflow: hidden;
-}
-
-.oq-table {
- width: 100%;
- border-radius: 0 !important;
-
- :deep(.vxe-table),
- :deep(.vxe-table--render-wrapper),
- :deep(.vxe-table--header-wrapper),
- :deep(.vxe-table--body-wrapper) {
- border-radius: 0 !important;
+ &__list-name {
+ min-width: 0;
+ font-size: 13px;
+ color: #1d2129;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
- :deep(.vxe-table--header-wrapper),
- :deep(.vxe-header--column) {
- background: #f8fafc;
- color: #64748b;
- font-weight: 600;
+ &__list-range {
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--el-color-primary);
+ text-align: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ &__list-count {
+ text-align: center;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--c);
+ font-variant-numeric: tabular-nums;
}
}
diff --git a/src/views/govern/setting/frontManagement/index.vue b/src/views/govern/setting/frontManagement/index.vue
index baf4574..d1f4f32 100644
--- a/src/views/govern/setting/frontManagement/index.vue
+++ b/src/views/govern/setting/frontManagement/index.vue
@@ -366,7 +366,7 @@ const currentChangeEvent = () => {
nodeId.value = tableRef.value.getRef().getCurrentRecord().id
// 检查返回的数据是否存在且不为空
if (res.data && res.data.processDeviceList) {
- dataSource.value = res.data.processDeviceList.filter(item => (item.name = item.processNo + ''))
+ dataSource.value = res.data.processDeviceList.filter(item => (item.name = item.processNo + '号进程'))
} else {
dataSource.value = []
}
diff --git a/src/views/pqs/cockpit/homePage/index.vue b/src/views/pqs/cockpit/homePage/index.vue
index 630afff..b472b74 100644
--- a/src/views/pqs/cockpit/homePage/index.vue
+++ b/src/views/pqs/cockpit/homePage/index.vue
@@ -315,13 +315,13 @@ onUnmounted(() => {