一、导入
0、关键代码
// 安装插件
npm i xlsx/yarn add xlsx
// 导入xlsx
import * as XLSX from 'xlsx';
点击提交的时候才整理数据。上传的时候文件保存在 state.form.file[0] 中的
// 定义字段映射关系
const fieldMap = {
sheet2json: {
技能名称: 'skill_name',
技能等级: 'skill_level',
技能描述: 'skill_desc',
技能类型: 'skill_type',
技能效果: 'skill_effect',
技能消耗: 'skill_cost',
技能持续时间: 'skill_duration',
技能范围: 'skill_range',
技能范围: 'skill_range',
技能目标: 'skill_target'
}
};
// 提交 --- 点击提交的时候才整理数据。上传的时候文件保存的 state.form.file[0] 中的
const handleSummit = () => {
formRef.value.validate().then(async () => {
try {
const data = await state.form.file[0].arrayBuffer(); // 使用 arrayBuffer 避免中文乱码
const workbook = XLSX.read(data, { type: 'buffer' });
const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
// 映射字段名并过滤掉不符合预期的数据
const mappedData = outdata
.map(row => {
return Object.keys(row).reduce((targetMap, key) => {
const mappedKey = fieldMap.sheet2json[key];
if (mappedKey) {
targetMap[mappedKey] = row[key];
}
return targetMap;
}, {});
})
.filter(item => Object.keys(item).length > 0); // 过滤空对象
console.log('------- 导入的数据 -------', mappedData);
emits('submit', mappedData);
handleClose();
} catch (error) {}
});
};
1、template
<a-form :model="state.form" name="form" ref="formRef" :label-col="{ style: { width: '120px' } }" autocomplete="off" :rules="rules">
<a-form-item label="导入文件上传" name="file" :rules="rules.file">
<div class="file-warp" style="position: relative">
<a-upload
style="margin-left: 20px"
:file-list="state.form.file"
name="file"
:customRequest="upload"
:beforeUpload="beforeUpload"
@remove="handleRemove"
accept=".xlsx, .xls">
<a-button type="primary">
<upload-outlined></upload-outlined>
上传文件
</a-button>
</a-upload>
<a-button type="primary" ghost style="position: absolute; top: 0; left: 150px" @click="handleDownload">
<VerticalAlignBottomOutlined></VerticalAlignBottomOutlined>
模版下载
</a-button>
</div>
</a-form-item>
</a-form>
2、script
import * as XLSX from 'xlsx';
import { reactive, ref } from 'vue';
const state = reactive({
form: {
file: []
}
});
const formRef = ref(null);
const open = ref(true);
const rules = {
file: [{ required: true, message: '请选择文件', trigger: ['blur', 'change'] }]
};
// 定义字段映射关系
const fieldMap = {
sheet2json: {
技能名称: 'skill_name',
技能等级: 'skill_level',
技能描述: 'skill_desc',
技能类型: 'skill_type',
技能效果: 'skill_effect',
技能消耗: 'skill_cost',
技能持续时间: 'skill_duration',
技能范围: 'skill_range',
技能范围: 'skill_range',
技能目标: 'skill_target'
},
json2sheet: {
skill_name: '技能名称',
skill_level: '技能等级',
skill_desc: '技能描述',
skill_type: '技能类型',
skill_effect: '技能效果',
skill_cost: '技能消耗',
skill_duration: '技能持续时间',
skill_range: '技能范围',
skill_target: '技能目标'
}
};
// 上传文件之前检测
const beforeUpload = file => {
const isXlsxOrXls = file.name.split('.')[1] == 'xlsx' || file.name.split('.')[1] == 'xls';
if (!isXlsxOrXls) {
message.error('只允许上传xlsx, xls格式的文件!');
return false;
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
message.error('文件不得大于10MB!');
return false;
}
return isXlsxOrXls && isLt10M;
};
// 选择文件
const upload = file => {
// 原本调用接口上传的
// uplaodFile(file.file).then(res => {
// fileList.value.push({ name: res.data.originalFilename, url: viteConfig.baseUrl + res.data.fileName, fileUrl: res.data.fileName });
// formRef.value.clearValidate();
// });
state.form.file = [file.file];
formRef.value.clearValidate();
};
// 移除文件
const handleRemove = file => {
state.form.file = [];
};
// 提交 转换数据
const handleSummit = () => {
formRef.value.validate().then(async () => {
try {
const data = await state.form.file[0].arrayBuffer(); // 使用 arrayBuffer 避免中文乱码
const workbook = XLSX.read(data, { type: 'buffer' });
const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
// 映射字段名并过滤掉不符合预期的数据
const mappedData = outdata
.map(row => {
return Object.keys(row).reduce((targetMap, key) => {
const mappedKey = fieldMap.sheet2json[key];
if (mappedKey) {
targetMap[mappedKey] = row[key];
}
return targetMap;
}, {});
})
.filter(item => Object.keys(item).length > 0); // 过滤空对象
console.log('------- 导入的数据 -------', mappedData);
emits('submit', mappedData);
handleClose();
} catch (error) {}
});
};
二、模板下载
1、script
// 定义字段映射关系
const fieldMap = {
json2sheet: {
skill_name: '技能名称',
skill_level: '技能等级',
skill_desc: '技能描述',
skill_type: '技能类型',
skill_effect: '技能效果',
skill_cost: '技能消耗',
skill_duration: '技能持续时间',
skill_range: '技能范围',
skill_target: '技能目标'
}
};
// 模板数据
let templateData = [
{
skill_name: '大刀斩',
skill_level: '5',
skill_desc: '技能描述',
skill_type: '大招',
skill_effect: '亚瑟王那样的大招',
skill_cost: '10000',
skill_duration: '10',
skill_range: '500',
skill_target: '目标:亚瑟王'
}
];
// 模版下载
const handleDownload = () => {
// 映射字段名并过滤掉不符合预期的数据
const list = templateData
.map(row => {
return Object.keys(row).reduce((targetMap, key) => {
const mappedKey = fieldMap.json2sheet[key];
if (mappedKey) {
targetMap[mappedKey] = row[key];
}
return targetMap;
}, {});
})
.filter(item => Object.keys(item).length > 0); // 过滤空对象;
const workSheet = XLSX.utils.json_to_sheet(list);
const workBook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workBook, workSheet, '技能表');
// 生成Excel文件并下载
XLSX.writeFile(workBook, '技能表模板.xlsx');
};
三、完整的文件
<!--
* @Description: ------------ fileDescription -----------
* @Author: snows_l snows_l@163.com
* @Date: 2024-07-18 14:46:47
* @LastEditors: snows_l snows_l@163.com
* @LastEditTime: 2024-07-19 15:51:19
* @FilePath: /digital-qiankun-you/cmdb/src/pages/ipSource/components/uploadFile.vue
-->
<template>
<div class="upeate-field-warp">
<a-modal width="800px" v-model:open="open" :z-index="10004" centered :title="'规划导入'">
<template #footer>
<a-button type="primary" @click="handleSummit">确认</a-button>
</template>
<div class="update-field-content-warp">
<a-form :model="state.form" name="form" ref="formRef" :label-col="{ style: { width: '120px' } }" autocomplete="off" :rules="rules">
<a-form-item label="导入文件上传" name="file" :rules="rules.file">
<div class="file-warp" style="position: relative">
<a-upload
style="margin-left: 20px"
:file-list="state.form.file"
name="file"
:customRequest="upload"
:beforeUpload="beforeUpload"
@remove="handleRemove"
accept=".xlsx, .xls">
<a-button type="primary">
<upload-outlined></upload-outlined>
上传文件
</a-button>
</a-upload>
<a-button type="primary" ghost style="position: absolute; top: 0; left: 150px" @click="handleDownload">
<VerticalAlignBottomOutlined></VerticalAlignBottomOutlined>
模版下载
</a-button>
</div>
</a-form-item>
</a-form>
</div>
</a-modal>
</div>
</template>
<script setup>
import { UploadOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons-vue';
import { reactive, ref } from 'vue';
import * as XLSX from 'xlsx';
const emits = defineEmits(['submit']);
const state = reactive({
form: {
file: []
}
});
const formRef = ref(null);
const open = ref(true);
const rules = {
file: [{ required: true, message: '请选择文件', trigger: ['blur', 'change'] }]
};
// 上传文件之前检测
const beforeUpload = file => {
const isXlsxOrXls = file.name.split('.')[1] == 'xlsx' || file.name.split('.')[1] == 'xls';
if (!isXlsxOrXls) {
message.error('只允许上传xlsx, xls格式的文件!');
return false;
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
message.error('文件不得大于10MB!');
return false;
}
return isXlsxOrXls && isLt10M;
};
// 选择文件
const upload = file => {
// 原本调用接口上传的
// uplaodFile(file.file).then(res => {
// fileList.value.push({ name: res.data.originalFilename, url: viteConfig.baseUrl + res.data.fileName, fileUrl: res.data.fileName });
// formRef.value.clearValidate();
// });
state.form.file = [file.file];
formRef.value.clearValidate();
};
// 移除文件
const handleRemove = file => {
state.form.file = [];
};
// 初始化
const init = () => {
open.value = true;
};
// 关闭
const handleClose = () => {
open.value = false;
};
// 定义字段映射关系
const fieldMap = {
sheet2json: {
技能名称: 'skill_name',
技能等级: 'skill_level',
技能描述: 'skill_desc',
技能类型: 'skill_type',
技能效果: 'skill_effect',
技能消耗: 'skill_cost',
技能持续时间: 'skill_duration',
技能范围: 'skill_range',
技能范围: 'skill_range',
技能目标: 'skill_target'
},
json2sheet: {
skill_name: '技能名称',
skill_level: '技能等级',
skill_desc: '技能描述',
skill_type: '技能类型',
skill_effect: '技能效果',
skill_cost: '技能消耗',
skill_duration: '技能持续时间',
skill_range: '技能范围',
skill_target: '技能目标'
}
};
// 模板数据
let templateData = [
{
skill_name: '大刀斩',
skill_level: '5',
skill_desc: '技能描述',
skill_type: '大招',
skill_effect: '亚瑟王那样的大招',
skill_cost: '10000',
skill_duration: '10',
skill_range: '500',
skill_target: '目标:亚瑟王'
}
];
// 提交
const handleSummit = () => {
formRef.value.validate().then(async () => {
try {
const data = await state.form.file[0].arrayBuffer(); // 使用 arrayBuffer 避免中文乱码
const workbook = XLSX.read(data, { type: 'buffer' });
const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
// 映射字段名并过滤掉不符合预期的数据
const mappedData = outdata
.map(row => {
return Object.keys(row).reduce((targetMap, key) => {
const mappedKey = fieldMap.sheet2json[key];
if (mappedKey) {
targetMap[mappedKey] = row[key];
}
return targetMap;
}, {});
})
.filter(item => Object.keys(item).length > 0); // 过滤空对象
console.log('------- 导入的数据 -------', mappedData);
emits('submit', mappedData);
handleClose();
} catch (error) {}
});
};
// 模版下载
const handleDownload = () => {
// 映射字段名并过滤掉不符合预期的数据
const list = templateData
.map(row => {
return Object.keys(row).reduce((targetMap, key) => {
const mappedKey = fieldMap.json2sheet[key];
if (mappedKey) {
targetMap[mappedKey] = row[key];
}
return targetMap;
}, {});
})
.filter(item => Object.keys(item).length > 0); // 过滤空对象;
const workSheet = XLSX.utils.json_to_sheet(list);
const workBook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workBook, workSheet, '技能表');
// 生成Excel文件并下载
XLSX.writeFile(workBook, '技能表模板.xlsx');
};
defineExpose({
init
});
</script>
<style lang="less" scoped>
.upeate-field-warp {
width: 100%;
}
</style>
<style lang="less">
.update-field-content-warp {
padding: 40px 20px;
.field-item {
display: flex;
align-items: center;
label {
min-width: 80px;
}
}
}
</style>
四、效果图:
原数据(图1)
导入组件(图2)
导出整理后的数据(图3)
模板下载(图4)
模板下载之后的文件(图5)