yanghao 1 неделя назад
Родитель
Сommit
4c15389c54

+ 4 - 0
src/api/pms/qhse/index.ts

@@ -394,6 +394,10 @@ export const IotSocSummaryApi = {
   // 下载SOC卡
   downloadIotSocSummary: async (id) => {
     return await request.download({ url: `/rq/iot-soc-summary/safety-card/download/${id}` })
+  },
+  // 统计
+  getSocSummaryStatistics: async (id) => {
+    return await request.get({ url: `/rq/iot-soc-summary/stat?deptId=${id}` })
   }
 }
 

+ 8 - 0
src/components/Echart/src/Echart.vue

@@ -15,6 +15,10 @@ import 'echarts/lib/component/markArea'
 
 defineOptions({ name: 'EChart' })
 
+const emit = defineEmits<{
+  chartClick: [params: any]
+}>()
+
 const { getPrefixCls, variables } = useDesign()
 
 const prefixCls = getPrefixCls('echart')
@@ -64,6 +68,10 @@ const initChart = () => {
   if (unref(elRef) && props.options) {
     echartRef = echarts.init(unref(elRef) as HTMLElement)
     echartRef?.setOption(unref(options))
+    echartRef?.off('click')
+    echartRef?.on('click', (params) => {
+      emit('chartClick', params)
+    })
   }
 }
 

+ 177 - 38
src/views/pms/qhse/socSummary/index.vue

@@ -4,6 +4,23 @@
     <DeptTree @node-click="handleDeptNodeClick" v-model:collapsed="isLeftContentCollapsed" />
 
     <el-col :span="isLeftContentCollapsed ? 24 : 20" :xs="24">
+      <ContentWrap style="border: none">
+        <div class="soc-summary-chart">
+          <div class="soc-summary-chart__header">
+            <div class="soc-summary-chart__title">
+              {{ currentDrilldownKey ? `${currentDrilldownKey}分类统计` : 'SOC卡分类统计' }}
+            </div>
+            <el-button v-if="currentDrilldownKey" link type="primary" @click="resetDrilldown">
+              返回总览
+            </el-button>
+          </div>
+          <Echart
+            :options="socChartOption"
+            :height="340"
+            @chart-click="handleChartClick" />
+        </div>
+      </ContentWrap>
+
       <ContentWrap style="border: none">
         <!-- 搜索工作栏 -->
         <el-form
@@ -11,16 +28,14 @@
           :model="queryParams"
           ref="queryFormRef"
           :inline="true"
-          label-width="68px"
-        >
+          label-width="68px">
           <el-form-item label="姓名" prop="userName">
             <el-input
               v-model="queryParams.userName"
               placeholder="请输入姓名"
               clearable
               @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
+              class="!w-240px" />
           </el-form-item>
 
           <el-form-item label="队伍名称" prop="deptName">
@@ -29,8 +44,7 @@
               placeholder="请输入队伍名称"
               clearable
               @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
+              class="!w-240px" />
           </el-form-item>
           <el-form-item>
             <el-button @click="handleQuery"
@@ -43,8 +57,7 @@
               type="primary"
               plain
               @click="openForm('create')"
-              v-hasPermi="['rq:iot-soc-summary:create']"
-            >
+              v-hasPermi="['rq:iot-soc-summary:create']">
               <Icon icon="ep:plus" class="mr-5px" /> 新增
             </el-button>
             <el-button
@@ -52,8 +65,7 @@
               plain
               @click="handleExport"
               :loading="exportLoading"
-              v-hasPermi="['rq:iot-soc-summary:export']"
-            >
+              v-hasPermi="['rq:iot-soc-summary:export']">
               <Icon icon="ep:download" class="mr-5px" /> 导出
             </el-button>
           </el-form-item>
@@ -77,8 +89,7 @@
             prop="observationDate"
             :formatter="dateFormatter"
             类型名称
-            width="180px"
-          />
+            width="180px" />
 
           <zm-table-column label="类型名称" align="center" prop="className" min-width="120" />
           <zm-table-column label="姓名" align="center" prop="userName" />
@@ -90,8 +101,7 @@
             align="center"
             prop="createTime"
             :formatter="dateFormatter"
-            width="180px"
-          />
+            width="180px" />
 
           <zm-table-column label="操作" align="center" min-width="180px" fixed="right" action>
             <template #default="scope">
@@ -101,16 +111,14 @@
                 link
                 type="primary"
                 @click="openForm('update', scope.row.id)"
-                v-hasPermi="['rq:iot-soc-summary:update']"
-              >
+                v-hasPermi="['rq:iot-soc-summary:update']">
                 编辑
               </el-button>
               <el-button
                 link
                 type="danger"
                 @click="handleDelete(scope.row.id)"
-                v-hasPermi="['rq:iot-soc-summary:delete']"
-              >
+                v-hasPermi="['rq:iot-soc-summary:delete']">
                 删除
               </el-button>
             </template>
@@ -121,8 +129,7 @@
           :total="total"
           v-model:page="queryParams.pageNo"
           v-model:limit="queryParams.pageSize"
-          @pagination="getList"
-        />
+          @pagination="getList" />
 
         <!-- <div id="docx-viewer" class="docx-viewer-container"></div> -->
         <teleport to="body">
@@ -130,8 +137,7 @@
             <div
               v-if="dialogVisible"
               class="custom-dialog-overlay"
