mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(m9): add CSV export for contract-sn report
This commit is contained in:
+30
@@ -4,11 +4,18 @@ import cn.craftlabs.platform.api.service.ReportService;
|
|||||||
import cn.craftlabs.platform.api.web.dto.CallbackStatsResponse;
|
import cn.craftlabs.platform.api.web.dto.CallbackStatsResponse;
|
||||||
import cn.craftlabs.platform.api.web.dto.ContractSnReportRow;
|
import cn.craftlabs.platform.api.web.dto.ContractSnReportRow;
|
||||||
import cn.craftlabs.platform.api.web.dto.ProjectHealthRow;
|
import cn.craftlabs.platform.api.web.dto.ProjectHealthRow;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -39,4 +46,27 @@ public class ReportController {
|
|||||||
public List<ProjectHealthRow> getProjectHealth() {
|
public List<ProjectHealthRow> getProjectHealth() {
|
||||||
return reportService.getProjectHealth();
|
return reportService.getProjectHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/export")
|
||||||
|
public ResponseEntity<Resource> exportReport(
|
||||||
|
@RequestParam String type,
|
||||||
|
@RequestParam(required = false) Long projectId,
|
||||||
|
@RequestParam(required = false) Long contractId) {
|
||||||
|
|
||||||
|
String csv = reportService.exportReport(type, projectId, contractId);
|
||||||
|
|
||||||
|
byte[] bytes = csv.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] bom = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
|
||||||
|
byte[] withBom = new byte[bom.length + bytes.length];
|
||||||
|
System.arraycopy(bom, 0, withBom, 0, bom.length);
|
||||||
|
System.arraycopy(bytes, 0, withBom, bom.length, bytes.length);
|
||||||
|
|
||||||
|
ByteArrayResource resource = new ByteArrayResource(withBom);
|
||||||
|
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"attachment; filename=report-" + type + "-" + LocalDate.now() + ".csv")
|
||||||
|
.contentType(MediaType.parseMediaType("text/csv; charset=utf-8"))
|
||||||
|
.body(resource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
@@ -162,6 +162,35 @@ public class ReportService {
|
|||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String exportReport(String type, Long projectId, Long contractId) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if ("contract-sn".equals(type)) {
|
||||||
|
sb.append("合同名称,客户,行项,应发,实发,已激活,缺口,状态\n");
|
||||||
|
List<ContractSnReportRow> rows = getContractSnReport(projectId, contractId);
|
||||||
|
for (ContractSnReportRow r : rows) {
|
||||||
|
sb.append(escapeCsv(r.getContractTitle())).append(",");
|
||||||
|
sb.append(escapeCsv(r.getCustomerName())).append(",");
|
||||||
|
sb.append(escapeCsv(r.getLineItemName())).append(",");
|
||||||
|
sb.append(r.getExpectedCount()).append(",");
|
||||||
|
sb.append(r.getIssuedCount()).append(",");
|
||||||
|
sb.append(r.getActivatedCount()).append(",");
|
||||||
|
sb.append(r.getGapCount()).append(",");
|
||||||
|
sb.append(escapeCsv(r.getStatus())).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeCsv(String value) {
|
||||||
|
if (value == null) return "";
|
||||||
|
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
|
||||||
|
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveCustomerName(Long customerId) {
|
private String resolveCustomerName(Long customerId) {
|
||||||
if (customerId == null) {
|
if (customerId == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -373,6 +373,9 @@ export function updateNotificationConfig(body) {
|
|||||||
export function getContractSnReport(params) {
|
export function getContractSnReport(params) {
|
||||||
return axios.get('/api/v1/reports/contract-sn', { params });
|
return axios.get('/api/v1/reports/contract-sn', { params });
|
||||||
}
|
}
|
||||||
|
export function exportReport(params) {
|
||||||
|
return axios.get('/api/v1/reports/export', { params, responseType: 'blob' });
|
||||||
|
}
|
||||||
export function getCallbackStats(params) {
|
export function getCallbackStats(params) {
|
||||||
return axios.get('/api/v1/reports/callback-stats', { params });
|
return axios.get('/api/v1/reports/callback-stats', { params });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<span class="title">合同 SN 报表</span>
|
<span class="title">合同 SN 报表</span>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<el-button @click="handleExport" :loading="exporting">导出 CSV</el-button>
|
||||||
<el-button type="primary" :loading="loading" @click="load">刷新</el-button>
|
<el-button type="primary" :loading="loading" @click="load">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,11 +50,12 @@
|
|||||||
import { ref, reactive, onMounted } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { useAuthStore } from "../stores/auth";
|
import { useAuthStore } from "../stores/auth";
|
||||||
import { getContractSnReport } from "../api/platform";
|
import { getContractSnReport, exportReport } from "../api/platform";
|
||||||
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const exporting = ref(false);
|
||||||
const rows = ref([]);
|
const rows = ref([]);
|
||||||
const kpi = reactive({ totalLineItems: 0, totalIssued: 0, totalActivated: 0, totalGap: 0 });
|
const kpi = reactive({ totalLineItems: 0, totalIssued: 0, totalActivated: 0, totalGap: 0 });
|
||||||
|
|
||||||
@@ -83,6 +85,25 @@ async function load() {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleExport() {
|
||||||
|
exporting.value = true
|
||||||
|
try {
|
||||||
|
const response = await exportReport({ type: 'contract-sn' })
|
||||||
|
const url = window.URL.createObjectURL(new Blob([response.data]))
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.setAttribute('download', `contract-sn-report-${new Date().toISOString().slice(0, 10)}.csv`)
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('导出失败')
|
||||||
|
} finally {
|
||||||
|
exporting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user