鸿蒙中保存csv文件到本地
·
1、权限
{
"name": "ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY",
"reason": "$string:permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
2、工具
// utils/CSVExporter.ts
import fs from '@ohos.file.fs';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 轨迹点接口
*/
export interface ITrackPoint {
latitude: number; // 纬度
longitude: number; // 经度
altitude: number; // 海拔(米)
speed: number; // 速度(m/s)
timestamp: number; // 时间戳(毫秒)
}
/**
* CSV 导出配置
*/
export interface CSVExportOptions {
fileName?: string; // 文件名(不含扩展名)
includeHeader?: boolean; // 是否包含表头(默认 true)
delimiter?: string; // 分隔符(默认逗号)
}
/**
* CSV 导出结果
*/
export interface CSVExportResult {
success: boolean;
filePath?: string;
error?: string;
fileSize?: number;
}
/**
* CSV 导出器
*/
export class CSVExporter {
/**
* 将轨迹点数组转换为 CSV 格式字符串
*/
/**
* 将轨迹点数组转换为 CSV 格式字符串
*/
static convertToCSV(
trackPoints: ITrackPoint[],
options?: CSVExportOptions
): string {
if (!trackPoints || trackPoints.length === 0) {
return '';
}
const delimiter = options?.delimiter || ',';
const includeHeader = options?.includeHeader !== false;
let csv = '';
if (includeHeader) {
const headers = ['latitude', 'longitude', 'altitude', 'speed', 'timestamp'];
csv = headers.join(delimiter) + '\n';
}
trackPoints.forEach((point: ITrackPoint) => {
const row: Array<number | string> = [
point.latitude,
point.longitude,
point.altitude,
point.speed,
point.timestamp
];
csv += row.join(delimiter) + '\n';
});
return csv;
}
/**
* 保存 CSV 文件到本地
*/
static async saveCSVFile(
context: Context,
csvContent: string,
fileName: string = 'track'
): Promise<string | null> {
if (!csvContent) {
return null;
}
try {
// 获取文件保存路径
const filesDir = context.filesDir;
const timestamp = Date.now();
const filePath = `${filesDir}/${fileName}_${timestamp}.csv`;
// 创建并写入文件
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
fs.writeSync(file.fd, csvContent);
fs.closeSync(file);
return filePath;
} catch (err) {
const error = err as BusinessError;
return null;
}
}
/**
* 一键导出:转换 + 保存
*/
static async exportToCSV(
context: Context,
trackPoints: ITrackPoint[],
options?: CSVExportOptions
): Promise<CSVExportResult> {
try {
// 验证数据
if (!trackPoints || trackPoints.length === 0) {
return {
success: false,
error: '轨迹点数组为空'
};
}
// 1. 转换为 CSV 格式
const csvContent = CSVExporter.convertToCSV(trackPoints, options);
if (!csvContent) {
return {
success: false,
error: 'CSV 内容为空'
};
}
// 2. 保存到文件
const fileName = options?.fileName || 'track';
const filePath = await CSVExporter.saveCSVFile(context, csvContent, fileName);
if (filePath) {
return {
success: true,
filePath: filePath,
fileSize: csvContent.length
};
} else {
return {
success: false,
error: '文件保存失败'
};
}
} catch (err) {
const error = err as BusinessError;
return {
success: false,
error: error.message || '未知错误'
};
}
}
/**
* 验证轨迹点数据
*/
static validateTrackPoint(point: ESObject): boolean {
if (typeof point !== 'object' || point === null) {
return false;
}
return (
typeof point.latitude === 'number' &&
typeof point.longitude === 'number' &&
typeof point.altitude === 'number' &&
typeof point.speed === 'number' &&
typeof point.timestamp === 'number'
);
}
/**
* 验证轨迹点数组
*/
static validateTrackPoints(trackPoints: ESObject[]): boolean {
if (!Array.isArray(trackPoints) || trackPoints.length === 0) {
return false;
}
return trackPoints.every((point: ESObject) => CSVExporter.validateTrackPoint(point));
}
/**
* 转换 ESObject 为 ITrackPoint
*/
static convertToTrackPoint(obj: ESObject): ITrackPoint | null {
if (!CSVExporter.validateTrackPoint(obj)) {
return null;
}
return {
latitude: obj.latitude as number,
longitude: obj.longitude as number,
altitude: obj.altitude as number,
speed: obj.speed as number,
timestamp: obj.timestamp as number
};
}
/**
* 转换 ESObject 数组为 ITrackPoint 数组
*/
static convertToTrackPoints(arr: ESObject[]): ITrackPoint[] {
const result: ITrackPoint[] = [];
arr.forEach((obj: ESObject) => {
const trackPoint = CSVExporter.convertToTrackPoint(obj);
if (trackPoint !== null) {
result.push(trackPoint);
}
});
return result;
}
}
3.使用
import { CSVExporter, CSVExportOptions, CSVExportResult, ITrackPoint } from './CSVExporter';
import fs from '@ohos.file.fs';
import { BusinessError } from '@kit.BasicServicesKit';
import picker from '@ohos.file.picker';
@Entry
@Component
struct TestCSVExportPage {
@State filePath: string = '';
@State csvContent: string = '';
@State showPreview: boolean = false;
private context = getContext(this);
// 测试数据
private testData: ITrackPoint[] = [
{
latitude: 36.089412,
longitude: 120.377728,
altitude: 4.9,
speed: 0.0,
timestamp: 1717223162000
},
{
latitude: 36.090038,
longitude: 120.377650,
altitude: 8.2,
speed: 3.5,
timestamp: 1717223295000
},
{
latitude: 36.091056,
longitude: 120.377515,
altitude: 12.5,
speed: 5.2,
timestamp: 1717223430000
},
{
latitude: 36.091212,
longitude: 120.376953,
altitude: 15.3,
speed: 6.8,
timestamp: 1717223565000
},
{
latitude: 36.091181,
longitude: 120.376372,
altitude: 18.7,
speed: 4.1,
timestamp: 1717223700000
},
{
latitude: 36.091181,
longitude: 120.375636,
altitude: 22.1,
speed: 7.5,
timestamp: 1717223835000
},
{
latitude: 36.091165,
longitude: 120.374571,
altitude: 25.8,
speed: 5.8,
timestamp: 1717223970000
},
{
latitude: 36.091150,
longitude: 120.373912,
altitude: 28.4,
speed: 3.2,
timestamp: 1717224105000
},
{
latitude: 36.091197,
longitude: 120.373292,
altitude: 31.2,
speed: 8.2,
timestamp: 1717224240000
},
{
latitude: 36.091744,
longitude: 120.373195,
altitude: 35.6,
speed: 4.5,
timestamp: 1717224375000
},
{
latitude: 36.092496,
longitude: 120.373195,
altitude: 38.9,
speed: 6.1,
timestamp: 1717224510000
},
{
latitude: 36.092731,
longitude: 120.373176,
altitude: 42.3,
speed: 2.8,
timestamp: 1717224645000
},
{
latitude: 36.093310,
longitude: 120.373157,
altitude: 45.7,
speed: 0.0,
timestamp: 1717224780000
}
];
/**
* 导出 CSV
*/
async exportCSV() {
try {
const options: CSVExportOptions = {
fileName: '我的轨迹',
includeHeader: false,
delimiter: ','
};
const result: CSVExportResult = await CSVExporter.exportToCSV(
this.context,
this.testData,
options
);
if (result.success && result.filePath) {
this.filePath = result.filePath;
AlertDialog.show({
title: '导出成功',
message: `已成功导出 ${this.testData.length} 个轨迹点\n\n文件大小: ${result.fileSize} 字节\n\n路径:\n${result.filePath}`,
confirm: {
value: '确定',
action: () => {
}
}
});
} else {
}
} catch (err) {
console.error('导出失败:', err);
}
}
/**
* 读取并预览 CSV 文件内容
*/
async previewCSV() {
if (!this.filePath) {
AlertDialog.show({
title: '提示',
message: '请先导出 CSV 文件',
confirm: {
value: '确定', action: () => {
}
}
});
return;
}
try {
// 读取文件内容
const file = fs.openSync(this.filePath, fs.OpenMode.READ_ONLY);
const stat = fs.statSync(this.filePath);
const buffer = new ArrayBuffer(stat.size);
fs.readSync(file.fd, buffer);
fs.closeSync(file);
// 转换为字符串
const uint8Array = new Uint8Array(buffer);
this.csvContent = this.arrayBufferToString(uint8Array);
// 显示预览
this.showPreview = true;
console.log('CSV 内容:', this.csvContent);
} catch (err) {
const error = err as BusinessError;
console.error('读取文件失败:', error);
AlertDialog.show({
title: '读取失败',
message: `无法读取文件: ${error.message}`,
confirm: {
value: '确定', action: () => {
}
}
});
}
}
/**
* ArrayBuffer 转字符串
*/
arrayBufferToString(buffer: Uint8Array): string {
let str = '';
for (let i = 0; i < buffer.length; i++) {
str += String.fromCharCode(buffer[i]);
}
return str;
}
async shareCSV() {
if (!this.filePath) {
AlertDialog.show({
title: '提示',
message: '请先导出 CSV 文件',
confirm: {
value: '确定', action: () => {
}
}
});
return;
}
try {
// 1. 配置文件选择器
const documentSaveOptions = new picker.DocumentSaveOptions();
documentSaveOptions.newFileNames = ['轨迹数据.csv'];
documentSaveOptions.fileSuffixChoices = ['.csv'];
// 2. 打开保存对话框
const documentPicker = new picker.DocumentViewPicker();
const uris: string[] = await documentPicker.save(documentSaveOptions);
if (!uris || uris.length === 0) {
return;
}
const targetUri = uris[0];
// 读取源文件
const sourceFile = fs.openSync(this.filePath, fs.OpenMode.READ_ONLY);
const stat = fs.statSync(this.filePath);
const buffer = new ArrayBuffer(stat.size);
fs.readSync(sourceFile.fd, buffer);
fs.closeSync(sourceFile);
// 写入目标文件(URI)
const targetFile = fs.openSync(targetUri, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE);
fs.writeSync(targetFile.fd, buffer);
fs.closeSync(targetFile);
AlertDialog.show({
title: '保存成功',
message: '文件已保存到外部存储\n\n可以用文件管理器或其他应用打开查看',
confirm: {
value: '确定', action: () => {
}
}
});
} catch (err) {
const error = err as BusinessError;
console.error('错误代码:', error.code);
console.error('错误信息:', error.message);
AlertDialog.show({
title: '分享失败',
message: `无法保存文件\n\n错误代码: ${error.code}\n错误信息: ${error.message}`,
confirm: {
value: '确定', action: () => {
}
}
});
}
}
/**
* 关闭预览
*/
closePreview() {
this.showPreview = false;
}
build() {
Stack() {
// 主界面
Column() {
Text('CSV 导出测试')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 50, bottom: 30 })
Text(`测试数据: ${this.testData.length} 个轨迹点`)
.fontSize(16)
.margin({ bottom: 30 })
// 按钮组
Column({ space: 15 }) {
Button('点击导出 CSV')
.width('80%')
.height(50)
.fontSize(18)
.backgroundColor('#0FD7B8')
.onClick(() => {
this.exportCSV();
})
if (this.filePath) {
Button(' 预览 CSV 内容')
.width('80%')
.height(50)
.fontSize(18)
.backgroundColor('#4A90E2')
.onClick(() => {
this.previewCSV();
})
Button(' 保存到手机')
.width('80%')
.height(50)
.fontSize(18)
.backgroundColor('#FF9800')
.onClick(() => {
this.shareCSV();
})
}
}
.margin({ bottom: 20 })
if (this.filePath) {
Column() {
Text(' 文件路径')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 8 })
Text(this.filePath)
.fontSize(11)
.fontColor('#666')
.textAlign(TextAlign.Center)
.maxLines(4)
}
.width('90%')
.padding(15)
.backgroundColor('#e8f5e9')
.borderRadius(8)
}
}
.width('100%')
.height('100%')
// 预览弹窗
if (this.showPreview) {
Column() {
// 标题栏
Row() {
Text('CSV 内容预览')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Button('关闭')
.fontSize(14)
.backgroundColor('#FF5252')
.onClick(() => {
this.closePreview();
})
}
.width('100%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius({ topLeft: 12, topRight: 12 })
// 内容区域
Scroll() {
Text(this.csvContent)
.fontSize(12)
.fontColor('#333')
.fontFamily('monospace')
.width('100%')
.padding(15)
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#F5F5F5')
.scrollBar(BarState.Auto)
}
.width('90%')
.height('70%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({
radius: 20,
color: '#000000',
offsetX: 0,
offsetY: 5
})
}
}
.width('100%')
.height('100%')
.backgroundColor(this.showPreview ? 'rgba(0, 0, 0, 0.5)' : '#FFFFFF')
}
}
更多推荐


所有评论(0)