-              @click.self="dialogVisible = false"
-            >
+              @click.self="dialogVisible = false">
               <div class="custom-dialog">
                 <div class="custom-dialog-header">
                   <span class="custom-dialog-title">文档预览</span>
@@ -160,11 +166,19 @@ import download from '@/utils/download'
 import { IotSocSummaryApi } from '@/api/pms/qhse/index'
 import IotSocSummaryForm from './IotSocSummaryForm.vue'
 import DeptTree from '@/views/system/user/DeptTree2.vue'
-import { renderAsync } from 'docx-preview'
 import { Close } from '@element-plus/icons-vue'
+import type { EChartsOption } from 'echarts'
+import { Echart } from '@/components/Echart'
 
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 const { ZmTable, ZmTableColumn } = useTableComponents()
+import { useUserStore } from '@/store/modules/user'
+const userStore = useUserStore()
+
+type SummaryItem = Record<string, number>
+type ChildMap = Record<string, SummaryItem[]>
+
+const DRILLDOWN_KEYS = ['个人防护', '规范操作', '规范指挥', '人员位置', '作业场所'] as const
 
 /** SOC卡汇总 列表 */
 defineOptions({ name: 'IotSocSummary' })
@@ -208,6 +222,8 @@ const getList = async () => {
 const handleDeptNodeClick = async (row) => {
   queryParams.deptId = row.id
   queryParams.pageNo = 1
+  resetDrilldown()
+  await getStatic()
   getList()
 }
 
@@ -257,21 +273,6 @@ const handleExport = async () => {
   }
 }
 
-const view = async (id) => {
-  dialogVisible.value = true
-  // 等待弹框渲染完成后再加载文档
-
-  // 等待弹框渲染完成后再加载文档
-  await nextTick()
-  const container = document.getElementById('docx-viewer')
-  if (container) {
-    container.innerHTML = '' // 清空之前的内容
-    const res = await IotSocSummaryApi.previewIotSocSummary(id)
-
-    await renderAsync(res, container)
-  }
-}
-
 const downloadSOC = async (row) => {
   const res = await IotSocSummaryApi.downloadIotSocSummary(row.id)
   download.excel(
@@ -280,9 +281,130 @@ const downloadSOC = async (row) => {
   )
 }
 
+const child = ref<ChildMap>({})
+const totalData = ref<SummaryItem[]>([])
+const currentDrilldownKey = ref('')
+
+const totalChartData = computed(() => {
+  return (totalData.value as SummaryItem[]).map((item) => {
+    const [name, value] = Object.entries(item || {})[0] || ['', 0]
+    return {
+      name,
+      value: Number(value) || 0
+    }
+  })
+})
+
+const childChartData = computed(() => {
+  if (!currentDrilldownKey.value) return []
+  const source = (child.value as ChildMap)?.[currentDrilldownKey.value] || []
+  return source.map((item) => {
+    const [name, value] = Object.entries(item || {})[0] || ['', 0]
+    return {
+      name,
+      value: Number(value) || 0
+    }
+  })
+})
+
+const socChartOption = computed<EChartsOption>(() => {
+  const isDrilldown = !!currentDrilldownKey.value
+  const sourceData = isDrilldown ? childChartData.value : totalChartData.value
+
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    grid: {
+      left: 24,
+      right: 24,
+      top: 36,
+      bottom: 40,
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: sourceData.map((item) => item.name),
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        interval: 0,
+        rotate: isDrilldown ? 18 : 0,
+        color: '#606266',
+        fontSize: 12
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#dcdfe6'
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      minInterval: 1,
+      axisLabel: {
+        color: '#606266'
+      },
+      splitLine: {
+        lineStyle: {
+          color: '#ebeef5'
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        barMaxWidth: isDrilldown ? 42 : 56,
+        data: sourceData.map((item) => ({
+          value: item.value,
+          itemStyle: {
+            color: isDrilldown ? '#67c23a' : '#409eff',
+            borderRadius: [6, 6, 0, 0]
+          }
+        })),
+        label: {
+          show: true,
+          position: 'top',
+          color: '#303133'
+        }
+      }
+    ]
+  }
+})
+
+async function getStatic() {
+  if (queryParams.deptId) {
+    const res = await IotSocSummaryApi.getSocSummaryStatistics(queryParams.deptId)
+    child.value = res.child
+    totalData.value = res.total
+  } else {
+    const res = await IotSocSummaryApi.getSocSummaryStatistics(userStore.user.deptId)
+    child.value = res.child
+    totalData.value = res.total
+  }
+
+}
+
+const handleChartClick = (params: any) => {
+  const name = params?.name
+  if (!name || currentDrilldownKey.value) return
+  if (DRILLDOWN_KEYS.some((item) => item === name)) {
+    currentDrilldownKey.value = name
+  }
+}
+
+const resetDrilldown = () => {
+  currentDrilldownKey.value = ''
+}
+
 /** 初始化 **/
 onMounted(() => {
   getList()
+  getStatic()
 })
 </script>
 
@@ -394,4 +516,21 @@ onMounted(() => {
 .dialog-fade-leave-to .custom-dialog {
   transform: scale(0.9);
 }
+
+.soc-summary-chart {
+  width: 100%;
+}
+
+.soc-summary-chart__header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
+}
+
+.soc-summary-chart__title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
 </style>