From 847580801376b5cc05e9a6d4ee588f3d8117f8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=87=A4=E5=90=89?= Date: Wed, 18 Jun 2025 11:45:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 2 + .../iflytop/profilometer/api/record/RecordApi.java | 94 +++++++++++++++++++++- .../profilometer/api/record/RecordRoutes.kt | 12 ++- .../profilometer/common/utils/FileUtil.java | 55 +++++++++++++ 5 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/iflytop/profilometer/common/utils/FileUtil.java diff --git a/app/build.gradle b/app/build.gradle index ea79e98..c3af224 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,9 +14,9 @@ android { minSdk 26 targetSdk 35 // 指定应用内部版本号 - versionCode 24 + versionCode 26 // 指定展示给用户的版本号 - versionName "1.1.24" + versionName "1.1.26" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aa6f427..c6ecc55 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + { + File downloadRoot = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + File exportDir = new File(downloadRoot, "廓形导出"); + if (!exportDir.exists() && !exportDir.mkdirs()) { + try { + throw new IOException("Failed to create export directory: " + exportDir); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if (ids.size() > 1) { + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String zipName = "export-" + timestamp + ".zip"; + + File zipFile = new File(exportDir, zipName); + try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) { + ProfileRecordDao recordDao = new ProfileRecordDao(context); + ProfileRecordPointSetDao pointSetDao = new ProfileRecordPointSetDao(context); + for (int i = 0; i < ids.size(); i++) { + Long id = ids.getLong(i); + ProfileRecordDescription desc = recordDao.getProfileRecordById(id); + ProfileRecordPointSet ps = pointSetDao.getProfileRecordPointSetByUuid(desc.getUuid()); + List pts = JSONUtil.toList(ps.getAlignPoints(), XYPoint.class); + + String entryName = "export-" + desc.getName() + ".txt"; + zos.putNextEntry(new ZipEntry(entryName)); + + for (XYPoint pt : pts) { + String line = String.format(Locale.US, "%1$.6f %2$.6f%n", pt.getX(), -pt.getY()); + zos.write(line.getBytes(StandardCharsets.UTF_8)); + } + + zos.closeEntry(); + } + new Handler(Looper.getMainLooper()).post(() -> { + Toast.makeText(context, "导出成功\n" + "/Download/廓形导出/\n" + zipName, Toast.LENGTH_LONG).show(); + }); + + } catch (Exception e) { + throw new RuntimeException(e); + } + }else{ + Long id = ids.getLong(0); + ProfileRecordDao recordDao = new ProfileRecordDao(context); + ProfileRecordPointSetDao pointSetDao = new ProfileRecordPointSetDao(context); + ProfileRecordDescription desc = recordDao.getProfileRecordById(id); + ProfileRecordPointSet ps = pointSetDao.getProfileRecordPointSetByUuid(desc.getUuid()); + List pts = JSONUtil.toList(ps.getAlignPoints(), XYPoint.class); + StringBuilder sb = new StringBuilder(); + for (XYPoint pt : pts) { + sb.append(String.format(Locale.US, "%1$.6f %2$.6f%n", pt.getX(), -pt.getY())); + } + String fileName = "export-" + desc.getName() + ".txt"; + try { + FileUtil.writeTextToDownloadSubDir("廓形导出",fileName,sb.toString()); + new Handler(Looper.getMainLooper()).post(() -> { + Toast.makeText(context, "导出成功\n" + "/Download/廓形导出/\n" + fileName, Toast.LENGTH_LONG).show(); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + }).start(); + return Result.success(); + } } diff --git a/app/src/main/java/com/iflytop/profilometer/api/record/RecordRoutes.kt b/app/src/main/java/com/iflytop/profilometer/api/record/RecordRoutes.kt index c215dba..5d538b7 100644 --- a/app/src/main/java/com/iflytop/profilometer/api/record/RecordRoutes.kt +++ b/app/src/main/java/com/iflytop/profilometer/api/record/RecordRoutes.kt @@ -4,7 +4,6 @@ import android.content.Context import cn.hutool.json.JSONUtil import io.ktor.http.ContentType import io.ktor.server.application.call -import io.ktor.server.request.receive import io.ktor.server.request.receiveText import io.ktor.server.response.respondText import io.ktor.server.routing.Routing @@ -49,5 +48,16 @@ fun Routing.recordRoutes(context: Context) { val jsonResponse = api.delete(ids) call.respondText(jsonResponse, ContentType.Application.Json) } + + /** + * 导出测量记录 + */ + post("/api/record/download") { + val requestBody = call.receiveText() + val jsonObj = JSONUtil.parseObj(requestBody) + val ids = jsonObj.getJSONArray("ids") + val jsonResponse = api.download(ids) + call.respondText(jsonResponse, ContentType.Application.Json) + } } diff --git a/app/src/main/java/com/iflytop/profilometer/common/utils/FileUtil.java b/app/src/main/java/com/iflytop/profilometer/common/utils/FileUtil.java new file mode 100644 index 0000000..45e8746 --- /dev/null +++ b/app/src/main/java/com/iflytop/profilometer/common/utils/FileUtil.java @@ -0,0 +1,55 @@ +package com.iflytop.profilometer.common.utils; + +import android.os.Environment; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class FileUtil { + + /** + * 在 /storage/emulated/0/Download// 下创建子目录, + * 并在该目录中写入文本文件 + * + * ⚠️ 调用前请确保已经: + * 1) 在 AndroidManifest.xml 中声明了 WRITE_EXTERNAL_STORAGE 权限; + * 2) 已经在运行时向用户请求并获得了该权限; + * 3) 如果 targetSdkVersion ≥ 29, 中开启了 requestLegacyExternalStorage="true"。 + * + * @param subDirName 子目录名称,例如 "MySubFolder" + * @param fileName 要写入的文件名,例如 "example.txt" + * @param content 文件内容 + * @throws IOException 外部存储不可用、目录创建失败或写入失败时抛出 + */ + public static void writeTextToDownloadSubDir(String subDirName, + String fileName, + String content) throws IOException { + // 1. 确保外部存储已挂载 + if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + throw new IOException("External storage is not mounted"); + } + + // 2. 获取公共 Download 目录 + File downloadRoot = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ); + if (downloadRoot == null) { + throw new IOException("Cannot access Download directory"); + } + + // 3. 在 Download 下创建子目录 + File targetDir = new File(downloadRoot, subDirName); + if (!targetDir.exists() && !targetDir.mkdirs()) { + throw new IOException("Failed to create sub-directory: " + targetDir.getAbsolutePath()); + } + + // 4. 在子目录中创建并写入文件 + File outFile = new File(targetDir, fileName); + try (FileOutputStream fos = new FileOutputStream(outFile)) { + fos.write(content.getBytes(StandardCharsets.UTF_8)); + fos.flush(); + } + } +}