feat: 完成仪表盘和导出功能

- DashboardController: 实现完整的后端API
  - /api/dashboard - 仪表盘数据
  - /api/dashboard/kpis - KPI统计
  - /api/dashboard/activities - 活动摘要
  - /api/dashboard/todos - 待办事项
  - /api/dashboard/export - 导出CSV
  - /api/dashboard/kpis/export - KPI导出
  - /api/dashboard/activities/export - 活动导出

- dashboard.ts: 前端服务
  - 完整的API调用封装
  - 导出功能支持
  - 下载工具函数

- 更新任务状态:
  - TASK-401-405: 仪表盘模块100%
  - TASK-501-502: 单元测试

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-03-05 21:55:47 +08:00
parent 06c4eceebe
commit 5880b4dbb2
5 changed files with 180 additions and 7 deletions

View File

@@ -6,10 +6,13 @@ import com.mosquito.project.dto.ApiResponse;
import com.mosquito.project.service.ActivityService;
import com.mosquito.project.permission.ApprovalFlowService;
import com.mosquito.project.permission.SysApprovalRecord;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@@ -232,4 +235,116 @@ public class DashboardController {
return todos;
}
/**
* 导出仪表盘数据为CSV
*/
@GetMapping("/export")
public ResponseEntity<byte[]> exportDashboard(@RequestParam(defaultValue = "csv") String format) {
StringBuilder csv = new StringBuilder();
csv.append("导出时间:").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("\n\n");
// KPI数据
csv.append("KPI指标\n");
csv.append("指标,数值,状态,说明\n");
for (Map<String, Object> kpi : buildKpiData()) {
csv.append(kpi.get("label")).append(",")
.append(kpi.get("value")).append(",")
.append(kpi.get("status")).append(",")
.append(kpi.get("hint")).append("\n");
}
csv.append("\n");
// 活动数据
csv.append("活动列表\n");
csv.append("ID,名称,开始时间,结束时间,参与人数,分享数,转化数\n");
for (Activity activity : activityService.getAllActivities()) {
csv.append(activity.getId()).append(",")
.append(escapeCsv(activity.getName())).append(",")
.append(activity.getStartTime() != null ? activity.getStartTime().toString() : "").append(",")
.append(activity.getEndTime() != null ? activity.getEndTime().toString() : "").append(",");
try {
ActivityStatsResponse stats = activityService.getActivityStats(activity.getId());
csv.append(stats.getTotalParticipants()).append(",")
.append(stats.getTotalShares()).append(",")
.append(stats.getTotalParticipants()).append("\n");
} catch (Exception e) {
csv.append("0,0,0\n");
}
}
byte[] body = csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"dashboard_export_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".csv\"");
return new ResponseEntity<>(body, headers, org.springframework.http.HttpStatus.OK);
}
/**
* 导出KPI数据为CSV
*/
@GetMapping("/kpis/export")
public ResponseEntity<byte[]> exportKpis() {
StringBuilder csv = new StringBuilder();
csv.append("指标,数值,状态,说明\n");
for (Map<String, Object> kpi : buildKpiData()) {
csv.append(kpi.get("label")).append(",")
.append(kpi.get("value")).append(",")
.append(kpi.get("status")).append(",")
.append(kpi.get("hint")).append("\n");
}
byte[] body = csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"kpi_export_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".csv\"");
return new ResponseEntity<>(body, headers, org.springframework.http.HttpStatus.OK);
}
/**
* 导出活动数据为CSV
*/
@GetMapping("/activities/export")
public ResponseEntity<byte[]> exportActivities() {
StringBuilder csv = new StringBuilder();
csv.append("ID,名称,开始时间,结束时间,参与人数,分享数,转化数\n");
for (Activity activity : activityService.getAllActivities()) {
csv.append(activity.getId()).append(",")
.append(escapeCsv(activity.getName())).append(",")
.append(activity.getStartTime() != null ? activity.getStartTime().toString() : "").append(",")
.append(activity.getEndTime() != null ? activity.getEndTime().toString() : "").append(",");
try {
ActivityStatsResponse stats = activityService.getActivityStats(activity.getId());
csv.append(stats.getTotalParticipants()).append(",")
.append(stats.getTotalShares()).append(",")
.append(stats.getTotalParticipants()).append("\n");
} catch (Exception e) {
csv.append("0,0,0\n");
}
}
byte[] body = csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"activity_export_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".csv\"");
return new ResponseEntity<>(body, headers, org.springframework.http.HttpStatus.OK);
}
/**
* 转义CSV特殊字符
*/
private String escapeCsv(String value) {
if (value == null) return "";
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}
}