qualityControl.vue 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. <template>
  2. <div class="quality-control">
  3. <!-- 质控活动标题 -->
  4. <div class="section-title">
  5. <h2>质控活动</h2>
  6. </div>
  7. <!-- 四个横向均分的内容区域 -->
  8. <div class="four-panel-layout">
  9. <!-- 第一个:任务计划安排统计 -->
  10. <div class="panel-item" v-show="!isTeamType && !isUserType && !isDepartmentType">
  11. <div class="panel-header">
  12. <h3>任务计划安排统计</h3>
  13. </div>
  14. <!-- 两个灰色背景统计卡片 -->
  15. <div class="stat-cards">
  16. <div class="stat-card">
  17. <div class="stat-title">专项任务</div>
  18. <div class="stat-count"><span>{{ specialTask.count }}</span>次</div>
  19. <div class="stat-trend">
  20. 同比<span :class="specialTask.yearOnYearTrend">{{ specialTask.yearOnYear }}</span>
  21. <template v-if="props.queryForm.dateRangeQueryType !== 'YEAR'">
  22. ,环比<span :class="specialTask.chainRatioTrend">{{ specialTask.chainRatio }}</span>
  23. </template>
  24. </div>
  25. </div>
  26. <div class="stat-card">
  27. <div class="stat-title">日常任务</div>
  28. <div class="stat-count"><span>{{ dailyTask.count }}</span>次</div>
  29. <div class="stat-trend">
  30. 同比<span :class="dailyTask.yearOnYearTrend">{{ dailyTask.yearOnYear }}</span>
  31. <template v-if="props.queryForm.dateRangeQueryType !== 'YEAR'">
  32. ,环比<span :class="dailyTask.chainRatioTrend">{{ dailyTask.chainRatio }}</span>
  33. </template>
  34. </div>
  35. </div>
  36. </div>
  37. <!-- 柱状图 -->
  38. <div class="chart-container">
  39. <div ref="planScheduleChartRef" class="echarts-chart"></div>
  40. </div>
  41. </div>
  42. <!-- 第二个:问题发现统计 -->
  43. <div class="panel-item">
  44. <div class="panel-header">
  45. <h3>问题发现统计</h3>
  46. </div>
  47. <!-- 描述卡片 -->
  48. <div class="describe-card" v-if="checkProblemDiscovery?.desc">
  49. <div class="describe-content">
  50. {{ checkProblemDiscovery?.desc }}
  51. </div>
  52. </div>
  53. <!-- 折线图 -->
  54. <div class="chart-container">
  55. <div ref="problemDiscoveryChartRef" class="echarts-chart"></div>
  56. </div>
  57. </div>
  58. <!-- 第三个:问题分布统计 -->
  59. <div class="panel-item">
  60. <div class="panel-header">
  61. <h3>问题分布统计</h3>
  62. </div>
  63. <!-- 描述卡片 -->
  64. <div class="describe-card" v-if="checkProblemDistribution?.desc">
  65. <div class="describe-content">
  66. {{ checkProblemDistribution?.desc }}
  67. </div>
  68. </div>
  69. <!-- 饼图 -->
  70. <div class="chart-container">
  71. <div ref="problemDistributionChartRef" class="echarts-chart"></div>
  72. </div>
  73. </div>
  74. <!-- 第四个:问题整改统计 -->
  75. <div class="panel-item">
  76. <div class="panel-header">
  77. <h3>问题整改统计</h3>
  78. </div>
  79. <!-- 描述卡片 -->
  80. <div class="describe-card" v-if="checkProblemCorrection?.desc">
  81. <div class="describe-content">
  82. {{ checkProblemCorrection?.desc }}
  83. </div>
  84. </div>
  85. <!-- 柱状图 -->
  86. <div class="chart-container">
  87. <div ref="problemRectificationChartRef" class="echarts-chart"></div>
  88. </div>
  89. </div>
  90. </div>
  91. </div>
  92. </template>
  93. <script setup>
  94. import { ref, onMounted, onUnmounted, watch } from 'vue'
  95. import { useEcharts } from '@/hooks/chart.js'
  96. import { getAnalysisReport } from '@/api/assistant/assistant.js'
  97. // 定义props接收queryForm参数
  98. const props = defineProps({
  99. queryForm: {
  100. type: Object,
  101. default: () => ({
  102. dateRangeQueryType: '',
  103. year: '',
  104. quarter: '',
  105. month: ''
  106. })
  107. },
  108. selectedDeptObject: {
  109. type: Object,
  110. default: null
  111. }
  112. })
  113. // 图表容器引用
  114. const planScheduleChartRef = ref(null)
  115. const problemDiscoveryChartRef = ref(null)
  116. const problemDistributionChartRef = ref(null)
  117. const problemRectificationChartRef = ref(null)
  118. // 图表实例
  119. const { setOption: setPlanScheduleOption, dispose: disposePlanSchedule } = useEcharts(planScheduleChartRef)
  120. const { setOption: setProblemDiscoveryOption, dispose: disposeProblemDiscovery } = useEcharts(problemDiscoveryChartRef)
  121. const { setOption: setProblemDistributionOption, dispose: disposeProblemDistribution } = useEcharts(problemDistributionChartRef)
  122. const { setOption: setProblemRectificationOption, dispose: disposeProblemRectification } = useEcharts(problemRectificationChartRef)
  123. // 计算属性:处理selectedDeptObject逻辑
  124. const selectedDeptType = computed(() => {
  125. return props.selectedDeptObject && props.selectedDeptObject?.deptType
  126. })
  127. const isUserType = computed(() => {
  128. return selectedDeptType.value === 'USER'
  129. })
  130. const isTeamType = computed(() => {
  131. return selectedDeptType.value === 'TEAMS'
  132. })
  133. // 计算属性:检查是否为STATION类型
  134. const isStationType = computed(() => {
  135. return selectedDeptType.value === 'STATION' || !selectedDeptType.value
  136. })
  137. // 计算属性:检查是否为DEPARTMENT类型
  138. const isDepartmentType = computed(() => {
  139. return selectedDeptType.value === 'MANAGER'
  140. })
  141. const isBrigadeType = computed(() => {
  142. return selectedDeptType.value === 'BRIGADE'
  143. })
  144. // 解析desc数据并更新专项任务和日常任务数据
  145. const parseTaskData = (desc) => {
  146. if (!desc) return
  147. // 解析desc格式:"专项任务:发布7次,同比上升0.00%,环比上升133.33%;\n日常任务:发布2次,同比上升0.00%,环比下降33.33%"
  148. const lines = desc.split(';')
  149. lines.forEach(line => {
  150. if (line.includes('专项任务')) {
  151. const countMatch = line.match(/发布(\d+)次/)
  152. const yearOnYearMatch = line.match(/同比(上升|下降|持平)([\d.-]+)%/)
  153. const chainRatioMatch = line.match(/环比(上升|下降|持平)([\d.-]+)%/)
  154. if (countMatch) {
  155. specialTask.value.count = parseInt(countMatch[1])
  156. }
  157. if (yearOnYearMatch) {
  158. const trendDirection = yearOnYearMatch[1]
  159. const trendValue = yearOnYearMatch[2]
  160. let trendType = 'neutral'
  161. let displayValue = trendValue
  162. // 处理趋势值为"--"的情况
  163. if (trendValue === '--') {
  164. trendType = 'neutral'
  165. displayValue = '--'
  166. } else {
  167. // 正常数值处理
  168. const value = parseFloat(trendValue)
  169. if (trendDirection === '上升' && value > 0) {
  170. trendType = 'up'
  171. } else if (trendDirection === '下降' || value < 0) {
  172. trendType = 'down'
  173. } else if (trendDirection === '持平') {
  174. trendType = 'neutral'
  175. }
  176. }
  177. specialTask.value.yearOnYearTrend = trendType
  178. specialTask.value.yearOnYear = displayValue + '%'
  179. }
  180. if (chainRatioMatch) {
  181. const trendDirection = chainRatioMatch[1]
  182. const trendValue = chainRatioMatch[2]
  183. let trendType = 'neutral'
  184. let displayValue = trendValue
  185. // 处理趋势值为"--"的情况
  186. if (trendValue === '--') {
  187. trendType = 'neutral'
  188. displayValue = '--'
  189. } else {
  190. // 正常数值处理
  191. const value = parseFloat(trendValue)
  192. if (trendDirection === '上升' && value > 0) {
  193. trendType = 'up'
  194. } else if (trendDirection === '下降' || value < 0) {
  195. trendType = 'down'
  196. } else if (trendDirection === '持平') {
  197. trendType = 'neutral'
  198. }
  199. }
  200. specialTask.value.chainRatioTrend = trendType
  201. specialTask.value.chainRatio = displayValue + '%'
  202. }
  203. } else if (line.includes('日常任务')) {
  204. const countMatch = line.match(/发布(\d+)次/)
  205. const yearOnYearMatch = line.match(/同比(上升|下降|持平)([\d.-]+)%/)
  206. const chainRatioMatch = line.match(/环比(上升|下降|持平)([\d.-]+)%/)
  207. if (countMatch) {
  208. dailyTask.value.count = parseInt(countMatch[1])
  209. }
  210. if (yearOnYearMatch) {
  211. const trendDirection = yearOnYearMatch[1]
  212. const trendValue = yearOnYearMatch[2]
  213. let trendType = 'neutral'
  214. let displayValue = trendValue
  215. // 处理趋势值为"--"的情况
  216. if (trendValue === '--') {
  217. trendType = 'neutral'
  218. displayValue = '--'
  219. } else {
  220. // 正常数值处理
  221. const value = parseFloat(trendValue)
  222. if (trendDirection === '上升' && value > 0) {
  223. trendType = 'up'
  224. } else if (trendDirection === '下降' || value < 0) {
  225. trendType = 'down'
  226. } else if (trendDirection === '持平') {
  227. trendType = 'neutral'
  228. }
  229. }
  230. dailyTask.value.yearOnYearTrend = trendType
  231. dailyTask.value.yearOnYear = displayValue + '%'
  232. }
  233. if (chainRatioMatch) {
  234. const trendDirection = chainRatioMatch[1]
  235. const trendValue = chainRatioMatch[2]
  236. let trendType = 'neutral'
  237. let displayValue = trendValue
  238. // 处理趋势值为"--"的情况
  239. if (trendValue === '--') {
  240. trendType = 'neutral'
  241. displayValue = '--'
  242. } else {
  243. // 正常数值处理
  244. const value = parseFloat(trendValue)
  245. if (trendDirection === '上升' && value > 0) {
  246. trendType = 'up'
  247. } else if (trendDirection === '下降' || value < 0) {
  248. trendType = 'down'
  249. } else if (trendDirection === '持平') {
  250. trendType = 'neutral'
  251. }
  252. }
  253. dailyTask.value.chainRatioTrend = trendType
  254. dailyTask.value.chainRatio = displayValue + '%'
  255. }
  256. }
  257. })
  258. }
  259. // 调用API获取质控分析数据
  260. const fetchAnalysisData = async (queryParams) => {
  261. try {
  262. // 添加null检查,防止props.selectedDeptObject为null
  263. const selectedDept = props.selectedDeptObject
  264. const { deptType = "", id } = selectedDept ? selectedDept : { deptType: "", id: "" }
  265. let progressParams = { ...queryParams }
  266. if (progressParams.dateRangeQueryType === 'YEAR') {
  267. progressParams.yearOnYear = true
  268. delete progressParams.chainRatio
  269. } else {
  270. progressParams.chainRatio = true
  271. progressParams.yearOnYear = true
  272. }
  273. let rangParams = {
  274. userId: deptType == 'USER' ? id : "",
  275. deptId: ["STATION", "MANAGER", "TEAMS", "BRIGADE"].includes(deptType) ? id : ""
  276. }
  277. const response = await getAnalysisReport({ ...progressParams, ...rangParams })
  278. console.log('质控分析数据:', response.data)
  279. // 解构赋值API返回的数据
  280. const { checkTaskDto, checkProblemDiscoveryDto, checkProblemDistributionDto, checkProblemCorrectionDto } = response.data;
  281. // 赋值到响应式数据
  282. checkTask.value = checkTaskDto || {}
  283. checkProblemDiscovery.value = checkProblemDiscoveryDto || {}
  284. checkProblemDistribution.value = checkProblemDistributionDto || {}
  285. checkProblemCorrection.value = checkProblemCorrectionDto || {}
  286. // 如果checkTaskDto中有desc字段,解析并更新任务数据
  287. if (checkTaskDto && checkTaskDto.desc) {
  288. parseTaskData(checkTaskDto.desc)
  289. }
  290. // 更新图表和统计信息
  291. updateChartsWithData()
  292. } catch (error) {
  293. console.error('获取质控分析数据失败:', error)
  294. }
  295. }
  296. // 初始化图表
  297. onMounted(() => {
  298. // 确保DOM完全渲染后再初始化图表
  299. const initCharts = () => {
  300. // 任务计划安排柱状图
  301. if (planScheduleChartRef.value && planScheduleChartRef.value.offsetHeight > 0) {
  302. setPlanScheduleOption(planScheduleOptions)
  303. }
  304. // 问题发现折线图
  305. if (problemDiscoveryChartRef.value && problemDiscoveryChartRef.value.offsetHeight > 0) {
  306. setProblemDiscoveryOption(problemDiscoveryOptions)
  307. }
  308. // 问题分布饼图
  309. if (problemDistributionChartRef.value && problemDistributionChartRef.value.offsetHeight > 0) {
  310. setProblemDistributionOption(problemDistributionOptions)
  311. }
  312. // 问题整改柱状图
  313. if (problemRectificationChartRef.value && problemRectificationChartRef.value.offsetHeight > 0) {
  314. setProblemRectificationOption(problemRectificationOptions)
  315. }
  316. }
  317. // 延迟初始化,确保CSS已应用
  318. setTimeout(() => {
  319. initCharts()
  320. }, 100)
  321. // 如果容器高度为0,延迟重试
  322. setTimeout(() => {
  323. initCharts()
  324. }, 500)
  325. })
  326. // 监听queryForm参数变化,调用API获取数据
  327. watch(() => props.queryForm, (newQueryForm) => {
  328. // 只有当所有必要的查询参数都存在时才调用API
  329. if (newQueryForm.dateRangeQueryType && newQueryForm.year) {
  330. fetchAnalysisData(newQueryForm)
  331. }
  332. }, { deep: true })
  333. // 定义响应式数据
  334. const checkTask = ref({})
  335. const checkProblemDiscovery = ref({})
  336. const checkProblemDistribution = ref({})
  337. const checkProblemCorrection = ref({})
  338. // 专项任务和日常任务数据
  339. const specialTask = ref({
  340. count: 0,
  341. yearOnYear: '',
  342. yearOnYearTrend: 'up',
  343. chainRatio: '',
  344. chainRatioTrend: 'up'
  345. })
  346. const dailyTask = ref({
  347. count: 0,
  348. yearOnYear: '',
  349. yearOnYearTrend: 'down',
  350. chainRatio: '',
  351. chainRatioTrend: 'down'
  352. })
  353. // 更新任务计划安排图表数据
  354. const updatePlanScheduleChart = () => {
  355. if (checkTask.value && checkTask.value.checkTaskItemDtoList) {
  356. const taskList = checkTask.value.checkTaskItemDtoList
  357. // 提取横坐标数据(desc字段)
  358. const xAxisData = [...new Set(taskList.map(item => item.desc))]
  359. // 提取专项任务数据
  360. const specialTaskData = taskList.filter(item => item.typeDesc === '专项任务').map((item) => item.count)
  361. // 提取日常任务数据
  362. const dailyTaskData = taskList.filter(item => item.typeDesc === '日常任务').map(item => item.count)
  363. // 更新图表配置
  364. planScheduleOptions.xAxis.data = xAxisData
  365. planScheduleOptions.series[0].data = specialTaskData
  366. planScheduleOptions.series[1].data = dailyTaskData
  367. // 重新设置图表选项
  368. setPlanScheduleOption(planScheduleOptions)
  369. console.log('任务计划图表已更新:', {
  370. xAxis: xAxisData,
  371. 专项任务: specialTaskData,
  372. 日常任务: dailyTaskData
  373. })
  374. } else {
  375. // 无数据时清空图表
  376. planScheduleOptions.xAxis.data = []
  377. planScheduleOptions.series[0].data = []
  378. planScheduleOptions.series[1].data = []
  379. setPlanScheduleOption(planScheduleOptions)
  380. }
  381. }
  382. // 更新问题发现统计图表数据
  383. const updateProblemDiscoveryChart = () => {
  384. if (checkProblemDiscovery.value && checkProblemDiscovery.value.checkProblemDiscoveryItemDtoList) {
  385. const discoveryList = checkProblemDiscovery.value.checkProblemDiscoveryItemDtoList
  386. // 提取横坐标数据(desc或name字段)
  387. const xAxisData = discoveryList.map(item => item.desc || item.name)
  388. // 为每个数据点创建包含tagDesc的对象
  389. const seriesData = [{
  390. name: '问题数量',
  391. type: 'line',
  392. smooth: true,
  393. symbol: 'circle',
  394. symbolSize: 6,
  395. itemStyle: {
  396. color: '#FF6B6B'
  397. },
  398. lineStyle: {
  399. color: '#FF6B6B',
  400. width: 3
  401. },
  402. label: {
  403. show: true,
  404. position: 'top',
  405. formatter: '{c}'
  406. },
  407. data: discoveryList.map(item => ({
  408. value: item.count || 0,
  409. tagDesc: item.tagDesc || '默认分类'
  410. }))
  411. }]
  412. // 更新图表配置
  413. problemDiscoveryOptions.xAxis.data = xAxisData
  414. // problemDiscoveryOptions.legend.data = ['问题数量']
  415. problemDiscoveryOptions.series = seriesData
  416. // 重新设置图表选项
  417. setProblemDiscoveryOption(problemDiscoveryOptions)
  418. console.log('问题发现图表已更新:', {
  419. })
  420. } else {
  421. // 无数据时清空图表
  422. problemDiscoveryOptions.xAxis.data = []
  423. problemDiscoveryOptions.series = [{
  424. name: '问题数量',
  425. type: 'line',
  426. smooth: true,
  427. symbol: 'circle',
  428. symbolSize: 6,
  429. itemStyle: {
  430. color: '#FF6B6B'
  431. },
  432. lineStyle: {
  433. color: '#FF6B6B',
  434. width: 3
  435. },
  436. label: {
  437. show: true,
  438. position: 'top',
  439. formatter: '{c}'
  440. },
  441. data: []
  442. }]
  443. setProblemDiscoveryOption(problemDiscoveryOptions)
  444. }
  445. }
  446. // 更新问题分布统计图表数据
  447. const updateProblemDistributionChart = () => {
  448. if (checkProblemDistribution.value && checkProblemDistribution.value.checkProblemDistributionItemDtoList) {
  449. const distributionList = checkProblemDistribution.value.checkProblemDistributionItemDtoList
  450. // 提取饼图数据
  451. const pieData = distributionList.map(item => ({
  452. value: item.total || 0,
  453. name: item.desc || item.name
  454. }))
  455. // 更新图表配置
  456. problemDistributionOptions.series[0].data = pieData
  457. // 重新设置图表选项
  458. setProblemDistributionOption(problemDistributionOptions)
  459. console.log('问题分布图表已更新:', pieData)
  460. } else {
  461. // 无数据时清空图表
  462. problemDistributionOptions.series[0].data = []
  463. setProblemDistributionOption(problemDistributionOptions)
  464. }
  465. }
  466. // 更新问题整改统计图表数据
  467. const updateProblemRectificationChart = () => {
  468. if (checkProblemCorrection.value && checkProblemCorrection.value.checkProblemCorrectionItemDtoList) {
  469. const rectificationList = checkProblemCorrection.value.checkProblemCorrectionItemDtoList
  470. // 提取横坐标数据(科室名称)
  471. const xAxisData = rectificationList.map(item => item.deptName || '未知科室')
  472. // 提取各个数据系列
  473. const onTimeCompletedData = rectificationList.map(item => item.onTimeCompletedCount || 0)
  474. const overTimeCompletedData = rectificationList.map(item => item.overTimeCompletedCount || 0)
  475. const onTimeUnfinishedData = rectificationList.map(item => item.onTimeUnfinishedCount || 0)
  476. const overTimeUnfinishedData = rectificationList.map(item => item.overTimeUnfinishedCount || 0)
  477. const otherData = rectificationList.map(item => item.otherCount || 0)
  478. // 更新图表配置
  479. problemRectificationOptions.xAxis.data = xAxisData
  480. problemRectificationOptions.series[0].data = onTimeCompletedData
  481. problemRectificationOptions.series[1].data = overTimeCompletedData
  482. problemRectificationOptions.series[2].data = onTimeUnfinishedData
  483. problemRectificationOptions.series[3].data = overTimeUnfinishedData
  484. problemRectificationOptions.series[4].data = otherData
  485. // 重新设置图表选项
  486. setProblemRectificationOption(problemRectificationOptions)
  487. console.log('问题整改图表已更新:', {
  488. xAxis: xAxisData,
  489. 按期已完成: onTimeCompletedData,
  490. 超期已完成: overTimeCompletedData,
  491. 按期整改中: onTimeUnfinishedData,
  492. 超期整改中: overTimeUnfinishedData,
  493. 其他: otherData
  494. })
  495. } else {
  496. // 无数据时清空图表
  497. problemRectificationOptions.xAxis.data = []
  498. problemRectificationOptions.series[0].data = []
  499. problemRectificationOptions.series[1].data = []
  500. problemRectificationOptions.series[2].data = []
  501. problemRectificationOptions.series[3].data = []
  502. problemRectificationOptions.series[4].data = []
  503. setProblemRectificationOption(problemRectificationOptions)
  504. }
  505. }
  506. // 根据API数据更新图表和统计信息
  507. const updateChartsWithData = () => {
  508. // 更新任务计划安排图表
  509. updatePlanScheduleChart()
  510. // 更新问题发现统计图表
  511. updateProblemDiscoveryChart()
  512. // 更新问题分布统计图表
  513. updateProblemDistributionChart()
  514. // 更新问题整改统计图表
  515. updateProblemRectificationChart()
  516. console.log('数据已更新,所有图表已刷新')
  517. }
  518. // 组件卸载时销毁图表
  519. onUnmounted(() => {
  520. disposePlanSchedule()
  521. disposeProblemDiscovery()
  522. disposeProblemDistribution()
  523. disposeProblemRectification()
  524. })
  525. // 任务计划安排柱状图配置
  526. const planScheduleOptions = {
  527. tooltip: {
  528. trigger: 'axis',
  529. axisPointer: {
  530. type: 'shadow'
  531. },
  532. formatter: function (params) {
  533. let result = `${params[0].axisValue}<br/>`
  534. params.forEach(param => {
  535. result += `${param.seriesName}: <span style="color:${param.color};font-weight:bold">${param.data}</span><br/>`
  536. })
  537. return result
  538. }
  539. },
  540. legend: {
  541. data: ['专项任务', '日常任务'],
  542. top: 0
  543. },
  544. grid: {
  545. left: '3%',
  546. right: '4%',
  547. bottom: '3%',
  548. top: '15%',
  549. containLabel: true
  550. },
  551. xAxis: {
  552. type: 'category',
  553. data: [],
  554. axisLine: {
  555. lineStyle: {
  556. color: '#999'
  557. }
  558. },
  559. axisLabel: {
  560. fontSize: 12
  561. }
  562. },
  563. yAxis: {
  564. type: 'value',
  565. name: '任务数量',
  566. axisLine: {
  567. lineStyle: {
  568. color: '#999'
  569. }
  570. },
  571. splitLine: {
  572. lineStyle: {
  573. color: '#f0f0f0'
  574. }
  575. }
  576. },
  577. series: [
  578. {
  579. name: '专项任务',
  580. type: 'bar',
  581. barWidth: '35%',
  582. itemStyle: {
  583. color: '#FF6B6B'
  584. },
  585. label: {
  586. show: true,
  587. position: 'top',
  588. formatter: '{c}'
  589. },
  590. data: []
  591. },
  592. {
  593. name: '日常任务',
  594. type: 'bar',
  595. barWidth: '35%',
  596. itemStyle: {
  597. color: '#4ECDC4'
  598. },
  599. label: {
  600. show: true,
  601. position: 'top',
  602. formatter: '{c}'
  603. },
  604. data: []
  605. }
  606. ]
  607. }
  608. // 问题发现折线图配置
  609. const problemDiscoveryOptions = {
  610. tooltip: {
  611. trigger: 'axis',
  612. axisPointer: {
  613. type: 'shadow'
  614. },
  615. formatter: function (params) {
  616. let result = `${params[0].axisValue}<br/>`
  617. params.forEach(param => {
  618. const tagDesc = param.data.tagDesc || '默认分类'
  619. const value = param.data.value || 0
  620. result += `${tagDesc}: <span style="color:${param.color};font-weight:bold">${value}</span><br/>`
  621. })
  622. return result
  623. }
  624. },
  625. legend: {
  626. data: [],
  627. top: 0
  628. },
  629. grid: {
  630. left: '3%',
  631. right: '4%',
  632. bottom: '3%',
  633. top: '15%',
  634. containLabel: true
  635. },
  636. xAxis: {
  637. type: 'category',
  638. data: [],
  639. axisLine: {
  640. lineStyle: {
  641. color: '#999'
  642. }
  643. },
  644. axisLabel: {
  645. fontSize: 12
  646. }
  647. },
  648. yAxis: {
  649. type: 'value',
  650. name: '问题数量',
  651. axisLine: {
  652. lineStyle: {
  653. color: '#999'
  654. }
  655. },
  656. splitLine: {
  657. lineStyle: {
  658. color: '#f0f0f0'
  659. }
  660. }
  661. },
  662. series: []
  663. }
  664. // 问题分布饼图配置
  665. const problemDistributionOptions = {
  666. tooltip: {
  667. trigger: 'item',
  668. formatter: '{a} <br/>{b}: {c}个 ({d}%)'
  669. },
  670. legend: {
  671. orient: 'horizontal',
  672. left: 'center',
  673. top: 'top',
  674. textStyle: {
  675. color: '#333',
  676. fontSize: 12
  677. }
  678. },
  679. color: ['#FF9F43', '#54C6EB', '#A3D9B1', '#FF6B9D', '#9B59B6'],
  680. series: [
  681. {
  682. name: '问题分布',
  683. type: 'pie',
  684. radius: '60%',
  685. center: ['50%', '50%'],
  686. avoidLabelOverlap: false,
  687. itemStyle: {
  688. borderRadius: 10,
  689. borderColor: '#fff',
  690. borderWidth: 2
  691. },
  692. label: {
  693. show: true,
  694. formatter: '{b}: {c}个',
  695. fontSize: 12
  696. },
  697. emphasis: {
  698. label: {
  699. show: true,
  700. fontSize: 14,
  701. fontWeight: 'bold'
  702. },
  703. itemStyle: {
  704. shadowBlur: 10,
  705. shadowOffsetX: 0,
  706. shadowColor: 'rgba(0, 0, 0, 0.5)'
  707. }
  708. },
  709. labelLine: {
  710. show: true,
  711. length: 10,
  712. length2: 20
  713. },
  714. data: []
  715. }
  716. ]
  717. }
  718. // 问题整改柱状图配置
  719. const problemRectificationOptions = {
  720. tooltip: {
  721. trigger: 'axis',
  722. axisPointer: {
  723. type: 'shadow'
  724. },
  725. formatter: function (params) {
  726. let result = `${params[0].axisValue}<br/>`
  727. params.forEach(param => {
  728. result += `${param.seriesName}: <span style="color:${param.color};font-weight:bold">${param.data}</span>个<br/>`
  729. })
  730. return result
  731. }
  732. },
  733. legend: {
  734. data: ['按期已完成', '超期已完成', '按期整改中', '超期整改中', '其他'],
  735. top: 0
  736. },
  737. grid: {
  738. left: '3%',
  739. right: '4%',
  740. bottom: '0%',
  741. top: '30%',
  742. containLabel: true
  743. },
  744. xAxis: {
  745. type: 'category',
  746. data: [],
  747. axisLine: {
  748. lineStyle: {
  749. color: '#999'
  750. }
  751. },
  752. axisLabel: {
  753. fontSize: 12
  754. }
  755. },
  756. yAxis: {
  757. type: 'value',
  758. name: '问题数量',
  759. axisLine: {
  760. lineStyle: {
  761. color: '#999'
  762. }
  763. },
  764. splitLine: {
  765. lineStyle: {
  766. color: '#f0f0f0'
  767. }
  768. }
  769. },
  770. series: [
  771. {
  772. name: '按期已完成',
  773. type: 'bar',
  774. barWidth: '10%', temStyle: {
  775. color: '#4ECDC4'
  776. },
  777. label: {
  778. show: false,
  779. position: 'top',
  780. formatter: '{c}个'
  781. },
  782. data: []
  783. },
  784. {
  785. name: '超期已完成',
  786. type: 'bar',
  787. barWidth: '10%',
  788. itemStyle: {
  789. color: '#FF6B6B'
  790. },
  791. label: {
  792. show: false,
  793. position: 'top',
  794. formatter: '{c}个'
  795. },
  796. data: []
  797. },
  798. {
  799. name: '按期整改中',
  800. type: 'bar',
  801. barWidth: '10%',
  802. itemStyle: {
  803. color: '#FFD166'
  804. },
  805. label: {
  806. show: false,
  807. position: 'top',
  808. formatter: '{c}个'
  809. },
  810. data: []
  811. },
  812. {
  813. name: '超期整改中',
  814. type: 'bar',
  815. barWidth: '10%',
  816. itemStyle: {
  817. color: '#9B59B6'
  818. },
  819. label: {
  820. show: false,
  821. position: 'top',
  822. formatter: '{c}个'
  823. },
  824. data: []
  825. },
  826. {
  827. name: '其他',
  828. type: 'bar',
  829. barWidth: '10%',
  830. itemStyle: {
  831. color: '#95A5A6'
  832. },
  833. label: {
  834. show: false,
  835. position: 'top',
  836. formatter: '{c}个'
  837. },
  838. data: []
  839. }
  840. ]
  841. }
  842. </script>
  843. <style scoped>
  844. .quality-control {
  845. width: 100%;
  846. }
  847. /* 质控活动标题 */
  848. .section-title {
  849. margin: 14px 0 14px 0;
  850. text-align: left;
  851. }
  852. .section-title h2 {
  853. font-size: 24px;
  854. font-weight: 600;
  855. color: #333;
  856. margin: 0;
  857. }
  858. /* 四个横向均分布局 */
  859. .four-panel-layout {
  860. display: flex;
  861. gap: 16px;
  862. margin-bottom: 20px;
  863. }
  864. .panel-item {
  865. flex: 1;
  866. background: white;
  867. border-radius: 8px;
  868. padding: 20px;
  869. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  870. display: flex;
  871. flex-direction: column;
  872. gap: 16px;
  873. }
  874. .panel-header {
  875. padding-bottom: 0;
  876. }
  877. .panel-header h3 {
  878. font-size: 16px;
  879. font-weight: 600;
  880. color: #333;
  881. margin: 0;
  882. }
  883. /* 统计卡片容器 */
  884. .stat-cards {
  885. display: flex;
  886. gap: 12px;
  887. }
  888. /* 统计卡片样式 */
  889. .stat-card {
  890. flex: 1;
  891. background: #F8F8F8;
  892. /* border: 1px dashed #CDCCCC; */
  893. border-radius: 6px;
  894. padding: 7px 10px;
  895. text-align: left;
  896. display: flex;
  897. flex-direction: column;
  898. gap: 4px;
  899. }
  900. .stat-title {
  901. font-size: 14px;
  902. font-weight: 500;
  903. color: #3D3D3D;
  904. }
  905. .stat-count {
  906. font-size: 13px;
  907. color: #222222;
  908. span {
  909. font-weight: 600;
  910. font-size: 16px;
  911. }
  912. }
  913. .stat-trend {
  914. font-size: 12px;
  915. font-weight: 500;
  916. }
  917. .stat-trend .down {
  918. color: #67C23A;
  919. }
  920. .stat-trend .up {
  921. color: #F56C6C;
  922. }
  923. .stat-trend .neutral {
  924. color: #909399;
  925. }
  926. /* 描述卡片样式 */
  927. .describe-card {
  928. background: #F8F8F8;
  929. border: 1px dashed #CDCCCC;
  930. border-radius: 6px;
  931. padding: 7px 10px;
  932. text-align: left;
  933. }
  934. .describe-content {
  935. font-size: 13px;
  936. color: #666;
  937. line-height: 1.5;
  938. }
  939. .stat-number {
  940. font-weight: 600;
  941. color: #557DDB;
  942. font-size: 16px;
  943. }
  944. /* 图表容器 */
  945. .chart-container {
  946. /* background: #F8F8F8;
  947. border-radius: 6px;
  948. padding: 16px; */
  949. min-height: 200px;
  950. height: 250px;
  951. position: relative;
  952. box-sizing: border-box;
  953. overflow: hidden;
  954. display: flex;
  955. align-items: center;
  956. justify-content: center;
  957. flex-shrink: 0;
  958. /* 防止被压缩 */
  959. }
  960. /* ECharts图表样式 */
  961. .echarts-chart {
  962. width: 100% !important;
  963. height: 100% !important;
  964. min-height: 200px;
  965. display: block;
  966. }
  967. /* 响应式设计 */
  968. @media (max-width: 768px) {
  969. .four-panel-layout {
  970. flex-direction: column;
  971. }
  972. .stat-cards {
  973. flex-direction: column;
  974. }
  975. }
  976. </style>