|
@@ -0,0 +1,2692 @@
|
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
|
+import * as echarts from 'echarts'
|
|
|
|
|
+import '@vuepic/vue-datepicker/dist/main.css'
|
|
|
|
|
+import {
|
|
|
|
|
+ AlarmClock,
|
|
|
|
|
+ Checked,
|
|
|
|
|
+ Clock,
|
|
|
|
|
+ CollectionTag,
|
|
|
|
|
+ DataAnalysis,
|
|
|
|
|
+ DocumentChecked,
|
|
|
|
|
+ Files,
|
|
|
|
|
+ Flag,
|
|
|
|
|
+ Histogram,
|
|
|
|
|
+ Odometer,
|
|
|
|
|
+ Postcard,
|
|
|
|
|
+ Warning,
|
|
|
|
|
+ Search,
|
|
|
|
|
+ Refresh
|
|
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
|
|
+import {
|
|
|
|
|
+ kanbanApi,
|
|
|
|
|
+ IotDangerApi,
|
|
|
|
|
+ IotHiddenApi,
|
|
|
|
|
+ IotInstrumentApi,
|
|
|
|
|
+ IotMeasureCertApi,
|
|
|
|
|
+ IotCertificateApi
|
|
|
|
|
+} from '@/api/pms/qhse/index'
|
|
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
+import {
|
|
|
|
|
+ ANIMATION,
|
|
|
|
|
+ CHART_RENDERER,
|
|
|
|
|
+ DESIGN_HEIGHT,
|
|
|
|
|
+ DESIGN_WIDTH,
|
|
|
|
|
+ FONT_FAMILY,
|
|
|
|
|
+ THEME,
|
|
|
|
|
+ createTooltip
|
|
|
|
|
+} from '@/utils/kb'
|
|
|
|
|
+import { DICT_TYPE, getBoolDictOptions, getStrDictOptions } from '@/utils/dict'
|
|
|
|
|
+import { formatDate } from '@/utils/formatTime'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({
|
|
|
|
|
+ name: 'PmsQhseKanban'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+type PermitStat = {
|
|
|
|
|
+ label: string
|
|
|
|
|
+ value: number
|
|
|
|
|
+ color: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type SafeDayMap = Record<string, number>
|
|
|
|
|
+type SafeDayBarItem = {
|
|
|
|
|
+ name: string
|
|
|
|
|
+ value: number
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type SummaryTabValue = 'home' | 'certificate'
|
|
|
|
|
+type QhseMetricValue = 'ltir' | 'trir' | 'pmva'
|
|
|
|
|
+const userStore = useUserStore()
|
|
|
|
|
+
|
|
|
|
|
+const type = ref('day')
|
|
|
|
|
+
|
|
|
|
|
+const handelChange = (value: any) => {
|
|
|
|
|
+ console.log('Selected time:', value)
|
|
|
|
|
+ // 在这里处理时间选择变化的逻辑,例如更新看板数据
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function padDateValue(value: number) {
|
|
|
|
|
+ return `${value}`.padStart(2, '0')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatDateValue(date = new Date()) {
|
|
|
|
|
+ return `${date.getFullYear()}-${padDateValue(date.getMonth() + 1)}-${padDateValue(date.getDate())}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getCurrentPickerValue(pickerType: string) {
|
|
|
|
|
+ const now = new Date()
|
|
|
|
|
+
|
|
|
|
|
+ if (pickerType === 'year') {
|
|
|
|
|
+ return `${now.getFullYear()}`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (pickerType === 'month') {
|
|
|
|
|
+ return `${now.getFullYear()}-${padDateValue(now.getMonth() + 1)}`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return formatDateValue(now)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const timeVal = ref(getCurrentPickerValue(type.value))
|
|
|
|
|
+
|
|
|
|
|
+const wrapperRef = ref<HTMLDivElement>()
|
|
|
|
|
+const hazardChartRef = ref<HTMLDivElement>()
|
|
|
|
|
+const safeDayChartRef = ref<HTMLDivElement>()
|
|
|
|
|
+const qhseTrendChartRef = 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 safeDayChart: echarts.ECharts | null = null
|
|
|
|
|
+let qhseTrendChart: echarts.ECharts | null = null
|
|
|
|
|
+let socChart: echarts.ECharts | null = null
|
|
|
|
|
+
|
|
|
|
|
+const summaryTabs: Array<{ label: string; value: SummaryTabValue }> = [
|
|
|
|
|
+ { label: '首页看板', value: 'home' },
|
|
|
|
|
+ { label: '证书信息', value: 'certificate' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const activeSummaryTab = ref<SummaryTabValue>('home')
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => activeSummaryTab.value,
|
|
|
|
|
+ async (value) => {
|
|
|
|
|
+ if (value === 'certificate') {
|
|
|
|
|
+ await getCertificateBoardList()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ initHazardChart()
|
|
|
|
|
+ initSafeDayChart()
|
|
|
|
|
+ initQhseTrendChart()
|
|
|
|
|
+ initSocChart()
|
|
|
|
|
+ resizeHazardChart()
|
|
|
|
|
+ resizeSafeDayChart()
|
|
|
|
|
+ resizeQhseTrendChart()
|
|
|
|
|
+ resizeSocChart()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const pageTitle = computed(() =>
|
|
|
|
|
+ activeSummaryTab.value === 'certificate' ? '证书信息看板' : 'QHSE管理看板'
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const summaryCards = ref([
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '人工时(小时)',
|
|
|
|
|
+ value: 671987,
|
|
|
|
|
+ note: '',
|
|
|
|
|
+ accent: '#4f8dff',
|
|
|
|
|
+ glow: 'rgba(79, 141, 255, 0.22)',
|
|
|
|
|
+ icon: Clock
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '车辆里程(公里)',
|
|
|
|
|
+ value: 631128,
|
|
|
|
|
+ note: '',
|
|
|
|
|
+ accent: '#4f8dff',
|
|
|
|
|
+ glow: 'rgba(79, 141, 255, 0.22)',
|
|
|
|
|
+ icon: Odometer
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '重大风险数量',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ note: '',
|
|
|
|
|
+ accent: '#ff5a62',
|
|
|
|
|
+ glow: 'rgba(255, 90, 98, 0.26)',
|
|
|
|
|
+ icon: Warning
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '特种人员持证率',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ note: 'Warn: 0',
|
|
|
|
|
+ accent: '#f2b800',
|
|
|
|
|
+ glow: 'rgba(242, 184, 0, 0.22)',
|
|
|
|
|
+ icon: Postcard
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '设备检测合规率',
|
|
|
|
|
+ value: '98%',
|
|
|
|
|
+ note: '',
|
|
|
|
|
+ accent: '#f2b800',
|
|
|
|
|
+ glow: 'rgba(242, 184, 0, 0.22)',
|
|
|
|
|
+ icon: DataAnalysis
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '作业许可证数量',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ note: '',
|
|
|
|
|
+ accent: '#4f8dff',
|
|
|
|
|
+ glow: 'rgba(79, 141, 255, 0.22)',
|
|
|
|
|
+ icon: DocumentChecked
|
|
|
|
|
+ }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+const hazardBars = ref([
|
|
|
|
|
+ { label: '总数', value: 0, color: '#4f8dff' },
|
|
|
|
|
+ { label: '已整改', value: 0, color: '#43c7ca' },
|
|
|
|
|
+ { label: '未整改', value: 0, color: '#ff981f' }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+const riskZones = ref([
|
|
|
|
|
+ { title: '', desc: '办公区 / 展厅 / 主通道', color: '#25b36a', value: 0 },
|
|
|
|
|
+ { title: '', desc: '物料库 / 维修 / 装卸区', color: '#3d7cff', value: 0 },
|
|
|
|
|
+ { title: '', desc: '焊接 / 机加 / 吊装区', color: '#ff9827', value: 0 },
|
|
|
|
|
+ { title: '', desc: '危化库 / 试压区 / 配电房', color: '#ff5b61', value: 0 }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+const permitStats = ref<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 = ref([
|
|
|
|
|
+ { label: '证件过期', value: 0, accent: '#ff7a7a', key: 'expired' },
|
|
|
|
|
+ { label: '即将到期', value: 0, accent: '#e6ab00', key: 'warn' }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+const qhseMetricTabs = [
|
|
|
|
|
+ { label: 'LTIR(百万工时)', value: 'ltir', accent: '#3d7cff', label2: '百万工时损失工时事故率' },
|
|
|
|
|
+ { label: 'TRIR(百万工时)', value: 'trir', accent: '#17b6c5', label2: '百万工时总可记录事件率' },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'PMVA(百万公里)',
|
|
|
|
|
+ value: 'pmva',
|
|
|
|
|
+ accent: '#f2a93b',
|
|
|
|
|
+ label2: '百万公里可预防性交通事故率'
|
|
|
|
|
+ }
|
|
|
|
|
+] as const
|
|
|
|
|
+
|
|
|
|
|
+const activeQhseMetric = ref<QhseMetricValue>('ltir')
|
|
|
|
|
+const dangerGradeOptions = computed(() => getStrDictOptions(DICT_TYPE.DANGER_GRADE))
|
|
|
|
|
+
|
|
|
|
|
+const qhseTrendSeries = ref<Record<QhseMetricValue, Array<{ year: string; value: number }>>>({
|
|
|
|
|
+ ltir: [
|
|
|
|
|
+ { year: '2026年1月', value: 0.42 },
|
|
|
|
|
+ { year: '2026年2月', value: 0.39 },
|
|
|
|
|
+ { year: '2026年3月', value: 0.36 },
|
|
|
|
|
+ { year: '2026年4月', value: 0.34 },
|
|
|
|
|
+ { year: '2026年5月', value: 0.31 },
|
|
|
|
|
+ { year: '2026年6月', value: 0.29 },
|
|
|
|
|
+ { year: '2026年7月', value: 0.27 },
|
|
|
|
|
+ { year: '2026年8月', value: 0.25 },
|
|
|
|
|
+ { year: '2026年9月', value: 0.24 },
|
|
|
|
|
+ { year: '2026年10月', value: 0.22 },
|
|
|
|
|
+ { year: '2026年11月', value: 0.21 },
|
|
|
|
|
+ { year: '2026年12月', value: 0.2 }
|
|
|
|
|
+ ],
|
|
|
|
|
+ trir: [
|
|
|
|
|
+ { year: '1月', value: 0.78 },
|
|
|
|
|
+ { year: '2月', value: 0.75 },
|
|
|
|
|
+ { year: '3月', value: 0.73 },
|
|
|
|
|
+ { year: '4月', value: 0.7 },
|
|
|
|
|
+ { year: '5月', value: 0.68 },
|
|
|
|
|
+ { year: '6月', value: 0.66 },
|
|
|
|
|
+ { year: '7月', value: 0.63 },
|
|
|
|
|
+ { year: '8月', value: 0.61 },
|
|
|
|
|
+ { year: '9月', value: 0.59 },
|
|
|
|
|
+ { year: '10月', value: 0.57 },
|
|
|
|
|
+ { year: '11月', value: 0.55 },
|
|
|
|
|
+ { year: '12月', value: 0.54 }
|
|
|
|
|
+ ],
|
|
|
|
|
+ pmva: [
|
|
|
|
|
+ { year: '1月', value: 0.22 },
|
|
|
|
|
+ { year: '2月', value: 0.21 },
|
|
|
|
|
+ { year: '3月', value: 0.2 },
|
|
|
|
|
+ { year: '4月', value: 0.18 },
|
|
|
|
|
+ { year: '5月', value: 0.17 },
|
|
|
|
|
+ { year: '6月', value: 0.16 },
|
|
|
|
|
+ { year: '7月', value: 0.15 },
|
|
|
|
|
+ { year: '8月', value: 0.14 },
|
|
|
|
|
+ { year: '9月', value: 0.14 },
|
|
|
|
|
+ { year: '10月', value: 0.13 },
|
|
|
|
|
+ { year: '11月', value: 0.12 },
|
|
|
|
|
+ { year: '12月', value: 0.12 }
|
|
|
|
|
+ ]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const bottomCards = ref([
|
|
|
|
|
+ {
|
|
|
|
|
+ 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: ['在用: 0台', '待检: 0项(重点关注)']
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ 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)
|
|
|
|
|
+
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ resizeHazardChart()
|
|
|
|
|
+ resizeSafeDayChart()
|
|
|
|
|
+ resizeQhseTrendChart()
|
|
|
|
|
+ resizeSocChart()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const staticData = ref<any>({})
|
|
|
|
|
+const safeDay = ref<SafeDayMap>({})
|
|
|
|
|
+const safeDayBarData = ref<SafeDayBarItem[]>([])
|
|
|
|
|
+const summaryPanel = ref<any>(null)
|
|
|
|
|
+const total = ref(0)
|
|
|
|
|
+const instrumentExpired = ref(0)
|
|
|
|
|
+const safeDayDisplayOrder = ['瑞气能源', '瑞霖技术', '5#项目', '酸化压裂', '气体增产', '钻修井']
|
|
|
|
|
+
|
|
|
|
|
+function buildSafeDayBarData(baseData: SafeDayMap) {
|
|
|
|
|
+ const mappedData = Object.entries(baseData || {}).map(([name, value]) => ({
|
|
|
|
|
+ name,
|
|
|
|
|
+ value: Number(value) || 0
|
|
|
|
|
+ }))
|
|
|
|
|
+ const orderedData = safeDayDisplayOrder
|
|
|
|
|
+ .map((name) => mappedData.find((item) => item.name === name))
|
|
|
|
|
+ .filter(Boolean) as SafeDayBarItem[]
|
|
|
|
|
+ const remainingData = mappedData.filter((item) => !safeDayDisplayOrder.includes(item.name))
|
|
|
|
|
+
|
|
|
|
|
+ safeDayBarData.value = [...orderedData, ...remainingData]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getStatic() {
|
|
|
|
|
+ const res = await IotDangerApi.getDangerStatistics(userStore.user.deptId)
|
|
|
|
|
+ staticData.value = res.classify || []
|
|
|
|
|
+
|
|
|
|
|
+ riskZones.value.forEach((zone, index) => {
|
|
|
|
|
+ const item = staticData.value[index]
|
|
|
|
|
+ if (!item) return
|
|
|
|
|
+ zone.value = item.count || 0
|
|
|
|
|
+ zone.title = `${item.classify}区`
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getCertStatic() {
|
|
|
|
|
+ const res = await IotMeasureCertApi.getIotMeasureCertStatistics(userStore.user.deptId)
|
|
|
|
|
+ qualificationWarnings.value[0].value = res.expired || 0
|
|
|
|
|
+ qualificationWarnings.value[1].value = res.warn || 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getInstrumentOverview() {
|
|
|
|
|
+ const listData = await IotInstrumentApi.getInstrumentList({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ deptId: userStore.user.deptId
|
|
|
|
|
+ })
|
|
|
|
|
+ total.value = listData.total || 0
|
|
|
|
|
+
|
|
|
|
|
+ const statData = await IotInstrumentApi.getInstrumentStatistics(userStore.user.deptId)
|
|
|
|
|
+ instrumentExpired.value = statData.expired || 0
|
|
|
|
|
+
|
|
|
|
|
+ bottomCards.value[1].lines = [
|
|
|
|
|
+ `在用: ${total.value}台`,
|
|
|
|
|
+ `待检: ${instrumentExpired.value}项(重点关注)`
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getHazardChartOption(): echarts.EChartsOption {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...ANIMATION,
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: 20,
|
|
|
|
|
+ right: 10,
|
|
|
|
|
+ top: 24,
|
|
|
|
|
+ bottom: 24,
|
|
|
|
|
+ containLabel: true
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: createTooltip({
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ confine: true,
|
|
|
|
|
+ appendToBody: false,
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'shadow',
|
|
|
|
|
+ shadowStyle: {
|
|
|
|
|
+ color: 'rgba(31, 91, 184, 0.08)'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ formatter(params: any) {
|
|
|
|
|
+ if (!params || (Array.isArray(params) && params.length === 0)) return ''
|
|
|
|
|
+ const item = Array.isArray(params) ? params[0] : params
|
|
|
|
|
+ return `${item.name}<br/>数量:${item.value}`
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: hazardBars.value.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',
|
|
|
|
|
+ name: '隐患数量',
|
|
|
|
|
+ nameLocation: 'end',
|
|
|
|
|
+ nameGap: 13,
|
|
|
|
|
+ nameTextStyle: {
|
|
|
|
|
+ color: '#657897',
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ // fontWeight: 600,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ },
|
|
|
|
|
+ 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.value.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: [5, 5, 0, 0]
|
|
|
|
|
+ }
|
|
|
|
|
+ })),
|
|
|
|
|
+ barWidth: 70,
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: 'top',
|
|
|
|
|
+ color: '#3c5f96',
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 700,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ shadowBlur: 18
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function initHazardChart() {
|
|
|
|
|
+ if (!hazardChartRef.value) return
|
|
|
|
|
+ hazardChart?.dispose()
|
|
|
|
|
+ hazardChart = echarts.init(hazardChartRef.value, undefined, {
|
|
|
|
|
+ renderer: CHART_RENDERER
|
|
|
|
|
+ })
|
|
|
|
|
+ hazardChart.setOption(getHazardChartOption(), true)
|
|
|
|
|
+ hazardChart.off('click')
|
|
|
|
|
+ hazardChart.on('click', handleHazardBarClick)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function updateHazardChart() {
|
|
|
|
|
+ if (!hazardChart) return
|
|
|
|
|
+ hazardChart.clear()
|
|
|
|
|
+ hazardChart.setOption(getHazardChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resizeHazardChart() {
|
|
|
|
|
+ if (!hazardChart) return
|
|
|
|
|
+ hazardChart.resize({ animation: { duration: 300 } })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function destroyHazardChart() {
|
|
|
|
|
+ hazardChart?.dispose()
|
|
|
|
|
+ hazardChart = null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getSafeDayChartOption(): echarts.EChartsOption {
|
|
|
|
|
+ const maxValue = Math.max(...safeDayBarData.value.map((item) => item.value), 0)
|
|
|
|
|
+ const axisMax = maxValue > 0 ? Math.ceil(maxValue / 300) * 300 : 300
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...ANIMATION,
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: 30,
|
|
|
|
|
+ right: 28,
|
|
|
|
|
+ top: 18,
|
|
|
|
|
+ bottom: 34,
|
|
|
|
|
+ containLabel: true
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: createTooltip({
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ confine: true,
|
|
|
|
|
+ appendToBody: false,
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'shadow',
|
|
|
|
|
+ shadowStyle: {
|
|
|
|
|
+ color: 'rgba(79, 141, 255, 0.08)'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ formatter(params: any) {
|
|
|
|
|
+ if (!params || (Array.isArray(params) && params.length === 0)) return ''
|
|
|
|
|
+ const item = Array.isArray(params) ? params[0] : params
|
|
|
|
|
+ return `${item.name}<br/>安全生产天数:<span style="font-weight:700">${item.value}</span>`
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ max: axisMax,
|
|
|
|
|
+ axisLine: {
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: 'rgba(106, 144, 221, 0.45)'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: '#5b6f8f',
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ fontWeight: 600,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ },
|
|
|
|
|
+ splitLine: {
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: 'rgba(104, 139, 205, 0.32)',
|
|
|
|
|
+ type: 'dashed'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ inverse: true,
|
|
|
|
|
+ data: safeDayBarData.value.map((item) => item.name),
|
|
|
|
|
+ axisLine: { show: false },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: '#2e4262',
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: 700,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ },
|
|
|
|
|
+ splitLine: { show: false }
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '安全生产天数',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: safeDayBarData.value.map((item) => item.value),
|
|
|
|
|
+ barWidth: 22,
|
|
|
|
|
+ showBackground: true,
|
|
|
|
|
+ backgroundStyle: {
|
|
|
|
|
+ color: 'rgba(116, 152, 216, 0.08)',
|
|
|
|
|
+ borderRadius: 11
|
|
|
|
|
+ },
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
|
|
|
|
+ { offset: 0, color: '#6f9af0' },
|
|
|
|
|
+ { offset: 1, color: '#5f88de' }
|
|
|
|
|
+ ]),
|
|
|
|
|
+ borderRadius: [0, 11, 11, 0],
|
|
|
|
|
+ shadowBlur: 10,
|
|
|
|
|
+ shadowColor: 'rgba(95, 136, 222, 0.25)'
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: false
|
|
|
|
|
+ },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ shadowBlur: 14,
|
|
|
|
|
+ shadowColor: 'rgba(95, 136, 222, 0.38)'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ graphic: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ bottom: 0,
|
|
|
|
|
+ style: {
|
|
|
|
|
+ text: '安全生产天数',
|
|
|
|
|
+ fill: '#5b6f8f',
|
|
|
|
|
+ font: `700 12px ${FONT_FAMILY}`
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function initSafeDayChart() {
|
|
|
|
|
+ if (!safeDayChartRef.value) return
|
|
|
|
|
+ safeDayChart?.dispose()
|
|
|
|
|
+ safeDayChart = echarts.init(safeDayChartRef.value, undefined, {
|
|
|
|
|
+ renderer: CHART_RENDERER
|
|
|
|
|
+ })
|
|
|
|
|
+ safeDayChart.setOption(getSafeDayChartOption(), true)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function updateSafeDayChart() {
|
|
|
|
|
+ if (!safeDayChart) return
|
|
|
|
|
+ safeDayChart.clear()
|
|
|
|
|
+ safeDayChart.setOption(getSafeDayChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resizeSafeDayChart() {
|
|
|
|
|
+ if (!safeDayChart) return
|
|
|
|
|
+ safeDayChart.resize({ animation: { duration: 300 } })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function destroySafeDayChart() {
|
|
|
|
|
+ safeDayChart?.dispose()
|
|
|
|
|
+ safeDayChart = null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getQhseTrendChartOption(): echarts.EChartsOption {
|
|
|
|
|
+ const activeTab =
|
|
|
|
|
+ qhseMetricTabs.find((item) => item.value === activeQhseMetric.value) || qhseMetricTabs[0]
|
|
|
|
|
+ const seriesData = qhseTrendSeries.value[activeQhseMetric.value] || []
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...ANIMATION,
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: 32,
|
|
|
|
|
+ right: 18,
|
|
|
|
|
+ top: 28,
|
|
|
|
|
+ bottom: 28,
|
|
|
|
|
+ containLabel: true
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: createTooltip({
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ confine: true,
|
|
|
|
|
+ appendToBody: false,
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: `${activeTab.accent}99`,
|
|
|
|
|
+ width: 1.5
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ formatter(params: any) {
|
|
|
|
|
+ if (!params || (Array.isArray(params) && params.length === 0)) return ''
|
|
|
|
|
+ const item = Array.isArray(params) ? params[0] : params
|
|
|
|
|
+ return `${item.axisValue}<br/>${activeTab.label}:${item.data}`
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ boundaryGap: false,
|
|
|
|
|
+ data: seriesData.map((item) => item.year),
|
|
|
|
|
+ axisLine: {
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: 'rgba(106, 144, 221, 0.45)'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: '#5b6f8f',
|
|
|
|
|
+ rotate: 28,
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ fontWeight: 600,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ axisLine: { show: false },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: '#8a9bb5',
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ },
|
|
|
|
|
+ splitLine: {
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: 'rgba(104, 139, 205, 0.22)',
|
|
|
|
|
+ type: 'dashed'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ symbol: 'circle',
|
|
|
|
|
+ symbolSize: 9,
|
|
|
|
|
+ showSymbol: true,
|
|
|
|
|
+ data: seriesData.map((item) => item.value),
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ width: 3,
|
|
|
|
|
+ color: activeTab.accent
|
|
|
|
|
+ },
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: activeTab.accent,
|
|
|
|
|
+ borderColor: '#fff',
|
|
|
|
|
+ borderWidth: 2
|
|
|
|
|
+ },
|
|
|
|
|
+ areaStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: `${activeTab.accent}55` },
|
|
|
|
|
+ { offset: 1, color: `${activeTab.accent}08` }
|
|
|
|
|
+ ])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function initQhseTrendChart() {
|
|
|
|
|
+ if (!qhseTrendChartRef.value) return
|
|
|
|
|
+ qhseTrendChart?.dispose()
|
|
|
|
|
+ qhseTrendChart = echarts.init(qhseTrendChartRef.value, undefined, {
|
|
|
|
|
+ renderer: CHART_RENDERER
|
|
|
|
|
+ })
|
|
|
|
|
+ qhseTrendChart.setOption(getQhseTrendChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function updateQhseTrendChart() {
|
|
|
|
|
+ if (!qhseTrendChart) return
|
|
|
|
|
+ qhseTrendChart.clear()
|
|
|
|
|
+ qhseTrendChart.setOption(getQhseTrendChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resizeQhseTrendChart() {
|
|
|
|
|
+ if (!qhseTrendChart) return
|
|
|
|
|
+ qhseTrendChart.resize({ animation: { duration: 300 } })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function destroyQhseTrendChart() {
|
|
|
|
|
+ qhseTrendChart?.dispose()
|
|
|
|
|
+ qhseTrendChart = null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getSocChartOption(): echarts.EChartsOption {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...ANIMATION,
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: 'SOC',
|
|
|
|
|
+ left: '34%',
|
|
|
|
|
+ top: '54%',
|
|
|
|
|
+ textAlign: 'center',
|
|
|
|
|
+ textVerticalAlign: 'middle',
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ color: '#3d5f94',
|
|
|
|
|
+ fontSize: 24,
|
|
|
|
|
+ fontWeight: 800,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: createTooltip({
|
|
|
|
|
+ trigger: 'item',
|
|
|
|
|
+ confine: true,
|
|
|
|
|
+ appendToBody: false,
|
|
|
|
|
+ formatter(params: any) {
|
|
|
|
|
+ if (!params) return ''
|
|
|
|
|
+ return `${params.name}<br/>数量:${params.value}<br/>占比:${params.percent}%`
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ orient: 'vertical',
|
|
|
|
|
+ right: 100,
|
|
|
|
|
+ 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.value.map((item) => ({
|
|
|
|
|
+ name: item.label,
|
|
|
|
|
+ value: item.value,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: item.color
|
|
|
|
|
+ }
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function initSocChart() {
|
|
|
|
|
+ if (!socChartRef.value) return
|
|
|
|
|
+ socChart?.dispose()
|
|
|
|
|
+ socChart = echarts.init(socChartRef.value, undefined, {
|
|
|
|
|
+ renderer: CHART_RENDERER
|
|
|
|
|
+ })
|
|
|
|
|
+ socChart.setOption(getSocChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function updateSocChart() {
|
|
|
|
|
+ if (!socChart) return
|
|
|
|
|
+ socChart.clear()
|
|
|
|
|
+ socChart.setOption(getSocChartOption(), { notMerge: true, lazyUpdate: false })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resizeSocChart() {
|
|
|
|
|
+ if (!socChart) return
|
|
|
|
|
+ socChart.resize({ animation: { duration: 300 } })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function destroySocChart() {
|
|
|
|
|
+ socChart?.dispose()
|
|
|
|
|
+ socChart = null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const tableRowStyle = ({ row }) => {
|
|
|
|
|
+ if (row.expired) {
|
|
|
|
|
+ return { backgroundColor: '#ffe6e6' }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (row.alertWarn) {
|
|
|
|
|
+ return { backgroundColor: '#e19f1a' }
|
|
|
|
|
+ }
|
|
|
|
|
+ return {}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const tableRowClassName = ({ row }) => {
|
|
|
|
|
+ if (row.expired) {
|
|
|
|
|
+ return 'expired-row'
|
|
|
|
|
+ }
|
|
|
|
|
+ if (row.alertWarn) {
|
|
|
|
|
+ return 'alert-warn-row'
|
|
|
|
|
+ }
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatPercent(numerator: number, denominator: number) {
|
|
|
|
|
+ if (!denominator || Number.isNaN(denominator)) {
|
|
|
|
|
+ return '0%'
|
|
|
|
|
+ }
|
|
|
|
|
+ return `${((numerator / denominator) * 100).toFixed(1)}%`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadHomeBoard() {
|
|
|
|
|
+ summaryPanel.value = await kanbanApi.getKanban()
|
|
|
|
|
+
|
|
|
|
|
+ summaryCards.value[3].value = formatPercent(
|
|
|
|
|
+ summaryPanel.value.totdalCert - summaryPanel.value.expiredCert,
|
|
|
|
|
+ summaryPanel.value.totdalCert
|
|
|
|
|
+ )
|
|
|
|
|
+ summaryCards.value[3].note = `Warn: ${summaryPanel.value.warnCert || 0}`
|
|
|
|
|
+
|
|
|
|
|
+ hazardBars.value[0].value = summaryPanel.value.totalHazard || 0
|
|
|
|
|
+ hazardBars.value[1].value =
|
|
|
|
|
+ (summaryPanel.value.totalHazard || 0) - (summaryPanel.value.todoHazard || 0)
|
|
|
|
|
+ hazardBars.value[2].value = summaryPanel.value.todoHazard || 0
|
|
|
|
|
+
|
|
|
|
|
+ safeDay.value = (await kanbanApi.getSafeDay(userStore.getUser.deptId)) || {}
|
|
|
|
|
+ buildSafeDayBarData(safeDay.value)
|
|
|
|
|
+
|
|
|
|
|
+ await Promise.all([getStatic(), getCertStatic(), getInstrumentOverview()])
|
|
|
|
|
+
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ updateHazardChart()
|
|
|
|
|
+ updateSafeDayChart()
|
|
|
|
|
+ updateQhseTrendChart()
|
|
|
|
|
+ updateSocChart()
|
|
|
|
|
+ resizeHazardChart()
|
|
|
|
|
+ resizeSafeDayChart()
|
|
|
|
|
+ resizeQhseTrendChart()
|
|
|
|
|
+ resizeSocChart()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const certLoading = ref(false)
|
|
|
|
|
+const certTableData = ref<any[]>([])
|
|
|
|
|
+const certTotal = ref(0)
|
|
|
|
|
+const certQueryParams = reactive({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+
|
|
|
|
|
+ expired: undefined as string | undefined,
|
|
|
|
|
+ alertWarn: undefined as string | undefined
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const certWarningDialogVisible = ref(false)
|
|
|
|
|
+const certWarningDialogTitle = ref('')
|
|
|
|
|
+const certWarningLoading = ref(false)
|
|
|
|
|
+const certWarningList = ref<any[]>([])
|
|
|
|
|
+const certWarningTotal = ref(0)
|
|
|
|
|
+const certWarningQueryParams = reactive({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ deptId: userStore.getUser.deptId,
|
|
|
|
|
+ type: undefined as string | undefined,
|
|
|
|
|
+ classify: undefined as string | undefined,
|
|
|
|
|
+ userName: undefined as string | undefined,
|
|
|
|
|
+ expired: undefined as boolean | undefined,
|
|
|
|
|
+ alertWarn: undefined as boolean | undefined
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const hazardDialogVisible = ref(false)
|
|
|
|
|
+const hazardDialogTitle = ref('')
|
|
|
|
|
+const hazardDialogLoading = ref(false)
|
|
|
|
|
+const hazardDialogList = ref<any[]>([])
|
|
|
|
|
+const hazardDialogTotal = ref(0)
|
|
|
|
|
+const hazardDialogQueryParams = reactive({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ deptId: userStore.getUser.deptId,
|
|
|
|
|
+ status: undefined as string | undefined,
|
|
|
|
|
+ address: '',
|
|
|
|
|
+ source: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const dangerDialogVisible = ref(false)
|
|
|
|
|
+const dangerDialogTitle = ref('')
|
|
|
|
|
+const dangerDialogLoading = ref(false)
|
|
|
|
|
+const dangerDialogList = ref<any[]>([])
|
|
|
|
|
+const dangerDialogTotal = ref(0)
|
|
|
|
|
+const dangerDialogQueryParams = reactive({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ riskGrade: '',
|
|
|
|
|
+ deptId: userStore.getUser.deptId
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const instrumentDialogVisible = ref(false)
|
|
|
|
|
+const instrumentDialogTitle = ref('')
|
|
|
|
|
+const instrumentDialogLoading = ref(false)
|
|
|
|
|
+const instrumentDialogList = ref<any[]>([])
|
|
|
|
|
+const instrumentDialogTotal = ref(0)
|
|
|
|
|
+const instrumentDialogQueryParams = reactive({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ measureName: undefined as string | undefined,
|
|
|
|
|
+ measureCertNo: undefined as string | undefined,
|
|
|
|
|
+ deptId: userStore.getUser.deptId,
|
|
|
|
|
+ expired: undefined as boolean | undefined,
|
|
|
|
|
+ alertWarn: undefined as boolean | undefined
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const getCertificateTypeText = (type: string) => {
|
|
|
|
|
+ const map: Record<string, string> = {
|
|
|
|
|
+ personal: '个人证书',
|
|
|
|
|
+ organization: '组织证书',
|
|
|
|
|
+ other: '其他'
|
|
|
|
|
+ }
|
|
|
|
|
+ return map[type] || type
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatDateCorrectly(timestamp: any) {
|
|
|
|
|
+ if (!timestamp) return ''
|
|
|
|
|
+ let time = Number(timestamp)
|
|
|
|
|
+ if (time < 10000000000) {
|
|
|
|
|
+ time = time * 1000
|
|
|
|
|
+ }
|
|
|
|
|
+ return formatDate(time).substring(0, 10)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+let dialogFileView = ref(false)
|
|
|
|
|
+let fileList = ref([])
|
|
|
|
|
+const viewFile = (file) => {
|
|
|
|
|
+ fileList.value = file.split(',')
|
|
|
|
|
+ dialogFileView.value = true
|
|
|
|
|
+ // window.open(file)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const viewFileInfo = (file) => {
|
|
|
|
|
+ window.open(
|
|
|
|
|
+ 'http://doc.deepoil.cc:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(file))
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const extractFileName = (url: string): string => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 移除查询参数和哈希
|
|
|
|
|
+ const cleanUrl = url.split('?')[0].split('#')[0]
|
|
|
|
|
+ // 获取最后一个斜杠后的内容
|
|
|
|
|
+ const parts = cleanUrl.split('/')
|
|
|
|
|
+ const fileName = parts[parts.length - 1]
|
|
|
|
|
+ // URL 解码
|
|
|
|
|
+ return decodeURIComponent(fileName) || url
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // 如果解析失败,返回原始 URL
|
|
|
|
|
+ return url
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDownload = async (url) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(url)
|
|
|
|
|
+ const blob = await response.blob()
|
|
|
|
|
+ const downloadUrl = window.URL.createObjectURL(blob)
|
|
|
|
|
+
|
|
|
|
|
+ const link = document.createElement('a')
|
|
|
|
|
+ link.href = downloadUrl
|
|
|
|
|
+ link.download = url.split('/').pop() // 自动获取文件名:ml-citation{ref="3" data="citationList"}
|
|
|
|
|
+ link.click()
|
|
|
|
|
+
|
|
|
|
|
+ URL.revokeObjectURL(downloadUrl)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('下载失败:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getCertificateBoardList() {
|
|
|
|
|
+ certLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await IotCertificateApi.getCertificateList({
|
|
|
|
|
+ ...certQueryParams
|
|
|
|
|
+ // deptId: userStore.user.deptId
|
|
|
|
|
+ // type: 'organization'
|
|
|
|
|
+ })
|
|
|
|
|
+ certTableData.value = data.list || []
|
|
|
|
|
+ certTotal.value = data.total || 0
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ certLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getCertificateWarningList() {
|
|
|
|
|
+ certWarningLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await IotMeasureCertApi.getIotMeasureCertPage(certWarningQueryParams)
|
|
|
|
|
+ certWarningList.value = data.list || []
|
|
|
|
|
+ certWarningTotal.value = data.total || 0
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ certWarningLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getHazardDialogList() {
|
|
|
|
|
+ hazardDialogLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await IotHiddenApi.getHiddenList(hazardDialogQueryParams)
|
|
|
|
|
+ hazardDialogList.value = data.list || []
|
|
|
|
|
+ hazardDialogTotal.value = data.total || 0
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ hazardDialogLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getDangerDialogList() {
|
|
|
|
|
+ dangerDialogLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await IotDangerApi.getDangerList(dangerDialogQueryParams)
|
|
|
|
|
+ dangerDialogList.value = data.list || []
|
|
|
|
|
+ dangerDialogTotal.value = data.total || 0
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ dangerDialogLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function getInstrumentDialogList() {
|
|
|
|
|
+ instrumentDialogLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await IotInstrumentApi.getInstrumentList(instrumentDialogQueryParams)
|
|
|
|
|
+ instrumentDialogList.value = data.list || []
|
|
|
|
|
+ instrumentDialogTotal.value = data.total || 0
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ instrumentDialogLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openHazardDialog(barLabel: string) {
|
|
|
|
|
+ hazardDialogQueryParams.pageNo = 1
|
|
|
|
|
+ hazardDialogQueryParams.pageSize = 10
|
|
|
|
|
+ hazardDialogQueryParams.deptId = userStore.getUser.deptId
|
|
|
|
|
+ hazardDialogQueryParams.address = ''
|
|
|
|
|
+ hazardDialogQueryParams.source = ''
|
|
|
|
|
+
|
|
|
|
|
+ if (barLabel === '未整改') {
|
|
|
|
|
+ hazardDialogTitle.value = '未整改隐患列表'
|
|
|
|
|
+ hazardDialogQueryParams.status = 'todo'
|
|
|
|
|
+ } else if (barLabel === '已整改') {
|
|
|
|
|
+ hazardDialogTitle.value = '已整改隐患列表'
|
|
|
|
|
+ hazardDialogQueryParams.status = 'finished'
|
|
|
|
|
+ } else {
|
|
|
|
|
+ hazardDialogTitle.value = '隐患总数列表'
|
|
|
|
|
+ hazardDialogQueryParams.status = undefined
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ hazardDialogVisible.value = true
|
|
|
|
|
+ getHazardDialogList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openDangerDialog(zone: { title?: string }) {
|
|
|
|
|
+ const riskGradeLabel = String(zone.title || '')
|
|
|
|
|
+ .replace(/区$/, '')
|
|
|
|
|
+ .trim()
|
|
|
|
|
+ const matchedOption = dangerGradeOptions.value.find((item) => item.label === riskGradeLabel)
|
|
|
|
|
+ const riskGrade = matchedOption?.value || ''
|
|
|
|
|
+ dangerDialogQueryParams.pageNo = 1
|
|
|
|
|
+ dangerDialogQueryParams.pageSize = 10
|
|
|
|
|
+ dangerDialogQueryParams.deptId = userStore.getUser.deptId
|
|
|
|
|
+ dangerDialogQueryParams.riskGrade = riskGrade
|
|
|
|
|
+ dangerDialogTitle.value = riskGradeLabel ? `${riskGradeLabel}风险列表` : '风险列表'
|
|
|
|
|
+ dangerDialogVisible.value = true
|
|
|
|
|
+ getDangerDialogList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openInstrumentDialog(type: 'all' | 'expired') {
|
|
|
|
|
+ instrumentDialogQueryParams.pageNo = 1
|
|
|
|
|
+ instrumentDialogQueryParams.pageSize = 10
|
|
|
|
|
+ instrumentDialogQueryParams.measureName = undefined
|
|
|
|
|
+ instrumentDialogQueryParams.measureCertNo = undefined
|
|
|
|
|
+ instrumentDialogQueryParams.deptId = userStore.getUser.deptId
|
|
|
|
|
+ instrumentDialogQueryParams.alertWarn = undefined
|
|
|
|
|
+
|
|
|
|
|
+ if (type === 'expired') {
|
|
|
|
|
+ instrumentDialogTitle.value = '待检设备列表'
|
|
|
|
|
+ instrumentDialogQueryParams.expired = true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ instrumentDialogTitle.value = '在用设备列表'
|
|
|
|
|
+ instrumentDialogQueryParams.expired = undefined
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ instrumentDialogVisible.value = true
|
|
|
|
|
+ getInstrumentDialogList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleHazardBarClick(params: { name?: string }) {
|
|
|
|
|
+ if (!params?.name) return
|
|
|
|
|
+ openHazardDialog(params.name)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openCertificateWarningDialog(type: 'expired' | 'warn') {
|
|
|
|
|
+ certWarningQueryParams.pageNo = 1
|
|
|
|
|
+ certWarningQueryParams.pageSize = 10
|
|
|
|
|
+ certWarningQueryParams.deptId = userStore.getUser.deptId
|
|
|
|
|
+ certWarningQueryParams.type = undefined
|
|
|
|
|
+ certWarningQueryParams.classify = undefined
|
|
|
|
|
+ certWarningQueryParams.userName = undefined
|
|
|
|
|
+
|
|
|
|
|
+ if (type === 'expired') {
|
|
|
|
|
+ certWarningDialogTitle.value = '证件过期'
|
|
|
|
|
+ certWarningQueryParams.expired = true
|
|
|
|
|
+ certWarningQueryParams.alertWarn = undefined
|
|
|
|
|
+ } else {
|
|
|
|
|
+ certWarningDialogTitle.value = '证件即将到期'
|
|
|
|
|
+ certWarningQueryParams.expired = undefined
|
|
|
|
|
+ certWarningQueryParams.alertWarn = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ certWarningDialogVisible.value = true
|
|
|
|
|
+ getCertificateWarningList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleCertificateQuery() {
|
|
|
|
|
+ certQueryParams.pageNo = 1
|
|
|
|
|
+ getCertificateBoardList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetCertificateQuery() {
|
|
|
|
|
+ certQueryParams.pageNo = 1
|
|
|
|
|
+ certQueryParams.pageSize = 10
|
|
|
|
|
+
|
|
|
|
|
+ certQueryParams.expired = undefined
|
|
|
|
|
+ certQueryParams.alertWarn = undefined
|
|
|
|
|
+ getCertificateBoardList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => activeSummaryTab.value,
|
|
|
|
|
+ async (value) => {
|
|
|
|
|
+ if (value === 'certificate') {
|
|
|
|
|
+ await getCertificateBoardList()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ resizeHazardChart()
|
|
|
|
|
+ resizeSafeDayChart()
|
|
|
|
|
+ resizeQhseTrendChart()
|
|
|
|
|
+ resizeSocChart()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => type.value,
|
|
|
|
|
+ (value) => {
|
|
|
|
|
+ timeVal.value = getCurrentPickerValue(value)
|
|
|
|
|
+ }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ // supportsZoom.value = typeof CSS !== 'undefined' && CSS.supports?.('zoom', '1') === true
|
|
|
|
|
+ nextTick(updateScale)
|
|
|
|
|
+ resizeObserver = new ResizeObserver(updateScale)
|
|
|
|
|
+ if (wrapperRef.value) {
|
|
|
|
|
+ resizeObserver.observe(wrapperRef.value)
|
|
|
|
|
+ }
|
|
|
|
|
+ initHazardChart()
|
|
|
|
|
+ initSafeDayChart()
|
|
|
|
|
+ initQhseTrendChart()
|
|
|
|
|
+ initSocChart()
|
|
|
|
|
+ window.addEventListener('resize', updateScale)
|
|
|
|
|
+ window.addEventListener('resize', resizeHazardChart)
|
|
|
|
|
+ window.addEventListener('resize', resizeSafeDayChart)
|
|
|
|
|
+ window.addEventListener('resize', resizeQhseTrendChart)
|
|
|
|
|
+ window.addEventListener('resize', resizeSocChart)
|
|
|
|
|
+
|
|
|
|
|
+ await loadHomeBoard()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ resizeObserver?.disconnect()
|
|
|
|
|
+ window.removeEventListener('resize', updateScale)
|
|
|
|
|
+ window.removeEventListener('resize', resizeHazardChart)
|
|
|
|
|
+ window.removeEventListener('resize', resizeSafeDayChart)
|
|
|
|
|
+ window.removeEventListener('resize', resizeQhseTrendChart)
|
|
|
|
|
+ window.removeEventListener('resize', resizeSocChart)
|
|
|
|
|
+ cancelAnimationFrame(resizeRaf)
|
|
|
|
|
+ destroyHazardChart()
|
|
|
|
|
+ destroySafeDayChart()
|
|
|
|
|
+ destroyQhseTrendChart()
|
|
|
|
|
+ destroySocChart()
|
|
|
|
|
+})
|
|
|
|
|
+</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="summary-toolbar">
|
|
|
|
|
+ <div class="summary-toolbar__tabs">
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="tab in summaryTabs"
|
|
|
|
|
+ :key="tab.value"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="summary-toolbar__tab"
|
|
|
|
|
+ :class="{ 'is-active': activeSummaryTab === tab.value }"
|
|
|
|
|
+ @click="activeSummaryTab = tab.value"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ tab.label }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="activeSummaryTab === 'home'" class="summary-toolbar__date">
|
|
|
|
|
+ <span class="summary-toolbar__date-label">日期:</span>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="flex items-center gap-2 datepicker">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="timeVal"
|
|
|
|
|
+ :teleport="true"
|
|
|
|
|
+ :type="type === 'day' ? 'date' : type === 'month' ? 'month' : 'year'"
|
|
|
|
|
+ class="summary-toolbar__picker el-date-picker"
|
|
|
|
|
+ popper-class="summary-toolbar__picker-popper"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ range-separator="~"
|
|
|
|
|
+ start-placeholder="开始"
|
|
|
|
|
+ end-placeholder="结束"
|
|
|
|
|
+ @change="handelChange"
|
|
|
|
|
+ style="width: 150px; height: 40px; margin-left: 10px; background-color: transparent"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">月度</span>
|
|
|
|
|
+ <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">季度</span>
|
|
|
|
|
+ <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">年度</span>
|
|
|
|
|
+ <span class="bg-[#f5f9ff] rounded-2xl px-2 py-0 cursor-pointer">初始化</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="activeSummaryTab === 'home'" 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>
|
|
|
|
|
+ QHSE关键数据
|
|
|
|
|
+ </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="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 risk-card--clickable"
|
|
|
|
|
+ @click="openDangerDialog(zone)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="risk-card__title">
|
|
|
|
|
+ <span class="risk-card__dot" :style="{ background: zone.color }"></span>
|
|
|
|
|
+ <span :style="{ color: zone.color }">{{ zone.title }}</span>
|
|
|
|
|
+ <CountTo
|
|
|
|
|
+ :duration="2600"
|
|
|
|
|
+ :end-val="zone.value"
|
|
|
|
|
+ :start-val="0"
|
|
|
|
|
+ :style="{ color: zone.color, fontSize: '22px' }"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="risk-card__desc">{{ zone.desc }}</div>
|
|
|
|
|
+ </article>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="risk-hazard-block mt-2!">
|
|
|
|
|
+ <div
|
|
|
|
|
+ ref="hazardChartRef"
|
|
|
|
|
+ class="chart-panel chart-panel--echart risk-hazard-block__chart"
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="left-column">
|
|
|
|
|
+ <section class="panel board-panel kb-stage-card kb-stage-card--3">
|
|
|
|
|
+ <div class="panel-title panel-title--center">
|
|
|
|
|
+ <span class="icon-decorator"><span></span><span></span></span>
|
|
|
|
|
+ QHSE指标
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="board-panel kb-stage-card kb-stage-card--5">
|
|
|
|
|
+ <div ref="safeDayChartRef" class="safe-day-chart-panel"></div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="board-panel kb-stage-card kb-stage-card--5">
|
|
|
|
|
+ <div class="qhse-metric-tabs">
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="item in qhseMetricTabs"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :title="item.label2"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="qhse-metric-tab"
|
|
|
|
|
+ :class="{ 'is-active': activeQhseMetric === item.value }"
|
|
|
|
|
+ @click="
|
|
|
|
|
+ () => {
|
|
|
|
|
+ activeQhseMetric = item.value
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ updateQhseTrendChart()
|
|
|
|
|
+ resizeQhseTrendChart()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ "
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ item.label }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div ref="qhseTrendChartRef" class="qhse-trend-chart-panel"></div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="right-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>
|
|
|
|
|
+ <section class="board-panel kb-stage-card kb-stage-card--5 pt-2">
|
|
|
|
|
+ <div ref="socChartRef" class="soc-chart-panel"></div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="board-panel kb-stage-card kb-stage-card--6 pl-4">
|
|
|
|
|
+ <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 qualification-item--clickable"
|
|
|
|
|
+ @click="openCertificateWarningDialog(item.key)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="qualification-item__label">{{ item.label }}:</span>
|
|
|
|
|
+ <strong :style="{ color: item.accent }">
|
|
|
|
|
+ <CountTo
|
|
|
|
|
+ :duration="2600"
|
|
|
|
|
+ :end-val="item.value"
|
|
|
|
|
+ :start-val="0"
|
|
|
|
|
+ :style="{ color: item.accent }"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="pl-2">人</span>
|
|
|
|
|
+ </strong>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </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">
|
|
|
|
|
+ <template v-if="card.title === '安全检测'">
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="bottom-card__action"
|
|
|
|
|
+ @click="openInstrumentDialog('all')"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ card.lines[0] }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="bottom-card__action"
|
|
|
|
|
+ @click="openInstrumentDialog('expired')"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ card.lines[1] }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <p v-for="line in card.lines" :key="line">{{ line }}</p>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </article>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="certificate-board">
|
|
|
|
|
+ <section class="panel certificate-filter-panel">
|
|
|
|
|
+ <div class="certificate-filters">
|
|
|
|
|
+ <div class="certificate-filter-item">
|
|
|
|
|
+ <span class="certificate-filter-item__label">是否过期</span>
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="certQueryParams.expired"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ placeholder="请选择是否过期"
|
|
|
|
|
+ class="certificate-filter-item__control"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
|
|
|
|
+ :key="dict.value"
|
|
|
|
|
+ :label="dict.label"
|
|
|
|
|
+ :value="dict.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="certificate-filter-item">
|
|
|
|
|
+ <span class="certificate-filter-item__label">是否预警</span>
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="certQueryParams.alertWarn"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ placeholder="请选择是否过期"
|
|
|
|
|
+ class="certificate-filter-item__control"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
|
|
|
|
+ :key="dict.value"
|
|
|
|
|
+ :label="dict.label"
|
|
|
|
|
+ :value="dict.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="certificate-filter-actions">
|
|
|
|
|
+ <el-button type="primary" :icon="Search" @click="handleCertificateQuery"
|
|
|
|
|
+ >查询</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button :icon="Refresh" @click="resetCertificateQuery">重置</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="panel certificate-table-panel">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :loading="certLoading"
|
|
|
|
|
+ :data="certTableData"
|
|
|
|
|
+ :row-style="tableRowStyle"
|
|
|
|
|
+ :row-class-name="tableRowClassName"
|
|
|
|
|
+ height="100vh"
|
|
|
|
|
+ :header-cell-style="{
|
|
|
|
|
+ height: '42px'
|
|
|
|
|
+ }"
|
|
|
|
|
+ class="device-list-table production-brief-table"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column label="序号" width="80" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ (certQueryParams.pageNo - 1) * certQueryParams.pageSize + scope.$index + 1 }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="证书类别" align="center" width="150" prop="classify">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag :type="DICT_TYPE.ORG_CERT" :value="scope.row.classify" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="证书名称"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="certName"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="所在部门" align="center" prop="deptName" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="颁发机构" align="center" prop="certOrg" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="证书标准" align="center" prop="certStandard" />
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="颁发时间" align="center" prop="certIssue">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatDateCorrectly(scope.row.certIssue) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="有效期" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatDateCorrectly(scope.row.certExpire) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="到期提醒" align="center">
|
|
|
|
|
+ <template #default="scope"> {{ scope.row.noticeBefore }}天前提醒 </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="备注" align="center" prop="remark" />
|
|
|
|
|
+ <el-table-column label="操作" align="center" fixed="right" min-width="180px" action>
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ v-if="scope.row.certPic"
|
|
|
|
|
+ @click="viewFile(scope.row.certPic)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 查看证书
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="certificate-pagination">
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="certTotal"
|
|
|
|
|
+ v-model:page="certQueryParams.pageNo"
|
|
|
|
|
+ v-model:limit="certQueryParams.pageSize"
|
|
|
|
|
+ @pagination="getCertificateBoardList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="shadow rounded-lg p-3 min-w-0 flex gap-4 justify-between">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-[#fef0f0] text-[#f57979] px-3 flex-1 flex items-center justify-center text-center py-2 text-sm rounded-md"
|
|
|
|
|
+ ><el-icon class="stats-card__icon" :size="28">
|
|
|
|
|
+ <Icon icon="ep:info-filled" /> </el-icon
|
|
|
|
|
+ >证书已过期红色预警</div
|
|
|
|
|
+ >
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-[#fdf6ec] flex-1 text-[#e6a23c] text-center flex items-center justify-center px-3 py-2 text-sm rounded-md"
|
|
|
|
|
+ ><el-icon class="stats-card__icon" :size="28">
|
|
|
|
|
+ <Icon icon="ep:bell-filled" /> </el-icon
|
|
|
|
|
+ >证书60天橙色预警</div
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <Dialog v-model="dialogFileView" title="附件">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(file, index) in fileList"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="mt-5 flex min-w-0 items-center justify-between gap-5"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="file-name-text flex-1 min-w-0">{{ extractFileName(file) }}</span>
|
|
|
|
|
+ <div class="flex shrink-0 items-center gap-5 flex-nowrap">
|
|
|
|
|
+ <el-button link type="primary" @click="viewFileInfo(file)">
|
|
|
|
|
+ <Icon icon="ep:view" class="mr-2px" />查看
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button link type="primary" @click="handleDownload(file)">
|
|
|
|
|
+ <Icon icon="ep:download" class="mr-2px" />下载
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer mt-10">
|
|
|
|
|
+ <el-button type="primary" @click="dialogFileView = false">确认</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <Dialog v-model="certWarningDialogVisible" :title="certWarningDialogTitle" width="1100px">
|
|
|
|
|
+ <div class="certificate-warning-dialog">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :loading="certWarningLoading"
|
|
|
|
|
+ :data="certWarningList"
|
|
|
|
|
+ :row-style="tableRowStyle"
|
|
|
|
|
+ :row-class-name="tableRowClassName"
|
|
|
|
|
+ height="400"
|
|
|
|
|
+ class="device-list-table production-brief-table"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column label="序号" width="80" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{
|
|
|
|
|
+ (certWarningQueryParams.pageNo - 1) * certWarningQueryParams.pageSize +
|
|
|
|
|
+ scope.$index +
|
|
|
|
|
+ 1
|
|
|
|
|
+ }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="证书类型" align="center" prop="type" width="120">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ getCertificateTypeText(scope.row.type) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="证书类别" align="center" width="150" prop="classify">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag
|
|
|
|
|
+ v-if="scope.row.type === 'organization'"
|
|
|
|
|
+ :type="DICT_TYPE.ORG_CERT"
|
|
|
|
|
+ :value="scope.row.classify"
|
|
|
|
|
+ />
|
|
|
|
|
+ <dict-tag v-else :type="DICT_TYPE.PERSON_CERT" :value="scope.row.classify" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="证书名称"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="certName"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ width="100"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column label="所属人" align="center" prop="userName" width="100" />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="所在部门"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="deptName"
|
|
|
|
|
+ width="100"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="颁发机构"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="certOrg"
|
|
|
|
|
+ width="180"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="证书标准"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="certStandard"
|
|
|
|
|
+ width="100"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column label="颁发时间" align="center" prop="certIssue" width="120">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatDateCorrectly(scope.row.certIssue) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="有效期" align="center" prop="certExpire" width="120">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatDateCorrectly(scope.row.certExpire) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="操作" align="center" fixed="right" min-width="100px">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ v-if="scope.row.certPic"
|
|
|
|
|
+ @click="viewFile(scope.row.certPic)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 查看证书
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="certificate-pagination">
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="certWarningTotal"
|
|
|
|
|
+ v-model:page="certWarningQueryParams.pageNo"
|
|
|
|
|
+ v-model:limit="certWarningQueryParams.pageSize"
|
|
|
|
|
+ @pagination="getCertificateWarningList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <Dialog v-model="hazardDialogVisible" :title="hazardDialogTitle" width="1400px">
|
|
|
|
|
+ <div class="certificate-warning-dialog">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :loading="hazardDialogLoading"
|
|
|
|
|
+ :data="hazardDialogList"
|
|
|
|
|
+ height="400"
|
|
|
|
|
+ class="device-list-table production-brief-table"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column label="序号" width="80" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{
|
|
|
|
|
+ (hazardDialogQueryParams.pageNo - 1) * hazardDialogQueryParams.pageSize +
|
|
|
|
|
+ scope.$index +
|
|
|
|
|
+ 1
|
|
|
|
|
+ }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="单位" align="center" prop="deptName" min-width="140" />
|
|
|
|
|
+ <el-table-column label="类型" align="center" prop="classifyName" min-width="120" />
|
|
|
|
|
+ <el-table-column label="来源" align="center" min-width="110">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag :type="DICT_TYPE.QHSE_HAZARD_SOURCE" :value="scope.row.source" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="地点" align="center" prop="address" min-width="140" />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="存在问题"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="problem"
|
|
|
|
|
+ min-width="180"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column label="隐患附件" align="center" min-width="100">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button v-if="row.hazardFile" link type="primary" @click="viewFile(row.hazardFile)">
|
|
|
|
|
+ 查看
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="整改人"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="correctPersonName"
|
|
|
|
|
+ min-width="100"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="整改要求"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="correctStandard"
|
|
|
|
|
+ min-width="160"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="整改情况"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="rectifyDesc"
|
|
|
|
|
+ min-width="160"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column label="整改附件" align="center" min-width="100">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="row.rectifyFile"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="viewFile(row.rectifyFile)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 查看
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="排查时间" align="center" min-width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatDateCorrectly(row.checkTime) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="状态" align="center" min-width="90">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag :type="DICT_TYPE.QHSE_HAZARD_STATUS" :value="scope.row.status" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="备注"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="remark"
|
|
|
|
|
+ min-width="120"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="certificate-pagination">
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="hazardDialogTotal"
|
|
|
|
|
+ v-model:page="hazardDialogQueryParams.pageNo"
|
|
|
|
|
+ v-model:limit="hazardDialogQueryParams.pageSize"
|
|
|
|
|
+ @pagination="getHazardDialogList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <Dialog v-model="dangerDialogVisible" :title="dangerDialogTitle" width="1400px">
|
|
|
|
|
+ <div class="certificate-warning-dialog">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :loading="dangerDialogLoading"
|
|
|
|
|
+ :data="dangerDialogList"
|
|
|
|
|
+ height="400"
|
|
|
|
|
+ class="device-list-table production-brief-table"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column label="序号" width="80" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{
|
|
|
|
|
+ (dangerDialogQueryParams.pageNo - 1) * dangerDialogQueryParams.pageSize +
|
|
|
|
|
+ scope.$index +
|
|
|
|
|
+ 1
|
|
|
|
|
+ }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="区域/位置"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="region"
|
|
|
|
|
+ min-width="140"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="危险因素描述"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="elementDescription"
|
|
|
|
|
+ min-width="180"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="可导致的后果"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="maybeResult"
|
|
|
|
|
+ min-width="180"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column label="风险评价" align="center">
|
|
|
|
|
+ <el-table-column prop="evalKn" label="可能性 (L)" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.evalKn }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="evalYz" label="严重性 (S)" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.evalYz }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="evalFxz" label="风险值 (R)" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.evalFxz }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="riskGrade" label="风险等级" width="100" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag :type="DICT_TYPE.DANGER_GRADE" :value="scope.row.riskGrade" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="控制措施"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="controlMethod"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="责任人"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="charge"
|
|
|
|
|
+ min-width="100"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="certificate-pagination">
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="dangerDialogTotal"
|
|
|
|
|
+ v-model:page="dangerDialogQueryParams.pageNo"
|
|
|
|
|
+ v-model:limit="dangerDialogQueryParams.pageSize"
|
|
|
|
|
+ @pagination="getDangerDialogList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <Dialog v-model="instrumentDialogVisible" :title="instrumentDialogTitle" width="1400px">
|
|
|
|
|
+ <div class="certificate-warning-dialog">
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :loading="instrumentDialogLoading"
|
|
|
|
|
+ :data="instrumentDialogList"
|
|
|
|
|
+ :row-style="tableRowStyle"
|
|
|
|
|
+ :row-class-name="tableRowClassName"
|
|
|
|
|
+ height="400"
|
|
|
|
|
+ class="device-list-table production-brief-table"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column label="序号" width="80" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{
|
|
|
|
|
+ (instrumentDialogQueryParams.pageNo - 1) * instrumentDialogQueryParams.pageSize +
|
|
|
|
|
+ scope.$index +
|
|
|
|
|
+ 1
|
|
|
|
|
+ }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="名称" align="center" prop="measureName" min-width="140" />
|
|
|
|
|
+ <el-table-column label="编码" align="center" prop="measureCode" min-width="120" />
|
|
|
|
|
+ <el-table-column label="序列号" align="center" prop="serialNo" min-width="120" />
|
|
|
|
|
+ <el-table-column label="部门名称" align="center" prop="deptName" min-width="140" />
|
|
|
|
|
+ <el-table-column label="计量单位" align="center" prop="measureUnit" min-width="100" />
|
|
|
|
|
+ <el-table-column label="责任人" align="center" prop="dutyPerson" min-width="100" />
|
|
|
|
|
+ <el-table-column label="品牌" align="center" prop="brand" min-width="100" />
|
|
|
|
|
+ <el-table-column label="规格型号" align="center" prop="modelName" min-width="120" />
|
|
|
|
|
+ <el-table-column label="分类" align="center" min-width="120">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag :type="DICT_TYPE.MEASURE_TYPE" :value="scope.row.classify" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="采购日期" align="center" min-width="120">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatDateCorrectly(scope.row.buyDate) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="价格" align="center" prop="measurePrice" min-width="100" />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="备注"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ prop="remark"
|
|
|
|
|
+ min-width="140"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="certificate-pagination">
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="instrumentDialogTotal"
|
|
|
|
|
+ v-model:page="instrumentDialogQueryParams.pageNo"
|
|
|
|
|
+ v-model:limit="instrumentDialogQueryParams.pageSize"
|
|
|
|
|
+ @pagination="getInstrumentDialogList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+@import url('@/styles/qhse_kb.scss');
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-select__wrapper) {
|
|
|
|
|
+ // height: 38px !important;
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+}
|
|
|
|
|
+.qhse-board {
|
|
|
|
|
+ color: #24364f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.board-body {
|
|
|
|
|
+ padding: 5px 18px 24px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 10px 18px 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__tabs {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__tab {
|
|
|
|
|
+ min-width: 120px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ padding: 0 22px;
|
|
|
|
|
+ font-family: YouSheBiaoTiHei, sans-serif;
|
|
|
|
|
+ font-size: 17px;
|
|
|
|
|
+ color: #2b76e9;
|
|
|
|
|
+ background: linear-gradient(180deg, rgb(255 255 255 / 92%) 0%, rgb(228 239 255 / 90%) 100%);
|
|
|
|
|
+ border: 1px solid rgb(126 174 244 / 44%);
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 96%),
|
|
|
|
|
+ 0 8px 14px rgb(63 103 171 / 8%);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition:
|
|
|
|
|
+ transform 0.2s ease,
|
|
|
|
|
+ box-shadow 0.2s ease,
|
|
|
|
|
+ background 0.2s ease,
|
|
|
|
|
+ color 0.2s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__tab.is-active {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: linear-gradient(180deg, #59a6ff 0%, #2d78ea 100%);
|
|
|
|
|
+ border-color: rgb(62 122 223 / 88%);
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 22%),
|
|
|
|
|
+ 0 10px 18px rgb(45 120 234 / 20%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__tab:hover {
|
|
|
|
|
+ transform: translateY(-1px);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__date {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__date-label {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #5c7393;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bottom-card__action {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ color: #617491;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: color 0.2s ease;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #5c7393;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bottom-card__action:hover {
|
|
|
|
|
+ color: #2d78ea;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-card--clickable {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition:
|
|
|
|
|
+ transform 0.2s ease,
|
|
|
|
|
+ box-shadow 0.2s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-card--clickable:hover {
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__picker {
|
|
|
|
|
+ width: 190px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__picker :deep(.el-input__wrapper) {
|
|
|
|
|
+ min-height: 38px;
|
|
|
|
|
+ background: rgb(255 255 255 / 80%);
|
|
|
|
|
+ border: 1px solid rgb(137 176 235 / 38%);
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 92%),
|
|
|
|
|
+ 0 8px 14px rgb(63 103 171 / 6%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__picker :deep(.el-input__inner),
|
|
|
|
|
+.summary-toolbar__picker :deep(.el-range-input),
|
|
|
|
|
+.summary-toolbar__picker :deep(.el-input__prefix),
|
|
|
|
|
+.summary-toolbar__picker :deep(.el-input__suffix) {
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__picker :deep(.el-input__inner::placeholder),
|
|
|
|
|
+.summary-toolbar__picker :deep(.el-range-input::placeholder) {
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper) {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper .el-date-table),
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper .el-date-table th),
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper .el-date-table td),
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper .el-month-table),
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper .el-year-table),
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper .el-picker-panel__icon-btn),
|
|
|
|
|
+:deep(.summary-toolbar__picker-popper .el-date-picker__header-label) {
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-panel {
|
|
|
|
|
+ padding: 0 10px 10px;
|
|
|
|
|
+ height: 200px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
|
|
|
+ gap: 24px;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-tile {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ height: 136px;
|
|
|
|
|
+ padding: 28px 18px 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: 30px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ color: #114a9b;
|
|
|
|
|
+ letter-spacing: 1px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-tile__note {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.board-main {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 1.02fr 1fr 0.98fr;
|
|
|
|
|
+ gap: 28px;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.left-column,
|
|
|
|
|
+.right-column {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: 24px;
|
|
|
|
|
+ align-content: start;
|
|
|
|
|
+}
|
|
|
|
|
+.left-column {
|
|
|
|
|
+ height: 540px;
|
|
|
|
|
+}
|
|
|
|
|
+.board-panel {
|
|
|
|
|
+ min-height: 258px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.board-panel.kb-stage-card:hover {
|
|
|
|
|
+ transform: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.board-panel--center {
|
|
|
|
|
+ min-height: 540px;
|
|
|
|
|
+ height: 540px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chart-panel {
|
|
|
|
|
+ margin-top: 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chart-panel--echart {
|
|
|
|
|
+ height: 188px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.safe-day-chart-panel {
|
|
|
|
|
+ height: 230px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qhse-metric-tabs {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: left;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+ padding-left: 30px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qhse-metric-tab {
|
|
|
|
|
+ min-height: 24px;
|
|
|
|
|
+ padding: 5px 12px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ line-height: 1.35;
|
|
|
|
|
+ color: #4d6487;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ background: linear-gradient(180deg, rgb(255 255 255 / 86%) 0%, rgb(228 239 255 / 82%) 100%);
|
|
|
|
|
+ border: 1px solid rgb(118 167 238 / 28%);
|
|
|
|
|
+ border-radius: 14px;
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 92%),
|
|
|
|
|
+ 0 10px 16px rgb(52 94 164 / 8%);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition:
|
|
|
|
|
+ transform 0.2s ease,
|
|
|
|
|
+ box-shadow 0.2s ease,
|
|
|
|
|
+ color 0.2s ease,
|
|
|
|
|
+ background 0.2s ease,
|
|
|
|
|
+ border-color 0.2s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qhse-metric-tab:hover {
|
|
|
|
|
+ transform: translateY(-1px);
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 94%),
|
|
|
|
|
+ 0 12px 18px rgb(52 94 164 / 10%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qhse-metric-tab.is-active {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: linear-gradient(180deg, #63adff 0%, #347eea 100%);
|
|
|
|
|
+ border-color: rgb(62 122 223 / 76%);
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset 0 1px 0 rgb(255 255 255 / 18%),
|
|
|
|
|
+ 0 12px 20px rgb(45 120 234 / 22%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qhse-trend-chart-panel {
|
|
|
|
|
+ height: 220px;
|
|
|
|
|
+ // margin-top: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.soc-chart-panel {
|
|
|
|
|
+ height: 280px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.panel-title--center {
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding-left: 0;
|
|
|
|
|
+ font-size: 32px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.panel-title--center .icon-decorator {
|
|
|
|
|
+ left: 28px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ padding: 10px 24px 12px;
|
|
|
|
|
+ // margin-top: 18px;
|
|
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
+ gap: 10px 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-hazard-block {
|
|
|
|
|
+ padding: 0 24px 5px;
|
|
|
|
|
+ margin-top: 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-hazard-block__title {
|
|
|
|
|
+ margin-top: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-hazard-block__chart {
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-card {
|
|
|
|
|
+ min-height: 124px;
|
|
|
|
|
+ padding: 20px 18px;
|
|
|
|
|
+ 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: 16px;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-card__dot {
|
|
|
|
|
+ width: 14px;
|
|
|
|
|
+ height: 14px;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ box-shadow: 0 0 0 4px rgb(255 255 255 / 35%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.risk-card__desc {
|
|
|
|
|
+ margin-top: 18px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #6f819a;
|
|
|
|
|
+ line-height: 1.35;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qualification-panel {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ margin-top: 26px;
|
|
|
|
|
+ padding: 28px;
|
|
|
|
|
+ grid-template-columns: 140px 1fr;
|
|
|
|
|
+ padding-left: 120px;
|
|
|
|
|
+ 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--clickable {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition:
|
|
|
|
|
+ transform 0.18s ease,
|
|
|
|
|
+ opacity 0.18s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qualification-item--clickable:hover {
|
|
|
|
|
+ transform: translateX(4px);
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qualification-item strong {
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-warning-dialog {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bottom-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
|
|
|
+ gap: 24px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bottom-card {
|
|
|
|
|
+ min-height: 180px;
|
|
|
|
|
+ padding: 15px 22px 0;
|
|
|
|
|
+ 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: 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bottom-card__content p {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #5d718e;
|
|
|
|
|
+ line-height: 1.7;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-board {
|
|
|
|
|
+ padding: 16px 18px 24px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-panel {
|
|
|
|
|
+ padding: 18px 20px 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filters {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
|
|
|
|
|
+ gap: 18px 20px;
|
|
|
|
|
+ align-items: end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-item__label {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #4f6583;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-item__control {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-item__control :deep(.el-select__wrapper),
|
|
|
|
|
+.certificate-filter-item__control :deep(.el-input__wrapper) {
|
|
|
|
|
+ height: 42px !important;
|
|
|
|
|
+ min-height: 42px !important;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-actions :deep(.el-button) {
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+ line-height: 42px;
|
|
|
|
|
+ padding: 0 22px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-item__control :deep(.el-input__inner) {
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+ line-height: 42px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-filter-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-table-panel {
|
|
|
|
|
+ margin-top: 18px;
|
|
|
|
|
+ padding: 18px 18px 12px;
|
|
|
|
|
+ height: 860px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-table {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.file-name-text {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// :deep(.el-input__inner) {
|
|
|
|
|
+// font-size: 14px !important;
|
|
|
|
|
+// border: none !important;
|
|
|
|
|
+// font-size: 14px !important;
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+:deep(.datepicker .el-input__wrapper) {
|
|
|
|
|
+ background: transparent !important;
|
|
|
|
|
+ border: none !important;
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-input) {
|
|
|
|
|
+ border: none !important;
|
|
|
|
|
+ outline: none !important;
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-date-editor.el-input, .el-date-editor.el-input__inner) {
|
|
|
|
|
+ border: none !important;
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-date-editor.is-active) {
|
|
|
|
|
+ border-color: #ff6a00 !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.certificate-pagination {
|
|
|
|
|
+ padding-top: 0px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.production-brief-table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-table__header-wrapper th.el-table__cell) {
|
|
|
|
|
+ font-size: calc(16px * var(--kb-scale, 1));
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-table__body td.el-table__cell) {
|
|
|
|
|
+ padding: calc(7px * var(--kb-scale, 1)) 0;
|
|
|
|
|
+ font-size: calc(14px * var(--kb-scale, 1));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-toolbar__picker {
|
|
|
|
|
+ :deep(*) {
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(input) {
|
|
|
|
|
+ font-size: 14px !important;
|
|
|
|
|
+ line-height: 14px !important;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 过期行的红色背景 - 基础状态 */
|
|
|
|
|
+:deep(.el-table__body tr.expired-row > td.el-table__cell) {
|
|
|
|
|
+ background-color: #ffe6e6 !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 过期行 - 鼠标悬浮状态 */
|
|
|
|
|
+:deep(.el-table__body tr.expired-row:hover > td.el-table__cell) {
|
|
|
|
|
+ background-color: #ffcccc !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保斑马纹不影响过期行 */
|
|
|
|
|
+:deep(.el-table__body tr.expired-row.el-table__row--striped > td.el-table__cell) {
|
|
|
|
|
+ background-color: #ffe6e6 !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-table__body tr.expired-row.el-table__row--striped:hover > td.el-table__cell) {
|
|
|
|
|
+ background-color: #ffcccc !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 预警行的橙色背景 - 基础状态 */
|
|
|
|
|
+:deep(.el-table__body tr.alert-warn-row > td.el-table__cell) {
|
|
|
|
|
+ background-color: #fff1df !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 预警行 - 鼠标悬浮状态 */
|
|
|
|
|
+:deep(.el-table__body tr.alert-warn-row:hover > td.el-table__cell) {
|
|
|
|
|
+ background-color: #ffe2bf !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保斑马纹不影响预警行 */
|
|
|
|
|
+:deep(.el-table__body tr.alert-warn-row.el-table__row--striped > td.el-table__cell) {
|
|
|
|
|
+ background-color: #fff1df !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-table__body tr.alert-warn-row.el-table__row--striped:hover > td.el-table__cell) {
|
|
|
|
|
+ background-color: #ffe2bf !important;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|