| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248 |
- <template>
- <div class="duty-organization">
- <!-- 勤务组织标题 -->
- <div class="section-title">
- <h2>勤务组织</h2>
- </div>
- <!-- 横向布局内容区域 -->
- <div class="content-layout">
- <!-- 左侧:出勤人次分析 -->
- <div class="left-panel panel-item">
- <div class="panel-header">
- <h3>出勤人次分析</h3>
- </div>
- <!-- 统计卡片 -->
- <div class="stat-card">
- <div class="stat-content">
- {{ attendanceData && attendanceData.length > 0 ? attendanceData[attendanceData.length -
- 1]?.deptName : ''
- }}出勤<span>{{ attendanceData && attendanceData.length > 0 ? attendanceData[attendanceData.length -
- 1]?.currentValue : 0
- }}</span>人次,同比
- <span
- :class="getTrendClass(attendanceData && attendanceData.length > 0 ? attendanceData[attendanceData.length - 1]?.yearOnYearValue : 0)">{{
- formatRate(attendanceData && attendanceData.length > 0 ? attendanceData[attendanceData.length -
- 1]?.yearOnYearValue : 0) }}</span>
- <template v-if="props.queryForm.dateRangeQueryType !== 'YEAR'">
- ,环比
- <span
- :class="getTrendClass(attendanceData && attendanceData.length > 0 ? attendanceData[attendanceData.length - 1]?.chainRatioValue : 0)">{{
- formatRate(attendanceData && attendanceData.length > 0 ? attendanceData[attendanceData.length -
- 1]?.chainRatioValue : 0)
- }}</span>
- </template>
- </div>
- </div>
- <!-- 科室数据卡片 -->
- <div class="dept-cards">
- <template v-if="attendanceData && attendanceData.length > 0">
- <div v-for="(item, index) in attendanceData.slice(0, 4)" :key="index" class="dept-card">
- <div class="dept-name">{{ item.deptName || selectedDeptObject?.label }}</div>
- <div class="dept-count"><span>{{ item.currentValue || 0 }}</span>人次</div>
- <div class="dept-trend">
- <div>
- 同比<span :class="getTrendClass(item.yearOnYearValue)">{{ formatRate(item.yearOnYearValue) }}</span>
- </div>
- <template v-if="props.queryForm.dateRangeQueryType !== 'YEAR'">
- <div>
- 环比<span :class="getTrendClass(item.chainRatioValue)">{{ formatRate(item.chainRatioValue) }}</span>
- </div>
- </template>
- </div>
- </div>
- </template>
- <template v-else>
- <!-- 空数据时显示占位卡片 -->
- <div v-for="i in 3" :key="i" class="dept-card empty">
- <div class="dept-name">暂无数据</div>
- <div class="dept-count"><span>0</span>人次</div>
- <div class="dept-trend">
- 同比<span class="trend-neutral">0%</span>
- <template v-if="props.queryForm.dateRangeQueryType !== 'YEAR'">
- 环比<span class="trend-neutral">0%</span>
- </template>
- </div>
- </div>
- </template>
- </div>
- <!-- 柱状图区域 -->
- <div class="chart-container">
- <div ref="attendanceBarChartRef" class="echarts-chart"></div>
- </div>
- </div>
- <!-- 右侧:资质等级分布 -->
- <div class="right-panel panel-item" v-show="!isUserType">
- <div class="panel-header">
- <h3>资质等级分布</h3>
- </div>
- <!-- 资质等级分布内容区域 -->
- <div class="qualification-content">
- <!-- 左侧:资质等级统计 -->
- <div class="qualification-left">
- <!-- 上方描述卡片 -->
- <div class="stat-card" v-if="qualificationPieDescriptionPart1">
- <div class="stat-content">
- {{ qualificationPieDescriptionPart1 || '资质等级分布数据加载中...' }}
- </div>
- </div>
- <!-- 下方饼图 -->
- <div class="chart-container">
- <div ref="pieChartRef" class="echarts-chart"></div>
- </div>
- </div>
- <!-- 右侧:资质分布趋势 -->
- <div class="qualification-right" v-show="isStationType">
- <!-- 上方描述卡片 -->
- <div class="stat-card" v-if="qualificationPieDescriptionPart2">
- <div class="stat-content">
- {{ qualificationPieDescriptionPart2 || '资质分布趋势数据加载中...' }}
- </div>
- </div>
- <!-- 下方柱状图 -->
- <div class="chart-container">
- <div ref="barChartRef" class="echarts-chart"></div>
- </div>
- </div>
- <div class="qualification-right" style="justify-content: center;" v-show="isTeamsType">
- <!-- 班组人员资质等级表格 -->
- <div class="table-container">
- <el-table :data="teamsQualificationData" size="small" height="250" border :scroll="{ x: 'max-content' }"
- stripe>
- <el-table-column prop="deptName" label="姓名" align="center" />
- <el-table-column prop="qualificationLevel" label="等级" align="center" />
- </el-table>
- </div>
- </div>
- </div>
- </div>
- <div class="right-panel-ability panel-item" v-show="isUserType">
- <div class="panel-header">
- <h3>资质能力</h3>
- </div>
- <!-- 统计卡片 -->
- <div class="stat-card">
- <div class="stat-content">资质等级:{{ user?.qualificationLevel || '未知' }}</div>
- <div class="stat-content">可上岗岗位:{{user?.sysPostList && user?.sysPostList.map(item => item.postName).join('、')
- || '未知'
- }}</div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
- import { useEcharts } from '@/hooks/chart.js'
- import { getCalculate, getCalculateTrendData, getQualificationPieChart, getQualificationBarChart } from '@/api/assistant/assistant.js'
- // 定义props接收queryForm参数
- const props = defineProps({
- queryForm: {
- type: Object,
- default: () => ({
- dateRangeQueryType: '',
- year: '',
- quarter: '',
- month: ''
- })
- },
- selectedDeptObject: {
- type: Object,
- default: null
- }
- })
- const user = ref({})
- const teamsQualificationData = ref([])
- // 饼图容器引用
- const pieChartRef = ref(null)
- const barChartRef = ref(null)
- const attendanceBarChartRef = ref(null)
- // 图表实例
- const { setOption: setPieOption, dispose: disposePie } = useEcharts(pieChartRef)
- const { setOption: setBarOption, dispose: disposeBar } = useEcharts(barChartRef)
- const { setOption: setAttendanceOption, dispose: disposeAttendance } = useEcharts(attendanceBarChartRef)
- // 响应式数据
- const attendanceData = ref({})
- const trendData = ref({})
- const qualificationPieData = ref({})
- const qualificationBarData = ref({})
- // 计算属性:处理selectedDeptObject逻辑
- const selectedDeptType = computed(() => {
- return props.selectedDeptObject && props.selectedDeptObject?.deptType
- })
- // 计算属性:检查是否为STATION类型
- const isStationType = computed(() => {
- console.log('selectedDeptType.value', selectedDeptType.value)
- return selectedDeptType.value === 'STATION' || !selectedDeptType.value
- })
- const isDepartmentType = computed(() => {
- return selectedDeptType.value === 'MANAGER'
- })
- const isTeamsType = computed(() => {
- return selectedDeptType.value === 'TEAMS'
- })
- const isUserType = computed(() => {
- return selectedDeptType.value === 'USER'
- })
- // 计算属性:检查是否为非STATION类型
- const isNotUserType = computed(() => {
- return !selectedDeptType.value !== 'USER'
- })
- // 计算属性:动态生成资质等级分布描述第一部分(句号前)
- const qualificationPieDescriptionPart1 = computed(() => {
- if (!qualificationPieData.value || !qualificationPieData.value.data || !Array.isArray(qualificationPieData.value.data)) {
- return ''
- }
- const pieData = qualificationPieData.value.data
- // 1. 找出占比最高的等级
- const highestLevel = pieData.reduce((max, item) => {
- return (item.count || 0) > (max.count || 0) ? item : max
- }, { levelName: '高级', count: 0 })
- // 2. 计算总人数
- const totalCount = pieData.reduce((sum, item) => sum + (item.count || 0), 0)
- // 3. 计算占比
- const highestPercentage = totalCount > 0 ? ((highestLevel.count / totalCount) * 100).toFixed(2) : '0.0'
- let deptName = attendanceData.value && attendanceData.value.length > 0 ? attendanceData.value[attendanceData.value.length -
- 1]?.deptName : ''
- // 4. 生成第一部分描述文字
- return `${deptName}资质等级以"${highestLevel.levelName || '高级'}"为主(占比为${highestPercentage}%)`
- })
- // 计算属性:动态生成资质等级分布描述第二部分(句号后)
- const qualificationPieDescriptionPart2 = computed(() => {
- if (!qualificationPieData.value || !qualificationPieData.value.data || !Array.isArray(qualificationPieData.value.data)) {
- return ''
- }
- // 从资质柱状图数据中找出"一级"人员最多的科室
- let topDeptForLevel1 = ''
- let level1Count = 0
- let totalDeptCount = 0
- let allDeptNames = []
- if (qualificationBarData.value && Array.isArray(qualificationBarData.value)) {
- const barData = qualificationBarData.value
- // 找出"一级"人员最多的科室
- let maxLevel1Count = 0
- let maxDeptName = ''
- let totalCountForDept = 0
- barData.forEach(dept => {
- allDeptNames.push(dept.deptName)
- if (dept.levelCounts && Array.isArray(dept.levelCounts)) {
- const level1Data = dept.levelCounts.find(level => level.levelName === '高级')
- const deptTotalCount = dept.levelCounts.reduce((sum, level) => sum + (level.count || 0), 0)
- if (level1Data && level1Data.count > maxLevel1Count) {
- maxLevel1Count = level1Data.count
- maxDeptName = dept.deptName || ''
- totalCountForDept = deptTotalCount
- }
- }
- })
- topDeptForLevel1 = maxDeptName
- level1Count = maxLevel1Count
- totalDeptCount = totalCountForDept
- }
- // 生成第二部分描述文字
- return `全站资质等级为"高级"的人员集中在${topDeptForLevel1}(${level1Count}人)${topDeptForLevel1}的人员规模(共${totalDeptCount}人)高于${allDeptNames.filter(name => name !== topDeptForLevel1).join(', ')}`
- })
- // 处理query参数,当dateRangeQueryType为YEAR时添加yearOnYear: true
- const processQueryParams = (queryParams) => {
- const processedParams = { ...queryParams }
- if (processedParams.dateRangeQueryType === 'YEAR') {
- processedParams.yearOnYear = true
- } else {
- processedParams.chainRatio = true
- processedParams.yearOnYear = true
- }
- return processedParams
- }
- // 格式化比率显示
- const formatRate = (rate) => {
- if (rate === null || rate === undefined) return '0%'
- const numRate = parseFloat(rate)
- if (numRate > 0) {
- return `+${numRate.toFixed(2)}%`
- } else if (numRate < 0) {
- return `${numRate.toFixed(2)}%`
- } else {
- return '--%'
- }
- }
- // 获取趋势CSS类名
- const getTrendClass = (rate) => {
- if (rate === null || rate === undefined) return 'neutral'
- const numRate = parseFloat(rate)
- if (numRate > 0) {
- return 'up'
- } else if (numRate < 0) {
- return 'down'
- } else {
- return 'neutral'
- }
- }
- // 调用API获取勤务组织数据
- const fetchDutyOrganizationData = async (queryParams) => {
- try {
- // 处理query参数
- const processedParams = processQueryParams(queryParams)
- const selectedDept = props.selectedDeptObject
- const { deptType = "", id } = selectedDept ? selectedDept : { deptType: "", id: "" }
- delete processedParams.deptId
- let calculateParams = {
- ...(['TEAMS', 'DEPARTMENT', 'BRIGADE','MANAGER'].includes(deptType) ? { deptId: id } : {}),
- ...(deptType == 'USER' ? { userId: id } : {})
- }
- // 获取出勤人次分析数据
- const attendanceResponse = await getCalculate({ ...processedParams, ...calculateParams })
- console.log('出勤人次分析数据:', attendanceResponse)
- attendanceData.value = attendanceResponse || []
- // 获取出勤人次趋势数据
- const trendResponse = await getCalculateTrendData({ ...processedParams, ...calculateParams })
- console.log('出勤人次趋势数据:', trendResponse)
- trendData.value = trendResponse || []
- // 获取资质等级分布饼图数据(如果部门类型不是STATION)
- if (isNotUserType.value) {
- const pieResponse = await getQualificationPieChart({ ...processedParams, ...calculateParams })
-
- qualificationPieData.value = pieResponse.data || []
- } else {
- qualificationPieData.value = []
- }
- if (isStationType.value) {
- //获取资质等级分布柱状图数据
- const barResponse = await getQualificationBarChart({ ...processedParams, ...calculateParams })
- qualificationBarData.value = barResponse.data?.brigades || []
- } else if (isTeamsType.value) {
- // 获取资质等级分布柱状图数据
- const barResponse = await getQualificationBarChart({ ...processedParams, ...calculateParams })
- console.log('资质等级分布柱状图数据:', barResponse.data?.brigades)
- // 处理班组人员资质等级数据
- if (barResponse.data?.brigades && Array.isArray(barResponse.data.brigades)) {
- teamsQualificationData.value = barResponse.data.brigades
- } else {
- teamsQualificationData.value = []
- }
- } else if (isUserType.value) {
- // 获取资质等级分布柱状图数据
- const barResponse = await getQualificationBarChart({ ...processedParams, ...calculateParams })
- console.log('资质等级分布柱状图数据:', barResponse.data.brigades)
- user.value = barResponse?.data?.brigades[0]
- }
- setTimeout(() => {
- // 更新图表和统计信息
- updateChartsWithData()
- }, 0);
- } catch (error) {
- console.error('获取勤务组织数据失败:', error)
- }
- }
- // 监听queryForm参数变化,调用API获取数据
- watch(() => props.queryForm, (newQueryForm) => {
- // 只有当所有必要的查询参数都存在时才调用API
- if (newQueryForm.dateRangeQueryType && newQueryForm.year) {
- fetchDutyOrganizationData(newQueryForm)
- }
- }, { deep: true })
- // 饼图配置
- const pieOptions = {
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c}人 ({d}%)'
- },
- legend: {
- orient: 'vertical',
- left: 'left',
- top: 'center',
- textStyle: {
- color: '#333',
- fontSize: 12
- }
- },
- color: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57'],
- series: [
- {
- name: '资质等级分布',
- type: 'pie',
- radius: '50%',
- center: ['60%', '50%'],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 10,
- borderColor: '#fff',
- borderWidth: 2
- },
- label: {
- show: true,
- formatter: '{b}: {c}人 ({d}%)',
- fontSize: 12
- },
- emphasis: {
- label: {
- show: true,
- fontSize: 14,
- fontWeight: 'bold'
- },
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- },
- labelLine: {
- show: true,
- length: 10,
- length2: 20
- },
- data: [
- ]
- }
- ]
- }
- // 资质趋势柱状图配置
- const trendBarOptions = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- },
- formatter: function (params) {
- let result = `${params[0].axisValue}<br/>`
- params.forEach(param => {
- result += `${param.seriesName}: <span style="color:${param.color};font-weight:bold">${param.data}</span>人<br/>`
- })
- return result
- }
- },
- legend: {
- data: [],
- top: 0
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- top: '10%',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: [],
- axisLine: {
- lineStyle: {
- color: '#999'
- }
- },
- axisLabel: {
- fontSize: 12
- }
- },
- yAxis: {
- type: 'value',
- name: '人数',
- axisLine: {
- lineStyle: {
- color: '#999'
- }
- },
- splitLine: {
- lineStyle: {
- color: '#f0f0f0'
- }
- }
- },
- series: [
- {
- name: '旅检一科',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#FF6B6B'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- },
- {
- name: '旅检二科',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#4ECDC4'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- },
- {
- name: '旅检三科',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#FFD166'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- }
- ]
- }
- // 出勤人次柱状图配置
- const attendanceBarOptions = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- },
- formatter: function (params) {
- let result = `${params[0].axisValue}<br/>`
- params.forEach(param => {
- result += `${param.seriesName}: <span style="color:${param.color};font-weight:bold">${param.data}</span>人<br/>`
- })
- return result
- }
- },
- legend: {
- data: ['安检一大队', '安检二大队', '安检三大队', '安检综合大队', '全站'],
- top: 0,
- show: true,
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '0%',
- top: '80',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: [],
- axisLine: {
- lineStyle: {
- color: '#999'
- }
- },
- axisLabel: {
- fontSize: 12
- }
- },
- yAxis: {
- type: 'value',
- name: '人数',
- axisLine: {
- lineStyle: {
- color: '#999'
- }
- },
- splitLine: {
- lineStyle: {
- color: '#f0f0f0'
- }
- }
- },
- series: [
- {
- name: '全站',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#5470C6'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- },
- {
- name: '安检一大队',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#FF6B6B'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- },
- {
- name: '安检二大队',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#4ECDC4'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- },
- {
- name: '安检三大队',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#FFD166'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- },
- {
- name: '安检综合大队',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#FFD166'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- }
- ]
- }
- // 出勤人次柱状图配置
- const attendanceBarOtherOptions = {
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- },
- formatter: function (params) {
- let result = `${params[0].axisValue}<br/>`
- params.forEach(param => {
- result += ` <span style="color:${param.color};font-weight:bold">${param.data}</span>人<br/>`
- })
- return result
- }
- },
- legend: {
- top: 0,
- show: true,
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '0%',
- top: '80',
- containLabel: true
- },
- xAxis: {
- type: 'category',
- data: [],
- axisLine: {
- lineStyle: {
- color: '#999'
- }
- },
- axisLabel: {
- fontSize: 12
- }
- },
- yAxis: {
- type: 'value',
- name: '人数',
- axisLine: {
- lineStyle: {
- color: '#999'
- }
- },
- splitLine: {
- lineStyle: {
- color: '#f0f0f0'
- }
- }
- },
- series: [
- {
- name: '',
- type: 'bar',
- barWidth: '10%',
- itemStyle: {
- color: '#5470C6'
- },
- label: {
- show: true,
- position: 'top',
- formatter: '{c}人'
- },
- data: []
- },
- ]
- }
- // 初始化图表
- onMounted(() => {
- // 确保DOM完全渲染后再初始化图表
- const initCharts = () => {
- // 资质等级饼图
- if (pieChartRef.value && pieChartRef.value.offsetHeight > 0) {
- setPieOption(pieOptions)
- }
- // 资质趋势柱状图
- if (barChartRef.value && barChartRef.value.offsetHeight > 0) {
- setBarOption(trendBarOptions)
- }
- // 出勤人次柱状图
- if (attendanceBarChartRef.value && attendanceBarChartRef.value.offsetHeight > 0) {
- setAttendanceOption(attendanceBarOptions)
- }
- }
- // 延迟初始化,确保CSS已应用
- setTimeout(() => {
- initCharts()
- }, 100)
- })
- // 更新资质等级分布饼图数据
- const updateQualificationPieChart = () => {
- if (qualificationPieData.value && qualificationPieData.value.data) {
- const pieData = qualificationPieData.value.data
- // 转换数据格式,使用levelName作为名称,count作为值
- const formattedData = pieData.map(item => ({
- name: item.levelName || '未知等级',
- value: item.count || 0
- })).filter(item => item.value > 0)
- // 更新饼图数据
- pieOptions.series[0].data = formattedData
- // 重新设置图表选项
- setPieOption(pieOptions)
- console.log('资质等级分布饼图已更新:', formattedData)
- } else {
- // 无数据时清空图表
- pieOptions.series[0].data = []
- setPieOption(pieOptions)
- }
- }
- // 更新资质趋势柱状图数据
- const updateTrendBarChart = () => {
- if (qualificationBarData.value && Array.isArray(qualificationBarData.value)) {
- const barData = qualificationBarData.value
- // 提取所有唯一的等级名称(横坐标)
- const allLevelNames = []
- barData.forEach(dept => {
- if (dept.levelCounts && Array.isArray(dept.levelCounts)) {
- dept.levelCounts.forEach(level => {
- if (level.levelName && !allLevelNames.includes(level.levelName)) {
- allLevelNames.push(level.levelName)
- }
- })
- }
- })
- // 按等级顺序排序(一级、二级、三级、四级、五级)
- const levelOrder = ['一级', '二级', '三级', '四级', '五级']
- const sortedLevelNames = allLevelNames.sort((a, b) => {
- return levelOrder.indexOf(a) - levelOrder.indexOf(b)
- })
- // 为每个科室创建数据系列
- const seriesData = []
- const colors = ['#FF6B6B', '#4ECDC4', '#FFD166', '#9B59B6', '#3498DB']
- barData.forEach((dept, index) => {
- if (dept.deptName && dept.levelCounts) {
- // 为每个等级查找对应的数量
- const levelCounts = sortedLevelNames.map(levelName => {
- const levelData = dept.levelCounts.find(level => level.levelName === levelName)
- return levelData ? levelData.count || 0 : 0
- })
- seriesData.push({
- name: dept.deptName,
- type: 'bar',
- barWidth: '15%',
- itemStyle: {
- color: colors[index % colors.length]
- },
- label: {
- show: true,
- position: 'top',
- formatter: function (params) {
- return params.value > 0 ? params.value + '人' : '';
- }
- },
- data: levelCounts
- })
- }
- })
- // 更新图表配置
- trendBarOptions.xAxis.data = sortedLevelNames
- trendBarOptions.legend.data = barData.map(dept => dept.deptName).filter(name => name)
- trendBarOptions.series = seriesData
- // 重新设置图表选项
- setBarOption(trendBarOptions)
- console.log('资质趋势柱状图已更新:', {
- xAxis: sortedLevelNames,
- series: seriesData
- })
- } else {
- // 无数据时清空图表
- trendBarOptions.xAxis.data = []
- trendBarOptions.legend.data = []
- trendBarOptions.series = []
- setBarOption(trendBarOptions)
- }
- }
- //非站长走这个逻辑
- const updateAttendanceBarOtherChart = () => {
- if (trendData.value && Array.isArray(trendData.value)) {
- const trendList = trendData.value
-
- // 提取横坐标数据(timeLabel字段)
- const xAxisData = trendList.map(item => item.timeLabel || '未知时间')
- // 提取各科室数据
- const allData = trendList.map(item => item.overall || 0)
- // 更新图表配置
- attendanceBarOtherOptions.xAxis.data = xAxisData
- attendanceBarOtherOptions.series[0].data = allData
- attendanceBarOtherOptions.legend.show = !!isStationType.value
- // 重新设置图表选项
- setAttendanceOption(attendanceBarOtherOptions,true)
- } else {
- // 无数据时清空图表
- attendanceBarOtherOptions.xAxis.data = []
- attendanceBarOtherOptions.series[0].data = []
- attendanceBarOtherOptions.legend.show = !!isStationType.value
- setAttendanceOption(attendanceBarOtherOptions,true)
- }
- }
- const updateAttendanceBarChart = () => {
- if (trendData.value && Array.isArray(trendData.value)) {
- const trendList = trendData.value
- // 提取横坐标数据(timeLabel字段)
- const xAxisData = trendList.map(item => item.timeLabel || '未知时间')
- // 提取各科室数据
- const allData = trendList.map(item => item.overall || 0)
- const dept1Data = trendList.map(item => item.data1 || 0)
- const dept2Data = trendList.map(item => item.data2 || 0)
- const dept3Data = trendList.map(item => item.data3 || 0)
- const dept4Data = trendList.map(item => item.data4 || 0)
- // 更新图表配置
- attendanceBarOptions.xAxis.data = xAxisData
- attendanceBarOptions.series[0].data = allData
- attendanceBarOptions.series[1].data = dept1Data
- attendanceBarOptions.series[2].data = dept2Data
- attendanceBarOptions.series[3].data = dept3Data
- attendanceBarOptions.series[4].data = dept4Data
- attendanceBarOptions.legend.show = !!isStationType.value
- // 重新设置图表选项
- setAttendanceOption(attendanceBarOptions)
- } else {
- // 无数据时清空图表
- attendanceBarOptions.xAxis.data = []
- attendanceBarOptions.series[0].data = []
- attendanceBarOptions.series[1].data = []
- attendanceBarOptions.series[2].data = []
- attendanceBarOptions.series[3].data = []
- attendanceBarOptions.legend.show = !!isStationType.value
- setAttendanceOption(attendanceBarOptions)
- }
- }
- // 根据API数据更新图表和统计信息
- const updateChartsWithData = () => {
-
- // 更新出勤人次柱状图
- if (isStationType.value) {
- updateAttendanceBarChart()
- } else{
- updateAttendanceBarOtherChart()
- }
- // 更新资质等级分布饼图
- updateQualificationPieChart()
- // 更新资质趋势柱状图
- updateTrendBarChart()
- console.log('数据已更新,图表已刷新')
- }
- // 组件卸载时销毁图表
- onUnmounted(() => {
- disposePie()
- disposeBar()
- disposeAttendance()
- })
- </script>
- <style scoped>
- .duty-organization {
- width: 100%;
- }
- /* 勤务组织标题 */
- .section-title {
- margin: 14px 0 14px 0;
- text-align: left;
- }
- .section-title h2 {
- margin: 0;
- font-size: 18px;
- font-weight: 600;
- color: #333;
- }
- /* 横向布局内容区域 */
- .content-layout {
- display: flex;
- gap: 20px;
- /* margin-bottom: 30px; */
- }
- .left-panel {
- flex: 1 1 40%;
- min-width: 300px;
- background: white;
- border-radius: 8px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
- .right-panel {
- flex: 1 1 60%;
- min-width: 400px;
- background: white;
- border-radius: 8px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
- .right-panel-ability {
- flex: 1 1 30%;
- min-width: 400px;
- background: white;
- border-radius: 8px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
- .panel-header {
- margin-bottom: 20px;
- padding-bottom: 0;
- }
- .panel-header h3 {
- font-size: 18px;
- font-weight: 600;
- color: #333;
- margin: 0;
- }
- /* 统计卡片 */
- .stat-card {
- background: #F8F8F8;
- border: 1px dashed #CDCCCC;
- border-radius: 6px;
- padding: 7px 10px;
- margin-bottom: 20px;
- text-align: left;
- }
- .stat-content {
- font-size: 13px;
- color: #666;
- line-height: 1.5;
- }
- .stat-number {
- font-weight: 600;
- color: #557DDB;
- font-size: 16px;
- }
- .stat-trend {
- font-weight: 500;
- }
- .stat-trend.up {
- color: #67C23A;
- }
- .stat-trend.down {
- color: #F56C6C;
- }
- /* 科室数据卡片 */
- .dept-cards {
- display: flex;
- gap: 16px;
- margin-bottom: 20px;
- }
- .dept-card {
- flex: 1;
- background: #F8F8F8;
- border-radius: 6px;
- padding: 16px;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- text-align: left;
- }
- .dept-name {
- font-size: 16px;
- font-weight: 600;
- color: #333;
- margin-bottom: 8px;
- }
- .dept-count {
- font-size: 13px;
- margin-bottom: 8px;
- span {
- font-weight: 600;
- font-size: 22px;
- }
- }
- .dept-trend {
- font-size: 14px;
- margin-bottom: 4px;
- display: flex;
- align-items: center;
- }
- .dept-trend:last-child {
- margin-bottom: 0;
- &>div:first-child {
- margin-right: 8px;
- }
- }
- .dept-trend {
- .down {
- color: #67C23A;
- }
- .up {
- color: #F56C6C;
- }
- .neutral {
- color: #909399;
- }
- }
- /* 柱状图区域 */
- .chart-container {
- /* background: #F8F8F8;
- border-radius: 6px;
- padding: 20px; */
- min-height: 200px;
- height: 300px;
- position: relative;
- box-sizing: border-box;
- overflow: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .chart-placeholder {
- text-align: center;
- color: #999;
- }
- .chart-placeholder p {
- margin: 0;
- font-size: 16px;
- }
- /* 右侧资质等级分布内容 */
- .qualification-content {
- display: flex;
- gap: 20px;
- height: 500px;
- }
- .qualification-left,
- .qualification-right {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
- /* 资质等级卡片 */
- .qualification-card {
- background: #F8F8F8;
- border-radius: 6px;
- padding: 16px;
- flex: 0 0 auto;
- }
- .card-content {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .card-title {
- font-size: 16px;
- font-weight: 600;
- color: #333;
- margin: 0;
- }
- .card-stats {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .stat-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 14px;
- }
- .stat-label {
- color: #666;
- flex: 1;
- }
- .stat-value {
- font-weight: 600;
- color: #333;
- margin-right: 8px;
- }
- .stat-value.up {
- color: #67C23A;
- }
- .stat-value.down {
- color: #F56C6C;
- }
- .stat-percent {
- color: #999;
- font-size: 12px;
- }
- /* 资质等级分布图表容器 */
- .qualification-left .chart-container,
- .qualification-right .chart-container {
- flex: 1;
- min-height: 200px;
- }
- /* ECharts图表样式 */
- .echarts-chart {
- width: 100% !important;
- height: 100% !important;
- min-height: 200px;
- display: block;
- }
- /* 响应式设计 */
- @media (max-width: 768px) {
- .content-layout {
- flex-direction: column;
- }
- .left-panel,
- .right-panel {
- flex: 1;
- }
- }
- </style>
|