Vue3 导入导出

Vue3 导入导出

常见的模式以及库

javascript 复制代码
// FileSaver

https://github.com/eligrey/FileSaver.js/

👉导出用户

这里后端接口对应我们Rust导出方式一

🍎安装依赖file-saver模式

javascript 复制代码
yarn add file-saver

🍎用户导出Excel文档部分

javascript 复制代码
import { ref, reactive, toRefs, onMounted, watch,getCurrentInstance} from 'vue'
const { proxy } = getCurrentInstance();

/** 导出按钮操作 */
function handleExport() {
  proxy.$download("/system/users/export", {
      ...queryParams.value,
  }, `role_${new Date().getTime()}.xlsx`);
}

🍎通用导出方法

这里我们可以直接写到axios的拦截器部分,因为我们导出的时候依靠的需要拦截流

src\utils\request.ts

这里我们拦截流,主要是我们响应拦截器,当后端返回来的是流数据的时候,前端这个时候就不能按照普通数据一样处理

javascript 复制代码
// 响应拦截器
// 在 axios 配置文件中
service.interceptors.response.use(
  (response) => {
    // 如果是 blob 响应,直接返回,不要解包
    if (response.config.responseType === 'blob') {
      return response; // 保留完整的 response 对象
    }
    // 其他响应正常解包
    return response.data;
  },
  (error) => {
    return Promise.reject(error);
  }
);
javascript 复制代码
// 通用导出下载方法
// 创建axios实例
import axios from 'axios'
import { saveAs } from 'file-saver'
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: "http://localhost:8888/api",//import.meta.env.VITE_APP_BASE_API,
  // 超时
  timeout: 10000
})

// 将JavaScript对象转换为 URL 查询参数字符串
export function transParams(params) {
  let result = ''
  for (const propName of Object.keys(params)) {
    const value = params[propName]
    var part = encodeURIComponent(propName) + "="
    if (value !== null && value !== "" && typeof (value) !== "undefined") {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
            let params = propName + '[' + key + ']'
            var subPart = encodeURIComponent(params) + "="
            result += subPart + encodeURIComponent(value[key]) + "&"
          }
        }
      } else {
        result += part + encodeURIComponent(value) + "&"
      }
    }
  }
  return result
}
// 下载方法
export const download = (url, params, filename, config = {}) => {
  return service.post(url, params, {
    transformRequest: [(params) => { return transParams(params) }],
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    responseType: 'blob',
    ...config
  }).then(async (response) => {
    // 确保 response 是完整的响应对象,不是被解包的数据
    const data = response.data || response;
    
    // 检查是否为错误响应(JSON 格式)
    const contentType = response.headers?.['content-type'] || '';
    if (contentType.includes('application/json')) {
      const text = await data.text();
      const json = JSON.parse(text);
      ElMessage.error(json.msg || '下载失败');
      return;
    }
    
    // 创建 blob 并下载
    const blob = new Blob([data], { type: 'text/csv;charset=utf-8;' });
    saveAs(blob, filename || 'users.csv');
  }).catch((r) => {
    console.error(r);
    ElMessage.error('下载文件出现错误,请联系管理员!');
  });
};

🍎注册使用

javascript 复制代码
import { download} from '@/utils/methods'

export default {
  install(app) {
    // 全局方法
    app.config.globalProperties.$download = download
  }
}

🍎测试一下这个地址

javascript 复制代码
http://localhost:8888/api/system/users/export

看看我们拿到的信息,直接访问这个地址。
或者去掉token认证信息,然后再进行访问

这个时候我们之前写的接口

javascript 复制代码
can not parse "export" to a i32

尝试一下,导出方法已经ok了,这就是采取最普通的file-saver的方式进行的导出

我们导出的数据大致如下

javascript 复制代码
用户ID,用户名,姓名,年龄,性别,电话,地址,状态,头像,身高,体重,疾病
44,666666,san,666666,1,18735797977,666666,0,/uploads/images/1746683201317-nexusvue-feedback.png,,,18735797977
65,123456,嗯,,1,18735797977,111,0,/uploads/images/1748584154718-2.png,,,18735797977
67,admin,,20,0,admin,,0,/uploads/images/9ff7b427-fe1b-46c6-a7c9-20823aac07c0.png,,,

🍎添加加载效果

这里添加一个导出时候的加载效果

glabalui.ts这里我们搭建一个文件。用于不同UI库使用同样代码实现

javascript 复制代码
// 全局UI展示
// 目的=> 切换Element Plus和 Antd库的UI组件
// 打开遮罩层
import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus'
let loadingInstance; // 全局loading

/**
 * 显示加载遮罩层的函数
 * @param {string} content - 加载时显示的文本内容
 */
export const loading=(content)=>{
  // 创建并显示一个全屏的加载遮罩层
  // ElLoading.service 是 Element UI 提供的全局服务方法
  loadingInstance = ElLoading.service({
    // 是否锁定屏幕滚动
    lock: true,
    // 加载时显示的文本内容
    text: content,
    // 遮罩层背景颜色,使用 rgba 格式设置半透明黑色
    background: "rgba(0, 0, 0, 0.7)",
  });
};

// 关闭遮罩层
export const closeLoading=()=> {
  loadingInstance.close();
}

