可支持大批量table长列表虚拟表格(几万行数据渲染是没问题的)
项目直接安装
npm install umy-ui
在main.js中引用
import UmyUi from 'umy-ui'
import 'umy-ui/lib/theme-chalk/index.css'// 引入样式
Vue.use(UmyUi)
实列:
<template>
<div class="content">
<i class="el-icon-edit"></i>
<i class="el-icon-minus"></i>
<el-button type="primary" @click="exportData">下载</el-button>
<el-button type="primary" @click="openDialog">筛选字段</el-button>
<div class="content_table">
<ux-grid
:key="Math.random()"
:edit-config="{trigger: 'click', mode: 'cell'}"
:highlight-current-row="false"
show-summary
:summary-method="summaryMethod"
ref="plTable"
:data="data"
:max-height="height"
use-virtual
show-header-overflow="tooltip"
show-footer-overflow="tooltip"
show-overflow="tooltip"
:stripe="true"
:row-style="rowStyle"
moveDownIcon="el-icon-caret-bottom"
moveUpIcon="el-icon-caret-top"
:showDialogIcon="true"
field-title="选择展示列表"
:field-sort="true"
:dialog-data="dialogData"
:checkbox-config="{highlight: true}"
@show-field="showField"
@toggle-tree-expand="toggleTreeExpand"
:tree-config="{ iconOpen: 'el-icon-minus', iconClose: 'el-icon-plus',children:'children' , indent:40, line:'true'}"
:header-row-style="{color:'#FFFFFF',backgroundColor:'rgba(1, 73, 95)'}"
border>
<ux-table-column type="expand1" width="55" title="展开" align="center" fixed="left" tree-node/>
<!-- <ux-table-column type="checkbox" width="55" align="center" fixed="left"/> -->
<ux-table-column type="index" width="50" title="序号" align="center" fixed="left"/>
<template v-for="(item,index) in columns">
<ux-table-column
v-if="item.dataIndex!='score'&&item.dataIndex!='sex'&&item.dataIndex!='avatar'&&item.dataIndex!='mtime'&&item.dataIndex!='action'&&findIndex(item.title)"
:key="index"
prop="date"
:filters="[{ data: '' }]"
:filter-method="filterNameMethod"
:resizable="true"
:show-overflow-tooltip="true"
:field="item.dataIndex"
:title="item.title"
:fixed="item.fixed"
:width="item.width"
:min-width="item.minWidth"
:align="item.align"
:edit-render="['select','input'].indexOf(item.type)>-1"
>
<!-- 可编辑项 -->
<template v-slot:edit="scope">
<a-input v-if="item.type=='input'" v-model="scope.row[item.dataIndex]"></a-input>
<el-select v-if="item.type=='select'" v-model="scope.row[item.dataIndex]">
<el-option
v-for="item in userList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
<template v-slot="{ row }">
{{ row[item.dataIndex]}}
</template>
<!-- 筛选功能 -->
<template v-slot:filter="{ $panel, column }">
<a-input type="type"
v-for="(option, index) in column.filters"
:key="index"
v-model="option.data"
@input="$panel.changeOption($event, option.data, option)"/>
</template>
</ux-table-column>
<!-- 将布尔值转化为数字 -->
<ux-table-column
:key="index"
v-if="item.dataIndex=='sex'&&findIndex(item.title)"
:resizable="true"
:show-overflow-tooltip="true"
:field="item.dataIndex"
:title="item.title"
:width="item.width"
:align="item.align">
<template v-slot="{ row }">
<select v-if="item.type=='select'&&item.dataIndex=='USER_TYPE'" v-model="scope.row.USER_TYPE">
<option v-for="(item1,index1) in userList" :key="item1.value" :value="item1.value">{{item1.label}}</option>
</select>
{{ row[item.dataIndex]?'男':'女'}}
</template>
</ux-table-column>
<!-- 将base64转体图片 -->
<ux-table-column
:key="index"
v-if="item.dataIndex=='avatar'&&findIndex(item.title)"
:resizable="true"
:show-overflow-tooltip="true"
:field="item.dataIndex"
:title="item.title"
:width="item.width"
:align="item.align">
<template v-slot="{ row }">
<img :src="row[item.dataIndex]" >
</template>
</ux-table-column>
<!-- 将数字转换为进度条 -->
<ux-table-column
:key="index"
v-if="item.dataIndex=='score'&&findIndex(item.title)"
:resizable="true"
:show-overflow-tooltip="true"
:field="item.dataIndex"
:title="item.title"
:width="item.width"
:align="item.align">
<template v-slot="{ row }">
<el-progress :percentage="row[item.dataIndex]" color="#5a1216"></el-progress>
</template>
</ux-table-column>
<!-- 筛选时间范围 -->
<ux-table-column
:key="index"
v-if="item.dataIndex=='mtime'&&findIndex(item.title)"
:filters="[{ data: [] }]"
:filter-method="filterNameMethod"
:show-overflow-tooltip="true"
:field="item.dataIndex"
:title="item.title"
:width="item.width"
:align="item.align">
<template v-slot="{ row }">
{{ row[item.dataIndex]}}
</template>
<!-- 时间筛选功能 -->
<template v-slot:filter="{ $panel, column }">
<cTimeRange
v-for="(option, index) in column.filters"
:key="index"
v-model="option.data"
:defaultValue = "option.data"
@input="$panel.changeOption($event, option.data, option)"
></cTimeRange>
</template>
</ux-table-column>
<ux-table-column
:key="index"
v-if="item.dataIndex=='action'&&findIndex(item.title)"
:resizable="true"
:show-overflow-tooltip="true"
:field="item.dataIndex"
:title="item.title"
:width="item.width"
:align="item.align">
<template v-slot="{ row }">
<router-link to="{name:'detail',params:'{id:row.id}'" >
<span>详情</span>
</router-link>
</template>
</ux-table-column>
</template>
</ux-grid>
</div>
</div>
</template>
<script>
import {umyData} from '../../utils/fakedata'
import axios from 'axios'
import cTimeRange from '../../component/cTimeRange.vue'
import moment from 'moment'
const columns = [
{
title: "id",
dataIndex: "id",
minWidth: 200, //可用作自适应
align: "center",
fixed: "left",
type:'text',
},
{
title: "姓名",
dataIndex: "nickname",
width: 100,
align: "center",
fixed: "left",
type:'input',
},
{
title: "头像",
dataIndex: "avatar",
width: 200,
align: "center",
fixed: "left",
type:'img',
},
{
title: "部门",
dataIndex: "dep",
width: 100,
align: "center",
fixed: "",
type:'text',
},
{
title: "地址",
dataIndex: "address",
width: 100,
align: "center",
fixed: "",
type:'text',
},
{
title: "城市",
dataIndex: "city",
width: 220,
align: "center",
fixed: "",
type:'text',
},
{
title: "性别",
dataIndex: "sex",
width: 70,
align: "center",
fixed: "",
type:'text',
},
{
title: "时间",
dataIndex: "mtime",
width: 270,
align: "center",
fixed: "",
type:'text',
},
{
title: "级别",
dataIndex: "rank",
width: 100,
align: "center",
fixed: "",
type:'select',
},
{
title: "成绩",
dataIndex: "score",
width: 170,
align: "center",
fixed: "",
type:'text',
},
{
title: "星数",
dataIndex: "stars",
width: 100,
align: "center",
fixed: "right",
type:'text',
},
{
title: "操作",
dataIndex: "action",
width: 100,
align: "center",
fixed: "right",
type:'text',
}
];
export default {
components:{
cTimeRange
},
data() {
return {
moment,
height: '470px',
data: umyData.list,//就用了101条数据。
columns: columns,
userList:[
{label:1,value:1},
{label:2,value:2},
{label:3,value:3},
],
dialogData:[
{ name: 'id', state: true, disabled: true },
{ name: '姓名', state: true,},
{ name: '头像', state: true },
{ name: '部门', state: true,},
{ name: '地址', state: true, },
{ name: '城市', state: true, },
{ name: '性别', state: true, },
{ name: '时间', state: true, },
{ name: '级别', state: true, },
{ name: '成绩', state: true, },
{ name: '星数', state: true, },
{ name: '操作', state: true, },
]
}
},
created() {},
mounted() {
console.log(umyData)
// let objData = this.$refs.plTable.getTableData().visibleData;
},
methods: {
//斑马纹
rowStyle({row, rowIndex}){
if(rowIndex % 2 !== 0){
return {background: 'rgba(33, 119, 184)', color: '#FFF',height:'30px'}
}else {
return {background: 'rgba(65, 174, 60)', color: '#FFF',height:'30px'}
}
},
//表头搜索重置
filterNameMethod({ option, row ,column}){
const property = column['property'];
let format = 'YYYY-MM-DD';
//判断字段是否为null、模糊查询
if(typeof row[property] === 'string'){
//时间筛选
if(property == 'mtime'){
let starT = moment(option.data[0],format);
let endT = moment(option.data[1],format);
let currentT = moment(row[property],format);
if(moment(currentT).diff(endT,'days')<=0&&moment(currentT).diff(starT,'days')>=0){
return true
}else{
return false
}
}else{
return row[property].indexOf(option.data) > -1?true:false
}
}else{
return false
}
},
//合计
summaryMethod ({ columns, data }) {
// 平均值算法(不需要自己去掉)
function cacl(arr, callback) {
let ret;
for (let i=0; i<arr.length;i++) {
ret = callback(arr[i], ret);
}
return ret;
}
// 平均值算法(不需要自己去掉)
Array.prototype.sum = function () {
return cacl(this, function (item, sum) {
if (typeof (sum) == 'undefined') {
return item;
}
else {
return sum += item;
}
});
};
const means = [] // 合计
columns.forEach((column, columnIndex) => {
if (columnIndex === 0) {
means.push('合计')
}
else if(column.title === '姓名'){
return;
}else if(column.title === '头像'){
return;
}else if(column.title === '时间'){
return;
}else {
const values = data.map(item => Number(item[column.property]));
// 合计
if (!values.every(value => isNaN(value))) {
means[columnIndex] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
// means[columnIndex] += ' 元'
// 改变了ele的合计方式,扩展了合计场景
// 你以为就只有上面这样玩吗?错啦,你还可以自定义样式哦
// means[columnIndex] = '<span style="color: red">' + means[columnIndex] + '元</span>'
// means[columnIndex] = '<span>' + means[columnIndex]+ '</span>'
// means[columnIndex] = '<span>' + Math.round(means[columnIndex]*100)/100+ '</span>'
means[columnIndex] = Math.round(means[columnIndex]*100)/100
} else {
means[columnIndex] = '';
}
}
})
return [means]
},
//展开关闭树形结构
toggleTreeExpand({ expanded, row, rowIndex, $rowIndex, column, columnIndex, $columnIndex, $event }){
console.log({ expanded, row, rowIndex, $rowIndex, column, columnIndex, $columnIndex, $event })
},
//导出
exportData(){
//获取当前表格数据(筛选后的数据)
let objData = this.$refs.plTable.getTableData().visibleData;
console.log(objData);
axios({
method: 'post',
url: '',
data: objData,
responseType: "blob",
}).then((res)=>{
const content = res.data;
const bLob = new Blob([content]);
const fileName = "表格数据.xLs";
if ("download" in document.createElement("a")) { //非IE下载
const elink = document.createElement("a");
elink.download = fileName;
elink.style.display = "none";
elink.href = URL.createObjectURL(bLob);
document.body.appendChild(elink) ;
elink.click();
URL.revokeObjectURL(elink.href); //释放URL对象
document.body.removeChild(elink) ;
} else {
navigator.msSaveBlob(bLob,fileName);
}
})
},
//筛选字段
openDialog(){
this.$refs.plTable.plDialogOpens()
},
showField(e){
// console.log(e);
this.dialogData = e
},
//查找索引,判断是否显示
findIndex(sele){
const index = this.dialogData.indexOf(this.dialogData.filter(d => d.name === sele)[0])
if(index> -1){
return this.dialogData[index]['state']
}else{
return false
}
},
}
};
</script>
<style scoped>
.content{
height: 100%;
width: 100%;
background: #c7d2d4;
}
.content_table{
padding: 10px;
width: calc(100% - 20px);
}
::v-deep ::-webkit-scrollbar {
height: 15px;
width: 15px;
}
::v-deep ::-webkit-scrollbar-thumb {
/* border-radius: 5px; */
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: rgba(0, 203, 255, 0.6);
}
::v-deep ::-webkit-scrollbar-track {
box-shadow: 0;
border-radius: 0;
background: rgba(0, 0, 0, 0.37);;
}
</style>
mock 假数据代码。
fakedata.js
// 使用 Mock
var Mock = require('mockjs')
var data = Mock.mock({
// 属性 list 的值是一个数组,其中含有 1 到 10 个元素
'list|10': [{
// 属性 id 是一个自增数,起始值为 1,每次增 1
'id': "@id()", // 随机生成id
"mtime": "@date", //随机生成日期时间
"dep": "一级部门",
"sex|1-2": true, //随机生成布尔值
"address": '@region', //随机生成区域
"city": '@county(true)', //随机生成城市
"avatar": "@dataImage('40x25','你的名字')", //生成base64图片
"score|0-100": 800, //随机生成1-100的数字
"rank|1-3": 1, //随机生成1-3的数字
"stars|1-5": '★', //随机生成1-5个★
"nickname": "@cname", //随机生成中文名字
"children|1-10": [{
'id': "@guid", // 随机生成guid
"mtime": "@date", //随机生成日期时间
"dep": "二级部门",
"sex|1-2": true, //随机生成布尔值
"address": '@region', //随机生成区域
"city": '@county(true)', //随机生成城市
"avatar": "@dataImage('40x25','你的名字')", //生成base64图片
"score|1-100": 800, //随机生成1-100的数字
"rank|1-3": 1, //随机生成1-3的数字
"stars|1-5": '★', //随机生成1-5个★
"nickname": "@cname", //随机生成中文名字
}]
}]
})
export const umyData = data
效果图
封装的时间范围组件(因为antd DatePicker组件不支持@input事件)
<template>
<div class="gtTimeRange">
<input type="date" v-model="defaultValue[0]" @input="startInput"/>
<span class="gtTimeRange_splitLine">-</span>
<input type="date" v-model="defaultValue[1]" @input="endInput" @focus="endMousemove"/>
</div>
</template>
<script>
import moment from "moment";
import "moment/locale/zh-cn";
export default {
name: "gtTimeRange",
props: {
defaultValue: {
type: Array,
// required: true,
default: () => [null,null]
},
},
watch:{
defaultValue(val){
// console.log(val)
}
},
mounted() {
},
data() {
return {
moment,
startTime:null,
endTime:null,
timeRange:null,
};
},
created(){
// console.log(moment().format("YYYY-MM-DD"));
},
methods:{
startInput(e){
this.$emit('input',this.defaultValue);
},
endInput(e){
this.$emit('input',this.defaultValue);
},
endMousemove(e){
// console.log(e);
},
};
</script>
<style scoped>
.gtTimeRange{
display: flex;
height: 50px;
width: 100%;
}
.gtTimeRange_splitLine{
color: rgb(10, 0, 0);
margin: 10px;
}
</style>