Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(excel): add import/export #40

Merged
merged 1 commit into from
Oct 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"vue-router": "^4.0.0-beta.13",
"vuex": "^4.0.0-beta.4",
"vuex-module-decorators": "^1.0.1",
"xlsx": "^0.16.8",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
Expand Down
6 changes: 6 additions & 0 deletions src/components/Excel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default as ImportExcel } from './src/ImportExcel';
export { default as ExportExcelModel } from './src/ExportExcelModel.vue';

export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel';

export * from './src/types';
57 changes: 57 additions & 0 deletions src/components/Excel/src/Export2Excel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import xlsx from 'xlsx';
import type { WorkBook } from 'xlsx';
import type { JsonToSheet, AoAToSheet } from './types';
// import { isObject } from '@/src/utils/is';

const { utils, writeFile } = xlsx;

export function jsonToSheetXlsx<T = any>({
data,
header,
filename = 'excel-list.xlsx',
json2sheetOpts = {},
write2excelOpts = { bookType: 'xlsx' },
}: JsonToSheet<T>) {
const arrData = [...data];
if (header) {
arrData.unshift(header);
json2sheetOpts.skipHeader = true;
}

const worksheet = utils.json_to_sheet(arrData, json2sheetOpts);

/* add worksheet to workbook */
const workbook: WorkBook = {
SheetNames: [filename],
Sheets: {
[filename]: worksheet,
},
};
/* output format determined by filename */
writeFile(workbook, filename, write2excelOpts);
/* at this point, out.xlsb will have been downloaded */
}
export function aoaToSheetXlsx<T = any>({
data,
header,
filename = 'excel-list.xlsx',
write2excelOpts = { bookType: 'xlsx' },
}: AoAToSheet<T>) {
const arrData = [...data];
if (header) {
arrData.unshift(header);
}

const worksheet = utils.aoa_to_sheet(arrData);

/* add worksheet to workbook */
const workbook: WorkBook = {
SheetNames: [filename],
Sheets: {
[filename]: worksheet,
},
};
/* output format determined by filename */
writeFile(workbook, filename, write2excelOpts);
/* at this point, out.xlsb will have been downloaded */
}
79 changes: 79 additions & 0 deletions src/components/Excel/src/ExportExcelModel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>
<BasicModal v-bind="$attrs" title="导出数据" @ok="handleOk" @register="registerModal">
<BasicForm
:labelWidth="100"
:schemas="schemas"
:showActionButtonGroup="false"
@register="registerForm"
/>
</BasicModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';

const schemas: FormSchema[] = [
{
field: 'filename',
component: 'Input',
label: '文件名',
rules: [{ required: true }],
},
{
field: 'bookType',
component: 'Select',
label: '文件类型',
defaultValue: 'xlsx',
rules: [{ required: true }],
componentProps: {
options: [
{
label: 'xlsx',
value: 'xlsx',
key: 'xlsx',
},
{
label: 'html',
value: 'html',
key: 'html',
},
{
label: 'csv',
value: 'csv',
key: 'csv',
},
{
label: 'txt',
value: 'txt',
key: 'txt',
},
],
},
},
];
export default defineComponent({
components: { BasicModal, BasicForm },
setup(_, { emit }) {
const [registerForm, { validateFields }] = useForm();
const [registerModal, { closeModal }] = useModalInner();

async function handleOk() {
const res = await validateFields();
const { filename, bookType } = res;

emit('success', {
filename: `${filename.split('.').shift()}.${bookType}`,
bookType,
});
closeModal();
}
return {
schemas,
handleOk,
registerForm,
registerModal,
};
},
});
</script>
120 changes: 120 additions & 0 deletions src/components/Excel/src/ImportExcel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { defineComponent, ref, unref } from 'vue';
import XLSX from 'xlsx';
import { getSlot } from '/@/utils/helper/tsxHelper';

