Adele0

Adele0 查看完整档案

成都编辑成都理工大学  |  英语 编辑  |  填写所在公司/组织 github.com/Adele0 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

Adele0 发布了文章 · 10月16日

vue+el动态列

效果图

image.png

后端返回の数据结构

返回的result.labelList是动态label。result.list,包含所有数据,其中labelMap是动态列的键值对结果。
image.png

前端HTML

*template很重要~~

<el-table-column
   v-for="label in labelArr"
   :key="label"
   :label="label"
  >
    <template slot-scope="scope">
      <span>{{ scope.row.labelMap[label] }}</span>
     </template>
   </el-table-column>

前端JS

this.labelArr = [];
this.$set(this, "labelArr", res.result.labelList);
this.$set(this, `${which}`, res.result.list);
查看原文

赞 0 收藏 0 评论 0

Adele0 发布了文章 · 8月21日

vue+el+oss上传/预览/下载/删除/存数据库(前端操作oss的详细实现)

注:
1. 一般情况不会让前端直接操作oss(增删查),可是我遇到了这样的需求
2. 默认已经开通oss,在oss官网完成了配置
3. 需要使用到element-ui的upload组件

点击跳转oss的官方API
点击跳转element的官方API

需求描述

  1. 前端直接操作oss,类型可以是任何类型。本demo以图片和压缩包为例,图片可以预览、删除;压缩包可以下载、删除。
  2. 在表单填写时,一个表单可能有多个不同类型文件的需要上传;表单涉及新增/查看/编辑功能,复用性要强。
  3. 新增表单的具体需求:表单新增之后oss即可编辑,一旦新增过文件后要么删除已上传文件,要么提交表单,提交不成功根据后端提示重新修改表单的错误重新提交,或者删除放弃提交。
  4. 查看表单的具体需求:查看表单不可操作oss,但是对于图片可以进行预览,压缩包可以进行下载。
  5. 修改表单的具体需求:一旦修改过文件(删除,新增,修改)必须提交表单,提交不成功根据后端提示重新修改表单的错误重新提交,直到成功为止。
  6. F5强刷也要保证上述5条,私密麻生,我太难了,我没做

效果展示

新增

新增

查看

查看

编辑

编辑

图片预览(放大)

图片预览(放大)

新增但不提交的提示

新增提示

删除但不提交的提示

删除提示

修改但不提交的提示

修改提示

前期准备

  1. npm install ali-oss
  2. 需要运维提供accessKeyIdaccessKeySecretbucket
  3. 其中bucket最好区分开发环境和生产环境
  4. oss官方推荐使用STS临时授权访问,我这里没有用,有兴趣的话可以移步OSS临时授权

utils.js(工具类)

import axios from 'axios'
import OSS from 'ali-oss'
import UUID4 from 'uuid/v4'

