新增:Excel导入导出

Signed-off-by: skyselang <215817969@qq.com>
This commit is contained in:
skyselang 2022-08-24 10:59:12 +08:00
parent cde7ea6e08
commit 608a91da19
15 changed files with 383 additions and 52 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5a848505"],{"2a80":function(t,n,e){"use strict";e.r(n),e.d(n,"excelExport",(function(){return c}));e("5d63"),e("697e"),e("7b93"),e("dc64"),e("737f"),e("e508");var o=e("391f");function c(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],e=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"export-excel",c=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"xlsx",r=arguments.length>4&&void 0!==arguments[4]&&arguments[4];n.length||(n=Object.keys(t[0])),e||(e="export-excel"),c||(c="xlsx");var h=[],a=[];n.forEach((function(t){"string"===typeof t?(h.push(t),a.push(t)):(h.push(Object.values(t)[0]),a.push(Object.keys(t)[0]))}));var u=[];u.push(h),t.forEach((function(t){var n=[];a.forEach((function(e){n.push(t[e])})),u.push(n)}));var i=o["b"].book_new(),s=o["b"].aoa_to_sheet(u);if(r){for(var l=u.map((function(t){return t.map((function(t){return null==t?{wch:10}:t.toString().charCodeAt(0)>255?{wch:2*t.toString().length+2}:{wch:t.toString().length+3}}))})),f=l[0],p=1;p<l.length;p++)for(var v=0;v<l[p].length;v++)f[v]["wch"]<l[p][v]["wch"]&&(f[v]["wch"]=l[p][v]["wch"]);s["!cols"]=f}else{var g=[];h.forEach((function(t){var n={};null===t||void 0===t?n.wch=10:t.toString().charCodeAt(0)>255?n.wch=2*t.toString().length+2:n.wch=t.toString().length+2,g.push(n)})),s["!cols"]=g}o["b"].book_append_sheet(i,s,e),o["c"](i,e+"."+c)}},ae0a:function(t,n,e){var o=e("d50f"),c=e("a63b"),r=e("307f"),h=e("8280"),a=e("dc05").f,u=c(a),i=c([].push),s=function(t){return function(n){var e,c=h(n),a=r(c),s=a.length,l=0,f=[];while(s>l)e=a[l++],o&&!u(c,e)||i(f,t?[e,c[e]]:c[e]);return f}};t.exports={entries:s(!0),values:s(!1)}},dc64:function(t,n,e){var o=e("50c8"),c=e("ae0a").values;o({target:"Object",stat:!0},{values:function(t){return c(t)}})}}]);

File diff suppressed because one or more lines are too long

View File

@ -17,6 +17,7 @@
"e-icon-picker": "^1.1.7",
"echarts": "5.3.3",
"element-ui": "2.15.8",
"file-saver": "^2.0.5",
"fuse.js": "3.6.1",
"js-cookie": "3.0.1",
"normalize.css": "8.0.1",
@ -24,10 +25,12 @@
"path-to-regexp": "6.2.1",
"qrcode.vue": "1.7.0",
"screenfull": "5.2.0",
"script-loader": "^0.7.2",
"vue": "2.6.14",
"vue-router": "3.5.4",
"vuex": "3.6.2",
"wangeditor": "4.7.15"
"wangeditor": "4.7.15",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.5.12",

View File

