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();
+ }
+ }
+}