// 下载oss指定地址
export function downloadURL(url) {
  let link = document.createElement('a');
  link.style.display = 'none';
  link.href = url;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

// 上传oss
export function ossUpload(picForProject, fileBuffer) {
  let bucketName = '';
  // 判断环境 生产环境并且不为测试状态
  process.env.NODE_ENV === 'production' ? bucketName = '填你自己申请的' : bucketName = '填你自己申请的';

  let client = new OSS({
    region: 'oss-cn-beijing',
    secure: true, // https
    accessKeyId: '填你自己申请的',
    accessKeySecret: '填你自己申请的',
    bucket: bucketName
  });

  const fileName = UUID4().toUpperCase().replace(/-/g, '')
  const suffix = fileBuffer.name.substr(fileBuffer.name.indexOf('.'))
  const url = `${picForProject}${fileName}${suffix}`

  async function put() {
    try {
      await client.put(url, fileBuffer)
      // await xxx(url) 在这里也可以直接把地址传给后端
      return { 'code': 200, 'url': url }
    } catch (e) {
      return e.message
    }
  }

  return put()
};

// 获取oss地址
export function showPrivateOss(picName) {
  // 这里和上传oss的client是一样的,应该放在store里面同一管理的,demo这里写在这里了
  let bucketName = '';
  process.env.NODE_ENV === 'production' ? bucketName = '填你自己申请的' : bucketName = '填你自己申请的';

  let client = new OSS({
    secure: true, // https
    accessKeyId: '填你自己申请的',
    accessKeySecret: '填你自己申请的',
    bucket: bucketName,
    endpoint: 'oss-cn-beijing.aliyuncs.com'
  });

  async function getOssAddr() {
    try {
      let signUrl = client.signatureUrl(picName, { expires: 1800 }); // expires单位为秒
      return { 'code': 200, 'url': signUrl }
    } catch (e) {
      return e.message
    }
  }

  return getOssAddr()
};

// 删除oss
export function deletePrivateOss(picName) {
  let bucketName = '';
  process.env.NODE_ENV === 'production' ? bucketName = '填你自己申请的' : bucketName = '填你自己申请的';

  let client = new OSS({
    secure: true, // https
    accessKeyId: '填你自己申请的',
    accessKeySecret: '填你自己申请的',
    bucket: bucketName,
    endpoint: 'oss-cn-beijing.aliyuncs.com'
  });

  async function deleteOssAddr() {
    try {
      return client.delete(picName);
    } catch (e) {
      return e.message
    }
  }

  return deleteOssAddr()
};

oss.vue(oss组件)

<template>
  <div>
    <el-upload
      class="avatar-uploader"
      action="oss地址"
      :show-file-list="false"
      :http-request="uploadToOss"
      :before-upload="beforeAvatarUpload"
      :disabled="disabled"
      :accept="accept"
      :typeName="typeName"
    >
      <div v-loading="loading">
        <template v-if="haveUrl">
          <img :data-original="imgUrl" class="avatar" />
          <span class="show_icon" v-if="!disabled">
            <i class="el-icon-view" @click.stop="handleView()" v-if="typeName==='图片'"></i>
            <i class="el-icon-download" @click.stop="handleDownload()" v-else></i>
            <i class="el-icon-delete" @click.stop="handleRemove()"></i>
          </span>
          <span class="show_icon" v-else>
            <i class="el-icon-view" @click.stop="handleView()" v-if="typeName==='图片'"></i>
            <i class="el-icon-download" @click.stop="handleDownload()" v-else></i>
          </span>
        </template>
        <i v-else class="el-icon-plus avatar-uploader-icon"></i>
      </div>
    </el-upload>
    <!--查看图片-->
    <el-dialog title="照片详情" :visible.sync="showBigPic" width="720px" append-to-body>
      <img :data-original="imgUrl" width="100%" />
    </el-dialog>
  </div>
</template>

<script>
// 引用工具类(下载指定地址的oss文件、上传oss、展示oss图片、删除指定地址的oss文件)
import {
  downloadURL,
  ossUpload,
  showPrivateOss,
  deletePrivateOss,
} from "@/assets/scripts/utils";
// 二次封装的element的message组件,代码略
import { errorMsg, successMsg } from "@/components/message";

export default {
  props: {
    //对文件的限制
    limit: {
      type: Object,
      default: function () {
        return {
          size: 5,
          types: ["image/jpeg", "image/png"],
        };
      },
    },
    // 是否有url
    haveUrl: {
      type: Boolean,
      default: false,
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false,
    },
    // url的名称
    urlName: {
      type: String,
      default: "",
    },
    // 对应的后端字段名,同一页面可能有多个需要上传oss的
    name: {
      type: String,
      default: "",
    },
    // 上传弹窗,过滤文件类型
    accept: {
      type: String,
      default: "image/*",
    },
    // 上传文件类型(文件大小过大提示语也会用到)
    typeName: {
      type: String,
      default: "图片",
    },
  },
  data() {
    return {
      showBigPic: false, // 预览图片
      imgUrl: null,     // 图片地址
      ossFileName: "", // oss文件名
      loading: false, // 是否上传中
    };
  },
  watch: {
    urlName: {
      handler(newValue, oldValue) {
        if (newValue) {
          if (this.typeName !== "图片" && this.haveUrl) {
            this.imgUrl = require("@/assets/images/uploaded.jpg");
          } else {
            showPrivateOss(newValue).then((res) => {
              if (res && res.code) {
                this.imgUrl = res.url;
              } else {
                errorMsg(res);
              }
            });
          }
        }
      },
      immediate: true,
    },
  },
  methods: {
    handleView() {
      this.showBigPic = true;
    },
    handleDownload() {
      showPrivateOss(this.urlName).then((res) => {
        if (res && res.code) {
          downloadURL(res.url);
        } else {
          errorMsg(res);
        }
      });
    },
    handleRemove() {
      let _this = this;
      deletePrivateOss(this.urlName).then((res) => {
        if (res.res.status === 204) {
          successMsg();
          // ossDone参数:url文件地址, haveUrl是否有地址, name字段名
          this.$emit("callback", null, false, _this.name);
        } else {
          errorMsg(res);
        }
      });
    },
    // 上传oss
    uploadToOss(elUpload) {
      this.loading = true;
      let _this = this;
      ossUpload("back-image/" + this.ossFileName, elUpload.file).then((res) => {
        if (res && res.code) {
          // ossDone参数:url文件地址, haveUrl是否有地址, name字段名
          this.$emit("callback", res.url, true, _this.name);
        } else {
          errorMsg(res);
        }
        this.loading = false;
      });
    },
    // 图片上传前 验证图片大小
    beforeAvatarUpload(file) {
      let isSize = false;
      if (this.limit.sizeType === "kb") {
        isSize = file.size / 1024 < this.limit.size;
      } else {
        isSize = file.size / 1024 / 1024 < this.limit.size;
      }
      if (!isSize) {
        errorMsg(
          `${this.typeName}大小不能超过${this.limit.size}${
            this.limit.sizeType ? this.limit.sizeType : "MB"
          }!`
        );
      }
      return isSize;
    },
  },
};
</script>

<style lang="less" scoped>
.show_icon {
  display: inline-block;
  position: absolute;
  top: 0;
  right: 0;
  width: 108px;
  height: 72px;
  i {
    display: none;
  }
  &:hover {
    background-color: rgba(0, 0, 0, 0.5);
    i {
      display: inline;
      line-height: 72px;
      color: #fff;
      font-size: 20px;
      &:not(:last-child) {
        margin-right: 10px;
      }
    }
  }
}
</style>

addOrEdit.vue(表单组件;新增/查看/编辑为同一个组件)

/*
**这是一个弹窗组件,为了代码简洁,删除了其他表单内容
**父组件传来id,即判定是查看/编辑,需要请求后端详情
**:before-close需要带参数,重要!!
*/
<template>
  <el-dialog
    @open="setData"
    :visible.sync="params.show"
    :before-close="() => cancel(false)"
    :close-on-click-modal="false"
    width="1100px"
    append-to-body
  >
    <div slot="title">
      {{params.title}}
      <span class="title_required">
        <span>*</span>为必填项
      </span>
    </div>
    <el-form
      :model="addForm"
      :inline="true"
      ref="addForm"
      label-position="left"
      label-width="125px"
    >
      // 其他item略
      <el-form-item
        label="营业执照照片"
        prop="businessLicenseUrl"
        :rules="[{required: true,message: '不能为空'}]"
      >
        <oss
          @callback="ossDone"
          :haveUrl="haveUrl_businessLicenseUrl"
          :disabled="isDisabled"
          :urlName="addForm.businessLicenseUrl"
          name="businessLicenseUrl"
        />
      </el-form-item>
      <el-form-item label="附件">
        <oss
          @callback="ossDone"
          :limit="limit"
          :haveUrl="haveUrl_attachment"
          :urlName="addForm.attachment"
          :disabled="isDisabled"
          accept=".zip, .rar"
          name="attachment"
          typeName="压缩包"
        />
      </el-form-item>
    </el-form>
    <span slot="footer">
      <el-button @click="cancel(false)" class="cancelBtn">取 消</el-button>
      <el-button
        @click="submit"
        type="primary"
        class="confirmBtn"
        v-if="this.params.title !=='查看'"
      >确 定</el-button>
    </span>
  </el-dialog>
</template>

<script>
import oss from "@/components/oss";
import { methodPost } from "@/api";  // 新增/修改用【代码略】
import { GET_DETAILS } from "@/api/company"; //详情【代码略】
import { errorMsg, successMsg } from "@/components/message"; // 二次封装的message【代码略】

export default {
  components: { oss },
  data() {
    return {
      // 是否有执照
      haveUrl_businessLicenseUrl: false,
      // 是否有附件
      haveUrl_attachment: false,
      // 编辑时,附件的返回值(作为flag)
      flag_attachment: null,
      // 编辑时,执照的返回值(作为flag)
      flag_businessLicenseUrl: null,
      // 附件的大小
      limit: {
        size: 10
      }
    };
  },
  methods: {
    // 操作oss后的$emit
    ossDone(url, haveUrl, name) {
      this.$set(this.addForm, `${name}`, url);
      this.$set(this, `haveUrl_${name}`, haveUrl);
    },
    // 弹窗初始化
    setData() {
      this.addForm = {};
      this.isDisabled = false;
      if (this.params.title !== "新增") {
        this.haveUrl_businessLicenseUrl = true;
        this.getDetails({ id: this.params.id });
      } else {
        this.haveUrl_attachment = false;
        this.haveUrl_businessLicenseUrl = false;
        this.flag_attachment = null;
        this.flag_businessLicenseUrl = null;
      }
    },
    getDetails(id) {
      GET_DETAILS(id).then((res) => {
        if (res.code === 10000) {
          this.addForm = res.result;
          this.flag_attachment = res.result.attachment;
          this.flag_businessLicenseUrl = res.result.businessLicenseUrl;
          if (res.result.attachment) {
            this.haveUrl_attachment = true;
          } else {
            this.haveUrl_attachment = false;
          }
        }
        if (this.params.title === "查看") {
          this.isDisabled = true;
        }
      });
    },
    submit() {
      // 新增/编辑
      this.$refs.addForm.validate((valid) => {
        if (valid) {
          let url = "/company/";
          if (this.params.data) {
            url += "update";
          } else {
            url += "insert";
          }
          methodPost(url, this.addForm, true).then((res) => {
            if (res.code === 10000) {
              successMsg();
              this.cancel(true);
            }
          });
        } else {
          errorMsg();
        }
      });
    },
    cancel(refresh) {
      if (this.params.title !== "新增") {
        if (!refresh) {
          if (this.flag_attachment !== this.addForm.attachment) {
            errorMsg("修改附件后,请点击‘确定’按钮");
            return;
          }
          if (
            this.flag_businessLicenseUrl !== this.addForm.businessLicenseUrl
          ) {
            errorMsg("修改营业执照后,请点击‘确定’按钮");
            return;
          }
        }
      } else {
        if (this.addForm.businessLicenseUrl || this.addForm.attachment) {
          errorMsg("点击‘确定’提交新增内容,或删除已上传文件");
          return;
        }
      }
      this.$refs.addForm.resetFields();
      this.$emit("callback", refresh); // 通知父组件刷新list页,按实际取舍,父组件代码略
    },
  },
};
</script>
查看原文

赞 0 收藏 0 评论 0

Adele0 赞了文章 · 4月24日

element-ui表格中勾选checkbox,高亮当前行

image

我们在做后台管理系统的时候经常需要操作表格,这里我们要实现的一个功能就是,勾选复选框,高亮显示当前行,也就是当前行样式改变。这是一个非常常见的使用场景,官网给我们提供了一个带Checkbox的table表格,但是并没有给出上述使用案例,解决办法有很多,我简单总结下我自己的实现过程,希望能帮助到有同样需求的小伙伴。

image

勾选表格中当前项时会触发selection-change事件,在<el-table>中绑定handleSelectionChange方法。

        <el-table
          @selection-change="handleSelectionChange"
        >

编写handleSelectionChange方法,实现思路就是根据勾选当前行的下标,改变当前样式。但是element table表格中没有获取勾选后当前行index的方法,这里主要通过两个forEach遍历实现。

在data中定义tableData的时候一定要设置id属性,因为这里我们是通过id的对比来获取当前行的下标。

        tableData: [{
          id:0,
          date: '2016-05-03',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1518 弄'
        }

下列方法打印出来的i就是你要获取的勾选行的索引值,我们在data中定义一个空数组,专门用来存储选中项的下标,方便使用。

      handleSelectionChange(val) {
       var arr = [];
       val.forEach((val, index) => {
      this.tableData.forEach((v, i) => {
       // id 是每一行的数据id
            if(val.id == v.id){
              // console.log(i);
              arr.push(i)
            }
          })
        })   
        this.multipleSelection = arr;
      }

获取到下标之后就需要改变样式,给<el-table>标签绑定修改当前行样式的方法rowStyle

        <el-table
          @selection-change="handleSelectionChange"
          :row-class-name="rowStyle"
        >

编写rowStyle方法,思路是循环便利multipleSelection数组,取出存储的下标,与当前行下标做对比,如果相同则返回rowStyle,改变当前行的样式。

这里需要注意一个问题:

forEach中return无效!我们希望达到某一条件时,跳出循环,代码终止,使用forEach进行遍历是无法实现的。

原因:forEach()无法在所有元素都传递给调用的函数之前终止遍历!

                this.multipleSelection.forEach((v)=>{
          if(rowIndex === v){
            return 'rowStyle'
          }
        })

解决方法:使用for替换forEach

      rowStyle({row, rowIndex}){
        let arr =  this.multipleSelection;
        for(let i = 0; i < arr.length; i++){
          if(rowIndex === arr[i]){
            return 'rowStyle'
          }
        } 
      }

在style中定义选中行的样式

</style>
  .rowStyle{
    background-color:red!important;
  }
</style>

另外如果想更改鼠标移入的hover样式,要注意需要在td上修改,因为事件是添加在td身上的,在tr上修改无效。

    .el-table{
      width: 1163px;  
      margin: 0 auto;
      .el-table__body{
        tr:hover>td{
          background-color:rgba(238,250,249,1)!important;
        }
      }
    }
查看原文

赞 1 收藏 0 评论 0

Adele0 发布了文章 · 2019-07-16

vue + hls 循环 实时监控视频(m3u8格式)列表

默认vue已经安装好了,UI组件这里以vux为例,用什么UI库问题不大
注:循环这种实时视频的做法其实不推荐,但是你保不准,真的有这样的需要

1. 安装依赖 hls.js

npm i hls.js -S

2. 使用

2.1 html 循环渲染video节点

<div v-for="video in videos" :key="video.ref" class="videoList">
  <p>
    <span>XX监控</span>
    <span>通道{{video.which}}</span>
    <span><x-button @click.native="playVideo(video.which)" mini>播放</x-button><span>
  </p>
  <div class="videoContainer">
    <video :ref='video.ref'></video>
  </div>
</div>

2.2 【js】hls挂载节点,解析

// 结构略
import Hls from 'hls.js';

data() {
  return {
    videos: []
  }
},

methods: {
  // 节点挂载---$refs
  attach() {
    for (let index = 0; index < this.videos.length; index++) {
      if (Hls.isSupported()) {
        this.videos[index].hls = new Hls();
        this.videos[index].hls.attachMedia(this.$refs[this.videos[index].ref][0]);
      }
    }
  },

  // 播放实时监控
  playVideo(channel) {
    let _this = this;
    let videoRef = this.videos[channel-2].ref;
    this.$refs[videoRef][0].controls = 'controls';
    // 请求和心跳等涉及业务的略
    _this.videos[channel-2].hls.loadSource(res.data.url);
    // 正常解析
    _this.videos[channel-2].hls.on(Hls.Events.MANIFEST_PARSED, function () {
      _this.$refs[videoRef][0].play()
    });
    // 失败
    _this.videos[channel-2].hls.on(Hls.Events.ERROR, function (event, data) {
      if (data.fatal) {
        switch(data.type) {
          // 网络错误导致的错误,据观察这种类型的错,占比最高
          case Hls.ErrorTypes.NETWORK_ERROR:
            // 根据自己业务(心跳/错误次数 需要加载还是重新请求,这里只给出简单的,加载情况作为示例)
            hls.startLoad();
            break;
          case Hls.ErrorTypes.MEDIA_ERROR:
            // 多媒体错误
            hls.recoverMediaError();
            break;
          default:
            _this.videos[channel-2].hls.destroy();
            _this.$nextTick(() => {
            // 非网络或多媒体错误,重新获取url
             _this.playVideo(channel);
           })
           break;
        }
      }
    }
  }
}

// 选择生命周期(需要$el已经存在,mounted()或者keep-alive的activated())
// 我这里使用的是activated()
activated(){
  // axios 请求略(这里演示用固定数量,通道从2开始)
  this.videos = [];
  for (let i = 0; i < 5; i++) {
    let item = {
      hls: null,
      ref: `video${i+2}`,
      which: i+2,
    }
    this.videos.push(item)
    this.$nextTick(() => {
      // 可以放到请求地址成功后面(推荐)
      this.attach()
    })
  }
}

// 销毁
deactivated() {
  for (let i = 0; i < this.videos.length; i++) {
    this.videos[i].hls.destroy();
  }
}
查看原文

赞 3 收藏 2 评论 1

Adele0 发布了文章 · 2019-03-01

vue+elementUi 实现密码显示/隐藏+小图标变化(js一共三行代码,其中一行为了美观)

【效果图】

图片描述
图片描述
图片描述

【html】

// 前后代码【略】
<el-form-item label="密码" prop="pwd">
  <el-input v-model="ruleForm.pwd" :type="pwdType" placeholder="请输入密码" @keyup.enter.native="login">
    <i slot="suffix" class="el-icon-view" @click="showPwd"></i>
  </el-input>
</el-form-item>

【js】

showPwd () {
  this.pwdType === 'password' ? this.pwdType = '' : this.pwdType = 'password';
  let e = document.getElementsByClassName('el-icon-view')[0];
  this.pwdType == '' ? e.setAttribute('style', 'color: #409EFF') : e.setAttribute('style', 'color: #c0c4cc');
},
查看原文

赞 0 收藏 0 评论 0

Adele0 发布了文章 · 2019-02-13

element-ui上传下载excel(超详细der)

1. 上传 EXCEL

Upload组件 点击跳转到该组件官方文档

用到的upload组件参数

参数说明类型可选默认值
action必选参数,上传的地址string------
file-list上传的文件列表array---[]
accept接受上传的文件类型string------
http-request覆盖默认的上传行为function------
limit最大允许上传个数number------
on-exceed文件超出个数限制时的钩子function(files, fileList)------

组件代码

html

<el-upload
    style="display: inline; margin-left: 10px;margin-right: 10px;"
    action=""
    :http-request="uploadFile"
    :limit=1
    :on-exceed="fileExceed"
    accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel"
    :file-list="uploadList"
    ref="fileupload">
</el-upload>

--说明--
style: 按项目需要添加,请按需保留
action:必需,自定义上传直接给空串;非自定义,将地址赋给action配合属性headers、on-success、on-error等
http-request:自定义方法,根据请求的响应手动实现on-success、on-error
file-list:本项目有清空需要【代码略】
ref:该上传组件放置dialog中,本项目有置空需求【代码略】,请按需保留

js

import HTTP_API from '@/httpApi' //  封装好的axios:get post请求(含headers和拦截器等【代码略】)

// methods
fileExceed () {
  this.$message.error('别贪心!一次只能上传一个哦~');
},

  // 请求成功
importSuccess (res) {
    // 后端的返回码--上传成功
  if (res.code == xxxxx) {
    // 显示√图标
    let e = document.getElementsByClassName('el-upload-list__item-status-label');
    e[0].setAttribute('style', 'display:block !important')
    // 成功提示
    this.$message.success('上传成功');
    // 重新调用列表请求(代码略)
    this.getList();
    // 后端的返回码--上传失败
  } else {
    // 隐藏√图标
    let e = document.getElementsByClassName('el-upload-list__item-status-label');
    e[0].setAttribute('style', 'display:none !important')
    // 失败提示--及后端返回的失败详情
    this.$message.error({
      dangerouslyUseHTMLString: true,
      duration: 4500,
      message: res.remark+',<br/>请删除上传失败的文件,修改后重新上传'
    });
  }
},

  // 请求失败
importError (err) {
  this.$message.error({
    dangerouslyUseHTMLString: true,
    duration: 4500,
    message: '上传出现异常,请稍后重试'+',<br/><br/>异常原因:'+err
  });
},

  // 自定义上传
uploadFile (item) {
  const form = new FormData()
  form.append('file', item.file)
  HTTP_API.xlsx_upload(form).then(res => {
    this.importSuccess(res)
  }).catch(err => {
    this.importError(err)
  })
}

2. 下载 EXCEL(远程地址/文档流)

button组件

组件代码

html

<el-button type="primary" @click="downTemplate" round>下载模板</el-button>

js--后端返回下载地址

import axios from 'axios'

// methods
// 导出模板
downTemplate () {
  axios({
    method: 'get',
    url:'xxx相对地址xxx',
    responseType: 'blob'
  }).then(res => this.downloads(res.data, res.headers.filename))
},

// 创建模板下载链接
downloads (data, name){
  if(!data){
    return
  }
  let url = window.URL.createObjectURL(new Blob([data]))
  let link = document.createElement('a')
  link.style.display ='none'
  link.href = url
  link.setAttribute('download', `前端拼接后端返回的名字${name}.xlsx`)
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  window.URL.revokeObjectURL(url)
},

js--后端返回文档流

import HTTP_API from '@/httpApi' //  封装好的axios:get post请求(含headers和拦截器等【代码略】)

// methods
// 导出excel
let selectedParams = {
  ids : this.idList.join(',')    //导出条件(按照选中的ID来导出,按实际需求)
}
// 解决文档流乱码问题设置响应类型
HTTP_API.exportSelected(selectedParams, {responseType: 'arraybuffer'}).then(res => {
  let url = window.URL.createObjectURL(new Blob([res], {type: "application/vnd.ms-excel;charset=UTF-8"}))
  let link = document.createElement('a')
  link.style.display ='none'
  link.href = url
  link.setAttribute('download', '记录列表.xls')
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  window.URL.revokeObjectURL(url)
});

3. 结束语

  1. 上传action必须有,空串也好,随便写点也行,反正得有
  2. element的提示允许html代码,但是要设置dangerouslyUseHTMLString为true
  3. 上传的请求成功(状态码200)不等于上传成功,element的√图标,需要用js实现显示隐藏
  4. 下载时文件名称为动态时,请求后端协助在响应的头部增加filename字段(filename字段中含文字会有问题,后端给我日期数字,相同的文字部分前端拼接)
  5. responseType设置为blob或者arraybuffer从结果上是一样的。点击跳转responseType文档
查看原文

赞 8 收藏 5 评论 2

Adele0 关注了用户 · 2019-01-09

花裤衩 @panjiachen

show me the code

关注 2549

Adele0 赞了回答 · 2018-08-08

解决vue从后台获取到数据并已渲染元素但还是报null错误

问题在这里啊,你初始赋值的assetDetailnull,初始渲染读取assetDetail.assetType当然是Cannot read property 'assetType' of null",解决办法是,给assetDetail初始赋值{},或者template模板里类似读取assetDetail父元素都加一个一个v-if="assetDetail != null"

<p v-if="assetDetail.assetType == 1">房产</p>
<p v-else-if="assetDetail.assetType == 2">车辆</p>
<p v-else-if="assetDetail.assetType == 3">信用贷</p>

关注 5 回答 3

Adele0 关注了专栏 · 2018-07-31

手摸手系列

手摸手带你学习前端系列

关注 1734

认证与成就

  • 获得 11 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-11-22
个人主页被 349 人浏览