@ -89,6 +89,17 @@ export function disable(data) {
data
})
}
/**
* 会员导入
* @param {array} data 请求数据
*/
export function imports(data) {
return request({
url: url + 'import',
method: 'post',
data
})
}
/**
* 会员统计
* @param {array} params 请求参数

View File

@ -0,0 +1,96 @@
import * as XLSX from 'xlsx'
/**
* 导出
* @param {array} data 数据
* @param {array} header 表头eg:[{ member_id: '会员id' }, { username: '会员名' }]
* @param {string} fileName 文件名
* @param {string} bookType 文件类型xlsx, csv, txt
* @param {bool} autoWidth 宽度是否自适应
*/
export function excelExport(data, header = [], fileName = 'export-excel', bookType = 'xlsx', autoWidth = false) {
if (!header.length) {
header = Object.keys(data[0])
}
if (!fileName) {
fileName = 'export-excel'
}
if (!bookType) {
bookType = 'xlsx'
}
// 表头名称,导出字段
const headerName = []
const headerField = []
header.forEach((ihn) => {
if (typeof ihn === 'string') {
headerName.push(ihn)
headerField.push(ihn)
} else {
headerName.push(Object.values(ihn)[0])
headerField.push(Object.keys(ihn)[0])
}
})
// 导出数据
const xlsxData = []
xlsxData.push(headerName)
data.forEach((id) => {
const rowData = []
headerField.forEach((ihf) => {
rowData.push(id[ihf])
})
xlsxData.push(rowData)
})
const workbook = XLSX.utils.book_new()
const worksheet = XLSX.utils.aoa_to_sheet(xlsxData)
// 设置列宽
if (autoWidth) {
const colWidth = xlsxData.map(row => row.map(val => {
if (val == null) {
return {
'wch': 10
}
} else if (val.toString().charCodeAt(0) > 255) {
return {
'wch': val.toString().length * 2 + 2
}
} else {
return {
'wch': val.toString().length + 3
}
}
}))
const cols = colWidth[0]
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (cols[j]['wch'] < colWidth[i][j]['wch']) {
cols[j]['wch'] = colWidth[i][j]['wch']
}
}
}
worksheet['!cols'] = cols
} else {
const cols = []
headerName.forEach((val) => {
const col = {}
if (val === null || val === undefined) {
col.wch = 10
} else if (val.toString().charCodeAt(0) > 255) {
col.wch = val.toString().length * 2 + 2
} else {
col.wch = val.toString().length + 2
}
cols.push(col)
})
worksheet['!cols'] = cols
}
// 下载文件
XLSX.utils.book_append_sheet(workbook, worksheet, fileName)
XLSX.writeFile(workbook, fileName + '.' + bookType)
}

View File

@ -0,0 +1,152 @@
<template>
<div style="display:flex;float:right">
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls, .csv" @change="handleClick">
<el-button :loading="loading" @click="handleUpload">{{ title }}</el-button>
<el-dialog :title="dialogTitle" :visible.sync="dialogSync" top="5vh" width="70%" :close-on-click-modal="false" :close-on-press-escape="false">
<el-form label-width="0">
<el-form-item label="" prop="">
<el-table v-loading="loading" :data="excelData.results" :height="height">
<el-table-column v-for="item of excelData.header" :key="item" :prop="item" :label="item" />
</el-table>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="loading" @click="cancel">取消</el-button>
<el-button :loading="loading" type="primary" @click="submit">导入</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as XLSX from 'xlsx'
import screenHeight from '@/utils/screen-height'
export default {
props: {
limitSize: { type: Number, default: 1 },
title: { type: String, default: '导入' }
},
data() {
return {
loading: false,
height: 580,
dialogTitle: '导入预览',
dialogSync: false,
excelData: {
header: null,
results: null
}
}
},
created() {
this.height = screenHeight()
},
methods: {
cancel() {
this.dialogSync = false
},
submit() {
this.dialogSync = false
this.$emit('on-import', this.excelData)
},
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.dialogSync = true
},
handleDrop(e) {
e.stopPropagation()
e.preventDefault()
if (this.loading) return
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$message.error('只能上传一个文件')
return
}
const rawFile = files[0]
if (!this.isExcel(rawFile)) {
this.$message.error('文件类型仅支持 xlsx、xls、csv')
return false
}
this.upload(rawFile)
e.stopPropagation()
e.preventDefault()
},
handleUpload() {
this.$refs['excel-upload-input'].click()
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null
if (!this.beforeUpload) {
this.readerData(rawFile)
return
}
const before = this.beforeUpload(rawFile)
if (before) {
this.readerData(rawFile)
}
},
beforeUpload(file) {
const limitSize = this.limitSize
const fileSize = file.size / 1024 / 1024
if (fileSize > limitSize) {
this.$message.error(`文件大小不能大于 ${limitSize} m`)
return false
}
return true
},
readerData(rawFile) {
this.loading = true
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateData({ header, results })
this.loading = false
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
},
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!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
},
isExcel(file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
}
}
</script>
<style scoped>
.excel-upload-input{
display: none;
z-index: -9999;
}
</style>

View File

