|
|
@@ -0,0 +1,950 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import * as echarts from 'echarts'
|
|
|
+import {
|
|
|
+ AlarmClock,
|
|
|
+ Checked,
|
|
|
+ CollectionTag,
|
|
|
+ DataAnalysis,
|
|
|
+ DocumentChecked,
|
|
|
+ Files,
|
|
|
+ Flag,
|
|
|
+ Histogram,
|
|
|
+ Opportunity,
|
|
|
+ Postcard,
|
|
|
+ Warning
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
+import {
|
|
|
+ ANIMATION,
|
|
|
+ CHART_RENDERER,
|
|
|
+ DESIGN_HEIGHT,
|
|
|
+ DESIGN_WIDTH,
|
|
|
+ FONT_FAMILY,
|
|
|
+ THEME,
|
|
|
+ createTooltip
|
|
|
+} from '@/utils/kb'
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'PmsQhseKanban'
|
|
|
+})
|
|
|
+
|
|
|
+type SummaryCard = {
|
|
|
+ title: string
|
|
|
+ value: string
|
|
|
+ note: string
|
|
|
+ accent: string
|
|
|
+ glow: string
|
|
|
+ icon: any
|
|
|
+}
|
|
|
+
|
|
|
+type MetricBar = {
|
|
|
+ label: string
|
|
|
+ value: number
|
|
|
+ color: string
|
|
|
+}
|
|
|
+
|
|
|
+type RiskZone = {
|
|
|
+ title: string
|
|
|
+ desc: string
|
|
|
+ color: string
|
|
|
+}
|
|
|
+
|
|
|
+type PermitStat = {
|
|
|
+ label: string
|
|
|
+ value: number
|
|
|
+ color: string
|
|
|
+}
|
|
|
+
|
|
|
+type BottomCard = {
|
|
|
+ title: string
|
|
|
+ icon: any
|
|
|
+ accent: string
|
|
|
+ glow: string
|
|
|
+ lines: string[]
|
|
|
+}
|
|
|
+
|
|
|
+const wrapperRef = ref<HTMLDivElement>()
|
|
|
+const hazardChartRef = ref<HTMLDivElement>()
|
|
|
+const socChartRef = ref<HTMLDivElement>()
|
|
|
+const scale = ref(1)
|
|
|
+const supportsZoom = ref(false)
|
|
|
+
|
|
|
+let resizeObserver: ResizeObserver | null = null
|
|
|
+let resizeRaf = 0
|
|
|
+let hazardChart: echarts.ECharts | null = null
|
|
|
+let socChart: echarts.ECharts | null = null
|
|
|
+
|
|
|
+const pageTitle = 'QHSE管理看板'
|
|
|
+
|
|
|
+const summaryCards: SummaryCard[] = [
|
|
|
+ {
|
|
|
+ title: '风险总数(处)',
|
|
|
+ value: '126',
|
|
|
+ note: 'R:12',
|
|
|
+ accent: '#ff5a62',
|
|
|
+ glow: 'rgba(255, 90, 98, 0.26)',
|
|
|
+ icon: Warning
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '本月隐患(条)',
|
|
|
+ value: '29',
|
|
|
+ note: 'Exp:2',
|
|
|
+ accent: '#ff9f2f',
|
|
|
+ glow: 'rgba(255, 159, 47, 0.24)',
|
|
|
+ icon: Opportunity
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '隐患整改率',
|
|
|
+ value: '92.3%',
|
|
|
+ note: '',
|
|
|
+ accent: '#2ac7c9',
|
|
|
+ glow: 'rgba(42, 199, 201, 0.26)',
|
|
|
+ icon: DataAnalysis
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '本月作业许可',
|
|
|
+ value: '11',
|
|
|
+ note: '',
|
|
|
+ accent: '#4f8dff',
|
|
|
+ glow: 'rgba(79, 141, 255, 0.22)',
|
|
|
+ icon: DocumentChecked
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '人员持证率',
|
|
|
+ value: '97.8%',
|
|
|
+ note: 'Warn:3',
|
|
|
+ accent: '#f2b800',
|
|
|
+ glow: 'rgba(242, 184, 0, 0.22)',
|
|
|
+ icon: Postcard
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const hazardBars: MetricBar[] = [
|
|
|
+ { label: '总数', value: 657, color: '#4f8dff' },
|
|
|
+ { label: '已整改', value: 628, color: '#43c7ca' },
|
|
|
+ { label: '未整改', value: 29, color: '#ff981f' }
|
|
|
+]
|
|
|
+
|
|
|
+const incidentStats = [
|
|
|
+ { label: '安全事故', value: '0起', accent: '#2ac7c9' },
|
|
|
+ { label: '安全生产天数', value: '3起', accent: '#f2c11a' }
|
|
|
+]
|
|
|
+
|
|
|
+const riskZones: RiskZone[] = [
|
|
|
+ { title: '高危风险区', desc: '危化库 / 试压区 / 配电房', color: '#ff4c49' },
|
|
|
+ { title: '中风险区', desc: '焊接 / 机加 / 吊装区', color: '#ff981f' },
|
|
|
+ { title: '低风险区', desc: '物料库 / 维修 / 装卸区', color: '#f2c11a' },
|
|
|
+ { title: '安全控制区', desc: '办公区 / 展厅 / 主通道', color: '#5794ff' }
|
|
|
+]
|
|
|
+
|
|
|
+const permitStats: PermitStat[] = [
|
|
|
+ { label: '个人防护', value: 18, color: '#4f8dff' },
|
|
|
+ { label: '规范操作', value: 26, color: '#43c7ca' },
|
|
|
+ { label: '规范指挥', value: 12, color: '#ffb14a' },
|
|
|
+ { label: '人员位置', value: 9, color: '#ff7a7a' },
|
|
|
+ { label: '作业场所', value: 15, color: '#8d8cff' }
|
|
|
+]
|
|
|
+
|
|
|
+const qualificationWarnings = [
|
|
|
+ { label: '证件过期', value: '0人', accent: '#24364f' },
|
|
|
+ { label: '即将到期', value: '3人(需复审)', accent: '#e6ab00' }
|
|
|
+]
|
|
|
+
|
|
|
+const bottomCards: BottomCard[] = [
|
|
|
+ {
|
|
|
+ title: '体系合规',
|
|
|
+ icon: Files,
|
|
|
+ accent: '#39c6cc',
|
|
|
+ glow: 'rgba(57, 198, 204, 0.22)',
|
|
|
+ lines: ['内审已完成', '外审待安排']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '安全检测',
|
|
|
+ icon: Histogram,
|
|
|
+ accent: '#4f8dff',
|
|
|
+ glow: 'rgba(79, 141, 255, 0.2)',
|
|
|
+ lines: ['在用: 32台', '待检: 2台(重点关注)']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '应急演练',
|
|
|
+ icon: AlarmClock,
|
|
|
+ accent: '#ff5b61',
|
|
|
+ glow: 'rgba(255, 91, 97, 0.22)',
|
|
|
+ lines: ['年度应急演练:80次']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '质量检验',
|
|
|
+ icon: Checked,
|
|
|
+ accent: '#f2c11a',
|
|
|
+ glow: 'rgba(242, 193, 26, 0.2)',
|
|
|
+ lines: ['产品合格率', '98.7%(达标)']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '环境危废',
|
|
|
+ icon: Flag,
|
|
|
+ accent: '#28c98b',
|
|
|
+ glow: 'rgba(40, 201, 139, 0.2)',
|
|
|
+ lines: ['危险暂存合规', '三废处置100%达标']
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const targetWrapperStyle = computed(() => ({
|
|
|
+ width: `${DESIGN_WIDTH * scale.value}px`,
|
|
|
+ height: `${DESIGN_HEIGHT * scale.value}px`
|
|
|
+}))
|
|
|
+
|
|
|
+const targetAreaStyle = computed(() => {
|
|
|
+ const style = {
|
|
|
+ width: `${DESIGN_WIDTH}px`,
|
|
|
+ height: `${DESIGN_HEIGHT}px`,
|
|
|
+ transformOrigin: '0 0'
|
|
|
+ } as Record<string, string | number>
|
|
|
+
|
|
|
+ if (supportsZoom.value) {
|
|
|
+ style.zoom = scale.value
|
|
|
+ } else {
|
|
|
+ style.transform = `scale(${scale.value})`
|
|
|
+ }
|
|
|
+
|
|
|
+ return style
|
|
|
+})
|
|
|
+
|
|
|
+function updateScale() {
|
|
|
+ cancelAnimationFrame(resizeRaf)
|
|
|
+
|
|
|
+ resizeRaf = requestAnimationFrame(() => {
|
|
|
+ const wrapper = wrapperRef.value
|
|
|
+ if (!wrapper) return
|
|
|
+
|
|
|
+ const { clientWidth, clientHeight } = wrapper
|
|
|
+ if (!clientWidth || !clientHeight) return
|
|
|
+
|
|
|
+ scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ supportsZoom.value = typeof CSS !== 'undefined' && CSS.supports?.('zoom', '1') === true
|
|
|
+ nextTick(updateScale)
|
|
|
+ resizeObserver = new ResizeObserver(updateScale)
|
|
|
+ if (wrapperRef.value) {
|
|
|
+ resizeObserver.observe(wrapperRef.value)
|
|
|
+ }
|
|
|
+ initHazardChart()
|
|
|
+ initSocChart()
|
|
|
+ window.addEventListener('resize', updateScale)
|
|
|
+ window.addEventListener('resize', resizeHazardChart)
|
|
|
+ window.addEventListener('resize', resizeSocChart)
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ resizeObserver?.disconnect()
|
|
|
+ window.removeEventListener('resize', updateScale)
|
|
|
+ window.removeEventListener('resize', resizeHazardChart)
|
|
|
+ window.removeEventListener('resize', resizeSocChart)
|
|
|
+ cancelAnimationFrame(resizeRaf)
|
|
|
+ destroyHazardChart()
|
|
|
+ destroySocChart()
|
|
|
+})
|
|
|
+
|
|
|
+function getHazardChartOption(): echarts.EChartsOption {
|
|
|
+ return {
|
|
|
+ ...ANIMATION,
|
|
|
+ grid: {
|
|
|
+ left: 20,
|
|
|
+ right: 10,
|
|
|
+ top: 24,
|
|
|
+ bottom: 24,
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ tooltip: createTooltip({
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow',
|
|
|
+ shadowStyle: {
|
|
|
+ color: 'rgba(31, 91, 184, 0.08)'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatter(params: any) {
|
|
|
+ const item = Array.isArray(params) ? params[0] : params
|
|
|
+ return `${item.name}<br/>数量:${item.value}`
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: hazardBars.map((item) => item.label),
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#5b6f8f',
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 700,
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
+ interval: 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#8a9bb5',
|
|
|
+ fontSize: 13,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: THEME.split,
|
|
|
+ type: 'dashed'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'bar',
|
|
|
+ data: hazardBars.map((item) => ({
|
|
|
+ value: item.value,
|
|
|
+ itemStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: item.color },
|
|
|
+ { offset: 1, color: `${item.color}99` }
|
|
|
+ ]),
|
|
|
+ shadowBlur: 14,
|
|
|
+ shadowColor: item.color,
|
|
|
+ borderRadius: [0, 0, 0, 0]
|
|
|
+ }
|
|
|
+ })),
|
|
|
+ barWidth: 70,
|
|
|
+
|
|
|
+ backgroundStyle: {
|
|
|
+ color: 'rgba(31, 91, 184, 0.06)',
|
|
|
+ borderRadius: 0
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'top',
|
|
|
+ color: '#3c5f96',
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 700,
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
+ formatter(params: any) {
|
|
|
+ return `${params.value}`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 18
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function initHazardChart() {
|
|
|
+ if (!hazardChartRef.value) return
|
|
|
+ if (hazardChart) {
|
|
|
+ hazardChart.dispose()
|
|
|
+ }
|
|
|
+ hazardChart = echarts.init(hazardChartRef.value, undefined, {
|
|
|
+ renderer: CHART_RENDERER
|
|
|
+ })
|
|
|
+ hazardChart.setOption(getHazardChartOption(), true)
|
|
|
+}
|
|
|
+
|
|
|
+function resizeHazardChart() {
|
|
|
+ hazardChart?.resize()
|
|
|
+}
|
|
|
+
|
|
|
+function destroyHazardChart() {
|
|
|
+ if (hazardChart) {
|
|
|
+ hazardChart.dispose()
|
|
|
+ hazardChart = null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getSocChartOption(): echarts.EChartsOption {
|
|
|
+ return {
|
|
|
+ ...ANIMATION,
|
|
|
+ tooltip: createTooltip({
|
|
|
+ trigger: 'item',
|
|
|
+ formatter(params: any) {
|
|
|
+ return `${params.name}<br/>数量:${params.value}<br/>占比:${params.percent}%`
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ right: 4,
|
|
|
+ top: 'center',
|
|
|
+ itemWidth: 12,
|
|
|
+ itemHeight: 12,
|
|
|
+ icon: 'circle',
|
|
|
+ textStyle: {
|
|
|
+ color: '#5b6f8f',
|
|
|
+ fontSize: 15,
|
|
|
+ fontWeight: 600,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: 'SOC卡类型',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['50%', '74%'],
|
|
|
+ center: ['34%', '54%'],
|
|
|
+ avoidLabelOverlap: true,
|
|
|
+ itemStyle: {
|
|
|
+ borderColor: 'rgba(255, 255, 255, 0.85)',
|
|
|
+ borderWidth: 2,
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowColor: 'rgba(31, 91, 184, 0.12)'
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ color: '#4e6483',
|
|
|
+ fontSize: 13,
|
|
|
+ fontWeight: 700,
|
|
|
+ formatter: '{d}%'
|
|
|
+ },
|
|
|
+ labelLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(91, 111, 143, 0.5)'
|
|
|
+ },
|
|
|
+ length: 10,
|
|
|
+ length2: 8
|
|
|
+ },
|
|
|
+ data: permitStats.map((item) => ({
|
|
|
+ name: item.label,
|
|
|
+ value: item.value,
|
|
|
+ itemStyle: {
|
|
|
+ color: item.color
|
|
|
+ }
|
|
|
+ })),
|
|
|
+ emphasis: {
|
|
|
+ scale: true,
|
|
|
+ scaleSize: 6
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ graphic: [
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ left: '28.8%',
|
|
|
+ top: '43%',
|
|
|
+ style: {
|
|
|
+ text: 'SOC卡',
|
|
|
+
|
|
|
+ fill: '#6b7f9c',
|
|
|
+ fontSize: 18,
|
|
|
+ fontWeight: 700,
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ left: '26.5%',
|
|
|
+ top: '50%',
|
|
|
+ textAlign: 'center',
|
|
|
+ style: {
|
|
|
+ text: `${permitStats.reduce((sum, item) => sum + item.value, 0)}`,
|
|
|
+
|
|
|
+ fill: '#114a9b',
|
|
|
+ fontSize: 30,
|
|
|
+ fontWeight: 700,
|
|
|
+ fontFamily: 'YouSheBiaoTiHei, sans-serif'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function initSocChart() {
|
|
|
+ if (!socChartRef.value) return
|
|
|
+ if (socChart) {
|
|
|
+ socChart.dispose()
|
|
|
+ }
|
|
|
+ socChart = echarts.init(socChartRef.value, undefined, {
|
|
|
+ renderer: CHART_RENDERER
|
|
|
+ })
|
|
|
+ socChart.setOption(getSocChartOption(), true)
|
|
|
+}
|
|
|
+
|
|
|
+function resizeSocChart() {
|
|
|
+ socChart?.resize()
|
|
|
+}
|
|
|
+
|
|
|
+function destroySocChart() {
|
|
|
+ if (socChart) {
|
|
|
+ socChart.dispose()
|
|
|
+ socChart = null
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
|
|
|
+ <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
|
|
|
+ <div id="qhse-kanban" class="bg qhse-board" :style="targetAreaStyle">
|
|
|
+ <header class="header">{{ pageTitle }}</header>
|
|
|
+ <div class="board-body">
|
|
|
+ <section class="panel summary-panel kb-stage-card kb-stage-card--1">
|
|
|
+ <div class="panel-title">
|
|
|
+ <span class="icon-decorator"><span></span><span></span></span>
|
|
|
+ 风险总览
|
|
|
+ </div>
|
|
|
+ <div class="summary-grid">
|
|
|
+ <article
|
|
|
+ v-for="card in summaryCards"
|
|
|
+ :key="card.title"
|
|
|
+ class="summary-card summary-tile"
|
|
|
+ :style="
|
|
|
+ {
|
|
|
+ '--card-accent': card.accent,
|
|
|
+ '--card-glow': card.glow
|
|
|
+ } as any
|
|
|
+ ">
|
|
|
+ <span class="summary-card__shine"></span>
|
|
|
+ <span class="summary-card__corner"></span>
|
|
|
+ <div class="summary-card__icon">
|
|
|
+ <el-icon class="summary-card__icon-glyph">
|
|
|
+ <component :is="card.icon" />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="summary-card__content">
|
|
|
+ <div class="summary-card__label">{{ card.title }}</div>
|
|
|
+ <div class="summary-tile__meta">
|
|
|
+ <span class="summary-tile__value">{{ card.value }}</span>
|
|
|
+ <span class="summary-tile__note" :style="{ color: card.accent }">{{
|
|
|
+ card.note
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <div class="board-main">
|
|
|
+ <div class="left-column">
|
|
|
+ <section class="panel board-panel kb-stage-card kb-stage-card--2">
|
|
|
+ <div class="panel-title">
|
|
|
+ <span class="icon-decorator"><span></span><span></span></span>
|
|
|
+ 隐患排查治理统计
|
|
|
+ </div>
|
|
|
+ <div ref="hazardChartRef" class="chart-panel chart-panel--echart"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel board-panel kb-stage-card kb-stage-card--3">
|
|
|
+ <div class="panel-title">
|
|
|
+ <span class="icon-decorator"><span></span><span></span></span>
|
|
|
+ 事故事件趋势(近12月)
|
|
|
+ </div>
|
|
|
+ <div class="incident-panel">
|
|
|
+ <div class="incident-graphic">
|
|
|
+ <div class="incident-graphic__axis"></div>
|
|
|
+ <div class="incident-graphic__area"></div>
|
|
|
+ <div class="incident-graphic__line"></div>
|
|
|
+ </div>
|
|
|
+ <div class="incident-metrics">
|
|
|
+ <div v-for="item in incidentStats" :key="item.label" class="incident-metric">
|
|
|
+ <span :style="{ color: item.label === '安全生产天数' ? '#259745' : '' }"
|
|
|
+ >{{ item.label }}:</span
|
|
|
+ >
|
|
|
+ <strong :style="{ color: item.accent }">{{ item.value }}</strong>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="center-column">
|
|
|
+ <section class="panel board-panel board-panel--center kb-stage-card kb-stage-card--4">
|
|
|
+ <div class="panel-title panel-title--center">
|
|
|
+ <span class="icon-decorator"><span></span><span></span></span>
|
|
|
+ 安全风险四色动态分布
|
|
|
+ </div>
|
|
|
+ <div class="risk-grid">
|
|
|
+ <article v-for="zone in riskZones" :key="zone.title" class="risk-card">
|
|
|
+ <div class="risk-card__title">
|
|
|
+ <span class="risk-card__dot" :style="{ background: zone.color }"></span>
|
|
|
+ <span :style="{ color: zone.color }">{{ zone.title }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="risk-card__desc">{{ zone.desc }}</div>
|
|
|
+ </article>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="right-column">
|
|
|
+ <section class="panel board-panel kb-stage-card kb-stage-card--5">
|
|
|
+ <div class="panel-title">
|
|
|
+ <span class="icon-decorator"><span></span><span></span></span>
|
|
|
+ SOC卡类型
|
|
|
+ </div>
|
|
|
+ <div ref="socChartRef" class="soc-chart-panel"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel board-panel kb-stage-card kb-stage-card--6">
|
|
|
+ <div class="panel-title">
|
|
|
+ <span class="icon-decorator"><span></span><span></span></span>
|
|
|
+ 人员资质风险预警
|
|
|
+ </div>
|
|
|
+ <div class="qualification-panel">
|
|
|
+ <div class="qualification-icon">
|
|
|
+ <el-icon>
|
|
|
+ <CollectionTag />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="qualification-list">
|
|
|
+ <div
|
|
|
+ v-for="item in qualificationWarnings"
|
|
|
+ :key="item.label"
|
|
|
+ class="qualification-item">
|
|
|
+ <span class="qualification-item__label">{{ item.label }}:</span>
|
|
|
+ <strong :style="{ color: item.accent }">{{ item.value }}</strong>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <section class="bottom-grid">
|
|
|
+ <article
|
|
|
+ v-for="(card, index) in bottomCards"
|
|
|
+ :key="card.title"
|
|
|
+ class="panel bottom-card kb-stage-card"
|
|
|
+ :class="`kb-stage-card--${index + 7}`"
|
|
|
+ :style="
|
|
|
+ {
|
|
|
+ '--card-accent': card.accent,
|
|
|
+ '--card-glow': card.glow
|
|
|
+ } as any
|
|
|
+ ">
|
|
|
+ <span class="summary-card__shine"></span>
|
|
|
+ <div class="bottom-card__header">
|
|
|
+ <div class="summary-card__icon bottom-card__icon">
|
|
|
+ <el-icon class="summary-card__icon-glyph">
|
|
|
+ <component :is="card.icon" />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="bottom-card__title">{{ card.title }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="bottom-card__content">
|
|
|
+ <p v-for="line in card.lines" :key="line">{{ line }}</p>
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@import url('@/styles/kb.scss');
|
|
|
+
|
|
|
+.qhse-board {
|
|
|
+ color: #24364f;
|
|
|
+}
|
|
|
+
|
|
|
+.board-body {
|
|
|
+ padding: 18px 18px 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-panel {
|
|
|
+ padding-bottom: 22px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
|
+ gap: 24px;
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-tile {
|
|
|
+ display: flex;
|
|
|
+ height: 136px;
|
|
|
+ padding: 28px 28px 24px;
|
|
|
+ border-radius: 22px;
|
|
|
+ align-items: center;
|
|
|
+ gap: 22px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-card__content {
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
+ min-width: 0;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-tile__meta {
|
|
|
+ display: flex;
|
|
|
+ margin-top: 14px;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-tile__value {
|
|
|
+ font-family: YouSheBiaoTiHei, sans-serif;
|
|
|
+ font-size: 40px;
|
|
|
+ line-height: 1;
|
|
|
+ color: #114a9b;
|
|
|
+ letter-spacing: 1px;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-tile__note {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+
|
|
|
+.board-main {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1.02fr 1fr 0.98fr;
|
|
|
+ gap: 28px;
|
|
|
+ margin-top: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.left-column,
|
|
|
+.right-column {
|
|
|
+ display: grid;
|
|
|
+ gap: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.center-column {
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.board-panel {
|
|
|
+ min-height: 258px;
|
|
|
+}
|
|
|
+
|
|
|
+.board-panel--center {
|
|
|
+ min-height: 540px;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-panel {
|
|
|
+ margin-top: 18px;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-panel--bars {
|
|
|
+ height: 176px;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-panel--echart {
|
|
|
+ height: 188px;
|
|
|
+}
|
|
|
+
|
|
|
+.incident-panel {
|
|
|
+ display: grid;
|
|
|
+ margin-top: 50px;
|
|
|
+ grid-template-columns: 180px 1fr;
|
|
|
+ align-items: center;
|
|
|
+ gap: 28px;
|
|
|
+}
|
|
|
+
|
|
|
+.incident-graphic {
|
|
|
+ position: relative;
|
|
|
+ width: 170px;
|
|
|
+ height: 132px;
|
|
|
+}
|
|
|
+
|
|
|
+.incident-graphic__axis {
|
|
|
+ position: absolute;
|
|
|
+ inset: 24px 16px 16px 16px;
|
|
|
+ border-bottom: 6px solid #4f8dff;
|
|
|
+ border-left: 6px solid #4f8dff;
|
|
|
+ border-radius: 2px;
|
|
|
+ opacity: 0.92;
|
|
|
+}
|
|
|
+
|
|
|
+.incident-graphic__area {
|
|
|
+ position: absolute;
|
|
|
+ right: 22px;
|
|
|
+ bottom: 24px;
|
|
|
+ width: 102px;
|
|
|
+ height: 68px;
|
|
|
+ background: linear-gradient(180deg, rgb(79 141 255 / 88%) 0%, rgb(79 141 255 / 28%) 100%);
|
|
|
+ clip-path: polygon(0 100%, 22% 50%, 54% 72%, 100% 0, 100% 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.incident-graphic__line {
|
|
|
+ position: absolute;
|
|
|
+ right: 22px;
|
|
|
+ bottom: 24px;
|
|
|
+ width: 102px;
|
|
|
+ height: 68px;
|
|
|
+ border-top: 5px solid #4f8dff;
|
|
|
+ clip-path: polygon(0 100%, 22% 50%, 54% 72%, 100% 0, 100% 5%, 54% 77%, 22% 55%, 0 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.incident-metrics {
|
|
|
+ display: grid;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.incident-metric {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #556b89;
|
|
|
+}
|
|
|
+
|
|
|
+.incident-metric strong {
|
|
|
+ margin-left: 4px;
|
|
|
+ font-size: 34px;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-title--center {
|
|
|
+ justify-content: center;
|
|
|
+ padding-left: 0;
|
|
|
+ font-size: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-title--center .icon-decorator {
|
|
|
+ left: 28px;
|
|
|
+}
|
|
|
+
|
|
|
+.risk-grid {
|
|
|
+ display: grid;
|
|
|
+ padding: 30px;
|
|
|
+ margin-top: 34px;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 26px 28px;
|
|
|
+}
|
|
|
+
|
|
|
+.risk-card {
|
|
|
+ min-height: 182px;
|
|
|
+ padding: 34px 24px;
|
|
|
+ background: linear-gradient(180deg, rgb(255 255 255 / 42%) 0%, rgb(220 232 250 / 28%) 100%);
|
|
|
+ border: 1px solid rgb(255 255 255 / 58%);
|
|
|
+ border-radius: 18px;
|
|
|
+ box-shadow:
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 74%),
|
|
|
+ 0 8px 18px rgb(63 103 171 / 7%);
|
|
|
+}
|
|
|
+
|
|
|
+.risk-card__title {
|
|
|
+ display: flex;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 800;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.risk-card__dot {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 999px;
|
|
|
+ box-shadow: 0 0 0 6px rgb(255 255 255 / 35%);
|
|
|
+}
|
|
|
+
|
|
|
+.risk-card__desc {
|
|
|
+ margin-top: 36px;
|
|
|
+ font-size: 17px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6f819a;
|
|
|
+ line-height: 1.55;
|
|
|
+}
|
|
|
+
|
|
|
+.soc-chart-panel {
|
|
|
+ height: 220px;
|
|
|
+ // margin-top: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.qualification-panel {
|
|
|
+ display: grid;
|
|
|
+ margin-top: 26px;
|
|
|
+ padding: 28px;
|
|
|
+ grid-template-columns: 140px 1fr;
|
|
|
+ align-items: center;
|
|
|
+ gap: 26px;
|
|
|
+}
|
|
|
+
|
|
|
+.qualification-icon {
|
|
|
+ display: flex;
|
|
|
+ width: 132px;
|
|
|
+ height: 132px;
|
|
|
+ font-size: 74px;
|
|
|
+ color: #f09717;
|
|
|
+ background: radial-gradient(
|
|
|
+ circle at 50% 40%,
|
|
|
+ rgb(255 255 255 / 74%) 0%,
|
|
|
+ rgb(231 240 255 / 42%) 100%
|
|
|
+ );
|
|
|
+ border: 1px solid rgb(255 255 255 / 72%);
|
|
|
+ border-radius: 32px;
|
|
|
+ box-shadow:
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 85%),
|
|
|
+ 0 10px 22px rgb(55 94 160 / 10%);
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.qualification-list {
|
|
|
+ display: grid;
|
|
|
+ gap: 22px;
|
|
|
+}
|
|
|
+
|
|
|
+.qualification-item {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #516785;
|
|
|
+}
|
|
|
+
|
|
|
+.qualification-item strong {
|
|
|
+ font-size: 28px;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-grid {
|
|
|
+ display: grid;
|
|
|
+ margin-top: 26px;
|
|
|
+ grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
|
+ gap: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-card {
|
|
|
+ min-height: 190px;
|
|
|
+ padding: 28px 22px 24px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-card__header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-card__icon {
|
|
|
+ width: 54px;
|
|
|
+ height: 54px;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-card__title {
|
|
|
+ font-family: YouSheBiaoTiHei, sans-serif;
|
|
|
+ font-size: 24px;
|
|
|
+ color: #114a9b;
|
|
|
+ letter-spacing: 1px;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-card__content {
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
+ margin-top: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-card__content p {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #5d718e;
|
|
|
+ line-height: 1.7;
|
|
|
+}
|
|
|
+</style>
|