| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- <template>
- <module-container title="模块二 查堵人员分析">
- <div class="module-two-content">
- <div class="top-row">
- <div class="table-card">
- <div class="chart-title">查堵-主管排行榜</div>
- <rank-list :rank-data="rankData1" title="查堵-主管排行榜" header-label="排名" header-name="分管主管" header-count="计数" />
- </div>
- <div class="table-card">
- <div class="chart-title">查堵-班组排行榜</div>
- <rank-list :rank-data="rankData2" title="查堵-班组排行榜" header-label="排名" header-name="分管班组长" header-count="计数" />
- </div>
- <div class="table-card">
- <div class="chart-title">查堵-人员排行榜</div>
- <rank-list :rank-data="rankData3" title="查堵-人员排行榜" header-label="排名" header-name="被回查人" header-count="计数" />
- </div>
- </div>
- <div class="middle-row">
- <div class="chart-card">
- <div class="chart-title">查堵-物品位置分布</div>
- <div ref="chart1" class="echarts"></div>
- </div>
- <div class="chart-card">
- <div class="chart-title">查堵-原因分类</div>
- <div ref="chart2" class="echarts"></div>
- </div>
- </div>
- <div class="bottom-row">
- <div class="chart-card">
- <div class="chart-title">查堵-开机人员年限分布</div>
- <div ref="chart3" class="echarts"></div>
- </div>
- <div class="chart-card">
- <div class="chart-title">查堵-开机人员性别比例</div>
- <div ref="chart4" class="echarts"></div>
- </div>
- <div class="chart-card">
- <div class="chart-title">查堵-图像难易程度比例</div>
- <div ref="chart5" class="echarts"></div>
- </div>
- </div>
- <div class="extra-row">
- <div class="chart-card">
- <div class="chart-title">大队开机人员年限分布</div>
- <div ref="chart6" class="echarts"></div>
- </div>
- <div class="chart-card">
- <div class="chart-title">大队开机人员证书分布</div>
- <div ref="chart7" class="echarts"></div>
- </div>
- </div>
- </div>
- </module-container>
- </template>
- <script setup>
- import { ref, onMounted, reactive, watch } from 'vue'
- import * as echarts from 'echarts'
- import ModuleContainer from './ModuleContainer.vue'
- import RankList from './RankList.vue'
- import { useEcharts } from '@/hooks/chart.js'
- import {
- supervisorRanking,
- teamLeaderRanking,
- reviewedUserRanking,
- itemLocationDistribution,
- missCheckReasonDistribution,
- brigadeOperatingYearsDistribution,
- brigadeGenderDistribution,
- brigadeDifficultyDistribution,
- brigadeCertificateDistribution,
- tenureListAll
- } from '@/api/blockingData/blockingDataScreen.js'
- import { formatDateHy } from '@/utils/index.js'
- // 接收筛选参数
- const props = defineProps({
- filterParams: {
- type: Object,
- default: () => ({})
- }
- })
- const chart1 = ref(null)
- const chart2 = ref(null)
- const chart3 = ref(null)
- const chart4 = ref(null)
- const chart5 = ref(null)
- const chart6 = ref(null)
- const chart7 = ref(null)
- const { setOption: setOption1 } = useEcharts(chart1)
- const { setOption: setOption2 } = useEcharts(chart2)
- const { setOption: setOption3 } = useEcharts(chart3)
- const { setOption: setOption4 } = useEcharts(chart4)
- const { setOption: setOption5 } = useEcharts(chart5)
- const { setOption: setOption6 } = useEcharts(chart6)
- const { setOption: setOption7 } = useEcharts(chart7)
- // 处理filterParams,将dateRange拆分为startTime和endTime
- const processFilterParams = (params) => {
- const { dateRange, ...rest } = params
- const processed = { ...rest }
- if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
- processed.startTime = formatDateHy(dateRange[0])
- processed.endTime = formatDateHy(dateRange[1])
- }
- if (processed.brigadeId == 'all') {
- delete processed.brigadeId
- }
- if (processed.terminalId == 'all') {
- delete processed.terminalId
- }
- return processed
- }
- // 数据加载函数
- const loadData = async () => {
- try {
- const queryParams = processFilterParams(props.filterParams)
- // 排行榜数据
- const [supervisorRankRes, teamLeaderRankRes, reviewedUserRankRes] = await Promise.allSettled([
- supervisorRanking(queryParams),
- teamLeaderRanking(queryParams),
- reviewedUserRanking(queryParams)
- ])
- // 更新排行榜数据
- if (supervisorRankRes?.value?.data) {
- rankData1.splice(0, rankData1.length, ...supervisorRankRes.value.data.map((item, index) => ({
- name: item.name || '未知',
- value: item.totalCount || 0
- })))
- }
- if (teamLeaderRankRes?.value?.data) {
- rankData2.splice(0, rankData2.length, ...teamLeaderRankRes.value.data.map((item, index) => ({
- name: item.name || '未知',
- value: item.totalCount || 0
- })))
- }
- if (reviewedUserRankRes?.value?.data) {
- rankData3.splice(0, rankData3.length, ...reviewedUserRankRes.value.data.map((item, index) => ({
- name: item.name || '未知',
- value: item.totalCount || 0
- })))
- }
- // 图表数据
- const [itemLocationRes, missCheckReasonRes, operatingYearsRes, genderRes, difficultyRes, certificateRes, tenureListRes] = await Promise.allSettled([
- itemLocationDistribution(queryParams),
- missCheckReasonDistribution(queryParams),
- brigadeOperatingYearsDistribution(queryParams),
- brigadeGenderDistribution(queryParams),
- brigadeDifficultyDistribution(queryParams),
- brigadeCertificateDistribution(queryParams),
- tenureListAll(queryParams)
- ])
- // 设置图表数据
- setOption1(pieOption(
- itemLocationRes?.value?.data?.map(item => ({
- name: item.itemLocation || '未知',
- value: item.count || 0
- })) || [],
- ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#64748b']
- ))
- setOption2(pieOption(
- missCheckReasonRes?.value?.data?.map(item => ({
- name: item.missCheckReasonCategory || item.reason || '未知',
- value: item.value || item.count || 0
- })) || [],
- ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#64748b'],
- true
- ))
- setOption3(barOption(
- operatingYearsRes?.value?.data?.map(item => ({
- name: item.operatingYears || '未知',
- value: item.count || 0
- })) || [],
- ['#3b82f6', '#60a5fa', '#93c5fd', '#bfdbfe', '#dbeafe', '#eff6ff']
- ))
- setOption4(pieOption(
- genderRes?.value?.data?.map(item => ({
- name: item.gender || '未知',
- value: item.count || 0
- })) || [],
- ['#3b82f6', '#ec4899']
- ))
- setOption5(pieOption(
- difficultyRes?.value?.data?.map(item => ({
- name: item.difficultyLevel || '未知',
- value: item.count || 0
- })) || [],
- ['#93c5fd', '#3b82f6', '#64748b', '#e2e8f0']
- ))
- // 大队开机人员年限分布
- const tenureListData = tenureListRes?.value?.data || []
- if (tenureListData.length > 0) {
- const categories = [...new Set(tenureListData.map(item => item.brigadeName))]
- const tenureLevels = [
- { name: '1年', key: 'cnt1Years' },
- { name: '2年', key: 'cnt2Years' },
- { name: '3年', key: 'cnt3Years' },
- { name: '4年', key: 'cnt4Years' },
- { name: '5年', key: 'cntGe5Year' }
- ]
- const seriesData = tenureLevels.map(level => ({
- name: level.name,
- type: 'bar',
- data: categories.map(brigade => {
- const item = tenureListData.find(d => d.brigadeName === brigade)
- return item ? item[level.key] : 0
- }),
- itemStyle: {
- color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6'][tenureLevels.indexOf(level) % 5]
- },
- barWidth: 15,
- label: { show: true, position: 'top', fontSize: 9 }
- }))
- setOption6({
- grid: { left: '15%', top: '10%', right: '5%', bottom: '15%', containLabel: true },
- xAxis: {
- type: 'category',
- data: categories,
- axisLabel: { fontSize: 9 },
- axisTick: {
- alignWithLabel: true
- }
- },
- yAxis: { type: 'value', axisLabel: { fontSize: 9 } },
- legend: { top: 0, right: 10, textStyle: { fontSize: 9 } },
- series: seriesData
- })
- }
- // 大队开机人员证书分布
- const certificateData = certificateRes?.value?.data || []
- if (certificateData.length > 0) {
- const certificateCategories = ['高级证书', '中级证书', '初级证书']
- const brigadeNames = [...new Set(certificateData.map(item => item.brigadeName))]
- const certificateSeriesData = brigadeNames.map((brigadeName, index) => {
- const brigadeData = certificateData.find(d => d.brigadeName === brigadeName)
- return {
- name: brigadeName,
- type: 'bar',
- data: [
- brigadeData?.seniorCount || 0,
- brigadeData?.middleCount || 0,
- brigadeData?.juniorCount || 0
- ],
- itemStyle: {
- color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6'][index % 6]
- },
- barWidth: 20,
- label: { show: true, position: 'top', fontSize: 9 }
- }
- })
- setOption7({
- grid: { left: '15%', top: '10%', right: '5%', bottom: '15%', containLabel: true },
- xAxis: {
- type: 'category',
- data: certificateCategories,
- axisLabel: { fontSize: 10 },
- axisLine: { lineStyle: { color: '#999' } }
- },
- yAxis: {
- type: 'value',
- axisLabel: { fontSize: 10 },
- axisLine: { lineStyle: { color: '#999' } },
- splitLine: { lineStyle: { color: '#eee' } }
- },
- legend: {
- top: 0,
- right: 10,
- textStyle: { fontSize: 10 }
- },
- series: certificateSeriesData
- })
- }
- } catch (error) {
- console.error('加载数据失败:', error)
- }
- }
- // 监听筛选参数变化
- watch(() => props.filterParams, () => {
- loadData()
- }, { deep: true })
- const rankData1 = reactive([
- ])
- const rankData2 = reactive([
- ])
- const rankData3 = reactive([
- ])
- const pieOption = (data, colors, isRing = false) => ({
- color: colors,
- legend: {
- orient: 'horizontal',
- top: 'top',
- textStyle: { fontSize: 10 }
- },
- series: [{
- type: 'pie',
- radius: isRing ? ['40%', '50%'] : '60%',
- center: ['50%', '55%'],
- data: data,
- label: {
- show: true,
- formatter: '{b}\n{c} ({d}%)',
- fontSize: 10
- },
- }]
- })
- const barOption = (data, colors) => ({
- grid: { left: '15%', top: '10%', right: '5%', bottom: '10%', containLabel: true },
- xAxis: { type: 'value', axisLabel: { fontSize: 9 } },
- yAxis: { type: 'category', data: data.map(d => d.name), axisLabel: { fontSize: 9 } },
- series: [{
- type: 'bar',
- data: data.map(d => d.value),
- itemStyle: {
- color: (params) => colors[params.dataIndex % colors.length]
- },
- barWidth: 15,
- label: { show: true, position: 'right', fontSize: 10 }
- }]
- })
- onMounted(() => {
- loadData()
- })
- </script>
- <style lang="less" scoped>
- .module-two-content {
- height: 100%;
- display: flex;
- flex-direction: column;
- gap: 8px;
- overflow-y: auto;
- }
- .top-row,
- .middle-row,
- .bottom-row,
- .extra-row {
- display: flex;
- gap: 8px;
- flex-shrink: 0;
- }
- .table-card {
- flex: 1;
- background: #fff;
- border-radius: 6px;
- padding: 8px;
- border: 1px solid #eee;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
- display: flex;
- flex-direction: column;
- min-height: 200px;
- }
- .chart-card {
- flex: 1;
- background: #fff;
- border-radius: 6px;
- padding: 15px;
- border: 1px solid #eee;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
- display: flex;
- flex-direction: column;
- min-height: 400px;
- }
- .chart-title {
- font-size: 17px;
- color: black;
- margin-bottom: 5px;
- text-align: left;
- }
- .rank-table {
- flex: 1;
- overflow-y: auto;
- }
- .rank-row {
- display: flex;
- align-items: center;
- padding: 4px 6px;
- border-bottom: 1px solid #f0f0f0;
- font-size: 11px;
- }
- .rank-row:last-child {
- border-bottom: none;
- }
- .rank-number {
- width: 45px;
- font-weight: bold;
- margin-right: 8px;
- }
- .rank-1 {
- color: #eab308;
- }
- .rank-2 {
- color: #94a3b8;
- }
- .rank-3 {
- color: #f97316;
- }
- .rank-name {
- flex: 1;
- color: #333;
- }
- .rank-value {
- color: #666;
- }
- .echarts {
- flex: 1;
- width: 100%;
- min-height: 250px;
- }
- </style>
|