You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ocr-web/src/views/final/comp/ImportExcelModal.vue

408 lines
9.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts" setup>
import { reactive, ref, unref } from "vue";
import * as XLSX from "xlsx";
import { arrayEquals } from "@/utils/index";
import { generateUuid } from "@/utils/uuid";
const props = defineProps<{
onSuccess: Function;
headerConfig: string[];
}>();
interface ExcelData {
header: string[] | null;
content: any[] | null;
}
interface ParseResults {
fileName: string;
results: any[];
uuid: string;
}
const cardStyle = {
"margin-bottom":"364px",
width: "484px",
"--n-padding-bottom": "10px",
"--n-padding-left": "0px",
};
const inputRef = ref(null);
let loading = false;
const excelData: ExcelData = { header: null, content: null };
const excelDatas: ParseResults[] = reactive([]);
function generateData(content) {
excelData.header = props.headerConfig;
excelData.content = content;
props.onSuccess && props.onSuccess(excelData);
}
function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
if (loading) return;
const files = e.dataTransfer.files;
const rawFiles = Array.from(files);
// eslint-disable-next-line dot-notation
const $message = window["$message"];
if (!isExcel(rawFiles)) {
$message.error("Only supports upload .xlsx, .xls, .csv suffix files");
return false;
}
uploadFiles(rawFiles);
e.stopPropagation();
e.preventDefault();
}
async function uploadFiles(files) {
const inputEl: HTMLInputElement | null = unref(inputRef);
inputEl!.value = "";
loading = true;
for (const file of files) {
const fileData = await readFileData(file);
const message = validate(fileData);
// TODO
if (message === undefined || true) {
const uuid = generateUuid();
excelDatas.push({
fileName: file.name,
results: (fileData as any).results,
uuid,
});
}
}
loading = false;
}
function commitData() {
const mergeResults: any[] = [];
if (excelDatas.length === 0) return;
excelDatas.forEach((item) => {
mergeResults.push(...item.results);
});
generateData(mergeResults);
}
function validate(fileData) {
const { header } = fileData;
// 校验表头是否匹配
const equal = arrayEquals(header, props.headerConfig);
if (!equal) return "表头不匹配";
// TODO校验值是否匹配
}
function readFileData(file) {
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 = getHeaderRow(worksheet);
const results = XLSX.utils.sheet_to_json(worksheet);
resolve({ header, results });
};
reader.readAsArrayBuffer(file);
});
}
function handleDragover(e) {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = "copy";
}
function handleUpload() {
(inputRef.value as any).click();
}
function handleClick(e) {
const files = e.target.files;
const rawFiles = Array.from(files);
uploadFiles(rawFiles);
}
function getHeaderRow(sheet) {
const headers: string[] = [];
const range = XLSX.utils.decode_range(sheet["!ref"]);
let C;
const R = range.s.r;
/* start in the first row */
for (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;
}
function isExcel(files) {
return files.every((file) => {
return /\.(xlsx|xls|csv)$/.test(file.name);
});
}
const show = ref(false);
function showModal() {
show.value = true;
}
function closeModal() {
show.value = false;
}
async function handleSumbit(e: MouseEvent) {
e.preventDefault();
commitData();
closeModal();
}
defineExpose({
showModal,
});
function removeHandler(id: string) {
const index = excelDatas.findIndex((item) => item.uuid === id);
excelDatas.splice(index, 1);
}
function afterLeave() {
excelDatas.length = 0;
}
</script>
<template>
<n-modal v-model:show="show" transform-origin="center" @after-leave="afterLeave">
<n-card
:style="cardStyle"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<div class="wrapper">
<div class="wrapper-header">
<span class="wrapper-left">批量导入</span>
<div class="wrapper-right">
<div class="wrapper-right-close" @pointerdown="closeModal">
<div class="wrapper-right-icon" />
</div>
</div>
</div>
<div class="topline"></div>
<n-divider />
<div class="wrapper-content">
<div
class="wrapper-content-dragger"
@drop="handleDrop"
@dragover="handleDragover"
@dragenter="handleDragover"
>
<input
ref="inputRef"
class="excel-upload-input"
type="file"
accept=".xlsx, .xls,.csv"
@change="handleClick"
/>
<SvgIcon
style="margin-top: 32px; margin-bottom: 13px"
size="45"
name="upload"
@click="handleUpload"
/>
<span class="wrapper-tip1">点击或拖拽审批文件到这里上传</span>
<span style="margin-top: 3px; margin-bottom: 19px" class="wrapper-tip2"
>支持上传格式.xls .xlsx .csv的文件</span
>
</div>
<div
v-for="(item, index) in excelDatas"
:key="index"
class="wrapper-content-files"
>
<div> <SvgIcon
size="24"
style="margin-top:-6px"
name="excelicon"
/><span style="margin-left:10px;paddin-top:5px">{{ item.fileName }}</span></div>
<div>
<SvgIcon
size="16px"
style="display: block; margin-left: auto; cursor: pointer"
name="clear"
@click="removeHandler(item.uuid)"
/>
</div>
</div>
</div>
<div class="bottomline"></div>
</div>
<template #footer>
<div class="footer">
<n-button type="info" style="width: 60px;
height: 36px;
background: #507afd;" @click="handleSumbit"> </n-button>
</div>
</template>
</n-card>
</n-modal>
</template>
<style lang="less" scoped>
.wrapper {
&-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0px 0px 0 24px;
// padding: 10px;
}
&-left {
font-size: 16px;
font-family: PingFang SC, PingFang SC-Medium;
font-weight: bolder;
text-align: left;
color: #222222;
line-height: 24px;
}
&-right {
&-close {
width: 12px;
height: 12px;
cursor: pointer;
margin-right: 25px;
color: #999999;
}
&-icon {
background: #999999;
display: inline-block;
width: 16px;
height: 1px;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
margin-bottom: 8px;
&:after {
content: "";
display: block;
width: 16px;
height: 1px;
background: #999999;
transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
}
}
}
&-content {
margin: 55px 24px 0 25px;
&-dragger {
height: 150px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px dashed #1980ff;
// width: 600px;
font-size: 14px;
font-weight: bold;
border-radius: 2px;
text-align: center;
background: rgba(202,210,221,0.10);
}
&-files {
margin-top: 17px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
}
}
.excel-upload-input {
display: none;
z-index: -9999;
}
.footer {
display: flex;
justify-content: flex-end;
padding-right: 30px;
}
::v-deep(.n-card.n-card--content-segmented > .n-card__content:not(:first-child)) {
border: 0px;
}
::v-deep(.n-card > .n-card-header) {
--n-padding-top: 0px;
--n-padding-bottom: 12px;
}
::v-deep(.n-divider:not(.n-divider--vertical)) {
margin-top: 0px;
margin-bottom: 0px;
}
::v-deep(.n-divider:not(.n-divider--dashed) .n-divider__line){
background: none;
}
::v-deep(.n-button){
width: 60px !important;
height: 36px !important;
background: #507afd !important;
border-radius: 3px;
}
.wrapper-tip1 {
font-size: 14px;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: lighter;
color: #666666;
line-height: 24px;
}
.wrapper-tip2 {
font-size: 12px;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: lighter;
text-align: left;
color: #999999;
line-height: 22px;
}
.topline{
background: rgba(0,0,0,0.09);
height: 1px;
width: 100%;
position: absolute;
top:56px
}
.bottomline{
background: rgba(0,0,0,0.09);
height: 1px;
width: 100%;
margin-top: 33px;
// position: absolute;
// bottom:68px
}
</style>