index.js 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247
  1. import * as echarts from 'echarts'
  2. import { getAuditTaskList } from '@/api/auditInitiation.js'
  3. import { getAllUnitList } from '@/api/auditEntityManage.js'
  4. import { analyzeStatistics } from '@/api/comprehensive'
  5. // 综合分析页面的通用mixin
  6. export const comprehensiveMixin = {
  7. data() {
  8. return {
  9. // 通用加载状态
  10. loading: false,
  11. projectOptions: [],
  12. auditedUnitOptions: [],
  13. // 指标树基础配置
  14. treeProps: {
  15. children: 'children',
  16. label: 'label',
  17. },
  18. // 图表实例集合
  19. chartInstances: {},
  20. // 组件是否已销毁标志
  21. isDestroyed: false,
  22. }
  23. },
  24. mounted() {
  25. this.getOptions()
  26. // 监听窗口大小变化
  27. window.addEventListener('resize', this.handleResize)
  28. },
  29. beforeDestroy() {
  30. // 标记组件已销毁
  31. this.isDestroyed = true
  32. // 销毁所有图表实例
  33. this.destroyCharts()
  34. // 移除窗口大小变化监听
  35. window.removeEventListener('resize', this.handleResize)
  36. },
  37. methods: {
  38. // 根据索引获取颜色
  39. getColorByIndex(index) {
  40. const colors = [
  41. '#5470C6', // 蓝色
  42. '#91CC75', // 绿色
  43. '#FAC858', // 黄色
  44. '#EE6666', // 红色
  45. '#73C0DE', // 浅蓝色
  46. '#3BA272', // 深绿色
  47. '#FC8452', // 橙色
  48. '#9A60B4', // 紫色
  49. ]
  50. return colors[index % colors.length]
  51. },
  52. // 将 costSurveysList 组装为树结构(用于左侧勾选)
  53. buildIndicatorTree(costSurveysList = []) {
  54. const processNode = (item) => {
  55. const node = {
  56. ...item,
  57. label: this.buildIndicatorLabel(item),
  58. children: [],
  59. }
  60. // 如果有子项,递归处理
  61. if (Array.isArray(item.costSurveysVos) && item.costSurveysVos.length) {
  62. node.children = item.costSurveysVos.map(processNode)
  63. }
  64. return node
  65. }
  66. return costSurveysList.map(processNode)
  67. },
  68. // label 规则:parentId 为 -1 时,用 “number、name”,否则 “number.name”
  69. buildIndicatorLabel(item) {
  70. const num = item.number || ''
  71. const nm = item.name || ''
  72. if (!num) return nm
  73. const isRoot = !item.parentId || item.parentId === '-1'
  74. return isRoot ? `${num}、${nm}` : `${num}.${nm}`
  75. },
  76. // 获取树的叶子 id 列表
  77. getLeafIds(tree = []) {
  78. const ids = []
  79. const dfs = (node) => {
  80. if (!node.children || node.children.length === 0) {
  81. ids.push(node.id)
  82. } else {
  83. node.children.forEach(dfs)
  84. }
  85. }
  86. tree.forEach(dfs)
  87. return ids
  88. },
  89. // 获取树的父级(拥有子节点)id 列表和节点列表
  90. getParentIds(tree = []) {
  91. const ids = []
  92. const items = []
  93. const dfs = (node) => {
  94. if (node.parentId == '-1') {
  95. ids.push(node.id)
  96. items.push(node)
  97. node.children.forEach(dfs)
  98. }
  99. }
  100. tree.forEach(dfs)
  101. return { ids, items }
  102. },
  103. // 去重成本列表:同级内去重,优先按 id,其次 rowId/rowid,再按 parentId+number+name
  104. dedupeCostSurveysList(list = []) {
  105. const dedupeLevel = (arr = []) => {
  106. const seenIds = new Set()
  107. const seenRowIds = new Set()
  108. const seenCombo = new Set()
  109. const result = []
  110. for (const node of arr || []) {
  111. if (!node) continue
  112. const normId =
  113. node.id !== undefined && node.id !== null
  114. ? String(node.id).trim()
  115. : ''
  116. const normRowId =
  117. node.rowId !== undefined && node.rowId !== null
  118. ? String(node.rowId).trim()
  119. : node.rowid !== undefined && node.rowid !== null
  120. ? String(node.rowid).trim()
  121. : ''
  122. const normParent =
  123. node.parentId !== undefined && node.parentId !== null
  124. ? String(node.parentId).trim()
  125. : ''
  126. const normNumber =
  127. node.number !== undefined && node.number !== null
  128. ? String(node.number).trim()
  129. : ''
  130. const normName =
  131. node.name !== undefined && node.name !== null
  132. ? String(node.name).trim()
  133. : ''
  134. const comboKey = `p:${normParent}|num:${normNumber}|name:${normName}`
  135. // 先用 id 去重
  136. if (normId && seenIds.has(normId)) {
  137. continue
  138. }
  139. // 其次用 rowId/rowid 去重
  140. if (!normId && normRowId && seenRowIds.has(normRowId)) {
  141. continue
  142. }
  143. // 再用组合字段去重(处理不同 id 但显示相同的重复)
  144. if (!normId && !normRowId && seenCombo.has(comboKey)) {
  145. continue
  146. }
  147. if (normId) {
  148. seenIds.add(normId)
  149. } else if (normRowId) {
  150. seenRowIds.add(normRowId)
  151. } else {
  152. seenCombo.add(comboKey)
  153. }
  154. const next = {
  155. ...node,
  156. id: normId || node.id,
  157. rowId: normRowId || node.rowId || node.rowid,
  158. parentId: normParent || node.parentId,
  159. number: normNumber || node.number,
  160. name: normName || node.name,
  161. }
  162. if (
  163. Array.isArray(node.costSurveysVos) &&
  164. node.costSurveysVos.length > 0
  165. ) {
  166. next.costSurveysVos = dedupeLevel(node.costSurveysVos)
  167. }
  168. result.push(next)
  169. }
  170. return result
  171. }
  172. return dedupeLevel(list)
  173. },
  174. // 统一请求统计数据(接口失败或无数据时使用mock)
  175. async requestStatistics(params = {}) {
  176. // 如果组件已销毁,直接返回 mock 数据
  177. if (this.isDestroyed) {
  178. return mockStatisticsData
  179. }
  180. try {
  181. const res = await analyzeStatistics(params)
  182. // 再次检查组件是否已销毁
  183. if (this.isDestroyed) {
  184. return mockStatisticsData
  185. }
  186. if (res && res.value) return res.value
  187. } catch (e) {
  188. // 如果是请求中止错误,静默处理
  189. if (e && e.message && e.message.includes('aborted')) {
  190. return mockStatisticsData
  191. }
  192. // 其他错误才打印警告
  193. if (!this.isDestroyed) {
  194. console.warn('analyzeStatistics 调用失败,使用 mock 数据', e)
  195. }
  196. }
  197. return mockStatisticsData
  198. },
  199. getOptions() {
  200. // 获取监审任务列表
  201. getAuditTaskList()
  202. .then((res) => {
  203. if (!this.isDestroyed && res && res.value) {
  204. this.projectOptions = res.value
  205. }
  206. })
  207. .catch((e) => {
  208. // 如果是请求中止错误,静默处理
  209. if (e && e.message && !e.message.includes('aborted')) {
  210. console.warn('获取项目列表失败', e)
  211. }
  212. })
  213. getAllUnitList()
  214. .then((res) => {
  215. if (!this.isDestroyed && res && res.value) {
  216. this.auditedUnitOptions = res.value
  217. }
  218. })
  219. .catch((e) => {
  220. // 如果是请求中止错误,静默处理
  221. if (e && e.message && !e.message.includes('aborted')) {
  222. console.warn('获取单位列表失败', e)
  223. }
  224. })
  225. },
  226. // 根据选中的指标 ID 过滤 costSurveysList
  227. filterCostSurveysListBySelectedIds(costSurveysList = [], selectedIds = []) {
  228. if (!selectedIds || selectedIds.length === 0) {
  229. return []
  230. }
  231. const selectedIdSet = new Set(selectedIds)
  232. // 递归过滤节点
  233. const filterNode = (node) => {
  234. if (!node) return null
  235. // 如果当前节点被选中,保留它及其所有子节点
  236. if (selectedIdSet.has(node.id)) {
  237. const filteredNode = { ...node }
  238. if (
  239. Array.isArray(node.costSurveysVos) &&
  240. node.costSurveysVos.length
  241. ) {
  242. // 如果父节点被选中,保留所有子节点
  243. filteredNode.costSurveysVos = node.costSurveysVos.map((child) => ({
  244. ...child,
  245. }))
  246. }
  247. return filteredNode
  248. }
  249. // 如果当前节点未被选中,检查是否有子节点被选中
  250. if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
  251. const filteredChildren = node.costSurveysVos
  252. .map(filterNode)
  253. .filter(Boolean)
  254. if (filteredChildren.length > 0) {
  255. // 如果有子节点被选中,保留父节点和选中的子节点
  256. return {
  257. ...node,
  258. costSurveysVos: filteredChildren,
  259. }
  260. }
  261. }
  262. return null
  263. }
  264. return costSurveysList.map(filterNode).filter(Boolean)
  265. },
  266. // 将 costSurveysList 展平为趋势数据 [{indicatorName, year, value}]
  267. // 规则:只展示选中节点对应的年度数据(surveysVos),不追加“合计年”;若无则递归子节点
  268. transformTrendFromCostList(costSurveysList = []) {
  269. const trendArr = []
  270. const dfs = (node) => {
  271. if (!node) return
  272. if (Array.isArray(node.surveysVos) && node.surveysVos.length) {
  273. node.surveysVos.forEach((sv) => {
  274. const yearKey = String(sv.name || sv.year || '').trim()
  275. if (!yearKey) return
  276. trendArr.push({
  277. indicatorName: node.name,
  278. year: yearKey,
  279. value: Number(sv.value) || 0,
  280. })
  281. })
  282. }
  283. if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
  284. node.costSurveysVos.forEach(dfs)
  285. }
  286. }
  287. costSurveysList.forEach(dfs)
  288. return trendArr
  289. },
  290. // 将 costSurveysList 汇总为构成数据 [{name, value}](叶子求和)
  291. transformCompositionFromCostList(costSurveysList = []) {
  292. const result = []
  293. const dfs = (node) => {
  294. if (!node) return
  295. if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
  296. node.costSurveysVos.forEach(dfs)
  297. } else {
  298. const total = Array.isArray(node.surveysVos)
  299. ? node.surveysVos.reduce(
  300. (sum, sv) => sum + (Number(sv.value) || 0),
  301. 0
  302. )
  303. : 0
  304. result.push({
  305. name: node.name,
  306. value: total,
  307. })
  308. }
  309. }
  310. costSurveysList.forEach(dfs)
  311. return result
  312. },
  313. // 构建趋势图数据结构(xAxis + series)
  314. buildTrendSeries(trendArr = []) {
  315. const years = [
  316. ...new Set(
  317. trendArr
  318. .map((item) =>
  319. String(item.year || item.name || '').replace('年', '')
  320. )
  321. .filter(Boolean)
  322. ),
  323. ]
  324. .sort()
  325. .map((y) => `${y}年`)
  326. const indicatorMap = {}
  327. trendArr.forEach((item) => {
  328. const yearKey = String(item.year || item.name || '').replace('年', '')
  329. if (!yearKey) return
  330. if (!indicatorMap[item.indicatorName]) {
  331. indicatorMap[item.indicatorName] = {
  332. name: item.indicatorName,
  333. data: {},
  334. color: this.getColorByIndex(Object.keys(indicatorMap).length),
  335. }
  336. }
  337. indicatorMap[item.indicatorName].data[yearKey] = Number(item.value) || 0
  338. })
  339. const series = Object.values(indicatorMap).map((indicator) => ({
  340. name: indicator.name,
  341. data: years.map((y) => {
  342. const key = y.replace('年', '')
  343. return indicator.data[key] || 0
  344. }),
  345. color: indicator.color,
  346. }))
  347. return { xAxis: years, series }
  348. },
  349. // 初始化图表
  350. initChart(chartId, containerId) {
  351. const chartDom = document.getElementById(containerId)
  352. if (chartDom) {
  353. this.chartInstances[chartId] = echarts.init(chartDom)
  354. return this.chartInstances[chartId]
  355. }
  356. return null
  357. },
  358. // 销毁指定图表实例
  359. destroyChart(chartId) {
  360. if (this.chartInstances[chartId]) {
  361. this.chartInstances[chartId].dispose()
  362. delete this.chartInstances[chartId]
  363. }
  364. },
  365. // 销毁所有图表实例
  366. destroyCharts() {
  367. Object.keys(this.chartInstances).forEach((chartId) => {
  368. this.destroyChart(chartId)
  369. })
  370. },
  371. // 处理窗口大小变化
  372. handleResize() {
  373. // 调整所有图表大小
  374. Object.values(this.chartInstances).forEach((chart) => {
  375. if (chart) {
  376. chart.resize()
  377. }
  378. })
  379. },
  380. // 更新图表数据
  381. updateChart(chartId, option) {
  382. if (this.chartInstances[chartId]) {
  383. this.chartInstances[chartId].setOption(option)
  384. this.$nextTick(() => {
  385. this.chartInstances[chartId].resize()
  386. })
  387. }
  388. },
  389. // 获取趋势图表基础配置
  390. getBaseTrendChartOption(
  391. seriesData = [],
  392. xAxisData = ['2022年', '2023年', '2024年']
  393. ) {
  394. return {
  395. tooltip: {
  396. trigger: 'axis',
  397. axisPointer: {
  398. type: 'cross',
  399. },
  400. },
  401. legend: {
  402. show: true,
  403. data: seriesData.map((item) => item.name),
  404. bottom: 0,
  405. left: 'center',
  406. itemGap: 20,
  407. textStyle: {
  408. fontSize: 12,
  409. color: '#333',
  410. },
  411. },
  412. grid: {
  413. left: '3%',
  414. right: '4%',
  415. bottom: '15%',
  416. top: '10%',
  417. containLabel: true,
  418. },
  419. xAxis: {
  420. type: 'category',
  421. boundaryGap: false,
  422. data: xAxisData,
  423. show: true,
  424. axisLine: {
  425. show: true,
  426. lineStyle: {
  427. color: '#333',
  428. },
  429. },
  430. axisTick: {
  431. show: true,
  432. },
  433. axisLabel: {
  434. show: true,
  435. fontSize: 12,
  436. color: '#333',
  437. },
  438. },
  439. yAxis: {
  440. type: 'value',
  441. show: true,
  442. axisLine: {
  443. show: true,
  444. lineStyle: {
  445. color: '#333',
  446. },
  447. },
  448. axisTick: {
  449. show: false,
  450. },
  451. axisLabel: {
  452. show: true,
  453. fontSize: 12,
  454. color: '#333',
  455. },
  456. splitLine: {
  457. show: true,
  458. lineStyle: {
  459. type: 'dashed',
  460. color: '#e0e0e0',
  461. },
  462. },
  463. },
  464. series: seriesData.map((item) => ({
  465. name: item.name,
  466. type: 'line',
  467. data: item.data,
  468. symbol: 'circle',
  469. symbolSize: 8,
  470. smooth: false,
  471. lineStyle: {
  472. width: 2,
  473. color: item.color || '#37A2DA',
  474. },
  475. itemStyle: {
  476. color: item.color || '#37A2DA',
  477. },
  478. emphasis: {
  479. focus: 'series',
  480. },
  481. })),
  482. }
  483. },
  484. // 获取构成图表基础配置(historyAnalysis.vue 使用)
  485. getBaseCompositionChartOption(data = []) {
  486. return {
  487. tooltip: {
  488. show: true,
  489. trigger: 'item',
  490. formatter: (p) => `${p.name}: ${p.value} (${p.percent}%)`,
  491. },
  492. legend: {
  493. show: true,
  494. orient: 'vertical',
  495. right: 10,
  496. top: 'center',
  497. data: data.map((item) => item.name),
  498. formatter: function (name) {
  499. return name
  500. },
  501. itemGap: 10,
  502. textStyle: {
  503. fontSize: 12,
  504. color: '#333',
  505. },
  506. itemWidth: 14,
  507. itemHeight: 14,
  508. },
  509. series: [
  510. {
  511. name: '成本构成',
  512. type: 'pie',
  513. radius: '60%',
  514. center: ['35%', '50%'],
  515. avoidLabelOverlap: true,
  516. itemStyle: {
  517. borderRadius: 0,
  518. borderColor: '#fff',
  519. borderWidth: 1,
  520. },
  521. label: {
  522. show: true,
  523. position: 'outside',
  524. formatter: '{b}',
  525. fontSize: 12,
  526. color: '#333',
  527. },
  528. labelLine: {
  529. show: true,
  530. length: 10,
  531. length2: 5,
  532. lineStyle: {
  533. width: 1,
  534. color: 'auto', // 线条跟随扇区颜色
  535. },
  536. },
  537. emphasis: {
  538. label: {
  539. show: true,
  540. fontSize: 12,
  541. fontWeight: 'normal',
  542. },
  543. itemStyle: {
  544. shadowBlur: 0,
  545. shadowOffsetX: 0,
  546. shadowColor: 'transparent',
  547. },
  548. labelLine: {
  549. lineStyle: {
  550. color: 'auto',
  551. },
  552. },
  553. },
  554. data: data,
  555. },
  556. ],
  557. }
  558. },
  559. // 获取构成图表基础配置(industryAnalysis.vue 使用)
  560. getIndustryCompositionChartOption(data = []) {
  561. const colorPalette = [
  562. '#5470C6', // 蓝色 - 基本工资
  563. '#73C0DE', // 青色/蓝绿色 - 津贴
  564. '#91CC75', // 绿色 - 奖金
  565. '#FAC858', // 黄色 - 福利费
  566. '#FF85C0', // 粉色 - 其他人员支出
  567. '#EE6666', // 红色
  568. '#3BA272', // 深绿色
  569. '#FC8452', // 橙色
  570. '#9A60B4', // 紫色
  571. '#E71D36', // 深红
  572. ]
  573. return {
  574. color: colorPalette,
  575. tooltip: {
  576. show: true,
  577. trigger: 'item',
  578. formatter: (p) => `${p.name}: ${p.value} (${p.percent}%)`,
  579. },
  580. legend: {
  581. show: true,
  582. orient: 'horizontal',
  583. bottom: 10,
  584. left: 'center',
  585. data: data.map((item) => item.name),
  586. icon: 'circle',
  587. itemGap: 20,
  588. textStyle: {
  589. fontSize: 12,
  590. color: '#333',
  591. },
  592. itemWidth: 10,
  593. itemHeight: 10,
  594. },
  595. series: [
  596. {
  597. name: '成本构成',
  598. type: 'pie',
  599. radius: ['40%', '60%'], // 环形图
  600. center: ['50%', '45%'],
  601. avoidLabelOverlap: true,
  602. silent: false,
  603. itemStyle: {
  604. borderRadius: 0,
  605. borderColor: '#fff',
  606. borderWidth: 1,
  607. },
  608. label: {
  609. show: true,
  610. position: 'outside',
  611. formatter: '{b}: {d}%', // 名称: 百分比
  612. fontSize: 12,
  613. color: '#333',
  614. },
  615. labelLine: {
  616. show: true,
  617. length: 10,
  618. length2: 5,
  619. lineStyle: {
  620. width: 1,
  621. color: 'auto', // 线条跟随扇区颜色
  622. },
  623. },
  624. emphasis: {
  625. label: {
  626. show: true,
  627. fontSize: 12,
  628. fontWeight: 'normal',
  629. },
  630. itemStyle: {
  631. shadowBlur: 0,
  632. shadowOffsetX: 0,
  633. shadowColor: 'transparent',
  634. },
  635. labelLine: {
  636. lineStyle: {
  637. color: 'auto',
  638. },
  639. },
  640. },
  641. data: data,
  642. },
  643. ],
  644. }
  645. },
  646. // 轮播功能基础配置(仅historyAnalysis.vue使用)
  647. initCarousel(chartId, totalPages = 1, onPageChange) {
  648. // 若已有轮播,先清理旧定时器,避免重复计时导致数据持续跳动
  649. if (this.carouselConfigs && this.carouselConfigs[chartId]) {
  650. this.pauseCarousel(chartId)
  651. }
  652. const carouselConfig = {
  653. totalPages,
  654. currentPage: 0,
  655. timer: null,
  656. interval: 5000,
  657. onPageChange,
  658. }
  659. // 保存轮播配置
  660. this.carouselConfigs = this.carouselConfigs || {}
  661. this.carouselConfigs[chartId] = carouselConfig
  662. // 启动轮播
  663. if (totalPages > 1) {
  664. this.startCarousel(chartId)
  665. } else {
  666. // 单页不需要轮播,但仍确保跳转到第一页
  667. this.goToCarouselPage(chartId, 0)
  668. }
  669. },
  670. // 启动轮播
  671. startCarousel(chartId) {
  672. const config = this.carouselConfigs?.[chartId]
  673. if (!config) return
  674. // 清除现有定时器
  675. this.pauseCarousel(chartId)
  676. // 设置新定时器
  677. config.timer = setInterval(() => {
  678. config.currentPage = (config.currentPage + 1) % config.totalPages
  679. this.goToCarouselPage(chartId, config.currentPage)
  680. }, config.interval)
  681. },
  682. // 暂停轮播
  683. pauseCarousel(chartId) {
  684. const config = this.carouselConfigs?.[chartId]
  685. if (config?.timer) {
  686. clearInterval(config.timer)
  687. config.timer = null
  688. }
  689. },
  690. // 恢复轮播
  691. resumeCarousel(chartId) {
  692. this.startCarousel(chartId)
  693. },
  694. // 跳转到指定轮播页
  695. goToCarouselPage(chartId, pageIndex) {
  696. const config = this.carouselConfigs?.[chartId]
  697. if (!config) return
  698. config.currentPage = pageIndex
  699. if (typeof config.onPageChange === 'function') {
  700. config.onPageChange(pageIndex)
  701. }
  702. },
  703. },
  704. }
  705. // mock 数据
  706. export const mockStatisticsData = {
  707. costSurveysList: [
  708. {
  709. id: '1997964654884069376',
  710. name: '人数',
  711. orderNum: '1',
  712. rowid: 'ce2c23fd-0a60-4d98-a27e-d5344c7f2f7b',
  713. number: '一',
  714. rvalue: '2',
  715. taskId: '1995015537499938816',
  716. nianfen: null,
  717. parentId: '-1',
  718. surveysVos: [
  719. { name: '2024', value: '30' },
  720. { name: '2025', value: '35' },
  721. ],
  722. costSurveysVos: [
  723. {
  724. id: '1997964655014092800',
  725. name: '1班',
  726. orderNum: '2',
  727. rowid: '0a075d08-1805-4ce0-a5e1-c4f264db76bd',
  728. number: '1',
  729. rvalue: '4',
  730. taskId: '1995015537499938816',
  731. nianfen: null,
  732. parentId: 'ce2c23fd-0a60-4d98-a27e-d5344c7f2f7b',
  733. surveysVos: [
  734. { name: '2024', value: '12' },
  735. { name: '2025', value: '15' },
  736. ],
  737. costSurveysVos: null,
  738. },
  739. {
  740. id: '1997964655131533312',
  741. name: '2班',
  742. orderNum: '3',
  743. rowid: 'ab3efd32-9a10-4355-8d6e-19d99ebdbe4a',
  744. number: '2',
  745. rvalue: '7',
  746. taskId: '1995015537499938816',
  747. nianfen: null,
  748. parentId: 'ce2c23fd-0a60-4d98-a27e-d5344c7f2f7b',
  749. surveysVos: [
  750. { name: '2024', value: '18' },
  751. { name: '2025', value: '20' },
  752. ],
  753. costSurveysVos: null,
  754. },
  755. ],
  756. },
  757. {
  758. id: '1997964655244779520',
  759. name: '1班费用',
  760. orderNum: '4',
  761. rowid: '6a81c1d3-ea8b-48d5-bd11-27e5b150cea1',
  762. number: '二',
  763. rvalue: '53',
  764. taskId: '1995015537499938816',
  765. nianfen: null,
  766. parentId: '-1',
  767. surveysVos: [
  768. { name: '2024', value: '120' },
  769. { name: '2025', value: '140' },
  770. ],
  771. costSurveysVos: [
  772. {
  773. id: '1997964655387385856',
  774. name: '押金',
  775. orderNum: '5',
  776. rowid: 'bf17169e-9d03-49f0-b7c7-7502879a935b',
  777. number: '1',
  778. rvalue: '124',
  779. taskId: '1995015537499938816',
  780. nianfen: null,
  781. parentId: '6a81c1d3-ea8b-48d5-bd11-27e5b150cea1',
  782. surveysVos: [
  783. { name: '2024', value: '50' },
  784. { name: '2025', value: '60' },
  785. ],
  786. costSurveysVos: null,
  787. },
  788. {
  789. id: '1997964655559352320',
  790. name: '缴纳',
  791. orderNum: '6',
  792. rowid: 'e130564b-dc73-4fa9-a004-01c3af418021',
  793. number: '2',
  794. rvalue: '146',
  795. taskId: '1995015537499938816',
  796. nianfen: null,
  797. parentId: '6a81c1d3-ea8b-48d5-bd11-27e5b150cea1',
  798. surveysVos: [
  799. { name: '2024', value: '70' },
  800. { name: '2025', value: '80' },
  801. ],
  802. costSurveysVos: null,
  803. },
  804. ],
  805. },
  806. {
  807. id: '1997964655680987136',
  808. name: '班级乘法',
  809. orderNum: '7',
  810. rowid: 'aec785e0-86fa-436e-be7b-bb9d2d9eaf32',
  811. number: '三',
  812. rvalue: '246',
  813. taskId: '1995015537499938816',
  814. nianfen: null,
  815. parentId: '-1',
  816. surveysVos: [
  817. { name: '2024', value: '300' },
  818. { name: '2025', value: '320' },
  819. ],
  820. costSurveysVos: [],
  821. },
  822. {
  823. id: '1997964655794233344',
  824. name: '人均费用',
  825. orderNum: '8',
  826. rowid: '857ba697-aaf0-4e26-97a6-4bd97d8ff6f7',
  827. number: '四',
  828. rvalue: '246',
  829. taskId: '1995015537499938816',
  830. nianfen: null,
  831. parentId: '-1',
  832. surveysVos: [
  833. { name: '2024', value: '15' },
  834. { name: '2025', value: '16' },
  835. ],
  836. costSurveysVos: [],
  837. },
  838. {
  839. id: '1997964655903279104',
  840. name: '材料成本',
  841. orderNum: '9',
  842. rowid: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  843. number: '五',
  844. rvalue: '580',
  845. taskId: '1995015537499938816',
  846. nianfen: null,
  847. parentId: '-1',
  848. surveysVos: [
  849. { name: '2022', value: '450' },
  850. { name: '2023', value: '520' },
  851. { name: '2024', value: '580' },
  852. { name: '2025', value: '620' },
  853. ],
  854. costSurveysVos: [
  855. {
  856. id: '1997964656012324864',
  857. name: '原材料',
  858. orderNum: '10',
  859. rowid: 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d',
  860. number: '1',
  861. rvalue: '320',
  862. taskId: '1995015537499938816',
  863. nianfen: null,
  864. parentId: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  865. surveysVos: [
  866. { name: '2022', value: '250' },
  867. { name: '2023', value: '290' },
  868. { name: '2024', value: '320' },
  869. { name: '2025', value: '350' },
  870. ],
  871. costSurveysVos: null,
  872. },
  873. {
  874. id: '1997964656121370624',
  875. name: '辅助材料',
  876. orderNum: '11',
  877. rowid: 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e',
  878. number: '2',
  879. rvalue: '180',
  880. taskId: '1995015537499938816',
  881. nianfen: null,
  882. parentId: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  883. surveysVos: [
  884. { name: '2022', value: '140' },
  885. { name: '2023', value: '160' },
  886. { name: '2024', value: '180' },
  887. { name: '2025', value: '200' },
  888. ],
  889. costSurveysVos: null,
  890. },
  891. {
  892. id: '1997964656230416384',
  893. name: '包装材料',
  894. orderNum: '12',
  895. rowid: 'c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f',
  896. number: '3',
  897. rvalue: '80',
  898. taskId: '1995015537499938816',
  899. nianfen: null,
  900. parentId: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  901. surveysVos: [
  902. { name: '2022', value: '60' },
  903. { name: '2023', value: '70' },
  904. { name: '2024', value: '80' },
  905. { name: '2025', value: '70' },
  906. ],
  907. costSurveysVos: null,
  908. },
  909. ],
  910. },
  911. {
  912. id: '1997964656339462144',
  913. name: '人工成本',
  914. orderNum: '13',
  915. rowid: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  916. number: '六',
  917. rvalue: '850',
  918. taskId: '1995015537499938816',
  919. nianfen: null,
  920. parentId: '-1',
  921. surveysVos: [
  922. { name: '2022', value: '680' },
  923. { name: '2023', value: '750' },
  924. { name: '2024', value: '850' },
  925. { name: '2025', value: '920' },
  926. ],
  927. costSurveysVos: [
  928. {
  929. id: '1997964656448507904',
  930. name: '基本工资',
  931. orderNum: '14',
  932. rowid: 'e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b',
  933. number: '1',
  934. rvalue: '520',
  935. taskId: '1995015537499938816',
  936. nianfen: null,
  937. parentId: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  938. surveysVos: [
  939. { name: '2022', value: '420' },
  940. { name: '2023', value: '460' },
  941. { name: '2024', value: '520' },
  942. { name: '2025', value: '580' },
  943. ],
  944. costSurveysVos: null,
  945. },
  946. {
  947. id: '1997964656557553664',
  948. name: '绩效奖金',
  949. orderNum: '15',
  950. rowid: 'f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c',
  951. number: '2',
  952. rvalue: '200',
  953. taskId: '1995015537499938816',
  954. nianfen: null,
  955. parentId: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  956. surveysVos: [
  957. { name: '2022', value: '150' },
  958. { name: '2023', value: '180' },
  959. { name: '2024', value: '200' },
  960. { name: '2025', value: '220' },
  961. ],
  962. costSurveysVos: null,
  963. },
  964. {
  965. id: '1997964656666599424',
  966. name: '社保公积金',
  967. orderNum: '16',
  968. rowid: 'a7b8c9d0-e1f2-4a3b-4c5d-6e7f8a9b0c1d',
  969. number: '3',
  970. rvalue: '130',
  971. taskId: '1995015537499938816',
  972. nianfen: null,
  973. parentId: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  974. surveysVos: [
  975. { name: '2022', value: '110' },
  976. { name: '2023', value: '120' },
  977. { name: '2024', value: '130' },
  978. { name: '2025', value: '120' },
  979. ],
  980. costSurveysVos: null,
  981. },
  982. ],
  983. },
  984. {
  985. id: '1997964656775645184',
  986. name: '设备成本',
  987. orderNum: '17',
  988. rowid: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  989. number: '七',
  990. rvalue: '420',
  991. taskId: '1995015537499938816',
  992. nianfen: null,
  993. parentId: '-1',
  994. surveysVos: [
  995. { name: '2022', value: '380' },
  996. { name: '2023', value: '400' },
  997. { name: '2024', value: '420' },
  998. { name: '2025', value: '450' },
  999. ],
  1000. costSurveysVos: [
  1001. {
  1002. id: '1997964656884690944',
  1003. name: '设备折旧',
  1004. orderNum: '18',
  1005. rowid: 'c9d0e1f2-a3b4-4c5d-6e7f-8a9b0c1d2e3f',
  1006. number: '1',
  1007. rvalue: '280',
  1008. taskId: '1995015537499938816',
  1009. nianfen: null,
  1010. parentId: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  1011. surveysVos: [
  1012. { name: '2022', value: '260' },
  1013. { name: '2023', value: '270' },
  1014. { name: '2024', value: '280' },
  1015. { name: '2025', value: '300' },
  1016. ],
  1017. costSurveysVos: null,
  1018. },
  1019. {
  1020. id: '1997964656993736704',
  1021. name: '设备维护',
  1022. orderNum: '19',
  1023. rowid: 'd0e1f2a3-b4c5-4d6e-7f8a-9b0c1d2e3f4a',
  1024. number: '2',
  1025. rvalue: '90',
  1026. taskId: '1995015537499938816',
  1027. nianfen: null,
  1028. parentId: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  1029. surveysVos: [
  1030. { name: '2022', value: '80' },
  1031. { name: '2023', value: '85' },
  1032. { name: '2024', value: '90' },
  1033. { name: '2025', value: '100' },
  1034. ],
  1035. costSurveysVos: null,
  1036. },
  1037. {
  1038. id: '1997964657102782464',
  1039. name: '设备租赁',
  1040. orderNum: '20',
  1041. rowid: 'e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b',
  1042. number: '3',
  1043. rvalue: '50',
  1044. taskId: '1995015537499938816',
  1045. nianfen: null,
  1046. parentId: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  1047. surveysVos: [
  1048. { name: '2022', value: '40' },
  1049. { name: '2023', value: '45' },
  1050. { name: '2024', value: '50' },
  1051. { name: '2025', value: '50' },
  1052. ],
  1053. costSurveysVos: null,
  1054. },
  1055. ],
  1056. },
  1057. {
  1058. id: '1997964657211828224',
  1059. name: '管理费用',
  1060. orderNum: '21',
  1061. rowid: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1062. number: '八',
  1063. rvalue: '360',
  1064. taskId: '1995015537499938816',
  1065. nianfen: null,
  1066. parentId: '-1',
  1067. surveysVos: [
  1068. { name: '2022', value: '320' },
  1069. { name: '2023', value: '340' },
  1070. { name: '2024', value: '360' },
  1071. { name: '2025', value: '380' },
  1072. ],
  1073. costSurveysVos: [
  1074. {
  1075. id: '1997964657320873984',
  1076. name: '办公费用',
  1077. orderNum: '22',
  1078. rowid: 'a3b4c5d6-e7f8-4a9b-0c1d-2e3f4a5b6c7d',
  1079. number: '1',
  1080. rvalue: '150',
  1081. taskId: '1995015537499938816',
  1082. nianfen: null,
  1083. parentId: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1084. surveysVos: [
  1085. { name: '2022', value: '130' },
  1086. { name: '2023', value: '140' },
  1087. { name: '2024', value: '150' },
  1088. { name: '2025', value: '160' },
  1089. ],
  1090. costSurveysVos: null,
  1091. },
  1092. {
  1093. id: '1997964657429919744',
  1094. name: '差旅费用',
  1095. orderNum: '23',
  1096. rowid: 'b4c5d6e7-f8a9-4b0c-1d2e-3f4a5b6c7d8e',
  1097. number: '2',
  1098. rvalue: '120',
  1099. taskId: '1995015537499938816',
  1100. nianfen: null,
  1101. parentId: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1102. surveysVos: [
  1103. { name: '2022', value: '110' },
  1104. { name: '2023', value: '115' },
  1105. { name: '2024', value: '120' },
  1106. { name: '2025', value: '130' },
  1107. ],
  1108. costSurveysVos: null,
  1109. },
  1110. {
  1111. id: '1997964657538965504',
  1112. name: '培训费用',
  1113. orderNum: '24',
  1114. rowid: 'c5d6e7f8-a9b0-4c1d-2e3f-4a5b6c7d8e9f',
  1115. number: '3',
  1116. rvalue: '90',
  1117. taskId: '1995015537499938816',
  1118. nianfen: null,
  1119. parentId: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1120. surveysVos: [
  1121. { name: '2022', value: '80' },
  1122. { name: '2023', value: '85' },
  1123. { name: '2024', value: '90' },
  1124. { name: '2025', value: '90' },
  1125. ],
  1126. costSurveysVos: null,
  1127. },
  1128. ],
  1129. },
  1130. {
  1131. id: '1997964657648011264',
  1132. name: '能源成本',
  1133. orderNum: '25',
  1134. rowid: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1135. number: '九',
  1136. rvalue: '280',
  1137. taskId: '1995015537499938816',
  1138. nianfen: null,
  1139. parentId: '-1',
  1140. surveysVos: [
  1141. { name: '2022', value: '250' },
  1142. { name: '2023', value: '270' },
  1143. { name: '2024', value: '280' },
  1144. { name: '2025', value: '290' },
  1145. ],
  1146. costSurveysVos: [
  1147. {
  1148. id: '1997964657757057024',
  1149. name: '电费',
  1150. orderNum: '26',
  1151. rowid: 'e7f8a9b0-c1d2-4e3f-4a5b-6c7d8e9f0a1b',
  1152. number: '1',
  1153. rvalue: '180',
  1154. taskId: '1995015537499938816',
  1155. nianfen: null,
  1156. parentId: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1157. surveysVos: [
  1158. { name: '2022', value: '160' },
  1159. { name: '2023', value: '170' },
  1160. { name: '2024', value: '180' },
  1161. { name: '2025', value: '190' },
  1162. ],
  1163. costSurveysVos: null,
  1164. },
  1165. {
  1166. id: '1997964657866102784',
  1167. name: '水费',
  1168. orderNum: '27',
  1169. rowid: 'f8a9b0c1-d2e3-4f4a-5b6c-7d8e9f0a1b2c',
  1170. number: '2',
  1171. rvalue: '60',
  1172. taskId: '1995015537499938816',
  1173. nianfen: null,
  1174. parentId: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1175. surveysVos: [
  1176. { name: '2022', value: '55' },
  1177. { name: '2023', value: '58' },
  1178. { name: '2024', value: '60' },
  1179. { name: '2025', value: '62' },
  1180. ],
  1181. costSurveysVos: null,
  1182. },
  1183. {
  1184. id: '1997964657975148544',
  1185. name: '燃气费',
  1186. orderNum: '28',
  1187. rowid: 'a9b0c1d2-e3f4-4a5b-6c7d-8e9f0a1b2c3d',
  1188. number: '3',
  1189. rvalue: '40',
  1190. taskId: '1995015537499938816',
  1191. nianfen: null,
  1192. parentId: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1193. surveysVos: [
  1194. { name: '2022', value: '35' },
  1195. { name: '2023', value: '38' },
  1196. { name: '2024', value: '40' },
  1197. { name: '2025', value: '38' },
  1198. ],
  1199. costSurveysVos: null,
  1200. },
  1201. ],
  1202. },
  1203. ],
  1204. }