👉下载用户模板

🍎导出逻辑

javascript 复制代码
<el-col :span="1.5">
    <el-button type="info" icon="Download" @click="handleDowload"
        v-hasPermi="['system:role:dowload']">下载模板</el-button>
</el-col>


// 导出用户模板
const handleDowload = async () => {
    proxy.$download("/system/users/exporttemplate", {}, `role_${new Date().getTime()}.xlsx`);
}

🍎通用方法

javascript 复制代码
import { saveAs } from 'file-saver';
// 将JavaScript对象转换为 URL 查询参数字符串
export function transParams(params) {
  let result = ''
  for (const propName of Object.keys(params)) {
    const value = params[propName]
    var part = encodeURIComponent(propName) + "="
    if (value !== null && value !== "" && typeof (value) !== "undefined") {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
            let params = propName + '[' + key + ']'
            var subPart = encodeURIComponent(params) + "="
            result += subPart + encodeURIComponent(value[key]) + "&"
          }
        }
      } else {
        result += part + encodeURIComponent(value) + "&"
      }
    }
  }
  return result
}
// 下载方法
export const download = (url, params, filename, config = {}) => {
  loading('正在下载,请稍候...');
  return service.post(url, params, {
    transformRequest: [(params) => { return transParams(params) }],
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    responseType: 'blob',
    ...config
  }).then(async (response) => {
    // 确保 response 是完整的响应对象,不是被解包的数据
    const data = response.data || response;
    
    // 检查是否为错误响应(JSON 格式)
    const contentType = response.headers?.['content-type'] || '';
    if (contentType.includes('application/json')) {
      const text = await data.text();
      const json = JSON.parse(text);
      ElMessage.error(json.msg || '下载失败');
      return;
    }
    
    // 创建 blob 并下载
    const blob = new Blob([data], { type: 'text/csv;charset=utf-8;' });
    saveAs(blob, filename || 'users.csv');
    closeLoading();
  }).catch((r) => {
    console.error(r);
    closeLoading();
    ElMessage.error('下载文件出现错误,请联系管理员!');
  });
};

👉导入用户

🍎导出功能

这里我们直接先把接口写上去,后面自己拼接一下就行

javascript 复制代码
 <el-col :span="1.5">
      <el-upload class="upload-demo"
      :on-success="handleSuccess"
      action='http://localhost:8888/api/system/users/import' 
      :before-upload="beforeFileUpload" accept=".xls,xlsx"
          :show-file-list="false">
          <el-button type="primary" icon="Upload">导入数据</el-button>
      </el-upload>
  </el-col>

方法逻辑

javascript 复制代码
const beforeFileUpload = (rawFile) => {
  const fileExtension = rawFile.name.split('.').pop().toLowerCase(); // 获取文件扩展名
  if (fileExtension !== 'xls' && fileExtension !== 'xlsx') {
    ElMessage.error('文件类型为.xls or .xlsx格式');
    return false;
  } else if (rawFile.size / 1024 / 1024 > 50) { // 设置最大文件大小为 5MB
    ElMessage.error('文件超过 50MB!');
    return false;
  }

  return true;
};

🍎完善成功信息提示

javascript 复制代码
// 导入成功
const handleSuccess = (res,uploadFile) => {
    if(res.code==200){
        ElMessage.success(res.msg)
    }else{
        ElMessage.error(res.msg)
    }
    // console.log(res,uploadFile);
}

🍎完善加载效果

javascript 复制代码
// 上传前文件检测
const beforeFileUpload = (rawFile) => {
  loading.value = true;
  const fileExtension = rawFile.name.split('.').pop().toLowerCase(); // 获取文件扩展名
  if (fileExtension !== 'xls' && fileExtension !== 'xlsx') {
    ElMessage.error('文件类型为.xls or .xlsx格式');
    loading.value = false;
    return false;
  } else if (rawFile.size / 1024 / 1024 > 50) { // 设置最大文件大小为 5MB
    ElMessage.error('文件超过 50MB!');
    loading.value = false;
    return false;
  }
  return true;
};

测试一下,ok。可以继续优化了

相关推荐
用户游民12 分钟前
Flutter 项目热更新方案对比与实现指南
前端
泉城老铁20 分钟前
前端实现人体动作识别
前端·vue.js
熊猫片沃子24 分钟前
忘记Mysql登录密码,还在傻傻的重装服务吗❓
前端·后端·mysql
拉不动的猪24 分钟前
移动端音频插件howler简单配置
前端·javascript·vue.js
前端老鹰25 分钟前
CSS counter-reset 与 counter-increment:用 CSS 实现自动编号的黑科技
前端·css·html
前端说明书28 分钟前
🚀🚀🚀 文心快码Zulu IDE :AI全能助手如何重塑开发工作流
前端·trae
李剑一40 分钟前
面试官:Vue 中 data 属性为什么是一个函数而不是对象?
前端·面试
默默地离开40 分钟前
小编第一次面试吓尿了,赶快来写篇文章压压经
前端·面试·程序员
7VI42 分钟前
ruoyi数据权限@DataPermission源码解析
前端
G等你下课1 小时前
基于 Transformer.js 的浏览器端文本转语音应用
前端·aigc