import type { ExcelData } from './types';
export default defineComponent({
name: 'ImportExcel',
setup(_, { slots, emit }) {
const inputRef = ref<HTMLInputElement | null>(null);
const loadingRef = ref<Boolean>(false);

/**
* @description: 第一行作为头部
*/
function getHeaderRow(sheet: XLSX.WorkSheet) {
if (!sheet || !sheet['!ref']) return [];
const headers: string[] = [];
// A3:B7=>{s:{c:0, r:2}, e:{c:1, r:6}}
const range = XLSX.utils.decode_range(sheet['!ref']);

const R = range.s.r;
/* start in the first row */
for (let C = range.s.c; C <= range.e.c; ++C) {
/* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C; // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);
headers.push(hdr);
}
return headers;
}

/**
* @description: 获得excel数据
*/
function getExcelData(workbook: XLSX.WorkBook) {
const excelData: ExcelData[] = [];
for (const sheetName of workbook.SheetNames) {
const worksheet = workbook.Sheets[sheetName];
const header: string[] = getHeaderRow(worksheet);
const results = XLSX.utils.sheet_to_json(worksheet);
excelData.push({
header,
results,
meta: {
sheetName,
},
});
}
return excelData;
}

/**
* @description: 读取excel数据
*/
function readerData(rawFile: File) {
loadingRef.value = true;
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async (e) => {
try {
const data = e.target && e.target.result;
const workbook = XLSX.read(data, { type: 'array' });
// console.log(workbook);
/* DO SOMETHING WITH workbook HERE */
const excelData = getExcelData(workbook);
emit('success', excelData);
resolve();
} catch (error) {
reject(error);
} finally {
loadingRef.value = false;
}
};
reader.readAsArrayBuffer(rawFile);
});
}

async function upload(rawFile: File) {
const inputRefDom = unref(inputRef);
if (inputRefDom) {
// fix can't select the same excel
inputRefDom.value = '';
}
readerData(rawFile);
}
/**
* @description: 触发选择文件管理器
*/
function handleInputClick(e: Event) {
const files = e && (e.target as HTMLInputElement).files;
const rawFile = files && files[0]; // only use files[0]
if (!rawFile) return;
upload(rawFile);
}
/**
* @description: 点击上传按钮
*/
function handleUpload() {
const inputRefDom = unref(inputRef);
inputRefDom && inputRefDom.click();
}

return () => {
return (
<div>
<input
ref={inputRef}
type="file"
accept=".xlsx, .xls"
style=" z-index: -9999; display: none;"
onChange={handleInputClick}
/>
<div onClick={handleUpload}>{getSlot(slots)}</div>
</div>
);
};
},
});
30 changes: 30 additions & 0 deletions src/components/Excel/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { JSON2SheetOpts, WritingOptions, BookType } from 'xlsx';

export interface ExcelData<T = any> {
header: string[];
results: T[];
meta: { sheetName: string };
}

// export interface ImportProps {
// beforeUpload: (file: File) => boolean;
// }

export interface JsonToSheet<T = any> {
data: T[];
header?: T;
filename?: string;
json2sheetOpts?: JSON2SheetOpts;
write2excelOpts?: WritingOptions;
}
export interface AoAToSheet<T = any> {
data: T[][];
header?: T[];
filename?: string;
write2excelOpts?: WritingOptions;
}

export interface ExportModalResult {
filename: string;
bookType: BookType;
}
14 changes: 14 additions & 0 deletions src/router/menus/modules/demo/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ const menu: MenuModule = {
path: '/strength-meter',
name: '密码强度组件',
},
{
path: '/excel',
name: 'excel',
children: [
{
path: '/export',
name: 'Export',
},
{
path: '/import',
name: 'Import',
},
],
},
],
},
};
Expand Down
26 changes: 26 additions & 0 deletions src/router/routes/modules/demo/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,31 @@ export default {
title: '密码强度组件',
},
},
{
path: '/excel',
name: 'ExcelDemo',
redirect: '/comp/excel/export',
meta: {
title: 'excel',
},
children: [
{
path: 'export',
name: 'Export2Excel',
component: () => import('/@/views/demo/comp/excel/ExportToExcel.vue'),
meta: {
title: 'Export2Excel',
},
},
{
path: 'import',
name: 'ImportExcel',
component: () => import('/@/views/demo/comp/excel/ImportExcel.vue'),
meta: {
title: 'ImportExcel',
},
},
],
},
],
} as AppRouteModule;
Loading