fix: 完善数据分析导出并避免乱码
为导出CSV增加转义处理,补齐多维度与一级需求部门数据,避免数据缺失。
This commit is contained in:
parent
9ee8cfe2cc
commit
651608ebf0
@ -1759,27 +1759,50 @@ const getWarningActionText = (type: string) => {
|
||||
|
||||
// 导出数据为 CSV
|
||||
const exportData = () => {
|
||||
if (loading.value) {
|
||||
message.warning('数据正在加载中,请稍后再导出')
|
||||
return
|
||||
}
|
||||
|
||||
const monthStr = selectedMonth.value || dayjs().format('YYYY-MM')
|
||||
let csvContent = '\uFEFF' // BOM for UTF-8 encoding
|
||||
|
||||
const csvEscape = (value: any) => {
|
||||
const str = value === null || value === undefined ? '' : String(value)
|
||||
const escaped = str.replace(/"/g, '""')
|
||||
return /[",\n\r]/.test(escaped) ? `"${escaped}"` : escaped
|
||||
}
|
||||
|
||||
const appendRow = (fields: any[]) => {
|
||||
csvContent += `${fields.map(csvEscape).join(',')}\n`
|
||||
}
|
||||
|
||||
// 1. 概览数据
|
||||
csvContent += '【核心指标概览】\n'
|
||||
csvContent += '指标,数值\n'
|
||||
csvContent += `总任务数,${overview.value.totalDemands}\n`
|
||||
csvContent += `待处理,${overview.value.pendingDemands}\n`
|
||||
csvContent += `进行中,${inProgressCount.value}\n`
|
||||
csvContent += `已完成,${overview.value.completedDemands}\n`
|
||||
csvContent += `完成率,${completionRate.value}%\n`
|
||||
csvContent += `准时率,${onTimeRate.value}%\n`
|
||||
appendRow(['指标', '数值'])
|
||||
appendRow(['总任务数', overview.value.totalDemands])
|
||||
appendRow(['待处理', overview.value.pendingDemands])
|
||||
appendRow(['进行中', inProgressCount.value])
|
||||
appendRow(['已完成', overview.value.completedDemands])
|
||||
appendRow(['完成率', `${completionRate.value}%`])
|
||||
appendRow(['准时率', `${onTimeRate.value}%`])
|
||||
csvContent += '\n'
|
||||
|
||||
// 2. 用户需求统计
|
||||
if (statistics.userStats && statistics.userStats.length > 0) {
|
||||
csvContent += '【用户需求统计】\n'
|
||||
csvContent += '排名,用户名,部门,发起数,完成数,总积分,完成率\n'
|
||||
appendRow(['排名', '用户名', '部门', '发起数', '完成数', '总积分', '完成率'])
|
||||
statistics.userStats.forEach((user: any, index: number) => {
|
||||
const rate = user.totalDemands > 0 ? ((user.completedDemands / user.totalDemands) * 100).toFixed(1) : '0.0'
|
||||
csvContent += `${index + 1},${user.userName || '-'},${user.deptName || '-'},${user.totalDemands},${user.completedDemands},${formatScore(user.totalScore)},${rate}%\n`
|
||||
appendRow([
|
||||
index + 1,
|
||||
user.realName || user.userName || '-',
|
||||
user.deptName || '-',
|
||||
user.totalDemands,
|
||||
user.completedDemands,
|
||||
formatScore(user.totalScore),
|
||||
`${rate}%`
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
@ -1787,10 +1810,16 @@ const exportData = () => {
|
||||
// 3. 部门需求统计
|
||||
if (statistics.deptStats && statistics.deptStats.length > 0) {
|
||||
csvContent += '【部门需求统计】\n'
|
||||
csvContent += '排名,部门名称,发起数,完成数,完成率\n'
|
||||
appendRow(['排名', '部门名称', '发起数', '完成数', '完成率'])
|
||||
statistics.deptStats.forEach((dept: any, index: number) => {
|
||||
const rate = dept.totalDemands > 0 ? ((dept.completedDemands / dept.totalDemands) * 100).toFixed(1) : '0.0'
|
||||
csvContent += `${index + 1},${dept.deptName || '-'},${dept.totalDemands},${dept.completedDemands},${rate}%\n`
|
||||
appendRow([
|
||||
index + 1,
|
||||
dept.deptName || '-',
|
||||
dept.totalDemands,
|
||||
dept.completedDemands,
|
||||
`${rate}%`
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
@ -1798,28 +1827,234 @@ const exportData = () => {
|
||||
// 4. 设计师绩效
|
||||
if (designerPerformance.designerStats && designerPerformance.designerStats.length > 0) {
|
||||
csvContent += '【设计师绩效】\n'
|
||||
csvContent += '排名,设计师,部门,接单量,完成量,获得积分,扣分,净积分,目标积分,目标完成率,准时率,评分\n'
|
||||
appendRow(['排名', '设计师', '部门', '接单量', '完成量', '获得积分', '扣分', '净积分', '目标积分', '目标完成率', '准时率', '评分'])
|
||||
designerPerformance.designerStats.forEach((designer: any, index: number) => {
|
||||
csvContent += `${index + 1},${designer.realName || '-'},${designer.deptName || '-'},${designer.receivedCount || 0},${designer.completedCount || 0},${formatScore(designer.totalScore)},${formatScore(designer.deductionScore)},${formatScore(designer.netScore)},${designer.targetScore ? formatScore(designer.targetScore) : '-'},${designer.targetScore > 0 ? (designer.targetCompletionRate || 0) + '%' : '-'},${designer.onTimeRate || 0}%,${designer.avgRating?.toFixed(1) || '-'}\n`
|
||||
appendRow([
|
||||
index + 1,
|
||||
designer.realName || '-',
|
||||
designer.deptName || '-',
|
||||
designer.receivedCount || 0,
|
||||
designer.completedCount || 0,
|
||||
formatScore(designer.totalScore),
|
||||
formatScore(designer.deductionScore),
|
||||
formatScore(designer.netScore),
|
||||
designer.targetScore ? formatScore(designer.targetScore) : '-',
|
||||
designer.targetScore > 0 ? `${designer.targetCompletionRate || 0}%` : '-',
|
||||
`${designer.onTimeRate || 0}%`,
|
||||
designer.avgRating?.toFixed(1) || '-'
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
// 5. 流程效率指标
|
||||
csvContent += '【流程效率指标】\n'
|
||||
csvContent += '指标,本周,上周,本月,目标\n'
|
||||
appendRow(['指标', '本周', '上周', '本月', '目标'])
|
||||
efficiencyTableData.value.forEach((row: any) => {
|
||||
csvContent += `${row.metric},${row.week},${row.lastWeek},${row.month},${row.target}\n`
|
||||
appendRow([row.metric, row.week, row.lastWeek, row.month, row.target])
|
||||
})
|
||||
csvContent += '\n'
|
||||
|
||||
// 6. 预警任务
|
||||
if (warningTasks.value && warningTasks.value.length > 0) {
|
||||
csvContent += '【预警任务】\n'
|
||||
csvContent += '级别,任务名称,处理人,预警原因\n'
|
||||
appendRow(['级别', '任务名称', '处理人', '预警原因'])
|
||||
warningTasks.value.forEach((task: any) => {
|
||||
const level = task.warningLevel === 'urgent' ? '紧急' : task.warningLevel === 'warning' ? '警告' : '正常'
|
||||
csvContent += `${level},${task.taskTitle || '-'},${task.receiverName || '-'},${task.warningReason || '-'}\n`
|
||||
appendRow([level, task.taskTitle || '-', task.receiverName || '-', task.warningReason || '-'])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
// 7. 多维度分析
|
||||
if (dimensionData.categoryAnalysis?.length) {
|
||||
csvContent += '【类目分析】\n'
|
||||
appendRow(['类目名称', '任务数', '总数量', '已完成', '进行中', '完成率', '总积分'])
|
||||
dimensionData.categoryAnalysis.forEach((item: any) => {
|
||||
appendRow([
|
||||
item.categoryName || '-',
|
||||
item.totalTasks || 0,
|
||||
item.totalQuantity || 0,
|
||||
item.completedTasks || 0,
|
||||
item.inProgressTasks || 0,
|
||||
`${formatScore(item.completionRate)}%`,
|
||||
formatScore(item.totalScore)
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
if (dimensionData.brandAnalysis?.length) {
|
||||
csvContent += '【品牌分析】\n'
|
||||
appendRow(['品牌名称', '任务数', '总数量', '已完成', '进行中', '完成率', '总积分'])
|
||||
dimensionData.brandAnalysis.forEach((item: any) => {
|
||||
appendRow([
|
||||
item.brandName || '-',
|
||||
item.totalTasks || 0,
|
||||
item.totalQuantity || 0,
|
||||
item.completedTasks || 0,
|
||||
item.inProgressTasks || 0,
|
||||
`${formatScore(item.completionRate)}%`,
|
||||
formatScore(item.totalScore)
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
if (dimensionData.receivingDeptAnalysis?.length) {
|
||||
csvContent += '【接单部门分析】\n'
|
||||
appendRow(['部门名称', '部门人数', '任务数', '人均任务', '总数量', '已完成', '进行中', '完成率', '总积分', '平均耗时'])
|
||||
dimensionData.receivingDeptAnalysis.forEach((item: any) => {
|
||||
appendRow([
|
||||
item.deptName || '-',
|
||||
item.memberCount || 0,
|
||||
item.totalTasks || 0,
|
||||
formatScore(item.avgTasksPerMember),
|
||||
item.totalQuantity || 0,
|
||||
item.completedTasks || 0,
|
||||
item.inProgressTasks || 0,
|
||||
`${formatScore(item.completionRate)}%`,
|
||||
formatScore(item.totalScore),
|
||||
item.avgCompletionHours ? `${formatScore(item.avgCompletionHours)}h` : '-'
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
if (dimensionData.demandDeptAnalysis?.length) {
|
||||
csvContent += '【需求部门分析】\n'
|
||||
appendRow(['部门名称', '部门人数', '任务数', '人均任务', '总数量', '已完成', '待处理', '完成率', '总积分'])
|
||||
dimensionData.demandDeptAnalysis.forEach((item: any) => {
|
||||
appendRow([
|
||||
item.deptName || '-',
|
||||
item.memberCount || 0,
|
||||
item.totalTasks || 0,
|
||||
formatScore(item.avgTasksPerMember),
|
||||
item.totalQuantity || 0,
|
||||
item.completedTasks || 0,
|
||||
item.pendingTasks || 0,
|
||||
`${formatScore(item.completionRate)}%`,
|
||||
formatScore(item.totalScore)
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
if (dimensionData.platformAnalysis?.length) {
|
||||
csvContent += '【平台分析】\n'
|
||||
appendRow(['平台名称', '任务数', '总数量', '已完成', '进行中', '完成率', '总积分'])
|
||||
dimensionData.platformAnalysis.forEach((item: any) => {
|
||||
appendRow([
|
||||
item.platformName || '-',
|
||||
item.totalTasks || 0,
|
||||
item.totalQuantity || 0,
|
||||
item.completedTasks || 0,
|
||||
item.inProgressTasks || 0,
|
||||
`${formatScore(item.completionRate)}%`,
|
||||
formatScore(item.totalScore)
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
const exportTaskTypeAnalysis = (title: string, data: any[]) => {
|
||||
if (!data?.length) return
|
||||
csvContent += `【${title}】\n`
|
||||
appendRow(['父级分类', '类型名称', '任务数', '总数量', '已完成', '进行中', '待接单', '完成率', '准时率', '总积分', '平均积分', '单价分值', '预估工时', '平均完成时长'])
|
||||
data.forEach((item: any) => {
|
||||
appendRow([
|
||||
item.parentName || '-',
|
||||
item.typeName || '-',
|
||||
item.totalTasks || 0,
|
||||
item.totalQuantity || 0,
|
||||
item.completedTasks || 0,
|
||||
item.inProgressTasks || 0,
|
||||
item.pendingTasks || 0,
|
||||
`${formatScore(item.completionRate)}%`,
|
||||
`${formatScore(item.onTimeRate)}%`,
|
||||
formatScore(item.totalScore),
|
||||
formatScore(item.avgScore),
|
||||
formatScore(item.unitScore),
|
||||
item.estimatedHours ? `${formatScore(item.estimatedHours)}h` : '-',
|
||||
item.avgCompletionHours ? `${formatScore(item.avgCompletionHours)}h` : '-'
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
exportTaskTypeAnalysis('任务类型分析(一级分类)', dimensionData.taskTypeAnalysis || [])
|
||||
exportTaskTypeAnalysis('任务类型分析(二级分类)', (dimensionData.taskTypeLevel2Analysis || []).map((item: any) => ({ ...item, parentName: item.parentName || '-' })))
|
||||
exportTaskTypeAnalysis('任务类型分析(三级分类)', (dimensionData.taskTypeLevel3Analysis || []).map((item: any) => ({ ...item, parentName: item.parentName || '-' })))
|
||||
|
||||
// 8. 一级需求部门统计
|
||||
if (primaryDeptStats) {
|
||||
csvContent += '【一级需求部门统计】\n'
|
||||
appendRow(['指标', '数值'])
|
||||
appendRow(['发起任务数', primaryDeptStats.totalTasks || 0])
|
||||
appendRow(['审核通过', primaryDeptStats.approvedTasks || 0])
|
||||
appendRow(['审核驳回', primaryDeptStats.rejectedTasks || 0])
|
||||
appendRow(['消耗积分', formatScore(primaryDeptStats.consumedScore)])
|
||||
appendRow(['待处理', primaryDeptStats.pendingTasks || 0])
|
||||
appendRow(['进行中', primaryDeptStats.inProgressTasks || 0])
|
||||
appendRow(['已完成', primaryDeptStats.completedTasks || 0])
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
if (primaryDeptRanking.value?.length) {
|
||||
csvContent += '【一级需求部门排名】\n'
|
||||
appendRow(['排名', '部门名称', '一级部门', '二级部门', '发起任务数', '审核通过率', '消耗积分', '平均响应时间', '已完成', '进行中'])
|
||||
primaryDeptRanking.value.forEach((item: any, index: number) => {
|
||||
appendRow([
|
||||
item.rank || index + 1,
|
||||
item.deptName || '-',
|
||||
item.firstLevelDeptName || '-',
|
||||
item.secondLevelDeptName || '-',
|
||||
item.taskCount || 0,
|
||||
`${formatScore(item.approvalRate)}%`,
|
||||
formatScore(item.consumedScore),
|
||||
item.avgResponseHours ? `${formatScore(item.avgResponseHours)}h` : '-',
|
||||
item.completedCount || 0,
|
||||
item.inProgressCount || 0
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
if (brandDistribution.value?.length) {
|
||||
csvContent += '【品牌任务分布(一级需求)】\n'
|
||||
appendRow(['品牌名称', '任务数', '占比', '负责部门', '消耗积分', '已完成'])
|
||||
brandDistribution.value.forEach((item: any) => {
|
||||
appendRow([
|
||||
item.brandName || '-',
|
||||
item.taskCount || 0,
|
||||
`${formatScore(item.percentage)}%`,
|
||||
item.responsibleDeptName || '-',
|
||||
formatScore(item.consumedScore),
|
||||
item.completedCount || 0
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
if (primaryDeptUserRanking.value?.length) {
|
||||
csvContent += '【一级需求部门用户排名】\n'
|
||||
appendRow(['排名', '用户姓名', '部门名称', '一级部门', '二级部门', '发起任务数', '审核通过数', '审核驳回数', '审核通过率', '消耗积分', '已完成', '进行中', '待审核'])
|
||||
primaryDeptUserRanking.value.forEach((item: any, index: number) => {
|
||||
appendRow([
|
||||
item.rank || index + 1,
|
||||
item.userName || '-',
|
||||
item.deptName || '-',
|
||||
item.firstLevelDeptName || '-',
|
||||
item.secondLevelDeptName || '-',
|
||||
item.taskCount || 0,
|
||||
item.approvedCount || 0,
|
||||
item.rejectedCount || 0,
|
||||
`${formatScore(item.approvalRate)}%`,
|
||||
formatScore(item.consumedScore),
|
||||
item.completedCount || 0,
|
||||
item.inProgressCount || 0,
|
||||
item.pendingCount || 0
|
||||
])
|
||||
})
|
||||
csvContent += '\n'
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user