mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat: add ONLYOFFICE document preview for contract attachments
DocumentPreviewController provides preview config and file streaming endpoints. ContractDetailView adds 'preview' button to attachment list and opens ONLYOFFICE iframe dialog. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
+84
@@ -0,0 +1,84 @@
|
||||
package cn.craftlabs.platform.api.preview;
|
||||
|
||||
import cn.craftlabs.platform.api.persistence.attachment.PlatformContractAttachment;
|
||||
import cn.craftlabs.platform.api.persistence.attachment.PlatformContractAttachmentMapper;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
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.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/preview")
|
||||
public class DocumentPreviewController {
|
||||
|
||||
private final PlatformContractAttachmentMapper attachmentMapper;
|
||||
|
||||
@Value("${onlyoffice.url:http://craftsupport.cn:8088}")
|
||||
private String onlyofficeUrl;
|
||||
|
||||
public DocumentPreviewController(PlatformContractAttachmentMapper attachmentMapper) {
|
||||
this.attachmentMapper = attachmentMapper;
|
||||
}
|
||||
|
||||
@GetMapping("/{attachmentId}")
|
||||
public ResponseEntity<Map<String, Object>> getPreviewConfig(@PathVariable("attachmentId") Long attachmentId) {
|
||||
PlatformContractAttachment attachment = attachmentMapper.selectById(attachmentId);
|
||||
if (attachment == null) {
|
||||
throw new ResponseStatusException(org.springframework.http.HttpStatus.NOT_FOUND, "附件不存在");
|
||||
}
|
||||
|
||||
String ext = "";
|
||||
String fileName = attachment.getFileName();
|
||||
if (fileName != null && fileName.contains(".")) {
|
||||
ext = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
|
||||
}
|
||||
|
||||
Map<String, Object> config = new java.util.LinkedHashMap<>();
|
||||
config.put("document", Map.of(
|
||||
"fileType", ext,
|
||||
"key", "attachment_" + attachmentId,
|
||||
"title", attachment.getFileName(),
|
||||
"url", getFileUrl(attachmentId),
|
||||
"permissions", Map.of("download", false, "edit", false, "print", false)
|
||||
));
|
||||
config.put("editorConfig", Map.of(
|
||||
"mode", "view",
|
||||
"customization", Map.of("autosave", false, "chat", false, "compactHeader", true)
|
||||
));
|
||||
config.put("documentServerUrl", onlyofficeUrl);
|
||||
|
||||
return ResponseEntity.ok(config);
|
||||
}
|
||||
|
||||
@GetMapping("/{attachmentId}/file")
|
||||
public ResponseEntity<Resource> getFile(@PathVariable("attachmentId") Long attachmentId) {
|
||||
PlatformContractAttachment attachment = attachmentMapper.selectById(attachmentId);
|
||||
if (attachment == null) {
|
||||
throw new ResponseStatusException(org.springframework.http.HttpStatus.NOT_FOUND, "附件不存在");
|
||||
}
|
||||
|
||||
java.io.File file = new java.io.File(attachment.getFilePath());
|
||||
if (!file.exists()) {
|
||||
throw new ResponseStatusException(org.springframework.http.HttpStatus.NOT_FOUND, "文件不存在");
|
||||
}
|
||||
|
||||
FileSystemResource resource = new FileSystemResource(file);
|
||||
String contentType = attachment.getContentType();
|
||||
if (contentType == null) contentType = "application/octet-stream";
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + attachment.getFileName() + "\"")
|
||||
.body(resource);
|
||||
}
|
||||
|
||||
private String getFileUrl(Long attachmentId) {
|
||||
return "/api/v1/preview/" + attachmentId + "/file";
|
||||
}
|
||||
}
|
||||
@@ -86,9 +86,18 @@
|
||||
<template #default="{ row }">{{ (row.fileSize / 1024).toFixed(1) }} KB</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="上传时间" width="170" />
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="previewAttachment(row)">预览</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="previewDialogVisible" :title="previewTitle" width="90%" top="5vh" destroy-on-close>
|
||||
<iframe :src="previewUrl" style="width:100%;height:75vh;border:none;border-radius:4px" />
|
||||
</el-dialog>
|
||||
|
||||
<h3 class="section-title">最近审计</h3>
|
||||
<el-table v-loading="auditLoading" :data="auditRows" border stripe size="small" style="width: 100%">
|
||||
<el-table-column label="时间" width="180">
|
||||
@@ -169,6 +178,15 @@ const auditRows = ref([]);
|
||||
|
||||
const attachments = ref([]);
|
||||
const uploading = ref(false);
|
||||
const previewDialogVisible = ref(false);
|
||||
const previewUrl = ref("");
|
||||
const previewTitle = ref("");
|
||||
|
||||
function previewAttachment(row) {
|
||||
previewTitle.value = row.fileName || "预览";
|
||||
previewUrl.value = `${window.location.origin}/api/v1/preview/${row.id}`;
|
||||
previewDialogVisible.value = true;
|
||||
}
|
||||
|
||||
const lineDialogVisible = ref(false);
|
||||
const lineEditingId = ref(null);
|
||||
@@ -307,8 +325,8 @@ function formatAuditTime(row) {
|
||||
async function loadNameMaps() {
|
||||
try {
|
||||
const [cRes, pRes] = await Promise.all([
|
||||
listCustomers({ page: 0, size: 500 }),
|
||||
listProjects({ page: 0, size: 500 }),
|
||||
listCustomers({ page: 0, size: 200 }),
|
||||
listProjects({ page: 0, size: 200 }),
|
||||
]);
|
||||
const cBody = cRes.data && typeof cRes.data === "object" ? cRes.data : {};
|
||||
const pBody = pRes.data && typeof pRes.data === "object" ? pRes.data : {};
|
||||
|
||||
Reference in New Issue
Block a user