@ -69,8 +69,12 @@
<el-button title="重置密码" @click="selectOpen('repwd')">密码</el-button>
<el-button title="是否禁用" @click="selectOpen('disable')">禁用</el-button>
<el-button title="删除" @click="selectOpen('dele')">删除</el-button>
<el-button title="导出" @click="selectOpen('export')">导出</el-button>
<el-button v-if="recycle" type="primary" @click="selectOpen('reco')">恢复</el-button>
<el-button v-else type="primary" @click="add()">添加</el-button>
<el-tooltip class="item" effect="dark" content="表头:昵称,用户名,手机,邮箱,密码" placement="left">
<excel-import v-if="checkPermission(['admin/member.Member/export'])" :limit-size="1" title="导入" @on-import="imports" />
</el-tooltip>
</el-col>
</el-row>
<el-dialog :title="selectTitle" :visible.sync="selectDialog" top="20vh" :close-on-click-modal="false" :close-on-press-escape="false">
@ -100,6 +104,20 @@
<span v-if="recycle" style="color:red">确定要彻底删除选中的{{ name }}删除后不可恢复</span>
<span v-else style="color:red">确定要删除选中的{{ name }}</span>
</el-form-item>
<div v-else-if="selectType==='export'">
<el-form-item label="文件名称" prop="">
<el-input v-model="exportFileName" placeholder="请输入文件名称" clearable />
</el-form-item>
<el-form-item label="文件类型" prop="">
<el-select v-model="exportBookType">
<el-option v-for="item in ['xlsx','csv', 'txt']" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="自动宽度" prop="">
<el-switch v-model="exportAutoWidth" :active-value="true" :inactive-value="false" />
<span> 宽度是否自适应</span>
</el-form-item>
</div>
<el-form-item v-else-if="selectType==='reco'" label="" prop="">
<span style="color:red">确定要恢复选中的{{ name }}</span>
</el-form-item>
@ -290,16 +308,18 @@
</template>
<script>
import checkPermission from '@/utils/permission' //
import screenHeight from '@/utils/screen-height'
import Pagination from '@/components/Pagination'
import FileManage from '@/components/FileManage'
import clip from '@/utils/clipboard'
import ExcelImport from '@/components/ExcelImport/index.vue'
import { arrayColumn } from '@/utils/index'
import { list, info, add, edit, dele, region, repwd, disable, recover, recoverReco, recoverDele } from '@/api/member/member'
import { list, info, add, edit, dele, region, repwd, disable, imports, recover, recoverReco, recoverDele } from '@/api/member/member'
export default {
name: 'Member',
components: { Pagination, FileManage },
components: { Pagination, FileManage, ExcelImport },
data() {
return {
name: '会员',
@ -350,7 +370,10 @@ export default {
region_id: 0,
password: '',
is_disable: 0,
fileDialog: false
fileDialog: false,
exportFileName: '',
exportBookType: 'xlsx',
exportAutoWidth: false
}
},
created() {
@ -359,6 +382,7 @@ export default {
this.list()
},
methods: {
checkPermission,
//
list() {
this.loading = true
@ -489,6 +513,14 @@ export default {
this.selectTitle = '是否禁用'
} else if (selectType === 'dele') {
this.selectTitle = '删除' + this.name
} else if (selectType === 'export') {
var date = new Date()
var month = date.getMonth() + 1
month = month < 10 ? '0' + month : month
this.exportFileName = this.name + date.getFullYear() + '-' + month + '-' + date.getDate()
this.selectTitle = '导出'
} else if (selectType === 'import') {
this.selectTitle = '导入'
} else if (selectType === 'reco') {
this.selectTitle = '恢复' + this.name
}
@ -512,6 +544,10 @@ export default {
this.disable(this.selection, true)
} else if (selectType === 'dele') {
this.dele(this.selection)
} else if (selectType === 'export') {
this.export(this.selection)
} else if (selectType === 'import') {
this.import(this.selection)
} else if (selectType === 'reco') {
this.reco(this.selection)
}
@ -604,6 +640,35 @@ export default {
}
}
},
//
export(row) {
this.loading = true
import('@/components/ExcelExport/index').then(excel => {
const header = [
{ member_id: '会员id' },
{ nickname: '昵称' },
{ username: '用户名' },
{ phone: '手机' },
{ email: '邮箱' },
{ remark: '备注' },
{ create_time: '注册时间' }
]
excel.excelExport(row, header, this.exportFileName, this.exportBookType, this.exportAutoWidth)
this.loading = false
})
},
// resultsheader
imports({ results, header }) {
this.loading = true
imports({
import: results
}).then(res => {
this.list()
this.$message.success(res.msg)
}).catch(() => {
this.loading = false
})
},
//
reco(row) {
if (!row.length) {