|
@@ -1,8 +1,8 @@
|
|
|
import * as echarts from 'echarts'
|
|
import * as echarts from 'echarts'
|
|
|
import { getAllUnitList } from '@/api/auditEntityManage.js'
|
|
import { getAllUnitList } from '@/api/auditEntityManage.js'
|
|
|
-import { getAuditTaskList } from '@/api/auditInitiation.js'
|
|
|
|
|
|
|
+// import { getAuditTaskList } from '@/api/auditInitiation.js'
|
|
|
// , getAuditTaskList
|
|
// , getAuditTaskList
|
|
|
-import { analyzeStatistics } from '@/api/comprehensive'
|
|
|
|
|
|
|
+import { analyzeStatistics, getAuditTaskList } from '@/api/comprehensive'
|
|
|
// 综合分析页面的通用mixin
|
|
// 综合分析页面的通用mixin
|
|
|
export const comprehensiveMixin = {
|
|
export const comprehensiveMixin = {
|
|
|
data() {
|
|
data() {
|
|
@@ -54,8 +54,15 @@ export const comprehensiveMixin = {
|
|
|
// 将 costSurveysList 组装为树结构(用于左侧勾选)
|
|
// 将 costSurveysList 组装为树结构(用于左侧勾选)
|
|
|
buildIndicatorTree(costSurveysList = []) {
|
|
buildIndicatorTree(costSurveysList = []) {
|
|
|
const processNode = (item) => {
|
|
const processNode = (item) => {
|
|
|
|
|
+ const nodeId =
|
|
|
|
|
+ item.uniqueId ||
|
|
|
|
|
+ item.id ||
|
|
|
|
|
+ item.rowid ||
|
|
|
|
|
+ item.rowId ||
|
|
|
|
|
+ `${item.number || item.name || ''}`
|
|
|
const node = {
|
|
const node = {
|
|
|
...item,
|
|
...item,
|
|
|
|
|
+ id: nodeId,
|
|
|
label: this.buildIndicatorLabel(item),
|
|
label: this.buildIndicatorLabel(item),
|
|
|
children: [],
|
|
children: [],
|
|
|
}
|
|
}
|
|
@@ -89,6 +96,88 @@ export const comprehensiveMixin = {
|
|
|
return isRoot ? `${num}、${nm}` : `${num}.${nm}`
|
|
return isRoot ? `${num}、${nm}` : `${num}.${nm}`
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
|
|
+ // 聚合某节点及其后代的年度与汇总数据
|
|
|
|
|
+ aggregateNodeData(node) {
|
|
|
|
|
+ const yearMap = new Map()
|
|
|
|
|
+ let surveysSum = 0
|
|
|
|
|
+
|
|
|
|
|
+ const dfs = (n) => {
|
|
|
|
|
+ if (!n) return
|
|
|
|
|
+ // 累加叶子上的 surveysVos(年度值通常在这里)
|
|
|
|
|
+ if (Array.isArray(n.surveysVos) && n.surveysVos.length) {
|
|
|
|
|
+ n.surveysVos.forEach((sv) => {
|
|
|
|
|
+ const yearKey = String(sv.name || sv.year || '').trim()
|
|
|
|
|
+ const val = Number(sv.value) || 0
|
|
|
|
|
+ if (yearKey) {
|
|
|
|
|
+ yearMap.set(yearKey, (yearMap.get(yearKey) || 0) + val)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ surveysSum += n.surveysVos.reduce(
|
|
|
|
|
+ (sum, sv) => sum + (Number(sv.value) || 0),
|
|
|
|
|
+ 0
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ // 若有子节点,继续向下汇总
|
|
|
|
|
+ if (Array.isArray(n.costSurveysVos) && n.costSurveysVos.length) {
|
|
|
|
|
+ n.costSurveysVos.forEach((child) => {
|
|
|
|
|
+ // 如果子项有surveysVos,先处理这些数据
|
|
|
|
|
+ if (Array.isArray(child.surveysVos) && child.surveysVos.length) {
|
|
|
|
|
+ child.surveysVos.forEach((sv) => {
|
|
|
|
|
+ const yearKey = String(sv.name || sv.year || '').trim()
|
|
|
|
|
+ const val = Number(sv.value) || 0
|
|
|
|
|
+ if (yearKey) {
|
|
|
|
|
+ yearMap.set(yearKey, (yearMap.get(yearKey) || 0) + val)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ surveysSum += child.surveysVos.reduce(
|
|
|
|
|
+ (sum, sv) => sum + (Number(sv.value) || 0),
|
|
|
|
|
+ 0
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果子项仍有子节点,递归;否则视为叶子年度数据
|
|
|
|
|
+ if (
|
|
|
|
|
+ Array.isArray(child.costSurveysVos) &&
|
|
|
|
|
+ child.costSurveysVos.length
|
|
|
|
|
+ ) {
|
|
|
|
|
+ dfs(child)
|
|
|
|
|
+ } else if (
|
|
|
|
|
+ !Array.isArray(child.surveysVos) ||
|
|
|
|
|
+ child.surveysVos.length === 0
|
|
|
|
|
+ ) {
|
|
|
|
|
+ // 只有当没有surveysVos时,才使用name/year和value
|
|
|
|
|
+ const yearKey = String(child.name || child.year || '').trim()
|
|
|
|
|
+ const val = Number(child.value) || 0
|
|
|
|
|
+ if (yearKey) {
|
|
|
|
|
+ yearMap.set(yearKey, (yearMap.get(yearKey) || 0) + val)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ // 汇总 surveysVos(构成图用)
|
|
|
|
|
+ if (Array.isArray(n.surveysVos) && n.surveysVos.length) {
|
|
|
|
|
+ surveysSum += n.surveysVos.reduce(
|
|
|
|
|
+ (sum, sv) => sum + (Number(sv.value) || 0),
|
|
|
|
|
+ 0
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ dfs(node)
|
|
|
|
|
+
|
|
|
|
|
+ // 将年度数据转换为 surveysVos 格式,保留完整的年度信息
|
|
|
|
|
+ const annualSurveysVos = Array.from(yearMap.entries()).map(
|
|
|
|
|
+ ([name, value]) => ({
|
|
|
|
|
+ name,
|
|
|
|
|
+ value,
|
|
|
|
|
+ })
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ costSurveysVos: annualSurveysVos,
|
|
|
|
|
+ surveysVos: annualSurveysVos, // 返回完整的年度数据而不仅仅是合计值
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
// 获取树的叶子 id 列表
|
|
// 获取树的叶子 id 列表
|
|
|
getLeafIds(tree = []) {
|
|
getLeafIds(tree = []) {
|
|
|
const ids = []
|
|
const ids = []
|
|
@@ -108,9 +197,16 @@ export const comprehensiveMixin = {
|
|
|
const ids = []
|
|
const ids = []
|
|
|
const items = []
|
|
const items = []
|
|
|
const dfs = (node) => {
|
|
const dfs = (node) => {
|
|
|
- if (node.parentId == '-1') {
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ node &&
|
|
|
|
|
+ (node.parentId === undefined ||
|
|
|
|
|
+ node.parentId === null ||
|
|
|
|
|
+ String(node.parentId) === '-1')
|
|
|
|
|
+ ) {
|
|
|
ids.push(node.id)
|
|
ids.push(node.id)
|
|
|
items.push(node)
|
|
items.push(node)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (node && node.children && node.children.length > 0) {
|
|
|
node.children.forEach(dfs)
|
|
node.children.forEach(dfs)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -130,7 +226,9 @@ export const comprehensiveMixin = {
|
|
|
if (this.isDestroyed) {
|
|
if (this.isDestroyed) {
|
|
|
return []
|
|
return []
|
|
|
}
|
|
}
|
|
|
- if (res && res.value) return res.value
|
|
|
|
|
|
|
+ if (res && res.value) {
|
|
|
|
|
+ return this.attachUniqueId(res.value)
|
|
|
|
|
+ }
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
// 如果是请求中止错误,静默处理
|
|
// 如果是请求中止错误,静默处理
|
|
|
if (e && e.message && e.message.includes('aborted')) {
|
|
if (e && e.message && e.message.includes('aborted')) {
|
|
@@ -144,6 +242,51 @@ export const comprehensiveMixin = {
|
|
|
return []
|
|
return []
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
|
|
+ // 为接口返回的列表生成唯一标识 uniqueId,兼容数组或 { costSurveysList } 结构
|
|
|
|
|
+ attachUniqueId(res) {
|
|
|
|
|
+ const markList = (list = [], parentKey = 'root', seen = new Set()) => {
|
|
|
|
|
+ return (list || []).map((item, idx) => {
|
|
|
|
|
+ // 使用多种可能的ID字段作为基础,但不修改原始ID
|
|
|
|
|
+ const base =
|
|
|
|
|
+ item.id ||
|
|
|
|
|
+ item.rowid ||
|
|
|
|
|
+ item.rowId ||
|
|
|
|
|
+ `${parentKey}-${item.number || item.name || idx}`
|
|
|
|
|
+ let key = base
|
|
|
|
|
+ let suffix = 1
|
|
|
|
|
+ while (seen.has(key)) {
|
|
|
|
|
+ key = `${base}-${suffix++}`
|
|
|
|
|
+ }
|
|
|
|
|
+ seen.add(key)
|
|
|
|
|
+
|
|
|
|
|
+ // 创建新对象并添加uniqueId,不修改原始item的id
|
|
|
|
|
+ const cloned = {
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ uniqueId: key,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ Array.isArray(item.costSurveysVos) &&
|
|
|
|
|
+ item.costSurveysVos.length
|
|
|
|
|
+ ) {
|
|
|
|
|
+ cloned.costSurveysVos = markList(item.costSurveysVos, key, seen)
|
|
|
|
|
+ }
|
|
|
|
|
+ return cloned
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Array.isArray(res)) {
|
|
|
|
|
+ return markList(res)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (res && Array.isArray(res.costSurveysList)) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...res,
|
|
|
|
|
+ costSurveysList: markList(res.costSurveysList),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return res
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
getOptions() {
|
|
getOptions() {
|
|
|
// 获取监审任务列表
|
|
// 获取监审任务列表
|
|
|
getAuditTaskList()
|
|
getAuditTaskList()
|
|
@@ -178,33 +321,41 @@ export const comprehensiveMixin = {
|
|
|
return []
|
|
return []
|
|
|
}
|
|
}
|
|
|
const selectedIdSet = new Set(selectedIds)
|
|
const selectedIdSet = new Set(selectedIds)
|
|
|
|
|
+ const isSelected = (node) => {
|
|
|
|
|
+ const keys = [
|
|
|
|
|
+ node?.id,
|
|
|
|
|
+ node?.uniqueId,
|
|
|
|
|
+ node?.rowid,
|
|
|
|
|
+ node?.rowId,
|
|
|
|
|
+ node?.number,
|
|
|
|
|
+ node?.name,
|
|
|
|
|
+ ].filter(Boolean)
|
|
|
|
|
+ return keys.some((k) => selectedIdSet.has(k))
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 递归过滤节点
|
|
|
|
|
const filterNode = (node) => {
|
|
const filterNode = (node) => {
|
|
|
if (!node) return null
|
|
if (!node) return null
|
|
|
|
|
|
|
|
- // 如果当前节点被选中,保留它及其所有子节点
|
|
|
|
|
- if (selectedIdSet.has(node.id)) {
|
|
|
|
|
|
|
+ // 当前节点命中选中,直接保留,子节点也带上(用于饼图父级展示子项/趋势展示自身)
|
|
|
|
|
+ if (isSelected(node)) {
|
|
|
const filteredNode = { ...node }
|
|
const filteredNode = { ...node }
|
|
|
if (
|
|
if (
|
|
|
Array.isArray(node.costSurveysVos) &&
|
|
Array.isArray(node.costSurveysVos) &&
|
|
|
node.costSurveysVos.length
|
|
node.costSurveysVos.length
|
|
|
) {
|
|
) {
|
|
|
- // 如果父节点被选中,保留所有子节点
|
|
|
|
|
- filteredNode.costSurveysVos = node.costSurveysVos.map((child) => ({
|
|
|
|
|
- ...child,
|
|
|
|
|
- }))
|
|
|
|
|
|
|
+ filteredNode.costSurveysVos = node.costSurveysVos
|
|
|
|
|
+ .map(filterNode)
|
|
|
|
|
+ .filter(Boolean)
|
|
|
}
|
|
}
|
|
|
return filteredNode
|
|
return filteredNode
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 如果当前节点未被选中,检查是否有子节点被选中
|
|
|
|
|
|
|
+ // 未命中,检查子节点
|
|
|
if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
|
|
if (Array.isArray(node.costSurveysVos) && node.costSurveysVos.length) {
|
|
|
const filteredChildren = node.costSurveysVos
|
|
const filteredChildren = node.costSurveysVos
|
|
|
.map(filterNode)
|
|
.map(filterNode)
|
|
|
.filter(Boolean)
|
|
.filter(Boolean)
|
|
|
if (filteredChildren.length > 0) {
|
|
if (filteredChildren.length > 0) {
|
|
|
- // 如果有子节点被选中,保留父节点和选中的子节点
|
|
|
|
|
return {
|
|
return {
|
|
|
...node,
|
|
...node,
|
|
|
costSurveysVos: filteredChildren,
|
|
costSurveysVos: filteredChildren,
|
|
@@ -229,10 +380,18 @@ export const comprehensiveMixin = {
|
|
|
node.surveysVos.forEach((sv) => {
|
|
node.surveysVos.forEach((sv) => {
|
|
|
const yearKey = String(sv.name || sv.year || '').trim()
|
|
const yearKey = String(sv.name || sv.year || '').trim()
|
|
|
if (!yearKey) return
|
|
if (!yearKey) return
|
|
|
|
|
+ const indicatorId =
|
|
|
|
|
+ node.uniqueId || node.id || node.rowid || node.rowId || node.name
|
|
|
trendArr.push({
|
|
trendArr.push({
|
|
|
- indicatorName: node.name,
|
|
|
|
|
|
|
+ indicatorId: indicatorId,
|
|
|
|
|
+ indicatorName: node.label || node.name,
|
|
|
year: yearKey,
|
|
year: yearKey,
|
|
|
- value: Number(sv.value) || 0,
|
|
|
|
|
|
|
+ value:
|
|
|
|
|
+ Number(
|
|
|
|
|
+ sv.value !== undefined && sv.value !== null
|
|
|
|
|
+ ? sv.value
|
|
|
|
|
+ : sv.rvalue
|
|
|
|
|
+ ) || 0,
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
@@ -247,10 +406,17 @@ export const comprehensiveMixin = {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// 将 costSurveysList 汇总为构成数据 [{name, value}](显示所有surveysVos)
|
|
// 将 costSurveysList 汇总为构成数据 [{name, value}](显示所有surveysVos)
|
|
|
- transformCompositionFromCostList(costSurveysList = []) {
|
|
|
|
|
|
|
+ transformCompositionFromCostList(
|
|
|
|
|
+ costSurveysList = [],
|
|
|
|
|
+ selectedItemsOrder = []
|
|
|
|
|
+ ) {
|
|
|
const result = []
|
|
const result = []
|
|
|
const dfs = (node) => {
|
|
const dfs = (node) => {
|
|
|
if (!node) return
|
|
if (!node) return
|
|
|
|
|
+ // 获取节点的唯一标识
|
|
|
|
|
+ const indicatorId =
|
|
|
|
|
+ node.uniqueId || node.id || node.rowid || node.rowId || node.name
|
|
|
|
|
+
|
|
|
if (
|
|
if (
|
|
|
Array.isArray(node.costSurveysVos) &&
|
|
Array.isArray(node.costSurveysVos) &&
|
|
|
node.costSurveysVos.length > 0
|
|
node.costSurveysVos.length > 0
|
|
@@ -261,28 +427,86 @@ export const comprehensiveMixin = {
|
|
|
: []
|
|
: []
|
|
|
result.push({
|
|
result.push({
|
|
|
name: sv.name,
|
|
name: sv.name,
|
|
|
|
|
+ indicatorId: indicatorId, // 添加唯一标识
|
|
|
value:
|
|
value:
|
|
|
- surveysList.reduce(
|
|
|
|
|
- (sum, item) => sum + Number(item.value) || 0,
|
|
|
|
|
- 0
|
|
|
|
|
- ) || 0,
|
|
|
|
|
|
|
+ surveysList.reduce((sum, item) => {
|
|
|
|
|
+ const v =
|
|
|
|
|
+ item.value !== undefined && item.value !== null
|
|
|
|
|
+ ? item.value
|
|
|
|
|
+ : item.rvalue
|
|
|
|
|
+ return sum + (Number(v) || 0)
|
|
|
|
|
+ }, 0) || 0,
|
|
|
surveysVos: surveysList,
|
|
surveysVos: surveysList,
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
} else {
|
|
} else {
|
|
|
result.push({
|
|
result.push({
|
|
|
name: node.name,
|
|
name: node.name,
|
|
|
- value: Number(node.rvalue) || 0,
|
|
|
|
|
|
|
+ indicatorId: indicatorId, // 添加唯一标识
|
|
|
|
|
+ value:
|
|
|
|
|
+ Number(
|
|
|
|
|
+ node.value !== undefined && node.value !== null
|
|
|
|
|
+ ? node.value
|
|
|
|
|
+ : node.rvalue
|
|
|
|
|
+ ) || 0,
|
|
|
surveysVos: node.surveysVos || [],
|
|
surveysVos: node.surveysVos || [],
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
costSurveysList.forEach(dfs)
|
|
costSurveysList.forEach(dfs)
|
|
|
- return result
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 统计相同名称的数量,用于区分显示
|
|
|
|
|
+ const nameCountMap = {}
|
|
|
|
|
+ result.forEach((item) => {
|
|
|
|
|
+ nameCountMap[item.name] = (nameCountMap[item.name] || 0) + 1
|
|
|
|
|
+ })
|
|
|
|
|
+ const nameIndexMap = {}
|
|
|
|
|
+
|
|
|
|
|
+ // 按照选中项顺序来排序结果
|
|
|
|
|
+ let orderedResult = result
|
|
|
|
|
+ if (selectedItemsOrder && selectedItemsOrder.length > 0) {
|
|
|
|
|
+ const indicatorIdToItem = {}
|
|
|
|
|
+ result.forEach((item) => {
|
|
|
|
|
+ indicatorIdToItem[item.indicatorId] = item
|
|
|
|
|
+ })
|
|
|
|
|
+ // 先添加按顺序的项
|
|
|
|
|
+ orderedResult = selectedItemsOrder
|
|
|
|
|
+ .map((item) => {
|
|
|
|
|
+ const key =
|
|
|
|
|
+ item.uniqueId || item.id || item.rowid || item.rowId || item.name
|
|
|
|
|
+ return indicatorIdToItem[key]
|
|
|
|
|
+ })
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ // 再添加不在选中项顺序中的项(以防有遗漏)
|
|
|
|
|
+ result.forEach((item) => {
|
|
|
|
|
+ if (!orderedResult.find((r) => r.indicatorId === item.indicatorId)) {
|
|
|
|
|
+ orderedResult.push(item)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 为相同名称的项添加序号
|
|
|
|
|
+ return orderedResult.map((item) => {
|
|
|
|
|
+ const nameCount = nameCountMap[item.name] || 1
|
|
|
|
|
+ let displayName = item.name
|
|
|
|
|
+ if (nameCount > 1) {
|
|
|
|
|
+ if (!nameIndexMap[item.name]) {
|
|
|
|
|
+ nameIndexMap[item.name] = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ nameIndexMap[item.name]++
|
|
|
|
|
+ displayName = `${item.name}(${nameIndexMap[item.name]})`
|
|
|
|
|
+ }
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ name: displayName, // 使用带序号的名字
|
|
|
|
|
+ originalName: item.name, // 保留原始名称
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// 构建趋势图数据结构(xAxis + series)
|
|
// 构建趋势图数据结构(xAxis + series)
|
|
|
- buildTrendSeries(trendArr = []) {
|
|
|
|
|
|
|
+ // selectedItemsOrder: 可选的选中项顺序数组,用于保持图例顺序与选中项顺序一致
|
|
|
|
|
+ buildTrendSeries(trendArr = [], selectedItemsOrder = []) {
|
|
|
const years = [
|
|
const years = [
|
|
|
...new Set(
|
|
...new Set(
|
|
|
trendArr
|
|
trendArr
|
|
@@ -299,24 +523,94 @@ export const comprehensiveMixin = {
|
|
|
trendArr.forEach((item) => {
|
|
trendArr.forEach((item) => {
|
|
|
const yearKey = String(item.year || item.name || '').replace('年', '')
|
|
const yearKey = String(item.year || item.name || '').replace('年', '')
|
|
|
if (!yearKey) return
|
|
if (!yearKey) return
|
|
|
- if (!indicatorMap[item.indicatorName]) {
|
|
|
|
|
- indicatorMap[item.indicatorName] = {
|
|
|
|
|
|
|
+ // 优先使用 indicatorId(uniqueId/id/rowid),确保相同名称但不同ID的项能区分
|
|
|
|
|
+ // 如果 indicatorId 为空,则使用 indicatorName 作为key(这种情况下相同名称会合并)
|
|
|
|
|
+ const key = item.indicatorId || item.indicatorName
|
|
|
|
|
+ if (!indicatorMap[key]) {
|
|
|
|
|
+ indicatorMap[key] = {
|
|
|
|
|
+ id: key,
|
|
|
name: item.indicatorName,
|
|
name: item.indicatorName,
|
|
|
data: {},
|
|
data: {},
|
|
|
color: this.getColorByIndex(Object.keys(indicatorMap).length),
|
|
color: this.getColorByIndex(Object.keys(indicatorMap).length),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- indicatorMap[item.indicatorName].data[yearKey] = Number(item.value) || 0
|
|
|
|
|
|
|
+ // 如果同一年份已有数据,累加而不是覆盖(避免相同名称的项数据丢失)
|
|
|
|
|
+ const existingValue = indicatorMap[key].data[yearKey] || 0
|
|
|
|
|
+ indicatorMap[key].data[yearKey] =
|
|
|
|
|
+ existingValue + Number(item.value) || 0
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- const series = Object.values(indicatorMap).map((indicator) => ({
|
|
|
|
|
- name: indicator.name,
|
|
|
|
|
- data: years.map((y) => {
|
|
|
|
|
- const key = y.replace('年', '')
|
|
|
|
|
- return indicator.data[key] || 0
|
|
|
|
|
- }),
|
|
|
|
|
- color: indicator.color,
|
|
|
|
|
- }))
|
|
|
|
|
|
|
+ // 统计相同名称的数量,用于区分图例显示
|
|
|
|
|
+ const nameCountMap = {}
|
|
|
|
|
+ Object.values(indicatorMap).forEach((indicator) => {
|
|
|
|
|
+ nameCountMap[indicator.name] = (nameCountMap[indicator.name] || 0) + 1
|
|
|
|
|
+ })
|
|
|
|
|
+ const nameIndexMap = {}
|
|
|
|
|
+
|
|
|
|
|
+ // 按照选中项顺序来生成 series,如果没有提供顺序则使用默认顺序
|
|
|
|
|
+ let orderedIndicators = []
|
|
|
|
|
+ if (selectedItemsOrder && selectedItemsOrder.length > 0) {
|
|
|
|
|
+ // 按照 selectedItemsOrder 的顺序来构建 series
|
|
|
|
|
+ const indicatorIdToItem = {}
|
|
|
|
|
+ selectedItemsOrder.forEach((item) => {
|
|
|
|
|
+ const key =
|
|
|
|
|
+ item.uniqueId || item.id || item.rowid || item.rowId || item.name
|
|
|
|
|
+ if (key && indicatorMap[key]) {
|
|
|
|
|
+ indicatorIdToItem[key] = indicatorMap[key]
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ // 先添加按顺序的项
|
|
|
|
|
+ orderedIndicators = selectedItemsOrder
|
|
|
|
|
+ .map((item) => {
|
|
|
|
|
+ const key =
|
|
|
|
|
+ item.uniqueId || item.id || item.rowid || item.rowId || item.name
|
|
|
|
|
+ return indicatorMap[key]
|
|
|
|
|
+ })
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ // 再添加不在选中项顺序中的项(以防有遗漏)
|
|
|
|
|
+ Object.keys(indicatorMap).forEach((key) => {
|
|
|
|
|
+ if (!indicatorIdToItem[key]) {
|
|
|
|
|
+ orderedIndicators.push(indicatorMap[key])
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有提供顺序,使用默认顺序
|
|
|
|
|
+ orderedIndicators = Object.values(indicatorMap)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 按照顺序重新分配颜色,确保每个项(即使名称相同)都使用不同的颜色
|
|
|
|
|
+ const series = orderedIndicators.map((indicator, index) => {
|
|
|
|
|
+ // 如果存在相同名称的项,在名称后添加序号来区分
|
|
|
|
|
+ const nameCount = nameCountMap[indicator.name] || 1
|
|
|
|
|
+ let displayName = indicator.name
|
|
|
|
|
+ if (nameCount > 1) {
|
|
|
|
|
+ if (!nameIndexMap[indicator.name]) {
|
|
|
|
|
+ nameIndexMap[indicator.name] = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ nameIndexMap[indicator.name]++
|
|
|
|
|
+ displayName = `${indicator.name}(${nameIndexMap[indicator.name]})`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: displayName,
|
|
|
|
|
+ indicatorId: indicator.id, // 保留原始ID用于区分
|
|
|
|
|
+ originalName: indicator.name, // 保留原始名称
|
|
|
|
|
+ data: years.map((y) => {
|
|
|
|
|
+ const key = y.replace('年', '')
|
|
|
|
|
+ return indicator.data[key] !== undefined ? indicator.data[key] : 0
|
|
|
|
|
+ }),
|
|
|
|
|
+ color: this.getColorByIndex(index), // 按照顺序重新分配颜色,确保每个项都不同
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 确保至少有一个空系列,以便图表可以正确初始化
|
|
|
|
|
+ if (series.length === 0) {
|
|
|
|
|
+ series.push({
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ data: years.map(() => 0),
|
|
|
|
|
+ color: '#ccc',
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
return { xAxis: years, series }
|
|
return { xAxis: years, series }
|
|
|
},
|
|
},
|
|
@@ -382,11 +676,16 @@ export const comprehensiveMixin = {
|
|
|
data: seriesData.map((item) => item.name),
|
|
data: seriesData.map((item) => item.name),
|
|
|
bottom: 0,
|
|
bottom: 0,
|
|
|
left: 'center',
|
|
left: 'center',
|
|
|
- itemGap: 20,
|
|
|
|
|
|
|
+ itemGap: 15,
|
|
|
textStyle: {
|
|
textStyle: {
|
|
|
fontSize: 12,
|
|
fontSize: 12,
|
|
|
color: '#333',
|
|
color: '#333',
|
|
|
},
|
|
},
|
|
|
|
|
+ type: 'plain', // 改为 plain 去掉分页控件
|
|
|
|
|
+ width: 'auto', // 自动宽度,确保能显示所有图例
|
|
|
|
|
+ formatter: function (name) {
|
|
|
|
|
+ return name // 确保显示完整的名称
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
grid: {
|
|
grid: {
|
|
|
left: '3%',
|
|
left: '3%',
|
|
@@ -463,15 +762,36 @@ export const comprehensiveMixin = {
|
|
|
|
|
|
|
|
// 获取构成图表基础配置(historyAnalysis.vue 使用)
|
|
// 获取构成图表基础配置(historyAnalysis.vue 使用)
|
|
|
getBaseCompositionChartOption(data = []) {
|
|
getBaseCompositionChartOption(data = []) {
|
|
|
|
|
+ // 为每个数据项分配不同的颜色
|
|
|
|
|
+ const dataWithColors = data.map((item, index) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: this.getColorByIndex(index),
|
|
|
|
|
+ },
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
tooltip: {
|
|
tooltip: {
|
|
|
show: true,
|
|
show: true,
|
|
|
trigger: 'item',
|
|
trigger: 'item',
|
|
|
formatter: (p) => {
|
|
formatter: (p) => {
|
|
|
- const header = `${p.name}: ${p.value} (${p.percent}%)`
|
|
|
|
|
const surveysVos = Array.isArray(p.data?.surveysVos)
|
|
const surveysVos = Array.isArray(p.data?.surveysVos)
|
|
|
? p.data.surveysVos
|
|
? p.data.surveysVos
|
|
|
: []
|
|
: []
|
|
|
|
|
+ // 计算所有年度值的总和
|
|
|
|
|
+ const totalSum = surveysVos.reduce((sum, sv) => {
|
|
|
|
|
+ const hasValue = sv.value !== undefined && sv.value !== null
|
|
|
|
|
+ const hasRvalue = sv.rvalue !== undefined && sv.rvalue !== null
|
|
|
|
|
+ const mainValue = hasValue
|
|
|
|
|
+ ? Number(sv.value) || 0
|
|
|
|
|
+ : hasRvalue
|
|
|
|
|
+ ? Number(sv.rvalue) || 0
|
|
|
|
|
+ : 0
|
|
|
|
|
+ return sum + mainValue
|
|
|
|
|
+ }, 0)
|
|
|
|
|
+ // 使用总和作为显示值,如果没有年度数据则使用原始值
|
|
|
|
|
+ const displayValue = totalSum > 0 ? totalSum : p.value
|
|
|
|
|
+ const header = `${p.name}: ${displayValue} (${p.percent}%)`
|
|
|
// 只展示年度和值,避免重复的字段名与分隔符
|
|
// 只展示年度和值,避免重复的字段名与分隔符
|
|
|
const details = surveysVos
|
|
const details = surveysVos
|
|
|
.map((sv) => {
|
|
.map((sv) => {
|
|
@@ -500,18 +820,25 @@ export const comprehensiveMixin = {
|
|
|
show: true,
|
|
show: true,
|
|
|
orient: 'vertical',
|
|
orient: 'vertical',
|
|
|
right: 10,
|
|
right: 10,
|
|
|
- top: 'center',
|
|
|
|
|
|
|
+ top: 'top',
|
|
|
data: data.map((item) => item.name),
|
|
data: data.map((item) => item.name),
|
|
|
formatter: function (name) {
|
|
formatter: function (name) {
|
|
|
return name
|
|
return name
|
|
|
},
|
|
},
|
|
|
- itemGap: 10,
|
|
|
|
|
|
|
+ itemGap: 8,
|
|
|
textStyle: {
|
|
textStyle: {
|
|
|
- fontSize: 12,
|
|
|
|
|
|
|
+ fontSize: 11,
|
|
|
color: '#333',
|
|
color: '#333',
|
|
|
},
|
|
},
|
|
|
itemWidth: 14,
|
|
itemWidth: 14,
|
|
|
itemHeight: 14,
|
|
itemHeight: 14,
|
|
|
|
|
+ type: 'scroll',
|
|
|
|
|
+ pageButtonPosition: 'end',
|
|
|
|
|
+ pageButtonItemGap: 5,
|
|
|
|
|
+ pageButtonGap: 5,
|
|
|
|
|
+ pageTextStyle: {
|
|
|
|
|
+ fontSize: 10,
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
series: [
|
|
series: [
|
|
|
{
|
|
{
|
|
@@ -558,7 +885,7 @@ export const comprehensiveMixin = {
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- data: data,
|
|
|
|
|
|
|
+ data: dataWithColors,
|
|
|
},
|
|
},
|
|
|
],
|
|
],
|
|
|
}
|
|
}
|
|
@@ -566,54 +893,58 @@ export const comprehensiveMixin = {
|
|
|
|
|
|
|
|
// 获取构成图表基础配置(industryAnalysis.vue 使用)
|
|
// 获取构成图表基础配置(industryAnalysis.vue 使用)
|
|
|
getIndustryCompositionChartOption(data = []) {
|
|
getIndustryCompositionChartOption(data = []) {
|
|
|
- const colorPalette = [
|
|
|
|
|
- '#5470C6', // 蓝色 - 基本工资
|
|
|
|
|
- '#73C0DE', // 青色/蓝绿色 - 津贴
|
|
|
|
|
- '#91CC75', // 绿色 - 奖金
|
|
|
|
|
- '#FAC858', // 黄色 - 福利费
|
|
|
|
|
- '#FF85C0', // 粉色 - 其他人员支出
|
|
|
|
|
- '#EE6666', // 红色
|
|
|
|
|
- '#3BA272', // 深绿色
|
|
|
|
|
- '#FC8452', // 橙色
|
|
|
|
|
- '#9A60B4', // 紫色
|
|
|
|
|
- '#E71D36', // 深红
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ // 为每个数据项分配不同的颜色,按照顺序使用 getColorByIndex
|
|
|
|
|
+ const dataWithColors = data.map((item, index) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: this.getColorByIndex(index),
|
|
|
|
|
+ },
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
- color: colorPalette,
|
|
|
|
|
tooltip: {
|
|
tooltip: {
|
|
|
show: true,
|
|
show: true,
|
|
|
trigger: 'item',
|
|
trigger: 'item',
|
|
|
formatter: (p) => {
|
|
formatter: (p) => {
|
|
|
- let tooltipContent = `${p.name}: ${p.value} (${p.percent}%)`
|
|
|
|
|
- // 如果有surveysVos信息,显示完整的surveysVos
|
|
|
|
|
- if (p.data && p.data.surveysVos) {
|
|
|
|
|
- const surveysVos = p.data.surveysVos
|
|
|
|
|
- tooltipContent += '<br/>'
|
|
|
|
|
- if (Array.isArray(surveysVos)) {
|
|
|
|
|
- // 如果是数组,遍历显示每个元素
|
|
|
|
|
- surveysVos.forEach((sv, index) => {
|
|
|
|
|
- if (index > 0) {
|
|
|
|
|
- tooltipContent += '<br/>---<br/>'
|
|
|
|
|
- }
|
|
|
|
|
- for (const key in sv) {
|
|
|
|
|
- if (sv[key] !== undefined && sv[key] !== null) {
|
|
|
|
|
- tooltipContent += `${key}: ${sv[key]}<br/>`
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- } else if (typeof surveysVos === 'object') {
|
|
|
|
|
- // 如果是对象,直接显示所有属性
|
|
|
|
|
- for (const key in surveysVos) {
|
|
|
|
|
- if (
|
|
|
|
|
- surveysVos[key] !== undefined &&
|
|
|
|
|
- surveysVos[key] !== null
|
|
|
|
|
- ) {
|
|
|
|
|
- tooltipContent += `${key}: ${surveysVos[key]}<br/>`
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return tooltipContent
|
|
|
|
|
|
|
+ const surveysVos = Array.isArray(p.data?.surveysVos)
|
|
|
|
|
+ ? p.data.surveysVos
|
|
|
|
|
+ : []
|
|
|
|
|
+ // 计算所有年度值的总和
|
|
|
|
|
+ const totalSum = surveysVos.reduce((sum, sv) => {
|
|
|
|
|
+ const hasValue = sv.value !== undefined && sv.value !== null
|
|
|
|
|
+ const hasRvalue = sv.rvalue !== undefined && sv.rvalue !== null
|
|
|
|
|
+ const mainValue = hasValue
|
|
|
|
|
+ ? Number(sv.value) || 0
|
|
|
|
|
+ : hasRvalue
|
|
|
|
|
+ ? Number(sv.rvalue) || 0
|
|
|
|
|
+ : 0
|
|
|
|
|
+ return sum + mainValue
|
|
|
|
|
+ }, 0)
|
|
|
|
|
+ // 使用总和作为显示值,如果没有年度数据则使用原始值
|
|
|
|
|
+ const displayValue = totalSum > 0 ? totalSum : p.value
|
|
|
|
|
+ const header = `${p.name}: ${displayValue} (${p.percent}%)`
|
|
|
|
|
+ // 只展示年度和值,避免重复的字段名与分隔符
|
|
|
|
|
+ const details = surveysVos
|
|
|
|
|
+ .map((sv) => {
|
|
|
|
|
+ const yearLabel = sv.name || sv.year || '-'
|
|
|
|
|
+ const hasValue = sv.value !== undefined && sv.value !== null
|
|
|
|
|
+ const hasRvalue = sv.rvalue !== undefined && sv.rvalue !== null
|
|
|
|
|
+ const mainValue = hasValue
|
|
|
|
|
+ ? sv.value
|
|
|
|
|
+ : hasRvalue
|
|
|
|
|
+ ? sv.rvalue
|
|
|
|
|
+ : '-'
|
|
|
|
|
+ // 如果 value 和 rvalue 同时存在且不同,附加 rvalue 以显示所有值
|
|
|
|
|
+ const extra =
|
|
|
|
|
+ hasValue && hasRvalue && sv.value !== sv.rvalue
|
|
|
|
|
+ ? ` (rvalue: ${sv.rvalue})`
|
|
|
|
|
+ : ''
|
|
|
|
|
+ return `${yearLabel}: ${mainValue}${extra}`
|
|
|
|
|
+ })
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ return details.length > 0
|
|
|
|
|
+ ? `${header}<br/>${details.join('<br/>')}`
|
|
|
|
|
+ : header
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
legend: {
|
|
legend: {
|
|
@@ -677,7 +1008,7 @@ export const comprehensiveMixin = {
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- data: data,
|
|
|
|
|
|
|
+ data: dataWithColors,
|
|
|
},
|
|
},
|
|
|
],
|
|
],
|
|
|
}
|
|
}
|
|
@@ -1070,7 +1401,7 @@ export const mockStatisticsData = {
|
|
|
id: '1997964656993736704',
|
|
id: '1997964656993736704',
|
|
|
name: '设备维护',
|
|
name: '设备维护',
|
|
|
orderNum: '19',
|
|
orderNum: '19',
|
|
|
- rowid: 'd0e1f2a3-b4c5-4d6e-7f8a-9b0c1d2e3f4a',
|
|
|
|
|
|
|
+ rowid: 'd0e1f2a3-b4c5-4d6e-7f8a-9b0c-1d2e3f4a5b4a',
|
|
|
number: '2',
|
|
number: '2',
|
|
|
rvalue: '90',
|
|
rvalue: '90',
|
|
|
taskId: '1995015537499938816',
|
|
taskId: '1995015537499938816',
|