第33集:uni-app 文件操作
核心知识点
1. 文件系统 API
uni-app 提供了丰富的文件系统 API,用于操作本地文件。主要包括以下几类:
- 文件管理 API:
uni.getFileSystemManager()获取文件管理器 - 文件读写 API:
fs.readFile()、fs.writeFile()、fs.appendFile()等 - 目录操作 API:
fs.mkdir()、fs.rmdir()、fs.readdir()等 - 文件信息 API:
fs.stat()、fs.access()等 - 文件删除 API:
fs.unlink() - 文件重命名 API:
fs.rename() - 文件复制 API:
fs.copyFile()
2. 文件路径
uni-app 中的文件路径分为以下几种类型:
- 临时文件路径:以
wxfile://tmp开头,用于临时存储,应用重启后可能被清理 - 本地文件路径:以
wxfile://usr开头,用于持久化存储 - 缓存文件路径:通过
uni.getStorageInfoSync().cachePath获取 - 本地存储路径:通过
uni.getStorageInfoSync().storagePath获取
3. 文件上传下载
uni-app 提供了文件上传和下载的 API:
- 文件上传:
uni.uploadFile() - 文件下载:
uni.downloadFile()
实用案例分析
案例1:实现文件管理功能
功能需求
实现一个简单的文件管理器,包括以下功能:
- 浏览本地文件目录
- 创建新文件夹
- 上传文件到服务器
- 从服务器下载文件
- 删除本地文件
代码实现
<template>
<view class="container">
<view class="header">
<text class="title">文件管理器</text>
<button @click="createFolder" type="primary" size="mini">新建文件夹</button>
</view>
<view class="path-bar">
<text>{{ currentPath }}</text>
</view>
<view class="file-list">
<view v-for="(file, index) in fileList" :key="index" class="file-item">
<view class="file-info" @click="handleFileClick(file)">
<view class="file-icon">
<text v-if="file.isDirectory">📁</text>
<text v-else>📄</text>
</view>
<view class="file-details">
<text class="file-name">{{ file.name }}</text>
<text class="file-size" v-if="!file.isDirectory">{{ formatFileSize(file.size) }}</text>
</view>
</view>
<button v-if="!file.isDirectory" @click="downloadFile(file)" type="default" size="mini">下载</button>
<button @click="deleteFile(file)" type="warn" size="mini">删除</button>
</view>
</view>
<view class="upload-area">
<text class="upload-title">上传文件</text>
<button @click="chooseFile" type="primary">选择文件</button>
<view v-if="uploadProgress > 0 && uploadProgress < 100" class="progress-bar">
<view class="progress" :style="{ width: uploadProgress + '%' }"></view>
</view>
<text v-if="uploadProgress === 100" class="upload-success">上传成功</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentPath: '/',
fileList: [],
uploadProgress: 0,
fs: null
};
},
onLoad() {
// 初始化文件系统管理器
this.fs = uni.getFileSystemManager();
// 加载根目录文件
this.loadFiles('/');
},
methods: {
// 加载文件列表
loadFiles(path) {
const storagePath = uni.getStorageInfoSync().storagePath;
const fullPath = storagePath + path;
this.fs.readdir({
dirPath: fullPath,
success: (res) => {
const files = [];
res.files.forEach(fileName => {
try {
const fileStat = this.fs.statSync(fullPath + '/' + fileName);
files.push({
name: fileName,
path: path + '/' + fileName,
size: fileStat.size,
isDirectory: fileStat.isDirectory()
});
} catch (e) {
console.error('获取文件信息失败:', e);
}
});
this.fileList = files;
this.currentPath = path;
},
fail: (err) => {
console.error('读取目录失败:', err);
uni.showToast({ title: '读取目录失败', icon: 'none' });
}
});
},
// 处理文件点击
handleFileClick(file) {
if (file.isDirectory) {
// 进入子目录
this.loadFiles(file.path);
} else {
// 打开文件
uni.openDocument({
filePath: uni.getStorageInfoSync().storagePath + file.path,
success: function (res) {
console.log('打开文档成功');
},
fail: function (err) {
console.error('打开文档失败:', err);
uni.showToast({ title: '无法打开此文件', icon: 'none' });
}
});
}
},
// 创建文件夹
createFolder() {
uni.showModal({
title: '新建文件夹',
content: '请输入文件夹名称',
editable: true,
placeholderText: '文件夹名称',
success: (res) => {
if (res.confirm && res.content) {
const storagePath = uni.getStorageInfoSync().storagePath;
const folderPath = storagePath + this.currentPath + '/' + res.content;
try {
this.fs.mkdirSync({ dirPath: folderPath, recursive: true });
uni.showToast({ title: '文件夹创建成功', icon: 'success' });
// 重新加载文件列表
this.loadFiles(this.currentPath);
} catch (e) {
console.error('创建文件夹失败:', e);
uni.showToast({ title: '创建文件夹失败', icon: 'none' });
}
}
}
});
},
// 选择文件上传
chooseFile() {
uni.chooseMessageFile({
count: 1,
type: 'all',
success: (res) => {
const tempFilePaths = res.tempFiles;
this.uploadFile(tempFilePaths[0]);
}
});
},
// 上传文件
uploadFile(file) {
const storagePath = uni.getStorageInfoSync().storagePath;
const localPath = storagePath + this.currentPath + '/' + file.name;
// 先将临时文件保存到本地
this.fs.copyFile({
srcPath: file.path,
destPath: localPath,
success: () => {
// 上传到服务器
uni.uploadFile({
url: 'https://example.com/upload',
filePath: localPath,
name: 'file',
formData: {
'user': 'test'
},
success: (uploadFileRes) => {
console.log('上传成功:', uploadFileRes.data);
this.uploadProgress = 100;
setTimeout(() => {
this.uploadProgress = 0;
}, 2000);
uni.showToast({ title: '上传成功', icon: 'success' });
// 重新加载文件列表
this.loadFiles(this.currentPath);
},
fail: (err) => {
console.error('上传失败:', err);
uni.showToast({ title: '上传失败', icon: 'none' });
},
progress: (res) => {
this.uploadProgress = res.progress;
console.log('上传进度:', res.progress);
}
});
},
fail: (err) => {
console.error('保存文件失败:', err);
uni.showToast({ title: '保存文件失败', icon: 'none' });
}
});
},
// 下载文件
downloadFile(file) {
uni.downloadFile({
url: 'https://example.com/download/' + file.name,
success: (res) => {
if (res.statusCode === 200) {
const storagePath = uni.getStorageInfoSync().storagePath;
const localPath = storagePath + this.currentPath + '/' + file.name;
// 保存下载的文件
this.fs.writeFile({
filePath: localPath,
data: res.tempFilePath,
encoding: 'binary',
success: () => {
console.log('文件下载成功并保存');
uni.showToast({ title: '下载成功', icon: 'success' });
// 重新加载文件列表
this.loadFiles(this.currentPath);
},
fail: (err) => {
console.error('保存文件失败:', err);
uni.showToast({ title: '保存文件失败', icon: 'none' });
}
});
}
},
fail: (err) => {
console.error('下载失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
},
// 删除文件
deleteFile(file) {
uni.showModal({
title: '删除文件',
content: `确定要删除 ${file.name} 吗?`,
success: (res) => {
if (res.confirm) {
const storagePath = uni.getStorageInfoSync().storagePath;
const filePath = storagePath + file.path;
try {
if (file.isDirectory) {
// 删除目录
this.fs.rmdirSync({ dirPath: filePath, recursive: true });
} else {
// 删除文件
this.fs.unlinkSync(filePath);
}
uni.showToast({ title: '删除成功', icon: 'success' });
// 重新加载文件列表
this.loadFiles(this.currentPath);
} catch (e) {
console.error('删除文件失败:', e);
uni.showToast({ title: '删除文件失败', icon: 'none' });
}
}
}
});
},
// 格式化文件大小
formatFileSize(size) {
if (size < 1024) {
return size + ' B';
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + ' KB';
} else if (size < 1024 * 1024 * 1024) {
return (size / (1024 * 1024)).toFixed(2) + ' MB';
} else {
return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
}
}
}
};
</script>
<style scoped>
.container {
padding: 20rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
}
.path-bar {
padding: 15rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
margin-bottom: 20rpx;
font-size: 28rpx;
}
.file-list {
margin-bottom: 30rpx;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #eaeaea;
}
.file-info {
display: flex;
align-items: center;
flex: 1;
}
.file-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.file-details {
flex: 1;
}
.file-name {
font-size: 30rpx;
margin-bottom: 5rpx;
display: block;
}
.file-size {
font-size: 24rpx;
color: #999;
}
.upload-area {
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
}
.upload-title {
font-size: 28rpx;
margin-bottom: 15rpx;
display: block;
}
.progress-bar {
height: 20rpx;
background-color: #eaeaea;
border-radius: 10rpx;
margin: 15rpx 0;
overflow: hidden;
}
.progress {
height: 100%;
background-color: #007aff;
border-radius: 10rpx;
}
.upload-success {
color: #07c160;
font-size: 24rpx;
margin-top: 10rpx;
display: block;
}
</style>案例2:实现图片压缩功能
功能需求
实现一个图片压缩功能,用户选择图片后,可以调整压缩质量,然后保存压缩后的图片。
代码实现
<template>
<view class="container">
<view class="header">
<text class="title">图片压缩工具</text>
</view>
<view class="content">
<button @click="chooseImage" type="primary">选择图片</button>
<view v-if="originalImage" class="image-section">
<text class="section-title">原图</text>
<image :src="originalImage" class="image" mode="aspectFit"></image>
<text class="image-info">大小: {{ originalSize }}</text>
</view>
<view v-if="originalImage" class="compression-section">
<text class="section-title">压缩设置</text>
<view class="slider-container">
<text>压缩质量: {{ quality }}%</text>
<slider v-model="quality" min="10" max="100" step="5" show-value></slider>
</view>
<button @click="compressImage" type="primary">开始压缩</button>
</view>
<view v-if="compressedImage" class="image-section">
<text class="section-title">压缩后</text>
<image :src="compressedImage" class="image" mode="aspectFit"></image>
<text class="image-info">大小: {{ compressedSize }}</text>
<text class="compression-ratio">压缩率: {{ compressionRatio }}%</text>
<button @click="saveCompressedImage" type="primary">保存图片</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
originalImage: '',
compressedImage: '',
originalSize: '',
compressedSize: '',
compressionRatio: 0,
quality: 80,
fs: null
};
},
onLoad() {
this.fs = uni.getFileSystemManager();
},
methods: {
// 选择图片
chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.originalImage = res.tempFilePaths[0];
this.compressedImage = '';
// 获取原图大小
const fileInfo = res.tempFiles[0];
this.originalSize = this.formatFileSize(fileInfo.size);
}
});
},
// 压缩图片
compressImage() {
uni.showLoading({ title: '压缩中...' });
uni.compressImage({
src: this.originalImage,
quality: this.quality,
success: (res) => {
this.compressedImage = res.tempFilePath;
// 获取压缩后图片大小
this.fs.getFileInfo({
filePath: res.tempFilePath,
success: (fileInfo) => {
this.compressedSize = this.formatFileSize(fileInfo.size);
// 计算压缩率
const originalSize = parseInt(this.originalSize);
const compressedSize = fileInfo.size;
this.compressionRatio = Math.round((1 - compressedSize / originalSize) * 100);
uni.hideLoading();
uni.showToast({ title: '压缩成功', icon: 'success' });
}
});
},
fail: (err) => {
console.error('压缩失败:', err);
uni.hideLoading();
uni.showToast({ title: '压缩失败', icon: 'none' });
}
});
},
// 保存压缩后的图片
saveCompressedImage() {
uni.saveImageToPhotosAlbum({
filePath: this.compressedImage,
success: () => {
uni.showToast({ title: '保存成功', icon: 'success' });
},
fail: (err) => {
console.error('保存失败:', err);
uni.showToast({ title: '保存失败', icon: 'none' });
}
});
},
// 格式化文件大小
formatFileSize(size) {
if (size < 1024) {
return size + ' B';
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + ' KB';
} else {
return (size / (1024 * 1024)).toFixed(2) + ' MB';
}
}
}
};
</script>
<style scoped>
.container {
padding: 20rpx;
}
.header {
margin-bottom: 30rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
display: block;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
}
.image-section {
margin: 20rpx 0;
width: 100%;
text-align: center;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 15rpx;
display: block;
}
.image {
width: 100%;
height: 400rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
margin-bottom: 10rpx;
}
.image-info {
font-size: 24rpx;
color: #666;
margin-bottom: 5rpx;
display: block;
}
.compression-ratio {
font-size: 24rpx;
color: #007aff;
margin-bottom: 15rpx;
display: block;
}
.compression-section {
width: 100%;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
margin: 20rpx 0;
}
.slider-container {
margin: 20rpx 0;
}
.slider-container text {
display: block;
margin-bottom: 10rpx;
font-size: 26rpx;
}
</style>学习目标
通过本集的学习,你应该能够:
- 掌握 uni-app 文件系统 API 的使用方法
- 理解不同类型的文件路径及其用途
- 实现文件的读写、复制、重命名和删除操作
- 实现文件的上传和下载功能
- 开发基于文件操作的实际应用
小结
本集详细介绍了 uni-app 中的文件操作功能,包括文件系统 API、文件路径、文件上传下载等核心知识点,并通过两个实际案例展示了如何实现文件管理和图片压缩功能。
文件操作是移动应用开发中的重要组成部分,掌握这些技能可以帮助你开发出更加丰富和实用的应用。在实际开发中,你还需要注意文件权限、存储空间管理等问题,以确保应用的稳定性和用户体验。
通过本集的学习,你已经具备了在 uni-app 中进行文件操作的基本能力,可以开始开发需要文件管理功能的应用了。