echarts 图表无数据/空数据 展示“暂无数据”
<div id="test_chart" style="width: 600px;height:400px;"></div>
//图表
echartsData() {
if (this.chart != null && this.chart != "" && this.chart != undefined) {
this.chart.clear();
this.chart.dispose();
}
// if (this.chart) {
// this.chart.clear();
// this.chart.dispose();
// }
const myChartSteam = this.$refs.myChartSteam;
if (myChartSteam) {
let ydata = this.eyList;
let xdata = this.exList;
this.chart = this.$echarts.init(myChartSteam);
let option;
if (xdata.length == 0) {
option = {
title: {
text: "暂无数据",
x: "center",
y: "center",
textStyle: {
fontSize: 14,
fontWeight: "normal",
},
},
};
} else {
option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
xAxis: {
type: "category",
data: xdata,
axisTick: {
show: false,
},
axisLabel: {
showMinLabel: true,
showMaxLabel: true,
width: 100,
overflow: "break",
},
},
yAxis: {
name: this.active == 1 ? "(t)" : this.active == 2 ? "(元)" : "",
type: "value",
axisLabel: {
formatter: (value) => {
if (value >= 10000) {
value = value / 10000 + "W";
}
return value;
},
},
},
grid: {
left: 45,
right: 45,
},
series: [
{
name:
this.active == 1
? "蒸汽用量"
: this.active == 2
? "蒸汽费用"
: "瞬时流量",
data: ydata,
type: "line",
color: this.active == 0 ? "#51C41D" : "#FFD289",
},
],
};
}
this.chart.setOption(option);
window.addEventListener("resize", function () {
if (this.chart) {
this.chart.resize();
}
});
}
},
charts统计图Y轴(或X轴)文字过长问题解决
axisLabel: {//超出指定数字省略号
color: "#000",
interval: 0,
formatter: function(value) {
if (value.length > 12) {
return value.substring(0, 12) + "...";
} else {
return value;
}
}
},
yAxis: {
type: 'value',
axisLabel:{
formatter:(value) => {
if(value >= 10000){
value = (value / 10000) + 'W';
}
if(value >= 1000){
value = (value / 1000) + 'K';
}
return value;
}
}
formatter属性可供用户自定义一些属性,很方便使用。
Echarts折线图配置项
chartsOption: {
tooltip: {
trigger: "axis",
backgroundColor: "rgba(22,42,209,1)",
borderColor: "#28a6d8",
textStyle: {
color: "#fff",
align: "left"
},
formatter: params => {
let { value, axisValue } = params[0];
let html = `<span>数值:${value.toFixed(2)}</span><br/>
<span>时间:${axisValue}</span>`;
return html;
}
},
grid: {
top: 10,
left: 50,
right: 40,
bottom: 10,
containLabel: true
},
xAxis: {
type: "category",
boundaryGap: false,
data: [],
//x轴
axisLine: {
show: true,
lineStyle: {
color: "#3d5bff"
}
},
//x轴上刻度文字
axisLabel: {
textStyle: {
color: "#8ea0ff",
fontSize: 16
},
//显示间隔
interval: (index, value) => {
return this.intervals[index].count;
},
formatter: function(value, index) {
return value.split(" ")[0];
}
//rotate: 30
}
},
yAxis: {
type: "value",
//y轴
axisLine: {
show: true,
lineStyle: {
color: "#3d5bff"
}
},
//y轴上刻度文字
axisLabel: {
textStyle: {
color: "#8ea0ff",
fontSize: 16
}
},
//横向网格线(诶嘿,想不到吧,横向网格线样式的设置居然是在yAxis中呢O(∩_∩)O)
splitLine: {
lineStyle: {
color: ["#0a2395"]
}
}
},
series: [
{
type: "line",
smooth: true,
//折线图拐点使用自定义图标
symbol: () => {
let circleIcon = require("@/assets/images/peiqi.png");
return `image://${circleIcon}`;
},
symbolSize: 20, //折线图拐点大小
itemStyle: {
normal: {
lineStyle: {
width: 2,
type: "dashed"
}
}
},
data: []
}
],
color: [], //折线颜色
//缩放及滚动条
dataZoom: [
{
type: "inside",
xAxisIndex: [0, 1],
start: 0,
end: 100
},
{
show: false,
xAxisIndex: [0, 1],
type: "slider",
top: "75%",
start: 0,
end: 100
}
]
}
Vue-Awesome1-Swiper基本能解决你所有的轮播需求
在我们使用的很多ui库(vant、antiUi、elementUi等)中,都有轮播组件,对于普通的轮播效果足够了。但是,某些时候,我们的轮播效果可能比较炫,这时候ui库中的轮播可能就有些力不从心了。当然,如果技术和时间上都还可以的话,可以自己造个比较炫的轮子。
这里我说一下vue-awesome-swiper这个轮播组件,真的非常强大,基本可以满足我们的轮播需求。swiper相信很多人都用过,很好用,也很方便我们二次开发,定制我们需要的轮播效果。vue-awesome-swiper组件实质上基于swiper的,或者说就是能在vue中跑的swiper。下面说下怎么使用:
- 安装
cnpm install vue-awesome-swiper --save - 在组件中使用的方法,全局使用意义不大:
// 引入组件
import 'swiper/dist/css/swiper.css'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
// 在components中注册组件
components: {
swiper,
swiperSlide
}
// template中使用轮播
// ref是当前轮播
// callback是回调
// 更多参数用法,请参考文档
<swiper :options="swiperOption" ref="mySwiper" @someSwiperEvent="callback">
<!-- slides -->
<swiper-slide><div class="item">1</div></swiper-slide>
<swiper-slide><div class="item">2</div></swiper-slide>
<swiper-slide><div class="item">3</div></swiper-slide>
<!-- Optional controls -->
<div class="swiper-pagination" slot="pagination"></div>
<div class="swiper-button-prev" slot="button-prev"></div>
<div class="swiper-button-next" slot="button-next"></div>
<div class="swiper-scrollbar" slot="scrollbar"></div>
</swiper>
复制代码
// 参数要写在data中
data() {
return {
// swiper轮播的参数
swiperOption: {
// 滚动条
scrollbar: {
el: '.swiper-scrollbar',
},
// 上一张,下一张
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
// 其他参数…………
}
}
},复制代码
swiper需要配置哪些功能需求,自己根据文档进行增加或者删减。附上文档:npm文档,swiper3.0/4.0文档,更多用法,请参考文档说明。
ant design vue 表格自动滚动
//a-table标签添加class="tableRect"
<a-table class="tableRect"/>
<script>
data() {
return {
scrollTimer: null, // 滚动定时器
scrollDirection: 'down', // 滚动方向 up向上 down向下
},
created() {
//this.scrollFun();
},
methods: {
scrollFun() {
// 如果定时器存在
if (this.scrollTimer) {
// 则先清除
clearInterval(this.scrollTimer)
this.scrollTimer = null
}
this.scrollTimer = setInterval(() => {
const scrollHeight = document.querySelectorAll(`.tableRect .ant-table-body`)[0].scrollHeight
const clientHeight = document.querySelectorAll(`.tableRect .ant-table-body`)[0].clientHeight
const scroll = scrollHeight - clientHeight
// 获取当前滚动条距离顶部高度 tableRect是a-table标签名
const scrollTop = document.querySelectorAll(`.tableRect .ant-table-body`)[0].scrollTop
console.log('scrollTop',scrollTop);
// 向下滚动
if (this.scrollDirection === 'down') {
// 滚动速度
const temp = scrollTop + 10
document.querySelectorAll(`.tableRect .ant-table-body`)[0].scrollTop = temp // 滚动
// 距离顶部高度 大于等于 滚动长度
if (scroll <= temp) {
// 滚动到底部 停止定时器
clearInterval(this.scrollTimer)
this.scrollTimer = null
}
}
}, 150)
},
}
</script>
//在a-table标签外添加一个div标签
<div v-on:mouseover="scrollFun"
v-on:mouseout="pauseScroll">
<a-table/>
</div>
<script>
methods: {
pauseScroll() {
// 定时器不为空
if (this.scrollTimer) {
// 清除定时器
clearInterval(this.scrollTimer)
this.scrollTimer = null
}
}
}
</script>
elementui
element固定列+固定高度后,滚动到底部错位
项目中自定义了滚动条样式,设置的宽高不一样。
解决方法:将宽高设置成一样
::-webkit-scrollbar{
width: 13px;
height: 13px;
background-color: #01064b;
}
参考文档:blog.csdn.net/a1983029606…
同一组件上存在多个table进行tabs和v-if/v-show切换时,多表格的数据会相互混淆,串在一起,引发bug
为每个table指定对应且唯一的key属性。
其他一些类似的问题也可以尝试为其添加key属性来解决
vue element 多个 Form 表单同时验证
<template>
<el-form ref="form1"></el-form>
<el-form ref="form2"></el-form>
<el-form ref="form3"></el-form>
</template>
<script>
export default{
methods: {
onValidate() { // 保存操作
const formArr =['form1', 'form2','form3']//三个form表单的ref
const resultArr = [] //用来接受返回结果的数组
let _self = this
function checkForm(formName) { //封装验证表单的函数
let result = new Promise(function (resolve, reject) {
_self.$refs[formName].validate((valid) => {
if (valid) {
resolve();
} else { reject() }
})
})
resultArr.push(result) // 得到promise的结果
}
formArr.forEach(item => { // 根据表单的ref校验
checkForm(item)
})
Promise.all(resultArr).then(values => {
// 此时必填完成,做保存后的业务操作
// ...
console.log('success');
}).catch(_ => {
console.log('err')
})
},
}
}
</script>
巧用$options
$options是一个记录当前Vue组件的初始化属性选项,当我们想把 data 里的某个值重置为初始值时,非常有用
例如:
this.value = this.$options.data().value;
dialog 里重置表单
利用上面介绍的 $options 特性
我们经常的业务场景是这样:一个el-dialog中有一个el-form,而且我们通常是新增和编辑复用同一个组件,现在我们要求每次打开el-dialog时都要重置el-form里的数据,并且清除校验状态。
// 弹框打开时
initForm(){
this.$refs['form'] && this.$refs['form'].resetFields()
this.form = this.$options.data.call(this).form;
Element UI 下拉框和级联选择器位置错乱问题
- 对于el-select来说添加 :popper-append-to-body = “false” 即可
- 对于el-cascader来说添加 :append-to-body=“false” 即可
v-loading与html2canvas同时使用时导致loading效果总是不能及时出现
问题描述:众所周知,html2canvas可以实现对页面内容进行截图,但是遇到稍微复杂一点的页面结构时会比较耗时,为了提升用户体验,我们希望在html2canvas执行前显示Loading,执行结束之后Loading消失。但是,在实际使用时,发现html2canvas会阻塞进程,导致loading无法在我们希望的时机出现和消失。
解决问题:先让loading出现,然后将html2canvas的逻辑处理放在setTimeout中执行,然后在html2canvas的成功回调中让loading消失,这样可以保证loading在我们需要时正确显隐。
el-form的resetField()方法无法清空表单内容
解决方法:保证表单项el-form-item中有prop属性且prop值与表单数据源的key一致
el-dialog嵌套导致无渲染
问题描述:项目中利用el-dialog封装了自定义的进度条组件,当时的业务场景是,某个地方的对话框加载时需要复杂且耗时的逻辑处理,所以需要在对话框出现的同时显示进度条,我就在对话框组件内引用了进度条组件,这样就就无形中产生了el-dialog嵌套的问题,导致进度条组件始终无法渲染。
解决问题:在对话框组件和自定义的进度条组件需要同时使用时,使其并列而非嵌套
el-form的表单验证方法validate()总是不进回调
解决方法:如果有自定义校验规则,必须保证确保所有出口(即每种条件)都有callback
el-tree主题样式复写之后通过小三角展开节点并离开后背景色变白色问题
.el-tree .el-tree-node:focus > .el-tree-node__content{ background-color:blue; }
el-input 限制输入框只能输入数字
<el-input v-model.number="num" onkeyup="this.value = this.value.replace(/[^\d.]/g,'');"></el-input>
el-input 过滤特殊字符或身份证脱敏
v-model拆分为:value和@input
<el-input :value="input" @input='e => input = idCardValid (e)' placeholder="请输入内容"></el-input>
methods:{
idCardValid(val){
const idCard= val.replace(/^(\d{6})\d+(\d{4})$/, "$1******$2")
console.log(idCard)
return idCard
}
},
修改走马灯指示器样式
::v-deep .el-carousel__indicators--horizontal {
position: absolute;
left: auto;
// right: 10px;
// bottom: 10px;
// overflow: hidden;
left: 50%;
top: 96%;
text-align: right;
.el-carousel__indicator--horizontal button {
width: 8px;
height: 8px;
// background: #962a2a;
background: #ffffff;
border-radius: 50%;
opacity: 0.5;
}
.el-carousel__indicator--horizontal.is-active button {
width: 24px;
height: 8px;
// background: #38429b;
background: #ffffff;
opacity: 0.5;
border-radius: 10px;
}
}
el-carousel走马灯 修改指示器样式、高度随着图片大小而更改
@load="imgLoad"` `第一次加载页面跟踪图片高度
<el-carousel :height="imgHeight">
<el-carousel-item v-for="item in imgList" :key="item">
<img ref="imgHeight" :src="item.src" @load="imgLoad">
</el-carousel-item>
</el-carousel>
data: {
imgHeight: '',
},
mounted() {
this.imgLoad()
window.onresize = () => {
this.imgLoad()
}
},
methods:{
imgLoad(){
this.$nextTick(()=>{
this.imgHeight = this.$refs.imgHeight['0'].height +'px'
})
}
}
destroyed() {
window.onresize = null;
}
设置样式的时候,一定要添加scoped
<style lang="less" scoped>
/deep/.el-carousel__indicators--horizontal {
position: absolute;
left: auto;
right: 10px;
bottom: 10px;
text-align: right;
.el-carousel__indicator--horizontal button {
width: 8px;
height: 8px;
background: #ffffff;
border-radius: 50%;
opacity: 0.5;
}
.el-carousel__indicator--horizontal.is-active button {
width: 24px;
height: 8px;
background: #ffffff;
opacity: 0.5;
border-radius: 10px;
}
}
</style>
走马灯轮播图,el-carousel左右箭头位置调整
<template>
<section class="excellentCases">
<p class="title">箭头轮播图</p>
<div class="baseContent">
<div class="prev">
<img src="~/assets/images/solution/wisdomGov/prev.png" @click="arrowClick('prev')" />
</div>
<div class="carousel">
<el-carousel :autoplay="false" arrow="never" indicator-position="outside" ref="cardShow" >
<el-carousel-item v-for="(item, index) in 4" :key="index">
<h3>{
{ item }}</h3>
</el-carousel-item>
</el-carousel>
</div>
<div class="next">
<img src="~/assets/images/solution/wisdomGov/next.png" @click="arrowClick('next')"/>
</div>
</div>
</section>
</template>
<script>
export default {
data() {
return {};
},
created() {},
mounted() {},
methods: {
arrowClick(val) {
if(val === 'next') {
this.$refs.cardShow.next()
} else {
this.$refs.cardShow.prev()
}
},
},
};
</script>
<style scoped lang="scss">
.excellentCases {
max-width: 1426px;
height: 530px;
margin: 0 auto;
background: #323233;
.title{
font-size: 34px;
font-weight: 500;
line-height: 34px;
color: #FFFFFF;
text-align: center;
margin: 56px 0 27px 0;
}
.baseContent{
position: relative;
.prev{
position: absolute;
left: 46px;
top:34%;
img{
width: 75px;
height: 74px;
}
}
.next{
position: absolute;
right: 46px;
top:34%;
img{
width: 75px;
height: 74px;
}
}
.carousel{
width: 1110px;
// height: 347px;
margin: 0 auto;
background-color: #cccc;
/deep/.el-carousel__container{
height: 347px;
}
/deep/ .el-carousel__indicators--outside{
background-color: #323233;;
}
}
}
}
@media screen and (max-width: 992px) {
}
</style>
搜索框重置,没有清除问题
```
<el-form-item label="签约人" prop="signatories">
<el-input v-model="queryParams.signatories" placeholder="签约人" clearable size="small"
@keyup.enter.native="handleQuery" style="width:240px" />
</el-form-item>
复制代码
```
是因为 prop="signatories" 没有设置或者设置与对应的输入框不一样导致
弹窗必填提示不显示
```
<el-form-item label="撤回原因" prop="reason">
<el-input v-model="withdrawalForm.reason" type="textarea" rows="5" maxlength="200"
show-word-limit/>
</el-form-item>
复制代码
```
一可能是没有设置 prop="reason",二是设置与输入框不一致,三对应的如rules规则没有设置对应的值
弹窗数据没有重新加载问题
```
<!-- 添加或修改区域政策对话框 :fullscreen='true' -->
<el-dialog
:title="title"
:visible.sync="open"
v-if="open"
width="1250px"
append-to-body
custom-class="chy-dialog"
>
复制代码
```
对话框增加一个 v-if="open" 即可
-
数据深度拷贝方法
el-table篇
1.el-table表格固定列之后下方边缘出现白线
解决方案:
.el-table__fixed-right::before, .el-table__fixed::before {
background-color: transparent;
}
复制代码
参考文档:blog.csdn.net/DDD4V/artic…
2.el-table表格XY方向都滚动时右下角出现的白色小方块
::-webkit-scrollbar-corner{
background: transparent;
}
复制代码
vue+elementUI在输入框中按回车键会刷新页面
当一个 form 元素中只有一个输入框时,在该输入框中按下回车应提交该表单。如果希望阻止这一默认行为,可以在 <el-form> 标签上添加 @submit.native.prevent。
<templat>
<el-form @submit.native.prevent >< /el-form >
</templat>
el-select 下拉框样式修改
使用样式穿透修改下拉框样式,你会发现打死都不生效,那是因为下拉框是默认挂载在 body 下面。解决办法:设置 :popper-append-to-body="false",然后再用样式穿透
element-ui select组件change事件传递多个参数的方法
- 方法一
@change="onChange($event,customParam)"
- 方法二
@change="((val)=>{changeEvent(val,args)})"
其他组件的的默认事件同样的方法传递
<el-dropdown trigger="click" @command="((val)=>{handleCommand(val,scope.row)})">
<span class="el-dropdown-link">
<i class="el-icon-more el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="volumes">新增</el-dropdown-item>
<el-dropdown-item command="log">查看</el-dropdown-item>
<el-dropdown-item command="shell">更新</el-dropdown-item>
<el-dropdown-item command="container">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
el-input type=number 去除聚焦时的上下箭头
解决
<el-input class="clear-number-input" type="number"></el-input>
<style scoped>
.clear-number-input ::v-deep input[type="number"]::-webkit-outer-spin-button,
.clear-number-input ::v-deep input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none !important;
}
</style>
3.el-table鼠标悬停时高亮背景颜色的修改,添加fixed后固定列高亮颜色不生效
.el-table__body .el-table__row.hover-row td{
background-color: #00b8ff !important;
}
复制代码
参考文档:blog.csdn.net/zeng092210/…
.el-upload+xlsx实现前端上传并解析excel
安装xlsx依赖:npm install xlsx
引入XLSX: import XLSX from "xlsx";
html中文件上传组件
<el-upload
action=""
accept=".xls, .xlsx"
:on-change="importFile"
:http-request="uploadRequest"
:show-file-list="false"
:multiple="false"
:limit="1"
class="uploader">
<span class="btn">数据录入</span>
</el-upload>
复制代码
js中methods部分
文件上传完成后的处理
importFile(file) {
this.file2Xce(file)
.then(res => {
if (res && res.length > 0) {
// 解析出来的json数据格式如:[{sheetName: sheet1, sheet: sheetData }]
//默认只处理第一个页签的数据
if (res[0] && res[0].sheet && res[0].sheet.length) {
let datas = res[0].sheet;
this.tableData = this.handleXlsData(datas, this.tableCols);
}
}
})
.catch(() => {
this.loading = false;
});
},
复制代码
解析excel文件的核心代码
/**
* 解析excel文件核心代码
* @param {Object} file
*/
file2Xce(file) {
return new Promise(function(resolve, reject) {
const reader = new FileReader();
reader.onload = function(e) {
const data = e.target.result;
this.wb = XLSX.read(data, {
type: "binary"
});
const result = [];
this.wb.SheetNames.forEach(sheetName => {
result.push({
sheetName: sheetName,
sheet: XLSX.utils.sheet_to_json(this.wb.Sheets[sheetName])
});
});
resolve(result);
};
reader.readAsBinaryString(file.raw);
});
},
复制代码
将直接解析出来的excel文件内容处理成适合自己表格使用的数据格式
/**
* 解析excel文件数据为表格数据
* @param {Array} data
* @param {Array} tableCols
*/
handleXlsData(data,tableCols) {
let result=[];
//循环遍历每一条数据
data.forEach(it => {
let temObj = {};
//循环遍历每条数据的每个属性,并对其进行转化
for (let k in it) {
if (k.includes('日期')) {
it[k]=this.formatDate(it[k],'-')
}
let colName = tableCols.filter(i => i.label === k)[0].prop;
temObj[colName] = it[k];
}
result.push(temObj)
})
return result
},
由于直接解析出来的excel表格数据一般都以表头文字(中文)作为key,比如:
这不符合我们实际开发中的习惯,所以需要对其进行相应转化处理,变成这种:
这里我是直接使用了循环遍历生成表格时所使用的表格列配置信息,其实主要就是表格列名和前端渲染时的列字段的对应关系,自己可以灵活配置,我这边大概配置如下:
另外,在使用xlsx插件来读取excel时,会将2018/10/16这种数据自动装换成48264.12584511这种样子,所以需要自己手动再转换回来(参考:www.cnblogs.com/cazj/p/1094…
/**
* @param {Number} numb是传过来的整数数字
* @param {String} format是之间间隔的符号
*/
formatDate(numb, format) {
const time = new Date((numb - 1) * 24 * 3600000 + 1)
time.setYear(time.getFullYear() - 70)
const year = time.getFullYear() + ''
const month = time.getMonth() + 1 + ''
const date = time.getDate() - 1 + ''
if (format && format.length === 1) {
return year + format + month + format + date
}
return year + (month < 10 ? '0' + month : month) + (date < 10 ? '0' + date : date)
},
备注:vue2项目中如果安装完xlsx依赖发现报错 “export ‘default‘ (imported as ‘XLSX‘) was not found in ‘xlsx‘,有可能是版本不兼容问题,可以通过npm install xlsx@XXX.XXX --save安装特定版本的插件来解决。建议版本:"xlsx": "^0.16.0"
扩展1:纯前端实现下载当前表格为excel
前置条件:安装插件 npm install --save xlsx file-saver
import XLSX from 'xlsx'
import FileSaver from 'file-saver'
exportExcel() {
var wb = XLSX.utils.table_to_book(
document.querySelector("#tableId")
);
var wbout = XLSX.write(wb, {
bookType: "xlsx",
bookSST: true,
type: "array"
});
try {
FileSaver.saveAs(
new Blob([wbout], { type: "application/octet-stream" }),
this.title+".xlsx"
);
} catch (e) {
if (typeof console !== "undefined") console.log(e, wbout);
}
return wbout;
}
参考文档:www.jianshu.com/p/a65122b9d…
注意,如果表格中有固定列,会导致生成重复表格数据
解决思路:先移除固定列Dom,然后再追加
实现代码如下:
exportExcel() {
// 解决生成重复数据-因为使用fixed属性 注意你的fixed是left还是right
var fix = document.querySelector("#daochu2 .el-table__fixed-right");
var wb;
// 判断要导出的节点中是否有fixed的表格,如果有,转换excel时先将该dom移除,然后append回去
if (fix) {
/* 从表生成工作簿对象 */
wb = XLSX.utils.table_to_book(
document.querySelector("#table").removeChild(fix),
{ raw: true }
);
document.querySelector("#table").appendChild(fix);
} else {
wb = XLSX.utils.table_to_book(document.querySelector("#table"), {
raw: true,
});
}
/* 获取二进制字符串作为输出 */
var wbout = XLSX.write(wb, {
bookType: "xlsx",
bookSST: true,
type: "array",
});
try {
FileSaver.saveAs(
new Blob([wbout], { type: "application/octet-stream" }),
// 设置导出文件名称
"统计核算.xlsx"
);
} catch (e) {
if (typeof console !== "undefined") console.log(e, wbout);
}
return wbout;
}
参考文档:www.jianshu.com/p/f6a03c734…
扩展2:纯前端实现将json数据转换为excel数据并下载
import XLSX from 'xlsx';
const export=()=>{
let head = ['商品','售价','颜色']
let data = [
['裙子','¥500','红色'],
['衬衫','¥300','白色'],
['裤子','¥200','黑色'],
['皮鞋','¥800','黑色'],
];
data = data.map(e => Object.values(e))
data.unshift(head)
let filename = "表格名字.xlsx";
let ws_name = "SheetJS";
let wb = XLSX.utils.book_new();
let ws = XLSX.utils.aoa_to_sheet(data);
XLSX.utils.book_append_sheet(wb, ws, ws_name);
XLSX.writeFile(wb, filename);
}