<!-- 资源管理分析 --数据看板 --> <script name="resourceManage" lang="ts" setup> import dayjs from 'dayjs' import draggable from 'vuedraggable' import { colors } from '../environment/colors' import { resourceManage } from './img' import type { deptType, dictType } from '@/global' import { getRangeAllTime } from '@/utils/date' import { getBaseInfo, getCertDistribute, getCertExpire, getFileCategory, getFileNovelty } from '@/api/dataManagement/data/resourceManagement' import { getDictByCode } from '@/api/system/dict' const props = defineProps({ lab: { type: String, default: '', }, }) // 检定结果分析查询条件 const searchQueryResult = ref({ groupCode: '', labCode: '', }) watch(() => searchQueryResult.value.groupCode, () => { fetchData() }) // 人员持证到期趋势时间条件 const datetimerange = ref() const certDistribute = ref({ timeStart: '', timeEnd: '', }) watch(() => datetimerange.value, (newVal) => { certDistribute.value.timeStart = '' certDistribute.value.timeEnd = '' if (Array.isArray(newVal)) { if (newVal.length) { certDistribute.value.timeStart = `${newVal[0]}` certDistribute.value.timeEnd = `${newVal[1]}` fetchPerson() } } }) // 部门 const groupCodeList = ref<dictType[]>([]) // 基础信息展示数据 const baseInfo = ref([ { name: '人员总数', value: 'staffAmount', data: '', icon: resourceManage['人员总数'], color: '#60ACFC', }, { name: '体系文件总数', value: 'systemFileAmount', data: '', icon: resourceManage['体系文件总数'], color: '#60ACFC', }, { name: '测试校准检定方法总数', value: 'techniqueFileAmount', data: '', icon: resourceManage['测试校准检定方法总数'], color: '#60ACFC', }, { name: '在用软件总数', value: 'softwareAmount', data: '', icon: resourceManage['在用软件总数'], color: '#60ACFC', }, { name: '合格供方总数', value: 'supplierAmount', data: '', icon: resourceManage['合格供方总数'], color: '#60ACFC', }, { name: '委托方总数', value: 'customerAmount', data: '', icon: resourceManage['委托方总数'], color: '#60ACFC', }, ]) watch(() => props.lab, (newVal) => { if (newVal) { searchQueryResult.value.labCode = props.lab === '全站' ? '' : props.lab fetchData() } }) const showChart = ref<any[]>([ { name: '人员持证到期趋势', type: 'line', source: 'system', xAxisData: [], data: [], smooth: false, gradient: true, loading: false, // 加载中 isEmpty: false, // 是否为空 }, { name: '人员证书分布', type: 'bar-double', source: 'system', legendData: [], xAxisData: [], data1: [], data2: [], loading: false, // 加载中 isEmpty: false, // 是否为空 }, { name: '体系文件分类', type: 'pie', source: 'system', data: [], labelPosition: 'outside', radius: ['60%', '45%'], title: ' ', showTotal: true, loading: false, // 加载中 isEmpty: false, // 是否为空 }, { name: '测试校准检定方法查新分析', type: 'bar-vertical', source: 'bar', loading: false, // 加载中 isEmpty: false, // 是否为空 }, ]) watch(() => showChart.value.length, () => { setTimeout(() => { const resize = new Event('resize') window.dispatchEvent(resize) }) }) // 人员持证到期趋势 const fetchPerson = () => { // 人员持证到期趋势 const current1 = showChart.value.filter(item => item.name === '人员持证到期趋势')[0] current1.loading = true getCertExpire({ ...searchQueryResult.value, ...certDistribute.value }).then((res) => { // console.log(res.data, '人员持证到期趋势') current1.data = [ { name: '人员证书到期数量', symbol: 'emptyCircle', data: res.data.map((item: any) => String(item.certExpireAmount)), }, ] current1.xAxisData = res.data.map((item: any) => `${item.year}第${item.quarter}季度`) current1.loading = false // 判断是否为空 if (!res.data || !res.data.length) { current1.isEmpty = true } else { current1.isEmpty = false } }).catch(() => { current1.loading = false }) } // 模拟数据 const loadingBase = ref(false) function fetchData() { // 获取基础信息 loadingBase.value = true getBaseInfo(searchQueryResult.value).then((res) => { // console.log(res.data, '基础信息') baseInfo.value.forEach((item) => { item.data = res.data[item.value] }) loadingBase.value = false }).catch(() => { loadingBase.value = false }) // 人员持证到期趋势 fetchPerson() // 人员证书分布 const current2 = showChart.value.filter(item => item.name === '人员证书分布')[0] current2.loading = true getCertDistribute(searchQueryResult.value).then((res) => { // console.log(res.data, '人员证书分布') current2.legendData = ['证书总数', '有效证书总数'] current2.xAxisData = res.data.map((item: any) => item.certTypeName) current2.data1 = res.data.map((item: any) => String(item.totalAmount)) current2.data2 = res.data.map((item: any) => String(item.validAmount)) current2.loading = false // 判断是否为空 if (current2.data1.every((item: any) => item === '0') && current2.data2.every((item: any) => item === '0')) { current2.isEmpty = true } else { current2.isEmpty = false } }).catch(() => { current2.loading = false }) // 体系文件分类 const current3 = showChart.value.filter(item => item.name === '体系文件分类')[0] current3.loading = true getFileCategory(searchQueryResult.value).then((res) => { // console.log(res.data, '体系文件分类') const data = [ { name: '质量手册', value: 'qualityManual', data: '', }, { name: '程序文件', value: 'programFile', data: '', }, { name: '作业指导书', value: 'homeworkGuidebook', data: '', }, { name: '其他', value: 'other', data: '', }, ] data.forEach((item) => { item.data = res.data[item.value] }) current3.data = data.map(item => ({ name: item.name, value: item.data })) current3.loading = false // 判断是否为空 if (current3.data.every((item: any) => String(item.value) === '0')) { current3.isEmpty = true } else { current3.isEmpty = false } }).catch(() => { current3.loading = false }) // 测试校准检定方法查新分析 const current4 = showChart.value.filter(item => item.name === '测试校准检定方法查新分析')[0] current4.loading = true getFileNovelty(searchQueryResult.value).then((res) => { // console.log(res.data, '测试校准检定方法查新分析') current4.xAxisData = res.data.map((item: any) => item.groupCodeName) current4.data = [ { name: '有查新', data: res.data.map((item: any) => String(item.haveNovelty)), }, { name: '无查新', data: res.data.map((item: any) => String(item.noNovelty)), }, ] current4.loading = false // 判断是否为空 if (res.data.map((item: any) => String(item.haveNovelty)).every((item: any) => item === '0') && res.data.map((item: any) => String(item.noNovelty)).every((item: any) => item === '0')) { current4.isEmpty = true } else { current4.isEmpty = false } }).catch(() => { current4.loading = false }) // loading.value = true // // 人员持证到期趋势 // const name1 = ['2023年第一季度', '2023年第二季度', '2023年第三季度', '2023年第四季度', '2024年第一季度', '2024年第二季度', '2024年第三季度', '2024年第四季度'] // showChart.value.filter((item: any) => item.name === '人员持证到期趋势')[0].data = [{ // name: '持证证书数量', // symbol: 'emptyCircle', // data: name1.map(item => Math.floor(Math.random() * 100) + 1), // }] // showChart.value.filter((item: any) => item.name === '人员持证到期趋势')[0].xAxisData = name1 // // 人员证书分布 // const name2 = ['证书总数', '有效证书总数'] // const name3 = ['检定员证', '内审员证', '其他'] // showChart.value.filter((item: any) => item.name === '人员证书分布')[0].legendData = name2 // showChart.value.filter((item: any) => item.name === '人员证书分布')[0].xAxisData = name3 // showChart.value.filter((item: any) => item.name === '人员证书分布')[0].data1 = name3.map(() => Math.floor(Math.random() * 100) + 1) // showChart.value.filter((item: any) => item.name === '人员证书分布')[0].data2 = showChart.value.filter((item: any) => item.name === '人员证书分布')[0].data1.map(item => item - 5 > 0 ? item - 5 : 0) // // 体系文件分类 // const name4 = ['质量手册', '程序文件', '作业指导书', '其他'] // showChart.value.filter((item: any) => item.name === '体系文件分类')[0].data = name4.map(item => ({ // name: item, // value: Math.floor(Math.random() * 100) + 1, // })) // console.log(showChart.value.filter((item: any) => item.name === '体系文件分类')[0], '体系文件分类') // // 测试校准检定方法查新分析 // const name5 = ['电学电源组', '无线脉冲组', '热工力学组'] // const name6 = ['有查新', '无查新'] // showChart.value.filter((item: any) => item.name === '测试校准检定方法查新分析')[0].data = name6.map(item => ({ // name: item, // data: name5.map(item => Math.floor(Math.random() * 100) + 1), // })) // showChart.value.filter((item: any) => item.name === '测试校准检定方法查新分析')[0].xAxisData = name5 // setTimeout(() => { // const resize = new Event('resize') // window.dispatchEvent(resize) // }) // setTimeout(() => { // loading.value = false // }, 3000) } // setTimeout(() => { // fetchData() // }, 100) defineExpose({ showChart, }) // 拖拽结束 const onEnd = () => { console.log('拖拽结束') const resize = new Event('resize') window.dispatchEvent(resize) } onMounted(() => { if (props.lab) { datetimerange.value = [`${new Date().getFullYear()}-01-01`, dayjs().format('YYYY-MM-DD')] searchQueryResult.value.labCode = props.lab === '全站' ? '' : props.lab // 部门 getDictByCode('bizGroupCode').then((response) => { const tempMenu = ['电学电源组', '热工力学组', '无线电脉冲组', '综合管理组'] tempMenu.forEach((item) => { const tempFindData = response.data.find( (e: { name: string; value: string }) => e.name === item, ) if (tempFindData) { groupCodeList.value.push({ name: tempFindData.name, id: `${tempFindData.id}`, value: `${tempFindData.value}`, }) } }) }) setTimeout(() => { fetchData() }) } }) </script> <template> <div class="container"> <div class="btns"> <div /> <!-- 部门 --> <el-select v-model="searchQueryResult.groupCode" style="width: 130px;" class="short-input" placeholder="部门" clearable > <el-option v-for="item in groupCodeList" :key="item.id" :label="item.name" :value="item.value" /> </el-select> </div> <!-- 展示内容 --> <div class="content-count"> <!-- 统计数据 --> <div v-loading="loadingBase" class="count"> <div v-for="item in baseInfo" :key="item.value" class="count-item"> <div class="header-icon"> <img :src="item.icon"> </div> <div class="header-content"> <div class="content-title"> {{ item.name }} </div> <div class="content-count" :style="{ color: item.color }"> {{ item.data }} </div> </div> </div> </div> <!-- 图表区域 --> <!-- <div class="chart"> --> <draggable v-model="showChart" item-key="name" class="chart" animation="300" drag-class="dragClass" ghost-class="ghostClass" chosen-class="chosenClass" @end="onEnd" > <template #item="{ element, index }"> <div :style="{ width: `${showChart.length === 4 ? '48%' : index === 0 || index === 1 ? '48%' : '31.5%'}` }" class="chart-item" > <div class="chart-name"> <!-- {{ element.name }} --> </div> <div v-loading="element.loading" class="chart-page"> <div style="width: 100%; height: 100%;"> <div class="chart-name" style="width: 100%; height: 10%; display: flex;"> {{ element.name }} <div v-if="element.name === '人员持证到期趋势'" style="width: 350px; margin-left: 40px;"> <el-date-picker v-model="datetimerange" type="daterange" value-format="YYYY-MM-DD" format="YYYY-MM-DD" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" /> </div> </div> <div style="width: 100%; height: 90%;"> <pie-chart v-if="element.type === 'pie' && !element.isEmpty" :title="element.title" :show-total="element.showTotal" :data="element.data" :colors="colors" :center="element.center" label-formatter="{style1|{c}}" :label-position="element.labelPosition" :radius="element.radius" :grid="{ top: 50, left: 15, right: 15, bottom: 10, containLabel: true }" :legend="{ itemWidth: 8, itemHeight: 8, type: 'scroll', orient: 'horizontal', icon: 'roundRect', left: '0', top: '10' }" /> <line-chart v-if="element.type === 'line' && !element.isEmpty" :colors="colors" :gradient="element.gradient" :x-axis-data="element.xAxisData" :data="element.data" :smooth="element.smooth" :grid="{ top: 47, left: 5, right: 5, bottom: 10, containLabel: true }" :legend="{ itemWidth: 8, itemHeight: 2, type: 'scroll', orient: 'horizontal', icon: 'roundRect', left: '0', top: '5' }" /> <bar-chart-double v-if="element.type === 'bar-double' && !element.isEmpty" :legend-data="element.legendData" :data1="element.data1" :data2="element.data2" :x-axis-data="element.xAxisData" :legend="{ itemWidth: 8, itemHeight: 8, type: 'scroll', orient: 'horizontal', icon: 'roundRect', left: '0', top: '10' }" /> <bar-chart-vertical v-if="element.type === 'bar-vertical' && !element.isEmpty" :bar-coner="0" :data="element.data" :x-axis-data="element.xAxisData" :legend="{ itemWidth: 8, itemHeight: 8, type: 'scroll', orient: 'horizontal', icon: 'roundRect', left: '0', top: '10' }" /> <bar-chart-horizontal v-if="element.type === 'bar-horizontal' && !element.isEmpty" bar-width="10" :bar-coner="0" :data="element.data" :x-axis-data="element.xAxisData" :legend="{ itemWidth: 8, itemHeight: 8, type: 'scroll', orient: 'horizontal', icon: 'roundRect', left: '0', top: '10' }" /> <scroll-table v-if="element.type === 'rank-table'" style="width: 100%;" :height="330" :data="element.data" :columns="element.columns" /> <el-empty v-if="element.isEmpty" description="暂无数据" /> </div> </div> </div> </div> </template> </draggable> <!-- </div> --> </div> </div> </template> <style lang="scss" scoped> .dragClass { /* background-color: blueviolet !important; */ opacity: 1 !important; box-shadow: none !important; outline: none !important; background-image: none !important; color: #6ff !important; } .ghostClass { /* background-color: blue !important; */ } .chosenClass { color: #6ff !important; /* background-color: #ccc !important; */ /* opacity: 1 !important; */ /* width: 32.5% !important; */ } .icon-button-icon { width: 50px; height: 50px; } .container { position: relative; .btns { width: 100%; position: absolute; top: -36px; right: 50%; // left: 50%; display: flex; justify-content: space-between; padding: 0 60px; } .count { display: flex; width: 80%; margin: 0 auto; position: absolute; top: -40px; left: 50%; transform: translateX(-50%); .count-item { margin-left: 20px; width: 18%; height: 75px; display: flex; // justify-content: space-around; // padding: 0 10px; .header-icon { width: 20%; display: flex; flex-direction: column; justify-content: center; } .header-content { // width: 60%; padding: 10px; display: flex; flex-direction: column; justify-content: space-around; .content-title { font-weight: 700; } .content-count { font-weight: 700; font-size: 24px; color: #1aaf8b; text-align: center; } } } } .chart { display: flex; flex-wrap: wrap; justify-content: space-around; // margin-top: 15px; padding-top: 20px; .chart-item { width: 33%; height: 348px; // border: 1px solid red; margin-top: 10px; .chart-name { color: #3d6fb6; font-size: 18px; font-weight: 700; } .chart-page { height: 330px; } } } } </style>