表格使用的avue
<template>
<basic-container>
<avue-crud
ref="crud"
v-model="form"
:page="page"
:data="tableData"
:table-loading="tableLoading"
:option="table"
>
<template slot="menuLeft">
<el-upload
class="upload-demo"
ref="upload"
action=""
:on-change="handleChange"
:on-exceed="onExceed"
:on-remove="handleRemove"
:http-request="uploadFile"
:limit="1"
:file-list="fileList"
:accept="accept"
:auto-upload="false"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只 能 上 传 Excel 文 件</div>
</el-upload>
</template>
<template slot="menuRight">
<el-button type="primary" size="small" @click="submitUpload">
导入
</el-button>
<el-button type="primary" size="small" @click="handleDownModel">
模板下载
</el-button>
<el-button type="primary" size="small" @click="exportData">
失败导出
</el-button>
</template>
</avue-crud>
</basic-container>
</template>
<script>
import { mapGetters } from 'vuex'
import XLSX from 'xlsx'
export default {
name: 'ImportExam',
components: {},
props: {
/** 下载模板名称 */
modelPath: {
type: String,
default: ''
},
/** 表头字段 */
headers: {
type: Object,
default: () => {
return { value: '值' }
}
},
crudAction: {
type: Object,
default: () => {
return {}
}
},
tableOption: {
type: Object
},
closeDialog: {
type: Function,
default: null
}
},
data() {
return {
tableData: [],
searchForm: {},
form: {},
page: {
// 分页参数
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
query: {
// 查询参数
},
tableLoading: false,
fileList: [],
accept: '.xlsx, .xls, .csv',
excelData: {
header: null,
results: null
},
fileData: [],
failData: []
}
},
computed: {
...mapGetters(['permissions', 'roles']),
table() {
return this.tableOption
}
},
created() {
this.clearData()
},
methods: {
handleDownModel() {
window.open(`./model/${this.modelPath}`)
},
clearData() {
this.tableData = []
this.$refs.upload.clearFiles()
},
// 导入
submitUpload() {
if (this.fileData.length === 0) {
this.$message.error('请选择文件!')
return
}
this.tableData = []
this.crudAction.importData(this.fileData).then(res => {
this.tableData = res.data
for (let i = 0; i < res.data.length; i++) {
if (res.data[i].flag === true) {
this.failData = res.data
this.$message.error('导入失败')
return
}
}
this.$message.success('导入成功')
this.$refs.upload.clearFiles()
this.closeDialog()
})
},
// 导出
exportData() {
if (this.failData.length === 0) {
this.$message.warning('只允许失败的时候导出!')
this.closeDialog()
return
}
const hea = Object.values(this.headers)
const arr = []
if (
hea.length &&
this.failData &&
Object.values(this.headers).some(item => hea.includes(item))
) {
this.failData.forEach(item => {
const row = {}
for (const key in this.headers) {
const element = this.headers[key]
row[element] = item[key]
}
arr.push(row)
})
}
this.exportExcel(arr, this.modelPath)
},
exportExcel(data, fileName) {
const ws = XLSX.utils.json_to_sheet(data)
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, 'sheet1')
XLSX.writeFile(wb, fileName)
},
uploadFile(item) {},
handleChange(file, fileList) {
if (file.name !== this.modelPath) {
this.$message.error(`请选择使用模板${this.modelPath}`)
this.$refs.upload.clearFiles()
return
}
this.readerData(file)
this.$refs.upload.clearFiles()
},
// 获取标题
getHeaderRow(sheet) {
const headers = []
const ref = sheet['!ref']
if (ref) {
const range = XLSX.utils.decode_range(ref)
let C
const R = range.s.r
for (C = range.s.c; C <= range.e.c; ++C) {
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
let hdr = 'UNKNOWN ' + C
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
}
return headers
},
// 字段名替换
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.fileData = []
if (
header.length &&
results &&
Object.values(this.headers).some(item => header.includes(item))
) {
results.forEach(item => {
const row = {}
for (const key in this.headers) {
const element = this.headers[key]
row[key] = item[element]
}
this.fileData.push(row)
this.tableData.push(row)
})
}
},
// 读取excel数据
readerData(file) {
const th = this
return new Promise(function (resolve) {
const reader = new FileReader()
reader.onload = function (e) {
const data = e.target.result
this.wb = XLSX.read(data, {
type: 'binary'
})
const firstSheetName = this.wb.SheetNames[0]
const worksheet = this.wb.Sheets[firstSheetName]
const header = th.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
th.generateData({ header, results })
resolve()
}
reader.readAsBinaryString(file.raw)
// reader.readAsBinaryString(file) // 传统input方法
})
},
handleRemove(file, fileList) {},
// 文件超出个数限制时的钩子
onExceed(files, fileList) {
this.$message.warning(`文件不允许多选`)
this.$refs.upload.clearFiles()
}
}
}
</script>
<style scoped>
.choice {
margin-left: 30px;
}
.import {
margin-left: 200px;
}
</style>
defaultOption
const propsHttp = {
name: 'filename',
url: 'uuid'
}
export { propsHttp }
export default {
propsHttp: {
...propsHttp
},
height: 'auto',
// header 50
// margin-top 15
// pagination 55
// padding-bottom 15
// margin-bottom 15
calcHeight: 50 + 15 + 55 + 15 + 15,
bottomOffset: 55 + 15 + 15,
align: 'center', // default left 表格列齐方式
border: true, // default false 表格边框
// defaultSort 表格的排序字段 {prop: 'date', order: 'descending'} prop默认排序字段,order排序方式
// dicData: {}, // 传入本次需要的静态字典(在column中dicData写对象key值即可加载)
dicUrl: `/admin/dict/type/{{key}}`, // 字典的网络请求接口(例如配置/xxx/xx/{{key}},这样的格式,在column中dicData写加载的字典,自动替换key挂载请求)
dic: '',
expand: false, // default false 是否展开折叠行
dialogClickModal: false, // default true
// dialogWidth: '50%', // default 50% 弹出表单的弹窗宽度
// formWidth: '100%', // default 100% 弹出表单宽度
index: true, // default false 是否显示表格序号
indexLabel: '序号', // default # 序号的标题
labelWidth: 110, // default 110 弹出表单标题宽度
size: 'small', // default medium 按钮大小
tableSize: 'medium', // default 跟随 size 表格大小
searchSize: 'small', // default small 搜索控件大小
// min: 3 个字 55px,最少三个字,因为两个中文字会自动补充空格变成三个字
// default: 4 个字 70px
// 5 个字 85px
// 6 个字 100px
// 7 个字 110px
searchLabelWidth: 70,
// size: 'small',
search: true, // default true 首次打开是否展开搜索栏目
searchInline: false, // default true 行内表单模式
selection: true, // default false 行可勾选
showHeader: true, // default true 是否显示表格的表头
stripe: true, // default false 是否显示表格的斑马条纹
page: true, // default true 是否显示分页
menu: true, // default true 是否显示操作菜单栏
// menuWidth: 300, // default 240 操作菜单栏的宽度
menuAlign: 'center', // default left 菜单栏对齐方式
addBtn: false, // default true 添加按钮
delBtn: false, // default true 删除按钮
cellBtn: false, // default true 表格单元格可编辑(当 column 中有搜索的属性中有 cell 为 true 的属性启用,只对 type 为 select 和 input 有作用)
columnBtn: false, // default true 列显隐按钮
editBtn: false, // default true 编辑按钮
selectClearBtn: true, // default true 清空选中按钮(当selection为true起作用)
searchBtn: true, // default true 搜索显隐按钮(当column中有搜索的属性,或则searchsolt为true时自定义搜索启动起作用)
refreshBtn: true, // default true 刷新按钮
tip: false,
// 'defaultSort': {
// prop: 'username',
// order: 'descending'
// },
// TODO: 配置优化,增加一个调用接口前是否需要赋值的配置
column: [
// {
// //! 两个字时是否自动填充空格变成三个字
// labelAutoFormatter: true
// }
]
}
tableOption
import defaultOption from '@/const/crud/default'
export const tableOption = Object.assign(
JSON.parse(JSON.stringify(defaultOption)),
{
dialogDrag: true,
align: 'center',
index: false, // default false 是否显示表格序号
selection: false, // default false 行可勾选
refreshBtn: false,
searchBtn: false,
dialogWidth: '75%',
searchShowBtn: false,
searchShow: false,
emptyBtn: false,
addBtn: false,
menu: false,
excelBtn: true,
column: [
{
label: '考试名称',
prop: 'exam_nm'
},
{
label: '考试开始时间',
prop: 'exam_beg_dt'
},
{
label: '考试结束时间',
prop: 'exam_end_dt'
},
{
label: '科目',
prop: 'subject'
},
{
label: '考试说明',
prop: 'exam_desc'
}
]
}
)
组件调用
<template>
<basic-container>
<avue-crud
ref="crud"
v-model="form"
:before-open="boxhandleOpen"
:page="page"
:data="tableData"
:table-loading="tableLoading"
:option="tableOption"
@current-change="currentChange"
@row-update="rowUpdate"
@row-save="rowSave"
@row-del="rowDel"
@refresh-change="refreshChange"
@search-change="searchChange"
@size-change="sizeChange"
>
<template slot="menuLeft">
<el-button
v-permission="'edu_exam_add'"
type="primary"
@click="handleAdd"
size="small"
>
新 增
</el-button>
<el-button type="primary" @click="importExamVisible" size="small">
导入
</el-button>
</template>
<template slot-scope="scope" slot="menu">
<!-- icon="el-icon-check" plain
icon="el-icon-delete"-->
<el-button
v-permission="'edu_exam_upd'"
type="primary"
size="small"
@click="handleEdit(scope.row, scope.index)"
>
编 辑
</el-button>
<el-button
v-permission="'edu_exam_del'"
type="danger"
size="small"
@click="handleDel(scope.row, scope.index)"
>
删 除
</el-button>
</template>
</avue-crud>
<el-dialog title="导入" width="900px" :visible.sync="dialogImportVisible">
<UploadData
ref="importExam"
:headers="headers"
model-path="考试信息导入模板.xlsx"
:table-option="importTableOption"
:crud-action="importDataAction"
:close-dialog="closeDialog"
></UploadData>
</el-dialog>
</basic-container>
</template>
<script>
import {
getPage,
postObj,
putObj,
deleteById,
importData
} from '@/api/edu/exam.js'
import { tableOption } from '@/const/crud/edu/examOption.js'
import UploadData from '@/components/exam/import-exam'
import { tableOption as importTableOption } from '@/const/crud/edu/examImportOption'
export default {
name: 'EduExam',
components: {
UploadData
},
data() {
return {
tableOption,
importTableOption,
tableData: [],
form: {},
page: {
// 分页参数
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
query: {
// 查询参数
},
tableLoading: false,
dialogImportVisible: false,
headers: {
exam_nm: '考试名称',
exam_beg_dt: '考试开始时间',
exam_end_dt: '考试结束时间',
subject: '科目',
exam_desc: '考试说明'
},
importDataAction: {
getPage,
postObj,
putObj,
deleteById,
importData
},
fileData: []
}
},
created() {
this.refreshChange()
},
computed: {},
methods: {
// 导入
importExamVisible() {
this.dialogImportVisible = true
this.$nextTick(() => {
this.$refs.importExam.clearData()
})
},
uploadSuccess({ header, results }) {
this.fileData = []
if (
header.length &&
results &&
Object.values(this.headers).some(item => header.includes(item))
) {
results.forEach(item => {
const row = {}
for (const key in this.headers) {
const element = this.headers[key]
row[key] = item[element]
}
this.fileData.push(row)
})
}
},
closeDialog() {
this.dialogImportVisible = false
this.refreshChange()
},
handleAdd() {
this.tableData = []
this.refreshChange()
this.$refs.crud.rowAdd()
},
handleEdit(row, index) {
this.$refs.crud.rowEdit(row, index)
},
handleDel(row, index) {
this.$refs.crud.rowDel(row, index)
},
// 导入
importExam() {},
/**
* @title 每页多少条回调函数
* @param val 显示多少条值
*/
sizeChange(val, searchForm) {
this.page.pageSize = val
this.refreshChange({ searchForm })
},
/**
* @title 表单打开前执行函数
* @param show 展示方法
*/
boxhandleOpen(show) {
show()
},
searchChange(searchForm) {
this.page.currentPage = 1
this.refreshChange({ searchForm })
},
/**
* @title 首次加载获取列表
* @param val 当前第几页
*/
refreshChange({ searchForm } = {}) {
this.tableLoading = true
getPage({
// 查询参数
current: this.page.currentPage,
size: this.page.pageSize,
// oFields: ['id'], // 排序字段
// oTypes: ['desc'], // asc desc
...searchForm
})
.then(({ data: { records, total } }) => {
this.tableData = records
this.page.total = total
})
.finally(() => {
this.tableLoading = false
})
},
/**
* @title 页码回调
* @param val 当前第几页
*/
currentChange(val, searchForm) {
this.page.currentPage = val
this.refreshChange({ searchForm })
},
/**
* @title 数据删除回调
* @param row 为当前的数据
* @param index 为当前的数据第几行
*/
rowDel({ id, version }, index) {
// ID 为 row["id"]
this.$confirm('是否确认删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await deleteById(id, { version })
this.$message.success('删除成功')
this.refreshChange()
})
},
/**
* @title 数据更新回调
* @param row 为当前的数据
* @param index 为当前的数据第几行
* @param done 为表单关闭函数
* @param loading 为取消按钮加载中函数
*/
rowUpdate(row, index, done, loading) {
putObj(row)
.then(() => {
this.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
this.refreshChange()
done()
})
.finally(() => {
loading()
})
},
/**
* @title 数据添加回调
* @param row 为当前的数据
* @param done 为表单关闭函数
* @param loading 为取消按钮加载中函数
*/
rowSave(row, done, loading) {
postObj(row)
.then(() => {
this.$message.success('新增成功')
this.refreshChange()
done()
})
.finally(() => {
loading()
})
}
}
}
</script>