index.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711
  1. import * as echarts from 'echarts'
  2. import { getAllUnitList } from '@/api/auditEntityManage.js'
  3. // import { getAuditTaskList } from '@/api/auditInitiation.js'
  4. // , getAuditTaskList
  5. import { analyzeStatistics, getAuditTaskList } from '@/api/comprehensive'
  6. // 综合分析页面的通用mixin
  7. export const comprehensiveMixin = {
  8. data() {
  9. return {
  10. // 通用加载状态
  11. loading: false,
  12. projectOptions: [],
  13. auditedUnitOptions: [],
  14. // 指标树基础配置
  15. treeProps: {
  16. children: 'children',
  17. label: 'label',
  18. },
  19. // 图表实例集合
  20. chartInstances: {},
  21. // 组件是否已销毁标志
  22. isDestroyed: false,
  23. }
  24. },
  25. mounted() {
  26. this.getOptions()
  27. // 监听窗口大小变化
  28. window.addEventListener('resize', this.handleResize)
  29. },
  30. beforeDestroy() {
  31. // 标记组件已销毁
  32. this.isDestroyed = true
  33. // 销毁所有图表实例
  34. this.destroyCharts()
  35. // 移除窗口大小变化监听
  36. window.removeEventListener('resize', this.handleResize)
  37. },
  38. methods: {
  39. // 根据索引获取颜色
  40. getColorByIndex(index) {
  41. const colors = [
  42. '#5470C6', // 蓝色
  43. '#91CC75', // 绿色
  44. '#FAC858', // 黄色
  45. '#EE6666', // 红色
  46. '#73C0DE', // 浅蓝色
  47. '#3BA272', // 深绿色
  48. '#FC8452', // 橙色
  49. '#9A60B4', // 紫色
  50. ]
  51. return colors[index % colors.length]
  52. },
  53. // 将 costSurveysList 组装为树结构(用于左侧勾选)
  54. buildIndicatorTree(costSurveysList = []) {
  55. const processNode = (item) => {
  56. const nodeId =
  57. item.uniqueId ||
  58. item.id ||
  59. item.rowid ||
  60. item.rowId ||
  61. `${item.number || item.name || ''}`
  62. const node = {
  63. ...item,
  64. id: nodeId,
  65. label: this.buildIndicatorLabel(item),
  66. children: [],
  67. }
  68. // 如果有子项,递归处理
  69. if (Array.isArray(item.costSurveysVos) && item.costSurveysVos.length) {
  70. node.children = item.costSurveysVos.map(processNode)
  71. // 对子节点按照orderNum从小到大排序
  72. node.children.sort((a, b) => {
  73. const orderA = parseInt(a.orderNum || 0)
  74. const orderB = parseInt(b.orderNum || 0)
  75. return orderA - orderB
  76. })
  77. }
  78. return node
  79. }
  80. // 处理根节点并按照orderNum从小到大排序
  81. return costSurveysList.map(processNode).sort((a, b) => {
  82. const orderA = parseInt(a.orderNum || 0)
  83. const orderB = parseInt(b.orderNum || 0)
  84. return orderA - orderB
  85. })
  86. },
  87. // label 规则:parentId 为 -1 时,用 “number、name”,否则 “number.name”
  88. buildIndicatorLabel(item) {
  89. const num = item.number || ''
  90. const nm = item.name || ''
  91. if (!num) return nm
  92. const isRoot = !item.parentId || item.parentId === '-1'
  93. return isRoot ? `${num}、${nm}` : `${num}.${nm}`
  94. },
  95. // 聚合某节点及其后代的年度与汇总数据
  96. aggregateNodeData(node) {
  97. const yearMap = new Map()
  98. let surveysSum = 0
  99. const dfs = (n) => {
  100. if (!n) return
  101. // 累加叶子上的 surveysVos(年度值通常在这里)
  102. if (Array.isArray(n.surveysVos) && n.surveysVos.length) {
  103. n.surveysVos.forEach((sv) => {
  104. const yearKey = String(sv.name || sv.year || '').trim()
  105. const val = Number(sv.value) || 0
  106. if (yearKey) {
  107. yearMap.set(yearKey, (yearMap.get(yearKey) || 0) + val)
  108. }
  109. })
  110. surveysSum += n.surveysVos.reduce(
  111. (sum, sv) => sum + (Number(sv.value) || 0),
  112. 0
  113. )
  114. }
  115. // 若有子节点,继续向下汇总
  116. if (Array.isArray(n.costSurveysVos) && n.costSurveysVos.length) {
  117. n.costSurveysVos.forEach((child) => {
  118. // 如果子项有surveysVos,先处理这些数据
  119. if (Array.isArray(child.surveysVos) && child.surveysVos.length) {
  120. child.surveysVos.forEach((sv) => {
  121. const yearKey = String(sv.name || sv.year || '').trim()
  122. const val = Number(sv.value) || 0
  123. if (yearKey) {
  124. yearMap.set(yearKey, (yearMap.get(yearKey) || 0) + val)
  125. }
  126. })
  127. surveysSum += child.surveysVos.reduce(
  128. (sum, sv) => sum + (Number(sv.value) || 0),
  129. 0
  130. )
  131. }
  132. // 如果子项仍有子节点,递归;否则视为叶子年度数据
  133. if (
  134. Array.isArray(child.costSurveysVos) &&
  135. child.costSurveysVos.length
  136. ) {
  137. dfs(child)
  138. } else if (
  139. !Array.isArray(child.surveysVos) ||
  140. child.surveysVos.length === 0
  141. ) {
  142. // 只有当没有surveysVos时,才使用name/year和value
  143. const yearKey = String(child.name || child.year || '').trim()
  144. const val = Number(child.value) || 0
  145. if (yearKey) {
  146. yearMap.set(yearKey, (yearMap.get(yearKey) || 0) + val)
  147. }
  148. }
  149. })
  150. }
  151. // 汇总 surveysVos(构成图用)
  152. if (Array.isArray(n.surveysVos) && n.surveysVos.length) {
  153. surveysSum += n.surveysVos.reduce(
  154. (sum, sv) => sum + (Number(sv.value) || 0),
  155. 0
  156. )
  157. }
  158. }
  159. dfs(node)
  160. // 将年度数据转换为 surveysVos 格式,保留完整的年度信息
  161. const annualSurveysVos = Array.from(yearMap.entries()).map(
  162. ([name, value]) => ({
  163. name,
  164. value,
  165. })
  166. )
  167. return {
  168. costSurveysVos: annualSurveysVos,
  169. surveysVos: annualSurveysVos, // 返回完整的年度数据而不仅仅是合计值
  170. }
  171. },
  172. // 获取树的叶子 id 列表
  173. getLeafIds(tree = []) {
  174. const ids = []
  175. const dfs = (node) => {
  176. if (!node.children || node.children.length === 0) {
  177. ids.push(node.id)
  178. } else {
  179. node.children.forEach(dfs)
  180. }
  181. }
  182. tree.forEach(dfs)
  183. return ids
  184. },
  185. // 获取树的父级(拥有子节点)id 列表和节点列表
  186. getParentIds(tree = []) {
  187. const ids = []
  188. const items = []
  189. const dfs = (node) => {
  190. if (
  191. node &&
  192. (node.parentId === undefined ||
  193. node.parentId === null ||
  194. String(node.parentId) === '-1')
  195. ) {
  196. ids.push(node.id)
  197. items.push(node)
  198. }
  199. if (node && node.children && node.children.length > 0) {
  200. node.children.forEach(dfs)
  201. }
  202. }
  203. tree.forEach(dfs)
  204. return { ids, items }
  205. },
  206. // 查找节点到根的路径
  207. findPathByKey(
  208. id,
  209. tree = this.indicatorData ||
  210. this.leftDataItems ||
  211. this.rightDataItems ||
  212. []
  213. ) {
  214. const getKeys = (item) =>
  215. [
  216. item.id,
  217. item.uniqueId,
  218. item.rowid,
  219. item.rowId,
  220. item.number,
  221. item.name,
  222. ].filter(Boolean)
  223. const dfs = (items, path = []) => {
  224. for (const item of items) {
  225. const currentPath = [...path, item]
  226. if (getKeys(item).includes(id)) {
  227. return currentPath
  228. }
  229. if (item.children && item.children.length > 0) {
  230. const childPath = dfs(item.children, currentPath)
  231. if (childPath) return childPath
  232. }
  233. }
  234. return null
  235. }
  236. return dfs(tree)
  237. },
  238. // 过滤选中项:
  239. // - 点击根节点:只保留当前根节点及其已勾选的子节点,其它根与节点取消
  240. // - 点击子/孙节点:只保留当前节点及其已勾选的子节点,其它节点取消
  241. sanitizeCheckedKeys(
  242. data,
  243. checkedKeys = [],
  244. tree = this.leftDataItems ||
  245. this.rightDataItems ||
  246. this.indicatorData ||
  247. []
  248. ) {
  249. if (!checkedKeys || checkedKeys.length === 0) return []
  250. const useTree = Array.isArray(tree) && tree.length > 0 ? tree : []
  251. const paths = []
  252. checkedKeys.forEach((key) => {
  253. const path = this.findPathByKey(key, useTree)
  254. if (path && path.length > 0) {
  255. paths.push(path)
  256. }
  257. })
  258. const sanitized = new Set()
  259. if (data && String(data.parentId ?? '').trim() === '-1') {
  260. // 根节点:只保留当前根及其已勾选的子节点
  261. paths
  262. .filter((path) => path[0] && path[0].id === data.id)
  263. .forEach((path) => {
  264. path.forEach((node) => sanitized.add(node.id))
  265. })
  266. sanitized.add(data.id)
  267. } else if (data) {
  268. // 子/孙节点:保留当前节点及其已勾选的子节点,并保留已勾选的根节点
  269. paths
  270. .filter((path) => path[0] && path[0].rowid === data.parentId)
  271. .forEach((root) => {
  272. root.forEach((node) => {
  273. sanitized.add(node.id)
  274. node.children.forEach((child) => {
  275. if (checkedKeys.includes(child.id)) {
  276. sanitized.add(child.id)
  277. }
  278. })
  279. })
  280. })
  281. }
  282. return Array.from(sanitized)
  283. },
  284. // 过滤数据:选什么就返回什么(父子可同时保留各自曲线)
  285. filterCostSurveysListForHistoryAnalysis(
  286. costSurveysList = [],
  287. selectedItems = []
  288. ) {
  289. if (!selectedItems || selectedItems.length === 0) {
  290. return []
  291. }
  292. const toKey = (v) =>
  293. v === undefined || v === null ? null : String(v).trim()
  294. // 兼容 id / uniqueId / rowid,统一转成字符串避免类型不一致
  295. const selectedIdSet = new Set(
  296. selectedItems
  297. .flatMap((item) => [item.id, item.uniqueId, item.rowid, item.rowId])
  298. .map(toKey)
  299. .filter(Boolean)
  300. )
  301. // 递归过滤节点,父子都可保留
  302. const filterNode = (node) => {
  303. if (!node) return null
  304. const keys = [node.id, node.uniqueId, node.rowid, node.rowId].map(toKey)
  305. const isSelected = keys.some((k) => selectedIdSet.has(k))
  306. let filteredChildren = []
  307. if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
  308. filteredChildren = node.costSurveysVos.map(filterNode).filter(Boolean)
  309. }
  310. if (isSelected) {
  311. // 选中的节点保留自身;不再用子节点汇总填充,避免父子数据重复
  312. const baseNode = {
  313. ...node,
  314. surveysVos: Array.isArray(node.surveysVos) ? node.surveysVos : [],
  315. costSurveysVos: filteredChildren, // 若子节点也选中则一并保留
  316. }
  317. return baseNode
  318. }
  319. // 未选中但有选中子节点,则只保留这些子节点
  320. if (filteredChildren.length > 0) {
  321. return {
  322. ...node,
  323. costSurveysVos: filteredChildren,
  324. }
  325. }
  326. return null
  327. }
  328. return costSurveysList.map(filterNode).filter(Boolean)
  329. },
  330. // 统一请求统计数据
  331. async requestStatistics(params = {}) {
  332. // 如果组件已销毁,直接返回 mock 数据
  333. if (this.isDestroyed) {
  334. return []
  335. }
  336. try {
  337. const res = await analyzeStatistics(params)
  338. // 再次检查组件是否已销毁
  339. if (this.isDestroyed) {
  340. return []
  341. }
  342. if (res && res.value) {
  343. return this.attachUniqueId(res.value)
  344. }
  345. } catch (e) {
  346. // 如果是请求中止错误,静默处理
  347. if (e && e.message && e.message.includes('aborted')) {
  348. return []
  349. }
  350. // 其他错误才打印警告
  351. if (!this.isDestroyed) {
  352. console.warn('analyzeStatistics 调用失败,使用 mock 数据', e)
  353. }
  354. }
  355. return []
  356. },
  357. // 为接口返回的列表生成唯一标识 uniqueId,兼容数组或 { costSurveysList } 结构
  358. attachUniqueId(res) {
  359. const markList = (list = [], parentKey = 'root', seen = new Set()) => {
  360. return (list || []).map((item, idx) => {
  361. // 使用多种可能的ID字段作为基础,但不修改原始ID
  362. const base =
  363. item.id ||
  364. item.rowid ||
  365. item.rowId ||
  366. `${parentKey}-${item.number || item.name || idx}`
  367. let key = base
  368. let suffix = 1
  369. while (seen.has(key)) {
  370. key = `${base}-${suffix++}`
  371. }
  372. seen.add(key)
  373. // 创建新对象并添加uniqueId,不修改原始item的id
  374. const cloned = {
  375. ...item,
  376. uniqueId: key,
  377. }
  378. if (
  379. Array.isArray(item.costSurveysVos) &&
  380. item.costSurveysVos.length
  381. ) {
  382. cloned.costSurveysVos = markList(item.costSurveysVos, key, seen)
  383. }
  384. return cloned
  385. })
  386. }
  387. if (Array.isArray(res)) {
  388. return markList(res)
  389. }
  390. if (res && Array.isArray(res.costSurveysList)) {
  391. return {
  392. ...res,
  393. costSurveysList: markList(res.costSurveysList),
  394. }
  395. }
  396. return res
  397. },
  398. getOptions() {
  399. // 获取监审任务列表
  400. getAuditTaskList()
  401. .then((res) => {
  402. if (!this.isDestroyed && res && res.value) {
  403. this.projectOptions = res.value
  404. }
  405. })
  406. .catch((e) => {
  407. // 如果是请求中止错误,静默处理
  408. if (e && e.message && !e.message.includes('aborted')) {
  409. console.warn('获取项目列表失败', e)
  410. }
  411. })
  412. getAllUnitList()
  413. .then((res) => {
  414. if (!this.isDestroyed && res && res.value) {
  415. this.auditedUnitOptions = res.value
  416. }
  417. })
  418. .catch((e) => {
  419. // 如果是请求中止错误,静默处理
  420. if (e && e.message && !e.message.includes('aborted')) {
  421. console.warn('获取单位列表失败', e)
  422. }
  423. })
  424. },
  425. // 根据选中的指标 ID 过滤 costSurveysList
  426. filterCostSurveysListBySelectedIds(costSurveysList = [], selectedIds = []) {
  427. if (!selectedIds || selectedIds.length === 0) {
  428. return []
  429. }
  430. // 统一转成字符串,避免数字/字符串类型不一致导致无法匹配
  431. const toKey = (v) =>
  432. v === undefined || v === null ? null : String(v).trim()
  433. const selectedIdSet = new Set(selectedIds.map(toKey).filter(Boolean))
  434. const isSelected = (node) => {
  435. const keys = [
  436. node?.id,
  437. node?.uniqueId,
  438. node?.rowid,
  439. node?.rowId,
  440. node?.number,
  441. node?.name,
  442. ]
  443. .map(toKey)
  444. .filter(Boolean)
  445. return keys.some((k) => selectedIdSet.has(k))
  446. }
  447. const filterNode = (node) => {
  448. if (!node) return null
  449. // 当前节点命中选中,直接保留,子节点也带上(用于饼图父级展示子项/趋势展示自身)
  450. if (isSelected(node)) {
  451. const filteredNode = { ...node }
  452. if (
  453. Array.isArray(node.costSurveysVos) &&
  454. node.costSurveysVos.length
  455. ) {
  456. filteredNode.costSurveysVos = node.costSurveysVos
  457. .map(filterNode)
  458. .filter(Boolean)
  459. }
  460. return filteredNode
  461. }
  462. // 未命中,检查子节点
  463. if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
  464. const filteredChildren = node.costSurveysVos
  465. .map(filterNode)
  466. .filter(Boolean)
  467. if (filteredChildren.length > 0) {
  468. return {
  469. ...node,
  470. costSurveysVos: filteredChildren,
  471. }
  472. }
  473. }
  474. return null
  475. }
  476. return costSurveysList.map(filterNode).filter(Boolean)
  477. },
  478. // 将 costSurveysList 展平为趋势数据 [{indicatorName, year, value}]
  479. // 规则:只展示选中节点对应的年度数据(surveysVos),不追加“合计年”;若无则递归子节点
  480. transformTrendFromCostList(costSurveysList = []) {
  481. const trendArr = []
  482. const dfs = (node) => {
  483. if (!node) return
  484. if (Array.isArray(node.surveysVos) && node.surveysVos.length) {
  485. node.surveysVos.forEach((sv) => {
  486. const yearKey = String(sv.name || sv.year || '').trim()
  487. if (!yearKey) return
  488. const indicatorId =
  489. node.uniqueId || node.id || node.rowid || node.rowId || node.name
  490. trendArr.push({
  491. indicatorId: indicatorId,
  492. indicatorName: node.name,
  493. year: yearKey,
  494. value:
  495. Number(
  496. sv.value !== undefined && sv.value !== null
  497. ? sv.value
  498. : sv.rvalue
  499. ) || 0,
  500. })
  501. })
  502. }
  503. if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
  504. node.costSurveysVos.forEach(dfs)
  505. }
  506. }
  507. costSurveysList.forEach(dfs)
  508. return trendArr
  509. },
  510. // 将 costSurveysList 汇总为构成数据 [{name, value}](显示所有surveysVos)
  511. transformCompositionFromCostList(
  512. costSurveysList = [],
  513. selectedItemsOrder = []
  514. ) {
  515. const result = []
  516. const dfs = (node) => {
  517. if (!node) return
  518. // 获取节点的唯一标识
  519. const indicatorId =
  520. node.uniqueId || node.id || node.rowid || node.rowId || node.name
  521. if (
  522. Array.isArray(node.costSurveysVos) &&
  523. node.costSurveysVos.length > 0
  524. ) {
  525. result.push({
  526. name: node.name,
  527. indicatorId: indicatorId, // 添加唯一标识
  528. value:
  529. Number(
  530. node.value !== undefined && node.value !== null
  531. ? node.value
  532. : node.rvalue
  533. ) || 0,
  534. surveysVos: node.surveysVos || [],
  535. })
  536. node.costSurveysVos.forEach((sv) => {
  537. const surveysList = Array.isArray(sv.surveysVos)
  538. ? sv.surveysVos
  539. : []
  540. // 子节点使用自己的唯一标识
  541. const childIndicatorId =
  542. sv.uniqueId || sv.id || sv.rowid || sv.rowId || sv.name
  543. result.push({
  544. name: sv.name,
  545. indicatorId: childIndicatorId, // 添加子节点的唯一标识
  546. value:
  547. surveysList.reduce((sum, item) => {
  548. const v =
  549. item.value !== undefined && item.value !== null
  550. ? item.value
  551. : item.rvalue
  552. return sum + (Number(v) || 0)
  553. }, 0) || 0,
  554. surveysVos: surveysList,
  555. })
  556. })
  557. } else {
  558. result.push({
  559. name: node.name,
  560. indicatorId: indicatorId, // 添加唯一标识
  561. value:
  562. Number(
  563. node.value !== undefined && node.value !== null
  564. ? node.value
  565. : node.rvalue
  566. ) || 0,
  567. surveysVos: node.surveysVos || [],
  568. })
  569. }
  570. }
  571. costSurveysList.forEach(dfs)
  572. // 统计相同名称的数量,用于区分显示
  573. const nameCountMap = {}
  574. result.forEach((item) => {
  575. nameCountMap[item.name] = (nameCountMap[item.name] || 0) + 1
  576. })
  577. const nameIndexMap = {}
  578. let orderedResult = [...result]
  579. // 为相同名称的项添加序号
  580. return orderedResult.map((item) => {
  581. const nameCount = nameCountMap[item.name] || 1
  582. let displayName = item.name
  583. if (nameCount > 1) {
  584. if (!nameIndexMap[item.name]) {
  585. nameIndexMap[item.name] = 0
  586. }
  587. nameIndexMap[item.name]++
  588. displayName = `${item.name}(${nameIndexMap[item.name]})`
  589. }
  590. return {
  591. ...item,
  592. name: displayName, // 使用带序号的名字
  593. originalName: item.name, // 保留原始名称
  594. }
  595. })
  596. },
  597. // 构建趋势图数据结构(xAxis + series)
  598. // selectedItemsOrder: 可选的选中项顺序数组,用于保持图例顺序与选中项顺序一致
  599. buildTrendSeries(trendArr = [], selectedItemsOrder = []) {
  600. const years = [
  601. ...new Set(
  602. trendArr
  603. .map((item) =>
  604. String(item.year || item.name || '').replace('年', '')
  605. )
  606. .filter(Boolean)
  607. ),
  608. ]
  609. .sort()
  610. .map((y) => `${y}年`)
  611. const indicatorMap = {}
  612. trendArr.forEach((item) => {
  613. const yearKey = String(item.year || item.name || '').replace('年', '')
  614. if (!yearKey) return
  615. // 优先使用 indicatorId(uniqueId/id/rowid),确保相同名称但不同ID的项能区分
  616. // 如果 indicatorId 为空,则使用 indicatorName 作为key(这种情况下相同名称会合并)
  617. const key = item.indicatorId || item.indicatorName
  618. if (!indicatorMap[key]) {
  619. indicatorMap[key] = {
  620. id: key,
  621. name: item.indicatorName,
  622. data: {},
  623. color: this.getColorByIndex(Object.keys(indicatorMap).length),
  624. }
  625. }
  626. // 如果同一年份已有数据,累加而不是覆盖(避免相同名称的项数据丢失)
  627. const existingValue = indicatorMap[key].data[yearKey] || 0
  628. indicatorMap[key].data[yearKey] =
  629. existingValue + Number(item.value) || 0
  630. })
  631. // 统计相同名称的数量,用于区分图例显示
  632. const nameCountMap = {}
  633. Object.values(indicatorMap).forEach((indicator) => {
  634. nameCountMap[indicator.name] = (nameCountMap[indicator.name] || 0) + 1
  635. })
  636. const nameIndexMap = {}
  637. // 按照选中项顺序来生成 series,如果没有提供顺序则使用默认顺序
  638. let orderedIndicators = []
  639. if (selectedItemsOrder && selectedItemsOrder.length > 0) {
  640. // 按照 selectedItemsOrder 的顺序来构建 series
  641. const indicatorIdToItem = {}
  642. selectedItemsOrder.forEach((item) => {
  643. const key =
  644. item.uniqueId || item.id || item.rowid || item.rowId || item.name
  645. if (key && indicatorMap[key]) {
  646. indicatorIdToItem[key] = indicatorMap[key]
  647. }
  648. })
  649. // 先添加按顺序的项
  650. orderedIndicators = selectedItemsOrder
  651. .map((item) => {
  652. const key =
  653. item.uniqueId || item.id || item.rowid || item.rowId || item.name
  654. return indicatorMap[key]
  655. })
  656. .filter(Boolean)
  657. // 再添加不在选中项顺序中的项(以防有遗漏)
  658. Object.keys(indicatorMap).forEach((key) => {
  659. if (!indicatorIdToItem[key]) {
  660. orderedIndicators.push(indicatorMap[key])
  661. }
  662. })
  663. } else {
  664. // 如果没有提供顺序,使用默认顺序
  665. orderedIndicators = Object.values(indicatorMap)
  666. }
  667. // 按照顺序重新分配颜色,确保每个项(即使名称相同)都使用不同的颜色
  668. const series = orderedIndicators.map((indicator, index) => {
  669. // 如果存在相同名称的项,在名称后添加序号来区分
  670. const nameCount = nameCountMap[indicator.name] || 1
  671. let displayName = indicator.name
  672. if (nameCount > 1) {
  673. if (!nameIndexMap[indicator.name]) {
  674. nameIndexMap[indicator.name] = 0
  675. }
  676. nameIndexMap[indicator.name]++
  677. displayName = `${indicator.name}(${nameIndexMap[indicator.name]})`
  678. }
  679. return {
  680. name: displayName,
  681. indicatorId: indicator.id, // 保留原始ID用于区分
  682. originalName: indicator.name, // 保留原始名称
  683. data: years.map((y) => {
  684. const key = y.replace('年', '')
  685. return indicator.data[key] !== undefined ? indicator.data[key] : 0
  686. }),
  687. color: this.getColorByIndex(index), // 按照顺序重新分配颜色,确保每个项都不同
  688. }
  689. })
  690. return { xAxis: years, series }
  691. },
  692. // 初始化图表
  693. initChart(chartId, containerId) {
  694. const chartDom = document.getElementById(containerId)
  695. if (chartDom) {
  696. this.chartInstances[chartId] = echarts.init(chartDom)
  697. return this.chartInstances[chartId]
  698. }
  699. return null
  700. },
  701. // 销毁指定图表实例
  702. destroyChart(chartId) {
  703. if (this.chartInstances[chartId]) {
  704. this.chartInstances[chartId].dispose()
  705. delete this.chartInstances[chartId]
  706. }
  707. },
  708. // 销毁所有图表实例
  709. destroyCharts() {
  710. Object.keys(this.chartInstances).forEach((chartId) => {
  711. this.destroyChart(chartId)
  712. })
  713. },
  714. // 处理窗口大小变化
  715. handleResize() {
  716. // 调整所有图表大小
  717. Object.values(this.chartInstances).forEach((chart) => {
  718. if (chart) {
  719. chart.resize()
  720. }
  721. })
  722. },
  723. // 更新图表数据
  724. updateChart(chartId, option) {
  725. if (this.chartInstances[chartId]) {
  726. this.chartInstances[chartId].setOption(option)
  727. this.$nextTick(() => {
  728. this.chartInstances[chartId].resize()
  729. })
  730. }
  731. },
  732. // 获取趋势图表基础配置
  733. getBaseTrendChartOption(
  734. seriesData = [],
  735. xAxisData = ['2022年', '2023年', '2024年']
  736. ) {
  737. return {
  738. tooltip: {
  739. trigger: 'axis',
  740. axisPointer: {
  741. type: 'cross',
  742. },
  743. },
  744. legend: {
  745. show: true,
  746. data: seriesData.map((item) => item.name),
  747. bottom: 0,
  748. left: 'center',
  749. itemGap: 15,
  750. textStyle: {
  751. fontSize: 12,
  752. color: '#333',
  753. },
  754. type: 'plain', // 改为 plain 去掉分页控件
  755. width: 'auto', // 自动宽度,确保能显示所有图例
  756. formatter: function (name) {
  757. return name // 确保显示完整的名称
  758. },
  759. },
  760. grid: {
  761. left: '3%',
  762. right: '4%',
  763. bottom: '15%',
  764. top: '10%',
  765. containLabel: true,
  766. },
  767. xAxis: {
  768. type: 'category',
  769. boundaryGap: false,
  770. data: xAxisData,
  771. show: true,
  772. axisLine: {
  773. show: true,
  774. lineStyle: {
  775. color: '#333',
  776. },
  777. },
  778. axisTick: {
  779. show: true,
  780. },
  781. axisLabel: {
  782. show: true,
  783. fontSize: 12,
  784. color: '#333',
  785. },
  786. },
  787. yAxis: {
  788. type: 'value',
  789. show: true,
  790. axisLine: {
  791. show: true,
  792. lineStyle: {
  793. color: '#333',
  794. },
  795. },
  796. axisTick: {
  797. show: false,
  798. },
  799. axisLabel: {
  800. show: true,
  801. fontSize: 12,
  802. color: '#333',
  803. },
  804. splitLine: {
  805. show: true,
  806. lineStyle: {
  807. type: 'dashed',
  808. color: '#e0e0e0',
  809. },
  810. },
  811. },
  812. series: seriesData.map((item) => ({
  813. name: item.name,
  814. type: 'line',
  815. data: item.data,
  816. symbol: 'circle',
  817. symbolSize: 8,
  818. smooth: false,
  819. lineStyle: {
  820. width: 2,
  821. color: item.color || '#37A2DA',
  822. },
  823. itemStyle: {
  824. color: item.color || '#37A2DA',
  825. },
  826. emphasis: {
  827. focus: 'series',
  828. },
  829. })),
  830. }
  831. },
  832. // 获取构成图表基础配置(historyAnalysis.vue 使用)
  833. getBaseCompositionChartOption(data = []) {
  834. // 为每个数据项分配不同的颜色
  835. const dataWithColors = data.map((item, index) => ({
  836. ...item,
  837. itemStyle: {
  838. color: this.getColorByIndex(index),
  839. },
  840. }))
  841. return {
  842. tooltip: {
  843. show: true,
  844. trigger: 'item',
  845. formatter: (p) => {
  846. const surveysVos = Array.isArray(p.data?.surveysVos)
  847. ? p.data.surveysVos
  848. : []
  849. // 计算所有年度值的总和
  850. const totalSum = surveysVos.reduce((sum, sv) => {
  851. const hasValue = sv.value !== undefined && sv.value !== null
  852. const hasRvalue = sv.rvalue !== undefined && sv.rvalue !== null
  853. const mainValue = hasValue
  854. ? Number(sv.value) || 0
  855. : hasRvalue
  856. ? Number(sv.rvalue) || 0
  857. : 0
  858. return sum + mainValue
  859. }, 0)
  860. // 使用总和作为显示值,如果没有年度数据则使用原始值
  861. const displayValue = totalSum > 0 ? totalSum : p.value
  862. const header = `${p.name}: ${displayValue} (${p.percent}%)`
  863. // 只展示年度和值,避免重复的字段名与分隔符
  864. const details = surveysVos
  865. .map((sv) => {
  866. const yearLabel = sv.name || sv.year || '-'
  867. const hasValue = sv.value !== undefined && sv.value !== null
  868. const hasRvalue = sv.rvalue !== undefined && sv.rvalue !== null
  869. const mainValue = hasValue
  870. ? sv.value
  871. : hasRvalue
  872. ? sv.rvalue
  873. : '-'
  874. // 如果 value 和 rvalue 同时存在且不同,附加 rvalue 以显示所有值
  875. const extra =
  876. hasValue && hasRvalue && sv.value !== sv.rvalue
  877. ? ` (rvalue: ${sv.rvalue})`
  878. : ''
  879. return `${yearLabel}: ${mainValue}${extra}`
  880. })
  881. .filter(Boolean)
  882. return details.length > 0
  883. ? `${header}<br/>${details.join('<br/>')}`
  884. : header
  885. },
  886. },
  887. legend: {
  888. show: true,
  889. orient: 'vertical',
  890. right: 10,
  891. top: 'top',
  892. data: data.map((item) => item.name),
  893. formatter: function (name) {
  894. return name
  895. },
  896. itemGap: 8,
  897. textStyle: {
  898. fontSize: 11,
  899. color: '#333',
  900. },
  901. itemWidth: 14,
  902. itemHeight: 14,
  903. type: 'scroll',
  904. pageButtonPosition: 'end',
  905. pageButtonItemGap: 5,
  906. pageButtonGap: 5,
  907. pageTextStyle: {
  908. fontSize: 10,
  909. },
  910. },
  911. series: [
  912. {
  913. name: '成本构成',
  914. type: 'pie',
  915. radius: '60%',
  916. center: ['35%', '50%'],
  917. avoidLabelOverlap: true,
  918. itemStyle: {
  919. borderRadius: 0,
  920. borderColor: '#fff',
  921. borderWidth: 1,
  922. },
  923. label: {
  924. show: true,
  925. position: 'outside',
  926. formatter: '{b}',
  927. fontSize: 12,
  928. color: '#333',
  929. },
  930. labelLine: {
  931. show: true,
  932. length: 10,
  933. length2: 5,
  934. lineStyle: {
  935. width: 1,
  936. color: 'auto', // 线条跟随扇区颜色
  937. },
  938. },
  939. emphasis: {
  940. label: {
  941. show: true,
  942. fontSize: 12,
  943. fontWeight: 'normal',
  944. },
  945. itemStyle: {
  946. shadowBlur: 0,
  947. shadowOffsetX: 0,
  948. shadowColor: 'transparent',
  949. },
  950. labelLine: {
  951. lineStyle: {
  952. color: 'auto',
  953. },
  954. },
  955. },
  956. data: dataWithColors,
  957. },
  958. ],
  959. }
  960. },
  961. // 获取构成图表基础配置(industryAnalysis.vue 使用)
  962. getIndustryCompositionChartOption(data = []) {
  963. // 为每个数据项分配不同的颜色,按照顺序使用 getColorByIndex
  964. const dataWithColors = data.map((item, index) => ({
  965. ...item,
  966. itemStyle: {
  967. color: this.getColorByIndex(index),
  968. },
  969. }))
  970. return {
  971. tooltip: {
  972. show: true,
  973. trigger: 'item',
  974. formatter: (p) => {
  975. const surveysVos = Array.isArray(p.data?.surveysVos)
  976. ? p.data.surveysVos
  977. : []
  978. // 计算所有年度值的总和
  979. const totalSum = surveysVos.reduce((sum, sv) => {
  980. const hasValue = sv.value !== undefined && sv.value !== null
  981. const hasRvalue = sv.rvalue !== undefined && sv.rvalue !== null
  982. const mainValue = hasValue
  983. ? Number(sv.value) || 0
  984. : hasRvalue
  985. ? Number(sv.rvalue) || 0
  986. : 0
  987. return sum + mainValue
  988. }, 0)
  989. // 使用总和作为显示值,如果没有年度数据则使用原始值
  990. const displayValue = totalSum > 0 ? totalSum : p.value
  991. const header = `${p.name}: ${displayValue} (${p.percent}%)`
  992. // 只展示年度和值,避免重复的字段名与分隔符
  993. const details = surveysVos
  994. .map((sv) => {
  995. const yearLabel = sv.name || sv.year || '-'
  996. const hasValue = sv.value !== undefined && sv.value !== null
  997. const hasRvalue = sv.rvalue !== undefined && sv.rvalue !== null
  998. const mainValue = hasValue
  999. ? sv.value
  1000. : hasRvalue
  1001. ? sv.rvalue
  1002. : '-'
  1003. // 如果 value 和 rvalue 同时存在且不同,附加 rvalue 以显示所有值
  1004. const extra =
  1005. hasValue && hasRvalue && sv.value !== sv.rvalue
  1006. ? ` (rvalue: ${sv.rvalue})`
  1007. : ''
  1008. return `${yearLabel}: ${mainValue}${extra}`
  1009. })
  1010. .filter(Boolean)
  1011. return details.length > 0
  1012. ? `${header}<br/>${details.join('<br/>')}`
  1013. : header
  1014. },
  1015. },
  1016. legend: {
  1017. show: true,
  1018. orient: 'horizontal',
  1019. bottom: 10,
  1020. left: 'center',
  1021. data: data.map((item) => item.name),
  1022. icon: 'circle',
  1023. itemGap: 20,
  1024. textStyle: {
  1025. fontSize: 12,
  1026. color: '#333',
  1027. },
  1028. itemWidth: 10,
  1029. itemHeight: 10,
  1030. },
  1031. series: [
  1032. {
  1033. name: '成本构成',
  1034. type: 'pie',
  1035. radius: ['40%', '60%'], // 环形图
  1036. center: ['50%', '45%'],
  1037. avoidLabelOverlap: true,
  1038. silent: false,
  1039. itemStyle: {
  1040. borderRadius: 0,
  1041. borderColor: '#fff',
  1042. borderWidth: 1,
  1043. },
  1044. label: {
  1045. show: true,
  1046. position: 'outside',
  1047. formatter: '{b}: {d}%', // 名称: 百分比
  1048. fontSize: 12,
  1049. color: '#333',
  1050. },
  1051. labelLine: {
  1052. show: true,
  1053. length: 10,
  1054. length2: 5,
  1055. lineStyle: {
  1056. width: 1,
  1057. color: 'auto', // 线条跟随扇区颜色
  1058. },
  1059. },
  1060. emphasis: {
  1061. label: {
  1062. show: true,
  1063. fontSize: 12,
  1064. fontWeight: 'normal',
  1065. },
  1066. itemStyle: {
  1067. shadowBlur: 0,
  1068. shadowOffsetX: 0,
  1069. shadowColor: 'transparent',
  1070. },
  1071. labelLine: {
  1072. lineStyle: {
  1073. color: 'auto',
  1074. },
  1075. },
  1076. },
  1077. data: dataWithColors,
  1078. },
  1079. ],
  1080. }
  1081. },
  1082. // 轮播功能基础配置(仅historyAnalysis.vue使用)
  1083. initCarousel(chartId, totalPages = 1, onPageChange) {
  1084. // 若已有轮播,先清理旧定时器,避免重复计时导致数据持续跳动
  1085. if (this.carouselConfigs && this.carouselConfigs[chartId]) {
  1086. this.pauseCarousel(chartId)
  1087. }
  1088. const carouselConfig = {
  1089. totalPages,
  1090. currentPage: 0,
  1091. timer: null,
  1092. interval: 5000,
  1093. onPageChange,
  1094. }
  1095. // 保存轮播配置
  1096. this.carouselConfigs = this.carouselConfigs || {}
  1097. this.carouselConfigs[chartId] = carouselConfig
  1098. // 启动轮播
  1099. if (totalPages > 1) {
  1100. this.startCarousel(chartId)
  1101. } else {
  1102. // 单页不需要轮播,但仍确保跳转到第一页
  1103. this.goToCarouselPage(chartId, 0)
  1104. }
  1105. },
  1106. // 启动轮播
  1107. startCarousel(chartId) {
  1108. const config = this.carouselConfigs?.[chartId]
  1109. if (!config) return
  1110. // 清除现有定时器
  1111. this.pauseCarousel(chartId)
  1112. // 设置新定时器
  1113. config.timer = setInterval(() => {
  1114. config.currentPage = (config.currentPage + 1) % config.totalPages
  1115. this.goToCarouselPage(chartId, config.currentPage)
  1116. }, config.interval)
  1117. },
  1118. // 暂停轮播
  1119. pauseCarousel(chartId) {
  1120. const config = this.carouselConfigs?.[chartId]
  1121. if (config?.timer) {
  1122. clearInterval(config.timer)
  1123. config.timer = null
  1124. }
  1125. },
  1126. // 恢复轮播
  1127. resumeCarousel(chartId) {
  1128. this.startCarousel(chartId)
  1129. },
  1130. // 跳转到指定轮播页
  1131. goToCarouselPage(chartId, pageIndex) {
  1132. const config = this.carouselConfigs?.[chartId]
  1133. if (!config) return
  1134. config.currentPage = pageIndex
  1135. if (typeof config.onPageChange === 'function') {
  1136. config.onPageChange(pageIndex)
  1137. }
  1138. },
  1139. },
  1140. }
  1141. // mock 数据
  1142. export const mockStatisticsData = {
  1143. costSurveysList: [
  1144. {
  1145. id: '1997964654884069376',
  1146. name: '人数',
  1147. orderNum: '1',
  1148. rowid: 'ce2c23fd-0a60-4d98-a27e-d5344c7f2f7b',
  1149. number: '一',
  1150. rvalue: '2',
  1151. taskId: '1995015537499938816',
  1152. nianfen: null,
  1153. parentId: '-1',
  1154. surveysVos: [
  1155. { name: '2024', value: '30' },
  1156. { name: '2025', value: '35' },
  1157. ],
  1158. costSurveysVos: [
  1159. {
  1160. id: '1997964655014092800',
  1161. name: '1班',
  1162. orderNum: '2',
  1163. rowid: '0a075d08-1805-4ce0-a5e1-c4f264db76bd',
  1164. number: '1',
  1165. rvalue: '4',
  1166. taskId: '1995015537499938816',
  1167. nianfen: null,
  1168. parentId: 'ce2c23fd-0a60-4d98-a27e-d5344c7f2f7b',
  1169. surveysVos: [
  1170. { name: '2024', value: '12' },
  1171. { name: '2025', value: '15' },
  1172. ],
  1173. costSurveysVos: null,
  1174. },
  1175. {
  1176. id: '1997964655131533312',
  1177. name: '2班',
  1178. orderNum: '3',
  1179. rowid: 'ab3efd32-9a10-4355-8d6e-19d99ebdbe4a',
  1180. number: '2',
  1181. rvalue: '7',
  1182. taskId: '1995015537499938816',
  1183. nianfen: null,
  1184. parentId: 'ce2c23fd-0a60-4d98-a27e-d5344c7f2f7b',
  1185. surveysVos: [
  1186. { name: '2024', value: '18' },
  1187. { name: '2025', value: '20' },
  1188. ],
  1189. costSurveysVos: null,
  1190. },
  1191. ],
  1192. },
  1193. {
  1194. id: '1997964655244779520',
  1195. name: '1班费用',
  1196. orderNum: '4',
  1197. rowid: '6a81c1d3-ea8b-48d5-bd11-27e5b150cea1',
  1198. number: '二',
  1199. rvalue: '53',
  1200. taskId: '1995015537499938816',
  1201. nianfen: null,
  1202. parentId: '-1',
  1203. surveysVos: [
  1204. { name: '2024', value: '120' },
  1205. { name: '2025', value: '140' },
  1206. ],
  1207. costSurveysVos: [
  1208. {
  1209. id: '1997964655387385856',
  1210. name: '押金',
  1211. orderNum: '5',
  1212. rowid: 'bf17169e-9d03-49f0-b7c7-7502879a935b',
  1213. number: '1',
  1214. rvalue: '124',
  1215. taskId: '1995015537499938816',
  1216. nianfen: null,
  1217. parentId: '6a81c1d3-ea8b-48d5-bd11-27e5b150cea1',
  1218. surveysVos: [
  1219. { name: '2024', value: '50' },
  1220. { name: '2025', value: '60' },
  1221. ],
  1222. costSurveysVos: null,
  1223. },
  1224. {
  1225. id: '1997964655559352320',
  1226. name: '缴纳',
  1227. orderNum: '6',
  1228. rowid: 'e130564b-dc73-4fa9-a004-01c3af418021',
  1229. number: '2',
  1230. rvalue: '146',
  1231. taskId: '1995015537499938816',
  1232. nianfen: null,
  1233. parentId: '6a81c1d3-ea8b-48d5-bd11-27e5b150cea1',
  1234. surveysVos: [
  1235. { name: '2024', value: '70' },
  1236. { name: '2025', value: '80' },
  1237. ],
  1238. costSurveysVos: null,
  1239. },
  1240. ],
  1241. },
  1242. {
  1243. id: '1997964655680987136',
  1244. name: '班级乘法',
  1245. orderNum: '7',
  1246. rowid: 'aec785e0-86fa-436e-be7b-bb9d2d9eaf32',
  1247. number: '三',
  1248. rvalue: '246',
  1249. taskId: '1995015537499938816',
  1250. nianfen: null,
  1251. parentId: '-1',
  1252. surveysVos: [
  1253. { name: '2024', value: '300' },
  1254. { name: '2025', value: '320' },
  1255. ],
  1256. costSurveysVos: [],
  1257. },
  1258. {
  1259. id: '1997964655794233344',
  1260. name: '人均费用',
  1261. orderNum: '8',
  1262. rowid: '857ba697-aaf0-4e26-97a6-4bd97d8ff6f7',
  1263. number: '四',
  1264. rvalue: '246',
  1265. taskId: '1995015537499938816',
  1266. nianfen: null,
  1267. parentId: '-1',
  1268. surveysVos: [
  1269. { name: '2024', value: '15' },
  1270. { name: '2025', value: '16' },
  1271. ],
  1272. costSurveysVos: [],
  1273. },
  1274. {
  1275. id: '1997964655903279104',
  1276. name: '材料成本',
  1277. orderNum: '9',
  1278. rowid: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  1279. number: '五',
  1280. rvalue: '580',
  1281. taskId: '1995015537499938816',
  1282. nianfen: null,
  1283. parentId: '-1',
  1284. surveysVos: [
  1285. { name: '2022', value: '450' },
  1286. { name: '2023', value: '520' },
  1287. { name: '2024', value: '580' },
  1288. { name: '2025', value: '620' },
  1289. ],
  1290. costSurveysVos: [
  1291. {
  1292. id: '1997964656012324864',
  1293. name: '原材料',
  1294. orderNum: '10',
  1295. rowid: 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d',
  1296. number: '1',
  1297. rvalue: '320',
  1298. taskId: '1995015537499938816',
  1299. nianfen: null,
  1300. parentId: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  1301. surveysVos: [
  1302. { name: '2022', value: '250' },
  1303. { name: '2023', value: '290' },
  1304. { name: '2024', value: '320' },
  1305. { name: '2025', value: '350' },
  1306. ],
  1307. costSurveysVos: null,
  1308. },
  1309. {
  1310. id: '1997964656121370624',
  1311. name: '辅助材料',
  1312. orderNum: '11',
  1313. rowid: 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e',
  1314. number: '2',
  1315. rvalue: '180',
  1316. taskId: '1995015537499938816',
  1317. nianfen: null,
  1318. parentId: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  1319. surveysVos: [
  1320. { name: '2022', value: '140' },
  1321. { name: '2023', value: '160' },
  1322. { name: '2024', value: '180' },
  1323. { name: '2025', value: '200' },
  1324. ],
  1325. costSurveysVos: null,
  1326. },
  1327. {
  1328. id: '1997964656230416384',
  1329. name: '包装材料',
  1330. orderNum: '12',
  1331. rowid: 'c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f',
  1332. number: '3',
  1333. rvalue: '80',
  1334. taskId: '1995015537499938816',
  1335. nianfen: null,
  1336. parentId: 'f3a8b2c1-5d6e-4f7a-8b9c-0d1e2f3a4b5c',
  1337. surveysVos: [
  1338. { name: '2022', value: '60' },
  1339. { name: '2023', value: '70' },
  1340. { name: '2024', value: '80' },
  1341. { name: '2025', value: '70' },
  1342. ],
  1343. costSurveysVos: null,
  1344. },
  1345. ],
  1346. },
  1347. {
  1348. id: '1997964656339462144',
  1349. name: '人工成本',
  1350. orderNum: '13',
  1351. rowid: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  1352. number: '六',
  1353. rvalue: '850',
  1354. taskId: '1995015537499938816',
  1355. nianfen: null,
  1356. parentId: '-1',
  1357. surveysVos: [
  1358. { name: '2022', value: '680' },
  1359. { name: '2023', value: '750' },
  1360. { name: '2024', value: '850' },
  1361. { name: '2025', value: '920' },
  1362. ],
  1363. costSurveysVos: [
  1364. {
  1365. id: '1997964656448507904',
  1366. name: '基本工资',
  1367. orderNum: '14',
  1368. rowid: 'e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b',
  1369. number: '1',
  1370. rvalue: '520',
  1371. taskId: '1995015537499938816',
  1372. nianfen: null,
  1373. parentId: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  1374. surveysVos: [
  1375. { name: '2022', value: '420' },
  1376. { name: '2023', value: '460' },
  1377. { name: '2024', value: '520' },
  1378. { name: '2025', value: '580' },
  1379. ],
  1380. costSurveysVos: null,
  1381. },
  1382. {
  1383. id: '1997964656557553664',
  1384. name: '绩效奖金',
  1385. orderNum: '15',
  1386. rowid: 'f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c',
  1387. number: '2',
  1388. rvalue: '200',
  1389. taskId: '1995015537499938816',
  1390. nianfen: null,
  1391. parentId: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  1392. surveysVos: [
  1393. { name: '2022', value: '150' },
  1394. { name: '2023', value: '180' },
  1395. { name: '2024', value: '200' },
  1396. { name: '2025', value: '220' },
  1397. ],
  1398. costSurveysVos: null,
  1399. },
  1400. {
  1401. id: '1997964656666599424',
  1402. name: '社保公积金',
  1403. orderNum: '16',
  1404. rowid: 'a7b8c9d0-e1f2-4a3b-4c5d-6e7f8a9b0c1d',
  1405. number: '3',
  1406. rvalue: '130',
  1407. taskId: '1995015537499938816',
  1408. nianfen: null,
  1409. parentId: 'd4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a',
  1410. surveysVos: [
  1411. { name: '2022', value: '110' },
  1412. { name: '2023', value: '120' },
  1413. { name: '2024', value: '130' },
  1414. { name: '2025', value: '120' },
  1415. ],
  1416. costSurveysVos: null,
  1417. },
  1418. ],
  1419. },
  1420. {
  1421. id: '1997964656775645184',
  1422. name: '设备成本',
  1423. orderNum: '17',
  1424. rowid: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  1425. number: '七',
  1426. rvalue: '420',
  1427. taskId: '1995015537499938816',
  1428. nianfen: null,
  1429. parentId: '-1',
  1430. surveysVos: [
  1431. { name: '2022', value: '380' },
  1432. { name: '2023', value: '400' },
  1433. { name: '2024', value: '420' },
  1434. { name: '2025', value: '450' },
  1435. ],
  1436. costSurveysVos: [
  1437. {
  1438. id: '1997964656884690944',
  1439. name: '设备折旧',
  1440. orderNum: '18',
  1441. rowid: 'c9d0e1f2-a3b4-4c5d-6e7f-8a9b0c1d2e3f',
  1442. number: '1',
  1443. rvalue: '280',
  1444. taskId: '1995015537499938816',
  1445. nianfen: null,
  1446. parentId: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  1447. surveysVos: [
  1448. { name: '2022', value: '260' },
  1449. { name: '2023', value: '270' },
  1450. { name: '2024', value: '280' },
  1451. { name: '2025', value: '300' },
  1452. ],
  1453. costSurveysVos: null,
  1454. },
  1455. {
  1456. id: '1997964656993736704',
  1457. name: '设备维护',
  1458. orderNum: '19',
  1459. rowid: 'd0e1f2a3-b4c5-4d6e-7f8a-9b0c-1d2e3f4a5b4a',
  1460. number: '2',
  1461. rvalue: '90',
  1462. taskId: '1995015537499938816',
  1463. nianfen: null,
  1464. parentId: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  1465. surveysVos: [
  1466. { name: '2022', value: '80' },
  1467. { name: '2023', value: '85' },
  1468. { name: '2024', value: '90' },
  1469. { name: '2025', value: '100' },
  1470. ],
  1471. costSurveysVos: null,
  1472. },
  1473. {
  1474. id: '1997964657102782464',
  1475. name: '设备租赁',
  1476. orderNum: '20',
  1477. rowid: 'e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b',
  1478. number: '3',
  1479. rvalue: '50',
  1480. taskId: '1995015537499938816',
  1481. nianfen: null,
  1482. parentId: 'b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e',
  1483. surveysVos: [
  1484. { name: '2022', value: '40' },
  1485. { name: '2023', value: '45' },
  1486. { name: '2024', value: '50' },
  1487. { name: '2025', value: '50' },
  1488. ],
  1489. costSurveysVos: null,
  1490. },
  1491. ],
  1492. },
  1493. {
  1494. id: '1997964657211828224',
  1495. name: '管理费用',
  1496. orderNum: '21',
  1497. rowid: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1498. number: '八',
  1499. rvalue: '360',
  1500. taskId: '1995015537499938816',
  1501. nianfen: null,
  1502. parentId: '-1',
  1503. surveysVos: [
  1504. { name: '2022', value: '320' },
  1505. { name: '2023', value: '340' },
  1506. { name: '2024', value: '360' },
  1507. { name: '2025', value: '380' },
  1508. ],
  1509. costSurveysVos: [
  1510. {
  1511. id: '1997964657320873984',
  1512. name: '办公费用',
  1513. orderNum: '22',
  1514. rowid: 'a3b4c5d6-e7f8-4a9b-0c1d-2e3f4a5b6c7d',
  1515. number: '1',
  1516. rvalue: '150',
  1517. taskId: '1995015537499938816',
  1518. nianfen: null,
  1519. parentId: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1520. surveysVos: [
  1521. { name: '2022', value: '130' },
  1522. { name: '2023', value: '140' },
  1523. { name: '2024', value: '150' },
  1524. { name: '2025', value: '160' },
  1525. ],
  1526. costSurveysVos: null,
  1527. },
  1528. {
  1529. id: '1997964657429919744',
  1530. name: '差旅费用',
  1531. orderNum: '23',
  1532. rowid: 'b4c5d6e7-f8a9-4b0c-1d2e-3f4a5b6c7d8e',
  1533. number: '2',
  1534. rvalue: '120',
  1535. taskId: '1995015537499938816',
  1536. nianfen: null,
  1537. parentId: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1538. surveysVos: [
  1539. { name: '2022', value: '110' },
  1540. { name: '2023', value: '115' },
  1541. { name: '2024', value: '120' },
  1542. { name: '2025', value: '130' },
  1543. ],
  1544. costSurveysVos: null,
  1545. },
  1546. {
  1547. id: '1997964657538965504',
  1548. name: '培训费用',
  1549. orderNum: '24',
  1550. rowid: 'c5d6e7f8-a9b0-4c1d-2e3f-4a5b6c7d8e9f',
  1551. number: '3',
  1552. rvalue: '90',
  1553. taskId: '1995015537499938816',
  1554. nianfen: null,
  1555. parentId: 'f2a3b4c5-d6e7-4f8a-9b0c-1d2e3f4a5b6c',
  1556. surveysVos: [
  1557. { name: '2022', value: '80' },
  1558. { name: '2023', value: '85' },
  1559. { name: '2024', value: '90' },
  1560. { name: '2025', value: '90' },
  1561. ],
  1562. costSurveysVos: null,
  1563. },
  1564. ],
  1565. },
  1566. {
  1567. id: '1997964657648011264',
  1568. name: '能源成本',
  1569. orderNum: '25',
  1570. rowid: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1571. number: '九',
  1572. rvalue: '280',
  1573. taskId: '1995015537499938816',
  1574. nianfen: null,
  1575. parentId: '-1',
  1576. surveysVos: [
  1577. { name: '2022', value: '250' },
  1578. { name: '2023', value: '270' },
  1579. { name: '2024', value: '280' },
  1580. { name: '2025', value: '290' },
  1581. ],
  1582. costSurveysVos: [
  1583. {
  1584. id: '1997964657757057024',
  1585. name: '电费',
  1586. orderNum: '26',
  1587. rowid: 'e7f8a9b0-c1d2-4e3f-4a5b-6c7d8e9f0a1b',
  1588. number: '1',
  1589. rvalue: '180',
  1590. taskId: '1995015537499938816',
  1591. nianfen: null,
  1592. parentId: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1593. surveysVos: [
  1594. { name: '2022', value: '160' },
  1595. { name: '2023', value: '170' },
  1596. { name: '2024', value: '180' },
  1597. { name: '2025', value: '190' },
  1598. ],
  1599. costSurveysVos: null,
  1600. },
  1601. {
  1602. id: '1997964657866102784',
  1603. name: '水费',
  1604. orderNum: '27',
  1605. rowid: 'f8a9b0c1-d2e3-4f4a-5b6c-7d8e9f0a1b2c',
  1606. number: '2',
  1607. rvalue: '60',
  1608. taskId: '1995015537499938816',
  1609. nianfen: null,
  1610. parentId: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1611. surveysVos: [
  1612. { name: '2022', value: '55' },
  1613. { name: '2023', value: '58' },
  1614. { name: '2024', value: '60' },
  1615. { name: '2025', value: '62' },
  1616. ],
  1617. costSurveysVos: null,
  1618. },
  1619. {
  1620. id: '1997964657975148544',
  1621. name: '燃气费',
  1622. orderNum: '28',
  1623. rowid: 'a9b0c1d2-e3f4-4a5b-6c7d-8e9f0a1b2c3d',
  1624. number: '3',
  1625. rvalue: '40',
  1626. taskId: '1995015537499938816',
  1627. nianfen: null,
  1628. parentId: 'd6e7f8a9-b0c1-4d2e-3f4a-5b6c7d8e9f0a',
  1629. surveysVos: [
  1630. { name: '2022', value: '35' },
  1631. { name: '2023', value: '38' },
  1632. { name: '2024', value: '40' },
  1633. { name: '2025', value: '38' },
  1634. ],
  1635. costSurveysVos: null,
  1636. },
  1637. ],
  1638. },
  1639. ],
  1640. }