|
@@ -0,0 +1,755 @@
|
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
|
+import {
|
|
|
|
|
+ AlarmClock,
|
|
|
|
|
+ Checked,
|
|
|
|
|
+ CollectionTag,
|
|
|
|
|
+ DataAnalysis,
|
|
|
|
|
+ DocumentChecked,
|
|
|
|
|
+ Files,
|
|
|
|
|
+ Flag,
|
|
|
|
|
+ Histogram,
|
|
|
|
|
+ Opportunity,
|
|
|
|
|
+ Postcard,
|
|
|
|
|
+ Warning
|
|
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
|
|
+import { DESIGN_HEIGHT, DESIGN_WIDTH } 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: string
|
|
|
|
|
+ accent: string
|
|
|
|
|
+ soft: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type BottomCard = {
|
|
|
|
|
+ title: string
|
|
|
|
|
+ icon: any
|
|
|
|
|
+ accent: string
|
|
|
|
|
+ glow: string
|
|
|
|
|
+ lines: string[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const wrapperRef = ref<HTMLDivElement>()
|
|
|
|
|
+const scale = ref(1)
|
|
|
|
|
+const supportsZoom = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+let resizeObserver: ResizeObserver | null = null
|
|
|
|
|
+let resizeRaf = 0
|
|
|
|
|
+
|
|
|
|
|
+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: '较昨日 +1.6%',
|
|
|
|
|
+ accent: '#2ac7c9',
|
|
|
|
|
+ glow: 'rgba(42, 199, 201, 0.26)',
|
|
|
|
|
+ icon: DataAnalysis
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '在线作业许可',
|
|
|
|
|
+ value: '11',
|
|
|
|
|
+ note: 'Active',
|
|
|
|
|
+ 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: '已整改 628', value: 100, color: '#43c7ca' },
|
|
|
|
|
+ { label: '未整改 29', value: 61, color: '#ff981f' },
|
|
|
|
|
+ { label: '超期 2', value: 38, color: '#ff4c49' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+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: '11', accent: '#4d8cff', soft: 'rgba(77, 140, 255, 0.16)' },
|
|
|
|
|
+ { label: '异常预警', value: '2', accent: '#d0a400', soft: 'rgba(208, 164, 0, 0.14)' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+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: ['年度计划完成率', '89%(需补1次)']
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ 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)
|
|
|
|
|
+ }
|
|
|
|
|
+ window.addEventListener('resize', updateScale)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ resizeObserver?.disconnect()
|
|
|
|
|
+ window.removeEventListener('resize', updateScale)
|
|
|
|
|
+ cancelAnimationFrame(resizeRaf)
|
|
|
|
|
+})
|
|
|
|
|
+</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 class="chart-panel chart-panel--bars">
|
|
|
|
|
+ <div v-for="bar in hazardBars" :key="bar.label" class="bar-card">
|
|
|
|
|
+ <div class="bar-card__chart">
|
|
|
|
|
+ <div class="bar-card__fill" :style="{ height: `${bar.value}%`, background: bar.color }"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="bar-card__label">{{ bar.label }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </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>{{ 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>
|
|
|
|
|
+ 作业许可实时监控
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="permit-grid">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="item in permitStats"
|
|
|
|
|
+ :key="item.label"
|
|
|
|
|
+ class="permit-card"
|
|
|
|
|
+ :style="{ '--permit-accent': item.accent, '--permit-soft': item.soft } as any"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="permit-card__label">{{ item.label }}</div>
|
|
|
|
|
+ <div class="permit-card__value">{{ item.value }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </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 {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ height: 176px;
|
|
|
|
|
+ padding: 6px 10px 0;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+ gap: 34px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bar-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bar-card__chart {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 120px;
|
|
|
|
|
+ padding: 0 20px;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bar-card__fill {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ min-height: 32px;
|
|
|
|
|
+ border-radius: 0;
|
|
|
|
|
+ box-shadow: 0 10px 24px rgb(31 91 184 / 10%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bar-card__label {
|
|
|
|
|
+ margin-top: 14px;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #5b6f8f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.incident-panel {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ margin-top: 22px;
|
|
|
|
|
+ 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;
|
|
|
|
|
+ margin-top: 34px;
|
|
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
+ gap: 26px 28px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-card {
|
|
|
|
|
+ min-height: 132px;
|
|
|
|
|
+ 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: 16px;
|
|
|
|
|
+ font-size: 17px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #6f819a;
|
|
|
|
|
+ line-height: 1.55;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.permit-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ margin-top: 32px;
|
|
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.permit-card {
|
|
|
|
|
+ min-height: 128px;
|
|
|
|
|
+ padding: 28px 20px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ background: linear-gradient(180deg, var(--permit-soft) 0%, rgb(255 255 255 / 18%) 100%);
|
|
|
|
|
+ border: 1px solid rgb(255 255 255 / 54%);
|
|
|
|
|
+ border-radius: 18px;
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 68%),
|
|
|
|
|
+ 0 8px 18px rgb(63 103 171 / 6%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.permit-card__label {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #506684;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.permit-card__value {
|
|
|
|
|
+ margin-top: 18px;
|
|
|
|
|
+ font-family: YouSheBiaoTiHei, sans-serif;
|
|
|
|
|
+ font-size: 44px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ color: var(--permit-accent);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qualification-panel {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ margin-top: 26px;
|
|
|
|
|
+ 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>
|