| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462 |
- <template>
- <div class="app-container">
- <!-- 查询条件 -->
- <div class="filter-section">
- <el-card>
- <div class="filter-container">
- <el-form :model="queryParams" ref="queryFormRef" :inline="true" class="search-form">
- <el-form-item label="考核月份" prop="assessmentMonth">
- <el-date-picker v-model="queryParams.assessmentMonth" type="month" placeholder="请选择考核月份"
- value-format="YYYY-MM" style="width: 200px" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- <div class="export-button">
- <el-button type="warning" icon="Document" @click="handleExport">导出文档</el-button>
- </div>
- </div>
- </el-card>
- </div>
- <!-- 非干部月度考核分数汇总 -->
- <div class="main-section">
- <h2 class="section-title">非干部月度考核分数汇总</h2>
- <!-- 第一行:两个区块 -->
- <div class="chart-row">
- <!-- 整体分值分布柱状图 -->
- <el-card class="chart-card">
- <div class="chart-header">整体分值分布柱状图</div>
- <div ref="overallBarChart" class="chart-container"></div>
- </el-card>
- <!-- 参与人数占比饼图 -->
- <el-card class="chart-card">
- <div class="chart-header">参与人数占比饼图</div>
- <div ref="participantPieChart" class="chart-container"></div>
- </el-card>
- </div>
- <!-- 第二行:两个区块 -->
- <div class="chart-row">
- <!-- 各部门分值分布对比图 -->
- <el-card class="chart-card">
- <div class="chart-header">各部门分值分布对比图</div>
- <div ref="departmentComparisonChart" class="chart-container"></div>
- </el-card>
- <!-- 汇总表 -->
- <el-card class="chart-card">
- <div class="chart-header">汇总表</div>
- <el-table :data="summaryTableData" border style="width: 100%; margin-top: 10px;">
- <el-table-column prop="rangeLabel" label="区间" align="center" min-width="100" />
- <el-table-column prop="totalCount" :label="summaryTableTitle" align="center" min-width="150" />
- <el-table-column v-for="col in summaryTableColumns" :key="col.deptId" :prop="'team' + col.deptId"
- :label="col.deptName" align="center" min-width="100" />
- </el-table>
- </el-card>
- </div>
- </div>
- <!-- 非干部月度考核分类结果汇总 -->
- <div class="main-section">
- <h2 class="section-title">非干部月度考核分类结果汇总</h2>
- <!-- 汇总统计表格 -->
- <el-card class="summary-table-card">
- <div class="chart-header">汇总统计</div>
- <el-table :data="classificationTableData" border style="width: 100%; margin-top: 10px;">
- <el-table-column prop="assessmentTeamCount" label="考核组人数" align="center" min-width="120" />
- <el-table-column prop="estimatedImprovementCount" label="测算待改进人数" align="center" min-width="140" />
- <el-table-column prop="improvementTotalCount" label="待改进总人数" align="center" min-width="120" />
- <el-table-column prop="improvementExemptedCount" label="待改进豁免" align="center" min-width="100" />
- <el-table-column prop="actualImprovementCount" label="实际待改进总人数" align="center" min-width="140" />
- <el-table-column prop="incompetentTotalCount" label="不称职总人数" align="center" min-width="120" />
- <el-table-column prop="incompetentExemptedCount" label="不称职豁免" align="center" min-width="100" />
- <el-table-column prop="actualIncompetentCount" label="实际不称职人数" align="center" min-width="140" />
- </el-table>
- </el-card>
- <!-- 第四行:两个区块 -->
- <div class="chart-row">
- <!-- 大队分布统计图 -->
- <el-card class="chart-card">
- <div class="chart-header">大队分布统计图</div>
- <div class="pie-charts-container">
- <div ref="brigadePieChart1" class="pie-chart"></div>
- <div ref="brigadePieChart2" class="pie-chart"></div>
- </div>
- </el-card>
- <!-- 岗位分布统计图 -->
- <el-card class="chart-card">
- <div class="chart-header">岗位分布统计图</div>
- <div class="pie-charts-container">
- <div ref="positionPieChart1" class="pie-chart"></div>
- <div ref="positionPieChart2" class="pie-chart"></div>
- </div>
- </el-card>
- </div>
- </div>
- <!-- 第一个遍历:表格和两个饼状图 -->
- <div class="traversal-section">
- <div v-for="(item, index) in traversalData1" :key="index" class="traversal-container">
- <div class="traversal-header">{{ item.title }}</div>
- <div class="traversal-content">
- <!-- 左边表格 -->
- <div class="table-section">
- <el-table :data="item.tableData" border style="width: 100%;">
- <el-table-column prop="assessmentTeam" label="考核组" align="center" min-width="100" />
- <el-table-column prop="assessmentTeamCount" label="考核组人数" align="center" min-width="120" />
- <el-table-column prop="estimatedImprovementCount" label="测算待改进人数" align="center" min-width="140" />
- <el-table-column prop="improvementTotalCount" label="待改进总人数" align="center" min-width="120" />
- <el-table-column prop="improvementExemptedCount" label="待改进豁免人数" align="center" min-width="140" />
- <el-table-column prop="actualImprovementCount" label="实际待改进人数" align="center" min-width="140" />
- <el-table-column prop="incompetentTotalCount" label="不称职总人数" align="center" min-width="120" />
- <el-table-column prop="incompetentExemptedCount" label="不称职豁免人数" align="center" min-width="140" />
- <el-table-column prop="actualIncompetentCount" label="实际不称职人数" align="center" min-width="140" />
- </el-table>
- </div>
- <!-- 右边两个饼状图 -->
- <div class="chart-section">
- <div class="pie-chart-container">
- <div class="pie-chart-title">{{ item.title }}实际待改进人数分布</div>
- <div :ref="el => setTraversalChartRef(el, `pieChart1_${index}`)" class="pie-chart"></div>
- </div>
- <div class="pie-chart-container">
- <div class="pie-chart-title">{{ item.title }}实际不称职人数分布</div>
- <div :ref="el => setTraversalChartRef(el, `pieChart2_${index}`)" class="pie-chart"></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 第二个遍历:表格、柱状图和饼状图 -->
- <div class="traversal-section">
- <div v-for="(item, index) in traversalData2" :key="index" class="traversal-container">
- <div class="traversal-header">{{ item.title }}</div>
- <div class="traversal-content">
- <!-- 左边表格 -->
- <div class="table-section">
- <el-table :data="item.tableData" border style="width: 100%;">
- <el-table-column prop="assessmentTeam" label="考核组" align="center" min-width="100" />
- <el-table-column prop="assessmentTeamCount" label="考核组人数" align="center" min-width="120" />
- <el-table-column prop="estimatedImprovementCount" label="测算待改进人数" align="center" min-width="140" />
- <el-table-column prop="improvementTotalCount" label="待改进人数" align="center" min-width="120" />
- <el-table-column prop="improvementExemptedCount" label="豁免" align="center" min-width="80" />
- <el-table-column prop="actualImprovementCount" label="实际待改进人数" align="center" min-width="140" />
- <el-table-column prop="incompetentTotalCount" label="不称职人数" align="center" min-width="120" />
- <el-table-column prop="incompetentExemptedCount" label="豁免" align="center" min-width="80" />
- <el-table-column prop="actualIncompetentCount" label="实际不称职人数" align="center" min-width="140" />
- </el-table>
- </div>
- <!-- 右边柱状图和饼状图 -->
- <div class="chart-section">
- <div class="bar-chart-container">
- <div class="chart-title">{{ item.title }}考核人数与待改进情况</div>
- <div :ref="el => setTraversalChartRef(el, `barChart_${index}`)" class="bar-chart"></div>
- </div>
- <div class="pie-chart-container">
- <div class="pie-chart-title">{{ item.title }}考核人数占比</div>
- <div :ref="el => setTraversalChartRef(el, `pieChart3_${index}`)" class="pie-chart"></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, onMounted, onUnmounted, nextTick, computed } from 'vue'
- import { ElMessage, ElLoading } from 'element-plus'
- import * as echarts from 'echarts'
- import html2canvas from 'html2canvas'
- import { Document, Packer, Paragraph, ImageRun, HeadingLevel, AlignmentType } from 'docx'
- import { saveAs } from 'file-saver'
- // API导入
- import {
- getScoreDistribution,
- getDeptParticipation,
- getDeptScoreDistribution,
- getAssessmentSummary,
- getActualImprovementDistribution,
- getActualIncompetentDistribution,
- getAssessmentTeamImprovementDistribution,
- getAssessmentTeamIncompetentDistribution,
- getDeptAssessmentTeamStatistics,
- getBrigadeImprovementDistribution,
- getBrigadeIncompetentDistribution,
- getFunctionalDeptSummary,
- getFunctionalDeptPersonnelDistribution,
- getFunctionalDeptDistributionPie
- } from '@/api/performance/monthlyAssessSum.js'
- import { listDept } from '@/api/system/dept.js'
- import { useDict } from '@/utils/dict'
- import { selectDictLabel } from '@/utils/ruoyi'
- // 获取字典数据
- const { assessment_team } = useDict('assessment_team')
- // 响应式数据
- const loading = ref(false)
- const queryFormRef = ref()
- // 查询参数
- const currentMonth = `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`
- const queryParams = reactive({
- assessmentMonth: currentMonth
- })
- const summaryTableTitle = computed(() => {
- if (queryParams.assessmentMonth) {
- const [year, month] = queryParams.assessmentMonth.split('-')
- return `${year}年${parseInt(month)}月模拟分数汇总`
- }
- return '模拟分数汇总'
- })
- // 图表引用
- const overallBarChart = ref(null)
- const participantPieChart = ref(null)
- const departmentComparisonChart = ref(null)
- const brigadePieChart1 = ref(null)
- const brigadePieChart2 = ref(null)
- const positionPieChart1 = ref(null)
- const positionPieChart2 = ref(null)
- // 遍历图表引用
- const traversalChartsRefs = ref({})
- // 表格数据
- const summaryTableData = ref([])
- const summaryTableColumns = ref([])
- const classificationTableData = ref([
- ])
- // 遍历数据
- const traversalData1 = ref([
- ])
- const traversalData2 = ref([
- ])
- // 图表实例
- let overallBarChartInstance = null
- let participantPieChartInstance = null
- let departmentComparisonChartInstance = null
- let brigadePieChart1Instance = null
- let brigadePieChart2Instance = null
- let positionPieChart1Instance = null
- let positionPieChart2Instance = null
- // 初始化图表
- const initCharts = () => {
- nextTick(() => {
- // 整体分值分布柱状图
- if (overallBarChart.value) {
- overallBarChartInstance = echarts.init(overallBarChart.value)
- overallBarChartInstance.setOption({
- tooltip: { trigger: 'axis' },
- xAxis: { type: 'category', data: ['90-100分', '80-89分', '70-79分', '60-69分', '60分以下'] },
- yAxis: { type: 'value' },
- series: [{ type: 'bar', data: [85, 120, 95, 60, 20], itemStyle: { color: '#3b82f6' } }]
- })
- }
- // 参与人数占比饼图
- if (participantPieChart.value) {
- participantPieChartInstance = echarts.init(participantPieChart.value)
- participantPieChartInstance.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '70%',
- data: [
- { value: 380, name: '参与考核' },
- { value: 20, name: '未参与考核' }
- ]
- }]
- })
- }
- // 各部门分值分布对比图
- if (departmentComparisonChart.value) {
- departmentComparisonChartInstance = echarts.init(departmentComparisonChart.value)
- departmentComparisonChartInstance.setOption({
- tooltip: { trigger: 'axis' },
- legend: { data: ['一队', '二队', '三队'] },
- xAxis: { type: 'category', data: ['90-100分', '80-89分', '70-79分', '60-69分', '60分以下'] },
- yAxis: { type: 'value' },
- series: [
- { name: '一队', type: 'bar', data: [15, 30, 25, 15, 5] },
- { name: '二队', type: 'bar', data: [20, 35, 30, 20, 8] },
- { name: '三队', type: 'bar', data: [25, 40, 35, 25, 7] }
- ]
- })
- }
- // 大队分布饼图1
- if (brigadePieChart1.value) {
- brigadePieChart1Instance = echarts.init(brigadePieChart1.value)
- brigadePieChart1Instance.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: [
- { value: 120, name: '一队' },
- { value: 150, name: '二队' },
- { value: 130, name: '三队' }
- ]
- }]
- })
- }
- // 大队分布饼图2
- if (brigadePieChart2.value) {
- brigadePieChart2Instance = echarts.init(brigadePieChart2.value)
- brigadePieChart2Instance.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: [
- { value: 45, name: '优秀' },
- { value: 280, name: '合格' },
- { value: 75, name: '待改进' }
- ]
- }]
- })
- }
- // 岗位分布饼图1
- if (positionPieChart1.value) {
- positionPieChart1Instance = echarts.init(positionPieChart1.value)
- positionPieChart1Instance.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: [
- { value: 150, name: '安检员' },
- { value: 120, name: '设备操作员' },
- { value: 80, name: '管理人员' },
- { value: 50, name: '其他' }
- ]
- }]
- })
- }
- // 岗位分布饼图2
- if (positionPieChart2.value) {
- positionPieChart2Instance = echarts.init(positionPieChart2.value)
- positionPieChart2Instance.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: [
- { value: 35, name: '优秀' },
- { value: 320, name: '合格' },
- { value: 45, name: '待改进' }
- ]
- }]
- })
- }
- })
- }
- // 设置遍历图表引用
- const setTraversalChartRef = (el, key) => {
- if (el) {
- traversalChartsRefs.value[key] = el
- }
- }
- // 初始化遍历图表
- const initTraversalCharts = () => {
- nextTick(() => {
- // 第一个遍历的饼状图
- traversalData1.value.forEach((item, index) => {
- // 饼状图1:考核结果分布
- const pieChart1Key = `pieChart1_${index}`
- if (traversalChartsRefs.value[pieChart1Key]) {
- const pieChart1 = echarts.init(traversalChartsRefs.value[pieChart1Key])
- pieChart1.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: [
- { value: 120, name: '优秀' },
- { value: 280, name: '合格' },
- { value: 75, name: '待改进' }
- ]
- }]
- })
- }
- // 饼状图2:改进情况分布
- const pieChart2Key = `pieChart2_${index}`
- if (traversalChartsRefs.value[pieChart2Key]) {
- const pieChart2 = echarts.init(traversalChartsRefs.value[pieChart2Key])
- pieChart2.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: [
- { value: 15, name: '已改进' },
- { value: 60, name: '待改进' },
- { value: 5, name: '未改进' }
- ]
- }]
- })
- }
- })
- // 第二个遍历的图表
- traversalData2.value.forEach((item, index) => {
- // 柱状图:考核分数分布
- const barChartKey = `barChart_${index}`
- if (traversalChartsRefs.value[barChartKey]) {
- const barChart = echarts.init(traversalChartsRefs.value[barChartKey])
- barChart.setOption({
- tooltip: { trigger: 'axis' },
- xAxis: { type: 'category', data: ['90-100分', '80-89分', '70-79分', '60-69分', '60分以下'] },
- yAxis: { type: 'value' },
- series: [{ type: 'bar', data: [85, 120, 95, 60, 20], itemStyle: { color: '#3b82f6' } }]
- })
- }
- // 饼状图:岗位分布
- const pieChart3Key = `pieChart3_${index}`
- if (traversalChartsRefs.value[pieChart3Key]) {
- const pieChart3 = echarts.init(traversalChartsRefs.value[pieChart3Key])
- pieChart3.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: [
- { value: 150, name: '安检员' },
- { value: 120, name: '设备操作员' },
- { value: 80, name: '管理人员' },
- { value: 50, name: '其他' }
- ]
- }]
- })
- }
- })
- })
- }
- // 窗口大小变化时重绘图表
- const handleResize = () => {
- const charts = [
- overallBarChartInstance,
- participantPieChartInstance,
- departmentComparisonChartInstance,
- brigadePieChart1Instance,
- brigadePieChart2Instance,
- positionPieChart1Instance,
- positionPieChart2Instance
- ]
- charts.forEach(chart => {
- if (chart) chart.resize()
- })
- }
- // 获取数据
- const getList = async () => {
- loading.value = true
- try {
- // 格式化参数:YYYY-MM -> YYYYMM
- const formatParams = (params) => {
- const formatted = { ...params }
- if (formatted.assessmentMonth) {
- formatted.assessmentMonth = formatted.assessmentMonth.replace('-', '')
- }
- return formatted
- }
- // 获取大队列表
- const brigadeListRes = await listDept()
- const brigadeList = (brigadeListRes.data || []).filter(item => item.deptType === 'BRIGADE' && item.isFunctionalDept == 0)
- // 其他接口调用
- const formattedQueryParams = formatParams(queryParams)
- const results = await Promise.allSettled([
- getScoreDistribution(formattedQueryParams),
- getDeptParticipation(formattedQueryParams),
- getDeptScoreDistribution(formattedQueryParams),
- getAssessmentSummary(formattedQueryParams),
- getActualImprovementDistribution(formattedQueryParams),
- getActualIncompetentDistribution(formattedQueryParams),
- getAssessmentTeamImprovementDistribution(formattedQueryParams),
- getAssessmentTeamIncompetentDistribution(formattedQueryParams),
- getFunctionalDeptSummary(formattedQueryParams),
- getFunctionalDeptPersonnelDistribution(formattedQueryParams),
- getFunctionalDeptDistributionPie(formattedQueryParams)
- ])
- const [scoreDistRes, deptPartRes, deptScoreRes, assessmentSumRes, actualImproveRes, actualIncompRes, teamImproveRes, teamIncompRes, functionalDeptRes, functionalDeptPersonnelRes, functionalDeptPieRes] = results
- // 对每个大队调用三个接口
- const brigadeDataPromises = brigadeList.map(async (brigade) => {
- const brigadeParams = formatParams({ ...queryParams, deptId: brigade.deptId })
- const brigadeResults = await Promise.allSettled([
- getDeptAssessmentTeamStatistics(brigadeParams),
- getBrigadeImprovementDistribution(brigadeParams),
- getBrigadeIncompetentDistribution(brigadeParams)
- ])
- const [deptTeamStatRes, brigadeImproveRes, brigadeIncompRes] = brigadeResults
- const statisticsData = deptTeamStatRes.status === 'fulfilled' ? (deptTeamStatRes.value.data || []) : []
- const brigadeImproveData = brigadeImproveRes.status === 'fulfilled' ? (brigadeImproveRes.value.data || []) : []
- const brigadeIncompData = brigadeIncompRes.status === 'fulfilled' ? (brigadeIncompRes.value.data || []) : []
- // 解析表格数据
- let tableData = []
- if (statisticsData.length > 0 && statisticsData[0].assessmentTeams) {
- tableData = statisticsData[0].assessmentTeams.map(item => ({
- ...item,
- assessmentTeam: selectDictLabel(assessment_team.value, item.assessmentTeam)
- }))
- }
- // 计算总计用于标题
- const totalImprovement = tableData.reduce((sum, item) => sum + (item.actualImprovementCount || 0), 0)
- const totalIncompetent = tableData.reduce((sum, item) => sum + (item.actualIncompetentCount || 0), 0)
- return {
- title: brigade.deptName,
- deptId: brigade.deptId,
- tableData,
- improveData: brigadeImproveData,
- incompData: brigadeIncompData,
- totalImprovement,
- totalIncompetent
- }
- })
- traversalData1.value = await Promise.all(brigadeDataPromises)
- traversalData1.value = traversalData1.value.filter(item => item.tableData?.length > 0 || item.improveData?.length > 0 || item.incompData?.length > 0)
- console.log(traversalData1.value)
- // debugger
- if (scoreDistRes.status === 'fulfilled' && scoreDistRes.value.data) {
- const data = scoreDistRes.value.data
- if (overallBarChartInstance && Array.isArray(data)) {
- overallBarChartInstance.setOption({
- xAxis: { data: data.map(item => item.scoreRange) },
- series: [{ data: data.map(item => item.count) }]
- })
- }
- }
- if (deptPartRes.status === 'fulfilled' && deptPartRes.value.data) {
- const data = deptPartRes.value.data
- if (participantPieChartInstance && Array.isArray(data)) {
- participantPieChartInstance.setOption({
- series: [{ data: data.map(item => ({ name: item.deptName, value: item.count })) }]
- })
- }
- }
- if (deptScoreRes.status === 'fulfilled' && deptScoreRes.value.data) {
- const data = deptScoreRes.value.data
- if (departmentComparisonChartInstance && Array.isArray(data)) {
- const xAxisData = data.map(item => item.rangeLabel)
- const firstDeptCounts = data.length > 0 ? data[0].deptCounts : []
- const series = firstDeptCounts.map(dept => ({
- name: dept.deptName,
- type: 'bar',
- data: data.map(item => {
- const deptData = item.deptCounts.find(d => d.deptId === dept.deptId)
- return deptData ? deptData.count : 0
- })
- }))
- departmentComparisonChartInstance.setOption({
- legend: { data: firstDeptCounts.map(d => d.deptName) },
- xAxis: { data: xAxisData },
- series: series
- })
- summaryTableColumns.value = firstDeptCounts
- summaryTableData.value = data.map(item => {
- const row = {
- rangeLabel: item.rangeLabel,
- totalCount: item.totalCount
- }
- item.deptCounts.forEach(dept => {
- row['team' + dept.deptId] = dept.count
- })
- return row
- })
- }
- }
- if (assessmentSumRes.status === 'fulfilled' && assessmentSumRes.value.data) {
- const data = assessmentSumRes.value.data
- if (data && typeof data === 'object') {
- classificationTableData.value = [data]
- }
- }
- if (actualImproveRes.status === 'fulfilled' && actualImproveRes.value.data) {
- const data = actualImproveRes.value.data
- if (brigadePieChart1Instance && Array.isArray(data)) {
- brigadePieChart1Instance.setOption({
- series: [{ data: data.map(item => ({ name: item.deptName, value: item.count })) }]
- })
- }
- }
- if (actualIncompRes.status === 'fulfilled' && actualIncompRes.value.data) {
- const data = actualIncompRes.value.data
- if (brigadePieChart2Instance && Array.isArray(data)) {
- brigadePieChart2Instance.setOption({
- series: [{ data: data.map(item => ({ name: item.deptName, value: item.count })) }]
- })
- }
- }
- if (teamImproveRes.status === 'fulfilled' && teamImproveRes.value.data) {
- const data = teamImproveRes.value.data
- if (positionPieChart1Instance && Array.isArray(data)) {
- positionPieChart1Instance.setOption({
- series: [{ data: data.map(item => ({ name: selectDictLabel(assessment_team.value, item.assessmentTeam), value: item.count })) }]
- })
- }
- }
- if (teamIncompRes.status === 'fulfilled' && teamIncompRes.value.data) {
- const data = teamIncompRes.value.data
- if (positionPieChart2Instance && Array.isArray(data)) {
- positionPieChart2Instance.setOption({
- series: [{ data: data.map(item => ({ name: selectDictLabel(assessment_team.value, item.assessmentTeam), value: item.count })) }]
- })
- }
- }
- // 初始化traversalData1对应的图表
- nextTick(() => {
- traversalData1.value.forEach((item, index) => {
- const chartKey1 = `pieChart1_${index}`
- const chartKey2 = `pieChart2_${index}`
- if (traversalChartsRefs.value[chartKey1] && item.improveData) {
- const pieChart1 = echarts.init(traversalChartsRefs.value[chartKey1])
- pieChart1.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: item.improveData.map(d => ({ name: selectDictLabel(assessment_team.value, d.assessmentTeam), value: d.count }))
- }]
- })
- }
- if (traversalChartsRefs.value[chartKey2] && item.incompData) {
- const pieChart2 = echarts.init(traversalChartsRefs.value[chartKey2])
- pieChart2.setOption({
- tooltip: { trigger: 'item' },
- series: [{
- type: 'pie',
- radius: '50%',
- data: item.incompData.map(d => ({ name: selectDictLabel(assessment_team.value, d.assessmentTeam), value: d.count }))
- }]
- })
- }
- })
- })
- if (functionalDeptRes.status === 'fulfilled' && functionalDeptRes.value.data) {
- const summaryData = functionalDeptRes.value.data || []
- const personnelData = functionalDeptPersonnelRes.status === 'fulfilled' ? (functionalDeptPersonnelRes.value.data || []) : []
- const pieData = functionalDeptPieRes.status === 'fulfilled' ? (functionalDeptPieRes.value.data || []) : []
- traversalData2.value = summaryData.map(item => {
- // 匹配人员数据
- const personnelItem = personnelData.find(p => p.deptId === item.deptId)
- // 匹配饼图数据
- const deptPieData = pieData.filter(p => p.deptId === item.deptId)
- return {
- title: item.deptName,
- deptId: item.deptId,
- tableData: (item.assessmentTeams || []).map(t => ({
- ...t,
- assessmentTeam: selectDictLabel(assessment_team.value, t.assessmentTeam)
- })),
- barData: personnelItem ? [
- { name: '考核组人数', value: personnelItem.assessmentTeamCount || 0 },
- { name: '实际待改进人数', value: personnelItem.actualImprovementCount || 0 },
- { name: '实际不称职人数', value: personnelItem.actualIncompetentCount || 0 }
- ] : [],
- pieData: deptPieData.map(p => ({ name: selectDictLabel(assessment_team.value, p.assessmentTeam), value: p.count }))
- }
- })
-
- nextTick(() => {
- traversalData2.value.forEach((item, index) => {
- const chartKey = `barChart_${index}`
- const pieChartKey = `pieChart3_${index}`
- if (traversalChartsRefs.value[chartKey] && item.barData.length > 0) {
- const barChart = echarts.init(traversalChartsRefs.value[chartKey])
- barChart.setOption({
- tooltip: { trigger: 'axis' },
- xAxis: { type: 'category', data: item.barData.map(d => d.name) },
- yAxis: { type: 'value' },
- series: [{ type: 'bar', data: item.barData.map(d => d.value), itemStyle: { color: '#3b82f6' } }]
- })
- }
- if (traversalChartsRefs.value[pieChartKey] && item.pieData.length > 0) {
- const pieChart = echarts.init(traversalChartsRefs.value[pieChartKey])
- pieChart.setOption({
- tooltip: { trigger: 'item' },
- series: [{ type: 'pie', radius: '50%', data: item.pieData }]
- })
- }
- })
- })
- }
- } catch (error) {
- console.error('获取汇总数据失败:', error)
- ElMessage.error('获取汇总数据失败')
- } finally {
- loading.value = false
- }
- }
- // 查询
- const handleQuery = () => {
- getList()
- }
- // 重置查询
- const resetQuery = () => {
- queryFormRef.value?.resetFields()
- getList()
- }
- // 导出
- const handleExport = async () => {
- const loading = ElLoading.service({
- lock: true,
- text: '正在生成Word文档...',
- background: 'rgba(0, 0, 0, 0.7)'
- })
- try {
- const children = []
- // 生成动态文档标题
- let reportTitle = '非干部月度考核汇总报告'
- if (queryParams.assessmentMonth) {
- const [year, month] = queryParams.assessmentMonth.split('-')
- reportTitle = `${year}年${parseInt(month)}月非干部月度考核汇总报告`
- }
- // 添加文档标题
- children.push(
- new Paragraph({
- text: reportTitle,
- heading: HeadingLevel.TITLE,
- alignment: AlignmentType.CENTER,
- }),
- new Paragraph({
- text: `导出时间: ${new Date().toLocaleString('zh-CN')}`,
- alignment: AlignmentType.CENTER,
- }),
- new Paragraph({ text: '' })
- )
- // 获取所有 main-section(两大区块)
- const mainSections = document.querySelectorAll('.main-section')
- for (let i = 0; i < mainSections.length; i++) {
- const section = mainSections[i]
- const sectionTitle = section.querySelector('.section-title')
- const sectionName = sectionTitle ? sectionTitle.textContent : `第${i + 1}部分`
- children.push(
- new Paragraph({
- text: `${i + 1}. ${sectionName}`,
- heading: HeadingLevel.HEADING_1,
- }),
- new Paragraph({ text: '' })
- )
- if (i === 0) {
- // 第一部分:非干部月度考核分数汇总
- // 处理第一行的两个图表
- const chartRow1 = section.querySelector('.chart-row')
- if (chartRow1) {
- const chartCards = chartRow1.querySelectorAll('.chart-card')
- for (let j = 0; j < chartCards.length; j++) {
- const card = chartCards[j]
- const header = card.querySelector('.chart-header')
- const chartTitle = header ? header.textContent : `图表${j + 1}`
- children.push(
- new Paragraph({
- text: `${i + 1}.${j + 1} ${chartTitle}`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- const chartContainer = card.querySelector('.chart-container')
- if (chartContainer) {
- await captureAndAddImage(chartContainer, children)
- }
- }
- }
- // 处理第二行的图表和汇总表
- const chartRow2 = section.querySelectorAll('.chart-row')
- if (chartRow2.length > 1) {
- const secondRow = chartRow2[1]
- const cards = secondRow.querySelectorAll('.chart-card')
- for (let j = 0; j < cards.length; j++) {
- const card = cards[j]
- const header = card.querySelector('.chart-header')
- const chartTitle = header ? header.textContent : `图表${j + 1}`
- children.push(
- new Paragraph({
- text: `${i + 1}.${j + 3} ${chartTitle}`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- if (j === 0) {
- // 各部门分值分布对比图
- const chartContainer = card.querySelector('.chart-container')
- if (chartContainer) {
- await captureAndAddImage(chartContainer, children)
- }
- } else {
- // 汇总表
- const table = card.querySelector('.el-table')
- if (table) {
- await captureTable(table, children)
- }
- }
- }
- }
- } else {
- // 第二部分:非干部月度考核分类结果汇总
- // 汇总统计表格
- const summaryTableCard = section.querySelector('.summary-table-card')
- if (summaryTableCard) {
- const header = summaryTableCard.querySelector('.chart-header')
- const tableTitle = header ? header.textContent : '汇总统计'
- children.push(
- new Paragraph({
- text: `${i + 1}.1 ${tableTitle}`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- const table = summaryTableCard.querySelector('.el-table')
- if (table) {
- await captureTable(table, children)
- }
- }
- // 第四行的两个图表
- const chartRows = section.querySelectorAll('.chart-row')
- if (chartRows.length > 0) {
- const chartRow = chartRows[0]
- const chartCards = chartRow.querySelectorAll('.chart-card')
- for (let j = 0; j < chartCards.length; j++) {
- const card = chartCards[j]
- const header = card.querySelector('.chart-header')
- const chartTitle = header ? header.textContent : `图表${j + 1}`
- children.push(
- new Paragraph({
- text: `${i + 1}.${j + 2} ${chartTitle}`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- const pieChartsContainer = card.querySelector('.pie-charts-container')
- if (pieChartsContainer) {
- await captureAndAddImage(pieChartsContainer, children)
- }
- }
- }
- }
- }
- // 处理第一个遍历区域(traversalData1)
- const traversalSections = document.querySelectorAll('.traversal-section')
- let sectionCounter = mainSections.length + 1
- if (traversalSections.length > 0) {
- const firstTraversal = traversalSections[0]
- const traversalContainers = firstTraversal.querySelectorAll('.traversal-container')
- for (let t = 0; t < traversalContainers.length; t++) {
- const container = traversalContainers[t]
- const headerEl = container.querySelector('.traversal-header')
- const title = headerEl ? headerEl.textContent : `部门${t + 1}`
- children.push(
- new Paragraph({
- text: `${sectionCounter}. ${title}`,
- heading: HeadingLevel.HEADING_1,
- }),
- new Paragraph({ text: '' })
- )
- const content = container.querySelector('.traversal-content')
- if (content) {
- // 左边表格
- const tableSection = content.querySelector('.table-section')
- if (tableSection) {
- const table = tableSection.querySelector('.el-table')
- if (table) {
- children.push(
- new Paragraph({
- text: `${sectionCounter}.1 考核组统计表`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- await captureTable(table, children)
- }
- }
- // 右边两个饼状图
- const chartSection = content.querySelector('.chart-section')
- if (chartSection) {
- const pieContainers = chartSection.querySelectorAll('.pie-chart-container')
- for (let p = 0; p < pieContainers.length; p++) {
- const pieContainer = pieContainers[p]
- const pieTitleEl = pieContainer.querySelector('.pie-chart-title')
- const pieTitle = pieTitleEl ? pieTitleEl.textContent : `饼图${p + 1}`
- children.push(
- new Paragraph({
- text: `${sectionCounter}.${p + 2} ${pieTitle}`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- const pieChart = pieContainer.querySelector('.pie-chart')
- if (pieChart) {
- await captureAndAddImage(pieChart, children)
- }
- }
- }
- }
- sectionCounter++
- }
- }
- // 处理第二个遍历区域(traversalData2)
- if (traversalSections.length > 1) {
- const secondTraversal = traversalSections[1]
- const traversalContainers = secondTraversal.querySelectorAll('.traversal-container')
- for (let t = 0; t < traversalContainers.length; t++) {
- const container = traversalContainers[t]
- const headerEl = container.querySelector('.traversal-header')
- const title = headerEl ? headerEl.textContent : `部门${t + 1}`
- children.push(
- new Paragraph({
- text: `${sectionCounter}. ${title}`,
- heading: HeadingLevel.HEADING_1,
- }),
- new Paragraph({ text: '' })
- )
- const content = container.querySelector('.traversal-content')
- if (content) {
- // 左边表格
- const tableSection = content.querySelector('.table-section')
- if (tableSection) {
- const table = tableSection.querySelector('.el-table')
- if (table) {
- children.push(
- new Paragraph({
- text: `${sectionCounter}.1 考核组统计表`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- await captureTable(table, children)
- }
- }
- // 右边柱状图和饼状图
- const chartSection = content.querySelector('.chart-section')
- if (chartSection) {
- const barContainer = chartSection.querySelector('.bar-chart-container')
- if (barContainer) {
- const chartTitleEl = barContainer.querySelector('.chart-title')
- const chartTitle = chartTitleEl ? chartTitleEl.textContent : '考核分数分布'
- children.push(
- new Paragraph({
- text: `${sectionCounter}.2 ${chartTitle}`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- const barChart = barContainer.querySelector('.bar-chart')
- if (barChart) {
- await captureAndAddImage(barChart, children)
- }
- }
- const pieContainer = chartSection.querySelector('.pie-chart-container')
- if (pieContainer) {
- const pieTitleEl = pieContainer.querySelector('.pie-chart-title')
- const pieTitle = pieTitleEl ? pieTitleEl.textContent : '岗位分布'
- children.push(
- new Paragraph({
- text: `${sectionCounter}.3 ${pieTitle}`,
- heading: HeadingLevel.HEADING_2,
- }),
- new Paragraph({ text: '' })
- )
- const pieChart = pieContainer.querySelector('.pie-chart')
- if (pieChart) {
- await captureAndAddImage(pieChart, children)
- }
- }
- }
- }
- sectionCounter++
- }
- }
- // 创建文档
- const doc = new Document({
- sections: [{
- properties: {},
- children: children
- }]
- })
- // 生成并下载
- const blob = await Packer.toBlob(doc)
- const fileName = `${reportTitle}_${new Date().toISOString().slice(0, 10)}.docx`
- saveAs(blob, fileName)
- ElMessage.success('Word文档导出成功')
- } catch (error) {
- console.error('导出失败:', error)
- ElMessage.error('导出失败: ' + (error.message || '请重试'))
- } finally {
- loading.close()
- }
- }
- // 截图图表容器并添加为图片
- const captureAndAddImage = async (element, children) => {
- try {
- const canvas = await html2canvas(element, {
- backgroundColor: '#ffffff',
- scale: 2,
- logging: false
- })
- const imageData = canvas.toDataURL('image/png')
- const base64Data = imageData.replace(/^data:image\/png;base64,/, '')
- if (!base64Data || base64Data.trim() === '') {
- return
- }
- const maxWidth = 500
- const originalWidth = canvas.width
- const originalHeight = canvas.height
- const aspectRatio = originalWidth / originalHeight
- let finalWidth = maxWidth
- let finalHeight = maxWidth / aspectRatio
- const maxHeight = 400
- if (finalHeight > maxHeight) {
- finalHeight = maxHeight
- finalWidth = maxHeight * aspectRatio
- }
- const imageBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0))
- children.push(
- new Paragraph({
- children: [
- new ImageRun({
- data: imageBytes,
- transformation: {
- width: Math.round(finalWidth),
- height: Math.round(finalHeight)
- }
- })
- ],
- alignment: AlignmentType.CENTER,
- }),
- new Paragraph({ text: '' })
- )
- } catch (error) {
- console.error('截图失败:', error)
- }
- }
- // 截图表格并添加为图片
- const captureTable = async (table, children) => {
- try {
- const originalMaxHeight = table.style.maxHeight || ''
- const originalOverflowY = table.style.overflowY || ''
- const originalOverflowX = table.style.overflowX || ''
- const originalTableWidth = table.style.width || ''
- table.style.maxHeight = 'none'
- table.style.overflowY = 'visible'
- table.style.overflowX = 'visible'
- table.style.width = '800px'
- let originalTableHeight = ''
- let originalTableWidthStyle = ''
- originalTableHeight = table.style.height || ''
- originalTableWidthStyle = table.style.width || ''
- table.style.height = 'auto'
- table.style.width = '800px'
- const bodyWrapper = table.querySelector('.el-table__body-wrapper')
- if (bodyWrapper) {
- bodyWrapper.style.overflowX = 'visible'
- bodyWrapper.style.overflowY = 'visible'
- }
- await new Promise(resolve => setTimeout(resolve, 200))
- const canvas = await html2canvas(table, {
- backgroundColor: '#ffffff',
- scale: 1.5,
- logging: false,
- useCORS: true,
- allowTaint: true,
- scrollX: 0,
- scrollY: 0,
- width: 800,
- height: table.scrollHeight
- })
- table.style.maxHeight = originalMaxHeight
- table.style.overflowY = originalOverflowY
- table.style.overflowX = originalOverflowX
- table.style.width = originalTableWidth
- table.style.height = originalTableHeight
- table.style.width = originalTableWidthStyle
- if (bodyWrapper) {
- bodyWrapper.style.overflowX = ''
- bodyWrapper.style.overflowY = ''
- }
- const imageData = canvas.toDataURL('image/png')
- const base64Data = imageData.replace(/^data:image\/png;base64,/, '')
- if (!base64Data || base64Data.trim() === '') {
- return
- }
- const maxWidth = 500
- const originalWidth = canvas.width
- const originalHeight = canvas.height
- const aspectRatio = originalWidth / originalHeight
- let finalWidth = maxWidth
- let finalHeight = maxWidth / aspectRatio
- const maxHeight = 400
- if (finalHeight > maxHeight) {
- finalHeight = maxHeight
- finalWidth = maxHeight * aspectRatio
- }
- const imageBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0))
- children.push(
- new Paragraph({
- children: [
- new ImageRun({
- data: imageBytes,
- transformation: {
- width: Math.round(finalWidth),
- height: Math.round(finalHeight)
- }
- })
- ],
- alignment: AlignmentType.CENTER,
- }),
- new Paragraph({ text: '' })
- )
- } catch (error) {
- console.error('表格截图失败:', error)
- }
- }
- onMounted(() => {
- initCharts()
- initTraversalCharts()
- window.addEventListener('resize', handleResize)
- getList()
- })
- // 组件卸载时移除事件监听器
- onUnmounted(() => {
- window.removeEventListener('resize', handleResize)
- // 销毁图表实例
- const charts = [
- overallBarChartInstance,
- participantPieChartInstance,
- departmentComparisonChartInstance,
- brigadePieChart1Instance,
- brigadePieChart2Instance,
- positionPieChart1Instance,
- positionPieChart2Instance
- ]
- charts.forEach(chart => {
- if (chart) chart.dispose()
- })
- })
- </script>
- <style lang="less" scoped>
- .app-container {
- padding: 20px;
- }
- .filter-section {
- margin-bottom: 20px;
- }
- .filter-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .search-form {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .export-button {
- margin-left: auto;
- }
- .main-section {
- margin-bottom: 30px;
- }
- .section-title {
- font-size: 20px;
- font-weight: bold;
- color: #333;
- margin-bottom: 20px;
- text-align: center;
- }
- .chart-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 20px;
- margin-bottom: 20px;
- }
- .chart-card {
- height: 400px;
- }
- .summary-table-card {
- margin-bottom: 20px;
- }
- .chart-header {
- font-size: 16px;
- font-weight: 600;
- color: #333;
- margin-bottom: 10px;
- text-align: center;
- }
- .chart-container {
- width: 100%;
- height: 350px;
- }
- .pie-charts-container {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
- height: 350px;
- }
- .pie-chart {
- width: 100%;
- height: 100%;
- }
- :deep(.el-table) {
- .el-table__header th {
- background-color: #f5f7fa;
- font-weight: 600;
- }
- }
- /* 遍历样式 */
- .traversal-section {
- margin-bottom: 30px;
- }
- .traversal-container {
- border: 1px solid #dcdfe6;
- border-radius: 8px;
- overflow: hidden;
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
- margin-bottom: 20px;
- }
- .traversal-header {
- background-color: #f5f7fa;
- padding: 15px 20px;
- font-size: 18px;
- font-weight: 600;
- color: #303133;
- border-bottom: 1px solid #dcdfe6;
- text-align: left;
- }
- .traversal-content {
- display: flex;
- gap: 20px;
- padding: 20px;
- }
- .table-section {
- flex: 1;
- min-width: 0;
- min-height: 400px;
- }
- .chart-section {
- flex: 1;
- min-width: 0;
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 15px;
- height: 400px;
- }
- .pie-chart-container,
- .bar-chart-container {
- display: flex;
- flex-direction: column;
- border: 1px solid #e4e7ed;
- border-radius: 6px;
- padding: 10px;
- background-color: #fff;
- }
- .pie-chart-title,
- .chart-title {
- font-size: 14px;
- font-weight: 600;
- color: #606266;
- margin-bottom: 10px;
- text-align: center;
- }
- .pie-chart,
- .bar-chart {
- flex: 1;
- min-height: 300px;
- }
- /* 响应式布局 */
- @media (max-width: 1200px) {
- .chart-row {
- grid-template-columns: 1fr;
- }
- .pie-charts-container {
- grid-template-columns: 1fr;
- }
- .traversal-content {
- flex-direction: column;
- gap: 15px;
- }
- .table-section,
- .chart-section {
- min-width: 0;
- }
- .chart-section {
- grid-template-columns: 1fr;
- height: auto;
- }
- .pie-chart,
- .bar-chart {
- min-height: 250px;
- }
- }
- @media (max-width: 768px) {
- .traversal-content {
- padding: 15px;
- }
- .chart-section {
- gap: 10px;
- }
- .pie-chart-container,
- .bar-chart-container {
- padding: 8px;
- }
- }
- </style>
|