第33集:uni-app 文件操作

核心知识点

1. 文件系统 API

uni-app 提供了丰富的文件系统 API,用于操作本地文件。主要包括以下几类:

  • 文件管理 APIuni.getFileSystemManager() 获取文件管理器
  • 文件读写 APIfs.readFile()fs.writeFile()fs.appendFile()
  • 目录操作 APIfs.mkdir()fs.rmdir()fs.readdir()
  • 文件信息 APIfs.stat()fs.access()
  • 文件删除 APIfs.unlink()
  • 文件重命名 APIfs.rename()
  • 文件复制 APIfs.copyFile()

2. 文件路径

uni-app 中的文件路径分为以下几种类型:

  • 临时文件路径:以 wxfile://tmp 开头,用于临时存储,应用重启后可能被清理
  • 本地文件路径:以 wxfile://usr 开头,用于持久化存储
  • 缓存文件路径:通过 uni.getStorageInfoSync().cachePath 获取
  • 本地存储路径:通过 uni.getStorageInfoSync().storagePath 获取

3. 文件上传下载

uni-app 提供了文件上传和下载的 API:

  • 文件上传uni.uploadFile()
  • 文件下载uni.downloadFile()

实用案例分析

案例1:实现文件管理功能

功能需求

实现一个简单的文件管理器,包括以下功能:

  1. 浏览本地文件目录
  2. 创建新文件夹
  3. 上传文件到服务器
  4. 从服务器下载文件
  5. 删除本地文件

代码实现

<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>

学习目标

通过本集的学习,你应该能够:

  1. 掌握 uni-app 文件系统 API 的使用方法
  2. 理解不同类型的文件路径及其用途
  3. 实现文件的读写、复制、重命名和删除操作
  4. 实现文件的上传和下载功能
  5. 开发基于文件操作的实际应用

小结

本集详细介绍了 uni-app 中的文件操作功能,包括文件系统 API、文件路径、文件上传下载等核心知识点,并通过两个实际案例展示了如何实现文件管理和图片压缩功能。

文件操作是移动应用开发中的重要组成部分,掌握这些技能可以帮助你开发出更加丰富和实用的应用。在实际开发中,你还需要注意文件权限、存储空间管理等问题,以确保应用的稳定性和用户体验。

通过本集的学习,你已经具备了在 uni-app 中进行文件操作的基本能力,可以开始开发需要文件管理功能的应用了。

« 上一篇 uni-app 扫码功能 下一篇 » uni-app 蓝牙功能