目录结构

源码
exportOptions.js
JavaScript
export default reactive([
{
label: '导出本页',
key: '1',
},
{
label: '导出全部',
key: '2',
},
])
index.vue
html
<template>
<div class="flex flex-justify-between flex-items-end">
<div>
<el-button-group>
<slot name="left"></slot>
<el-dropdown trigger="click" v-if="$attrs.onExport" @command="exportTable">
<el-button :size="size">
导出<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in exportOptions" :command="item.key">{{ item.label }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click" v-if="$attrs.onPrint" @command="printTable">
<el-button :size="size">
打印<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in printOptions" :command="item.key">{{ item.label }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-button-group>
</div>
<el-button-group :size="size">
<slot name="right"></slot>
</el-button-group>
</div>
<el-table ref="table" :header-cell-style="headerCellStyle" :cell-style="cellStyle" :row-class-name="rowClassName"
:size="size" :data="data" :[heightProp]="height || maxHeight" :border="border" :row-key="rowKey"
:empty-text="emptyText" v-loading="loading" :show-summary="showSummary" :summary-method="summaryMethod"
tooltip-effect="dark" @row-click="rowClick" @cell-click="cellClick" @select="select" @select-all="selectAll"
@selection-change="selectionChange" element-loading-text="拼命加载中" element-loading-background="rgba(0, 0, 0, 0.5)"
@row-dblclick="rowDblclick">
<el-table-column v-if="showSelection" fixed="left" type="selection" :reserve-selection="reserveSelection" width="40"
align="center" :selectable="selecteisDisabled">
</el-table-column>
<el-table-column v-if="showIndex" fixed="left" type="index" label="序号" min-width="50">
<template slot-scope="scope">
{{ pageSize * (currentPage - 1) + (scope.$index + 1) }}
</template>
</el-table-column>
<slot name="columns"></slot>
</el-table>
<el-pagination class="text-end mt-3" v-if="$attrs.handleCurrentChange || $attrs.onSizeChange" :size="size"
@size-change="dataSizeChange" @current-change="handleCurrentChange" :page-sizes="[VITE_APP_DATA_SIZE, 20, 30, 50]"
:page-size="pageSize" :disabled="total <= 0 || loading" :current-page="currentPage"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</template>
<script setup>
import { ElButtonGroup, ElButton, ElDropdown, ElTable, ElPagination, ElNotification } from "element-plus";
import { exportExcel } from '@/utils/export-file.js'
import exportOptions from "./exportOptions.js"
import printOptions from "./printOptions.js"
import propsData from "./props.js"
const instance = getCurrentInstance();
const props = defineProps(propsData)
const VITE_APP_DATA_SIZE = import.meta.env.VITE_APP_DATA_SIZE;
const emits = defineEmits(['current-change', 'size-change', 'row-click', 'cell-click', 'row-dblclick', 'select', 'select-all', 'selection-change']);
const showSelection = computed(() => {
return ["select", "selection-change", "select-all"].includes(Object.keys(instance.proxy.$attrs));
})
const heightProp = computed(() => {
// 因为elementUI不能 height 和 max-height 同时存在,所以采用动态属性
// console.log("height:", props.height, "maxHeight:", props.maxHeight);
return props.height ? "height" : "max-height";
})
watch(() => props.total, (now) => {
if (now === 0) {
return
}
for (let index in exportOptions) {
exportOptions[index].disabled = false
printOptions[index].disabled = false
}
})
// 翻页
function handleCurrentChange(val) {
console.log(`当前页: ${val}`)
emits('current-change', val)
}
// 页量
function dataSizeChange(val) {
console.log(`每页 ${val} 条`)
emits('size-change', val)
}
// 点击行
function rowClick(row, column, event) {
// console.log(row, column, event);
emits("row-click", row, column, event);
}
// 点击单元格
function cellClick(row, column, cell, event) {
emits("cell-click", row, column, cell, event);
}
// 双击行
function rowDblclick(row, column, event) {
emits("row-dblclick", row, column, event);
}
// 选择数据
function select(selection, row) {
// console.log(election, row);
emits("select", selection, row);
}
// 选择数据
function selectionChange(selection) {
// console.log(selection);
emits("selection-change", selection);
}
// 全选数据
function selectAll(selection) {
console.log(selection);
emits("select-all", selection);
}
// 导出表格
function exportTable(command) {
if (props.total < 1) {
$message.warning('无数据可导')
return
}
if (command === '1' || (command === '2' && props.total < 3000)) {
exportingExcel(command)
} else {
$dialog.warning({
content: '导出数据量过大,您可以选择联系开发人员导出',
positiveText: '好',
negativeText: '不导了',
onPositiveClick: () => {
window.open(
import.meta.env.VITE_DEPARTMENT_SERVICE,
'_blank',
'location=no, status=no, menubar=no'
)
},
})
}
}
// 执行导出表格
async function exportingExcel(command) {
const title = prompt('文件名称:', '表' || '')
if (!title) return
const notify = notification('开始下载', title)
const res = await exporting(command)
try {
if (res.code === 0 && res.total > 0) {
const columns = []
props.columns.filter((item) => {
if (item.key) {
columns.push({
header: item.title, // header是exceljs能识别的字段
key: item.key,
})
}
})
if (props.showIndex) {
res.rows = res.rows.map((item, index) => {
return {
index: index + 1,
...item,
}
})
}
exportExcel(columns, res.rows, title)
notification('下载成功', title)
}
} catch {
notification('数据响应失败', title)
}
try {
notify.close()
} catch {
notify.destroy()
}
}
function notification(content, title) {
return Notification.permission === 'granted'
? new Notification(content, { body: title })
: ElNotification.info({ content, meta: title })
}
// 打印表格数据
function printTable(command) {
if (props.total < 1) {
return $message.info('无数据打印')
}
emits('printing', command)
}
defineExpose({
exportExcel
})
</script>
<style scoped lang="scss">
:deep(.n-data-table-tr .n-data-table-resize-button) {
width: 1px;
left: unset;
right: 0;
}
:deep(.n-data-table-tr .n-data-table-resize-button::after) {
width: 1px;
right: -1px;
left: unset;
}
</style>
printOptions.js
JavaScript
export default reactive([
{
label: '打印本页',
key: '1',
},
{
label: '打印全部',
key: '2',
},
])
props.js
html
export default {
// 显示序号列
showIndex: {
type: Boolean,
default: false,
},
data: {
type: Array,
default: () => {
return []
},
},
columns: {
type: Array,
default: () => {
return []
},
},
loading: {
type: Boolean,
default: true,
},
size: {
type: String,
default: 'medium',
},
emptyText: {
type: String,
default: '暂无数据',
},
border: {
type: Boolean,
default: true,
},
total: {
type: Number,
default: 0,
},
pageSize: {
type: Number,
default: Number(import.meta.env.VITE_APP_DATA_SIZE),
},
currentPage: {
type: Number,
default: 0,
},
height: {
type: Number,
default: 550,
},
maxHeight: {
type: Number,
},
headerCellStyle: {
type: [Function, Object],
},
cellStyle: {
type: [Function, Object],
},
// 返回值用来决定这一行的 CheckBox 是否可以勾选
selecteisDisabled: {
type: Function
},
rowKey: {
type: Function,
default: undefined,
},
showSummary: {
type: Boolean,
default: false,
},
summaryMethod: {
type: Function
},
exporting: {
type: Function,
default: undefined,
},
}
使用说明
html
<q-table :data="tableData">
<template v-slot:left>
表头上左侧容器
</template>
<template v-slot:right>
表头上右侧容器
</template>
<template v-slot:columns>
数据表体部分
</template>
</q-table>
-
参数
见
\props.js
-
事件
['current-change', 'size-change', 'row-click', 'cell-click', 'row-dblclick', 'select', 'select-all', 'selection-change']
-
导出
传入
onExporting
事件,就会显示导出摁扭 -
打印
传入
onPrinting
事件,就会显示打印摁扭 -
选择列
传入
'select', 'select-all', 'selection-change'
其中一种事件,就会显示选择列