| | |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, ref, unref } from 'vue'; |
| | | <script lang="ts" setup> |
| | | import { ref, unref } from 'vue'; |
| | | import * as XLSX from 'xlsx'; |
| | | import { dateUtil } from '/@/utils/dateUtil'; |
| | | |
| | | import { dateUtil } from '@/utils/dateUtil'; |
| | | import type { ExcelData } from './typing'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'ImportExcel', |
| | | props: { |
| | | // 日期时间格式。如果不提供或者提供空值,将返回原始Date对象 |
| | | dateFormat: { |
| | | type: String, |
| | | }, |
| | | // 时区调整。实验性功能,仅为了解决读取日期时间值有偏差的问题。目前仅提供了+08:00时区的偏差修正值 |
| | | // https://github.com/SheetJS/sheetjs/issues/1470#issuecomment-501108554 |
| | | timeZone: { |
| | | type: Number, |
| | | default: 8, |
| | | }, |
| | | // 是否直接返回选中文件 |
| | | isReturnFile: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | defineOptions({ name: 'ImportExcel' }); |
| | | |
| | | const props = defineProps({ |
| | | // 日期时间格式。如果不提供或者提供空值,将返回原始Date对象 |
| | | dateFormat: { |
| | | type: String, |
| | | }, |
| | | emits: ['success', 'error', 'cancel'], |
| | | setup(props, { emit }) { |
| | | const inputRef = ref<HTMLInputElement | null>(null); |
| | | const loadingRef = ref<Boolean>(false); |
| | | const cancelRef = ref<Boolean>(true); |
| | | |
| | | function shapeWorkSheel(sheet: XLSX.WorkSheet, range: XLSX.Range) { |
| | | let str = ' ', |
| | | char = 65, |
| | | customWorkSheet = { |
| | | t: 's', |
| | | v: str, |
| | | r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>', |
| | | h: str, |
| | | w: str, |
| | | }; |
| | | if (!sheet || !sheet['!ref']) return []; |
| | | let c = 0, |
| | | r = 1; |
| | | while (c < range.e.c + 1) { |
| | | while (r < range.e.r + 1) { |
| | | if (!sheet[String.fromCharCode(char) + r]) { |
| | | sheet[String.fromCharCode(char) + r] = customWorkSheet; |
| | | } |
| | | r++; |
| | | } |
| | | r = 1; |
| | | str += ' '; |
| | | customWorkSheet = { |
| | | t: 's', |
| | | v: str, |
| | | r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>', |
| | | h: str, |
| | | w: str, |
| | | }; |
| | | c++; |
| | | char++; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @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.Range = XLSX.utils.decode_range(sheet['!ref']); |
| | | shapeWorkSheel(sheet, range); |
| | | 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[] = []; |
| | | const { dateFormat, timeZone } = props; |
| | | for (const sheetName of workbook.SheetNames) { |
| | | const worksheet = workbook.Sheets[sheetName]; |
| | | const header: string[] = getHeaderRow(worksheet); |
| | | let results = XLSX.utils.sheet_to_json(worksheet, { |
| | | raw: true, |
| | | dateNF: dateFormat, //Not worked |
| | | }) as object[]; |
| | | results = results.map((row: object) => { |
| | | for (let field in row) { |
| | | if (row[field] instanceof Date) { |
| | | if (timeZone === 8) { |
| | | row[field].setSeconds(row[field].getSeconds() + 43); |
| | | } |
| | | if (dateFormat) { |
| | | row[field] = dateUtil(row[field]).format(dateFormat); |
| | | } |
| | | } |
| | | } |
| | | return row; |
| | | }); |
| | | |
| | | 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', cellDates: true }); |
| | | // console.log(workbook); |
| | | /* DO SOMETHING WITH workbook HERE */ |
| | | const excelData = getExcelData(workbook); |
| | | emit('success', excelData); |
| | | resolve(''); |
| | | } catch (error) { |
| | | reject(error); |
| | | emit('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 = ''; |
| | | } |
| | | await readerData(rawFile); |
| | | } |
| | | |
| | | /** |
| | | * @description: 触发选择文件管理器 |
| | | */ |
| | | function handleInputClick(e: Event) { |
| | | const target = e && (e.target as HTMLInputElement); |
| | | const files = target?.files; |
| | | const rawFile = files && files[0]; // only setting files[0] |
| | | |
| | | target.value = ''; |
| | | |
| | | if (!rawFile) return; |
| | | |
| | | cancelRef.value = false; |
| | | if (props.isReturnFile) { |
| | | emit('success', rawFile); |
| | | return; |
| | | } |
| | | upload(rawFile); |
| | | } |
| | | |
| | | /** |
| | | * @description 文件选择器关闭后,判断取消状态 |
| | | */ |
| | | function handleFocusChange() { |
| | | const timeId = setInterval(() => { |
| | | if (cancelRef.value === true) { |
| | | emit('cancel'); |
| | | } |
| | | clearInterval(timeId); |
| | | window.removeEventListener('focus', handleFocusChange); |
| | | }, 1000); |
| | | } |
| | | |
| | | /** |
| | | * @description: 点击上传按钮 |
| | | */ |
| | | function handleUpload() { |
| | | const inputRefDom = unref(inputRef); |
| | | if (inputRefDom) { |
| | | cancelRef.value = true; |
| | | inputRefDom.click(); |
| | | window.addEventListener('focus', handleFocusChange); |
| | | } |
| | | } |
| | | |
| | | return { handleUpload, handleInputClick, inputRef }; |
| | | // 时区调整。实验性功能,仅为了解决读取日期时间值有偏差的问题。目前仅提供了+08:00时区的偏差修正值 |
| | | // https://github.com/SheetJS/sheetjs/issues/1470#issuecomment-501108554 |
| | | timeZone: { |
| | | type: Number, |
| | | default: 8, |
| | | }, |
| | | // 是否直接返回选中文件 |
| | | isReturnFile: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(['success', 'error', 'cancel']); |
| | | |
| | | const inputRef = ref<HTMLInputElement | null>(null); |
| | | const loadingRef = ref<Boolean>(false); |
| | | const cancelRef = ref<Boolean>(true); |
| | | |
| | | function shapeWorkSheel(sheet: XLSX.WorkSheet, range: XLSX.Range) { |
| | | let str = ' ', |
| | | char = 65, |
| | | customWorkSheet = { |
| | | t: 's', |
| | | v: str, |
| | | r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>', |
| | | h: str, |
| | | w: str, |
| | | }; |
| | | if (!sheet || !sheet['!ref']) return []; |
| | | let c = 0, |
| | | r = 1; |
| | | while (c < range.e.c + 1) { |
| | | while (r < range.e.r + 1) { |
| | | if (!sheet[String.fromCharCode(char) + r]) { |
| | | sheet[String.fromCharCode(char) + r] = customWorkSheet; |
| | | } |
| | | r++; |
| | | } |
| | | r = 1; |
| | | str += ' '; |
| | | customWorkSheet = { |
| | | t: 's', |
| | | v: str, |
| | | r: '<t> </t><phoneticPr fontId="1" type="noConversion"/>', |
| | | h: str, |
| | | w: str, |
| | | }; |
| | | c++; |
| | | char++; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @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.Range = XLSX.utils.decode_range(sheet['!ref']); |
| | | shapeWorkSheel(sheet, range); |
| | | 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[] = []; |
| | | const { dateFormat, timeZone } = props; |
| | | for (const sheetName of workbook.SheetNames) { |
| | | const worksheet = workbook.Sheets[sheetName]; |
| | | const header: string[] = getHeaderRow(worksheet); |
| | | let results = XLSX.utils.sheet_to_json(worksheet, { |
| | | raw: true, |
| | | dateNF: dateFormat, //Not worked |
| | | }) as object[]; |
| | | results = results.map((row: object) => { |
| | | for (let field in row) { |
| | | if (row[field] instanceof Date) { |
| | | if (timeZone === 8) { |
| | | row[field].setSeconds(row[field].getSeconds() + 43); |
| | | } |
| | | if (dateFormat) { |
| | | row[field] = dateUtil(row[field]).format(dateFormat); |
| | | } |
| | | } |
| | | } |
| | | return row; |
| | | }); |
| | | |
| | | 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', cellDates: true }); |
| | | // console.log(workbook); |
| | | /* DO SOMETHING WITH workbook HERE */ |
| | | const excelData = getExcelData(workbook); |
| | | emit('success', excelData); |
| | | resolve(''); |
| | | } catch (error) { |
| | | reject(error); |
| | | emit('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 = ''; |
| | | } |
| | | await readerData(rawFile); |
| | | } |
| | | |
| | | /** |
| | | * @description: 触发选择文件管理器 |
| | | */ |
| | | function handleInputClick(e: Event) { |
| | | const target = e && (e.target as HTMLInputElement); |
| | | const files = target?.files; |
| | | const rawFile = files && files[0]; // only setting files[0] |
| | | |
| | | target.value = ''; |
| | | |
| | | if (!rawFile) return; |
| | | |
| | | cancelRef.value = false; |
| | | if (props.isReturnFile) { |
| | | emit('success', rawFile); |
| | | return; |
| | | } |
| | | upload(rawFile); |
| | | } |
| | | |
| | | /** |
| | | * @description 文件选择器关闭后,判断取消状态 |
| | | */ |
| | | function handleFocusChange() { |
| | | const timeId = setInterval(() => { |
| | | if (cancelRef.value === true) { |
| | | emit('cancel'); |
| | | } |
| | | clearInterval(timeId); |
| | | window.removeEventListener('focus', handleFocusChange); |
| | | }, 1000); |
| | | } |
| | | |
| | | /** |
| | | * @description: 点击上传按钮 |
| | | */ |
| | | function handleUpload() { |
| | | const inputRefDom = unref(inputRef); |
| | | if (inputRefDom) { |
| | | cancelRef.value = true; |
| | | inputRefDom.click(); |
| | | window.addEventListener('focus', handleFocusChange); |
| | | } |
| | | } |
| | | </script> |