<el-upload
action="#"
:before-upload="handleBeforeUpload"
:show-file-list="false"
accept=".xlsx, .xls"
>
<el-button type="primary">导入设备列表</el-button>
</el-upload>
- 使用
el-upload
组件,指定上传地址为#
(即不实际上传)。 - 在上传前调用
handleBeforeUpload
方法进行自定义校验或处理。 - 隐藏文件列表显示。
- 限制只能上传
.xlsx
和.xls
格式的文件。 - 提供一个按钮,点击后触发文件选择对话框。
const handleBeforeUpload = async (file) => {
// 1. 读取Excel文件
const reader = new FileReader();
reader.onload = async (e) => {
try {
// 2. 解析Excel数据
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
// 3. 数据校验
if (!jsonData.length) {
ElMessage.error('文件内容为空');
return false;
}
// 4. 字段校验
const requiredFields = ['设备名称', '设备编码', '父节点编码'];
const missingFields = requiredFields.filter(field => !jsonData[0].hasOwnProperty(field));
if (missingFields.length) {
ElMessage.error(`缺少必要字段:${missingFields.join(', ')}`);
return false;
}
// 5. 构建校验映射表
const existingCodes = new Set();
const existingNames = new Set();
const walkNodes = (nodes) => {
nodes.forEach(node => {
existingCodes.add(node.code);
existingNames.add(node.label);
if (node.children) walkNodes(node.children);
});
};
walkNodes(menulist.value);
// 6. 数据预处理
const validData = [];
const errorMessages = [];
jsonData.forEach((row, index) => {
// 6.1 空值校验
if (!row.设备名称 || !row.设备编码) {
errorMessages.push(`第${index + 2}行:设备名称和编码不能为空`);
return;
}
// 6.2 唯一性校验
if (existingCodes.has(row.设备编码)) {
errorMessages.push(`第${index + 2}行:设备编码 ${row.设备编码} 已存在`);
return;
}
if (existingNames.has(row.设备名称)) {
errorMessages.push(`第${index + 2}行:设备名称 ${row.设备名称} 已存在`);
return;
}
// 6.3 父节点存在性校验
if (row.父节点编码 && row.父节点编码 !== '根节点' && !existingCodes.has(row.父节点编码)) {
errorMessages.push(`第${index + 2}行:父节点编码 ${row.父节点编码} 不存在`);
return;
}
validData.push({
label: row.设备名称,
code: row.设备编码,
parentCode: row.父节点编码 === '根节点' ? null : row.父节点编码
});
existingCodes.add(row.设备编码);
existingNames.add(row.设备名称);
});
// 7. 错误处理
if (errorMessages.length) {
ElMessage.error({
message: `导入失败,存在问题:\n${errorMessages.join('\n')}`,
duration: 5000
});
return false;
}
// 8. 构建树形结构
const buildTree = (nodes, parentCode = null) => {
return nodes
.filter(node => node.parentCode === parentCode)
.map(node => ({
...node,
children: buildTree(nodes, node.code)
}));
};
// 9. 合并新旧数据(保持响应式)
const mergedData = [...menulist.value, ...buildTree(validData)];
// 10. 响应式更新(使用深拷贝保证响应式触发)
menulist.value = JSON.parse(JSON.stringify(mergedData));
ElMessage.success(`成功导入 ${validData.length} 条设备数据`);
} catch (error) {
ElMessage.error(`文件解析失败:${error.message}`);
}
};
reader.readAsArrayBuffer(file);
return false; // 阻止自动上传
};