英文:
Vue/Javascript - Using excel file reader in q-file filtering function
问题
目前,我正在进行一个需要上传Excel文件的项目。然而,在上传Excel文件之前,我必须检查文件的标题是否正确。然而,我遇到了问题,因为我正在使用的自定义筛选函数需要一个异步函数。
这是我使用的q-file模板:
<template v-slot:body-cell-Upload="props">
<q-td :props="props" class="q-gutter-sm q-pa-md">
<q-file
style="max-width: 300px"
v-model="FileUpload"
filled
label="上传Excel文件"
:filter="files => files.filter(file => checkFileHeader(file, props.row))"
@rejected="onRejected"
/>
</q-td>
</template>
这是我使用的自定义筛选函数的脚本:
export default {
setup() {
async function checkFileHeader(file, row) {
const source = row.source;
const reader = new FileReader();
reader.onload = async function (e) {
const data = new Uint8Array(reader.result);
const wb = XLSX.read(data);
isValid = headerChecker(wb, source);
};
reader.readAsArrayBuffer(file);
return isValid;
}
}
};
headerChecker是一个返回布尔值的函数。我还没有编写检查标题本身的函数,但JavaScript会抛出isValid未定义的错误。
function headerChecker(wb, source) {
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const arr_data = XLSX.utils.sheet_to_json(ws);
const ws_header = Object.keys(arr_data[0]);
return true;
}
我尝试将checkFileHeader转换为异步函数,以便reader.onload函数将布尔值分配给isValid变量,但出现某种原因,这并没有发生。
英文:
Currently, I'm working on a project that requires uploading excel files. However, before uploading the excel file, I have to see if the file's header files are correct. However, I'm having a problem since the custom filter function I'm using requires an asynchrous function.
Here's the template for the q-file I'm using:
<template v-slot:body-cell-Upload="props">
<q-td :props="props" class="q-gutter-sm q-pa-md">
<q-file
style="max-width: 300px"
v-model="FileUpload"
filled
label="Upload excel file here"
:filter="files => files.filter(file => checkFileHeader(file, props.row))"
@rejected="onRejected"
/>
</q-td>
</template>
Here's the script for the custom filtering function I'm using:
export default {
setup () {
async function checkFileHeader (file, row) {
const source = row.source
const reader = new FileReader()
reader.onload = await function(e) {
const data = new Uint8Array(reader.result);
const wb = XLSX.read(data);
isValid = headerChecker(wb, source)
}
reader.readAsArrayBuffer(file)
return isValid
}
}
}
HeaderChecker is a function that returns a boolean. I've yet have to still write the function checking the headers itself but Javascript throws an error that isValid is undefined.
function headerChecker(wb, source) {
const wsname = wb.SheetNames[0]
const ws = wb.Sheets[wsname]
const arr_data = XLSX.utils.sheet_to_json(ws)
const ws_header = Object.keys(arr_data[0])
return true
}
I tried to convert checkFileHeader into a async function so that the reader.onload function will assign the boolean to isValid variable, but for some reason it did not happen.
答案1
得分: 0
`isValid` 在你的代码中没有初始定义,即未使用 `const` 或 `let`,因此出现了错误。 但无论如何,还有一些其他问题。
1) `Array.filter` 不支持使用异步函数。可以通过先获取异步结果,然后基于该结果进行筛选来解决:
```js
const promises = files.map((f) => processFile(f)); // 返回解析为 true 或 false 的 Promise
const results = await Promise.all(promises); // [true, true, false, ...]
return files.filter((f, i) => results[i]); // 使用相同的索引进行筛选
- 不论前面的解决方法如何,
<q-file>
上的filter
属性期望一个数组 而不是其他任何东西。返回一个 Promise,即使它解析为一个数组,也会导致文件选择器失败。对此无法做任何处理,因为你想要做的事情需要一个异步函数,而异步函数总是返回 Promise。
解决方案:
移除 filter
属性,使用 @update:model-value
,它在每次选择文件时都会运行。然后你可以直接对 v-model 进行筛选,无需担心任何返回值。
我从 Quasar 文件选择器的示例代码开始,所以并不是完全相同,但希望足够接近。
<q-file
style="max-width: 300px"
v-model="files"
filled
label="选择文件(s)"
multiple
@update:model-value="checkFilesHeader"
></q-file>
import { useQuasar } from "quasar";
import { ref } from "vue";
export default {
setup() {
const $q = useQuasar();
const files = ref(null);
async function processFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async function () {
try {
const data = new Uint8Array(reader.result);
resolve(false); // 解析你的处理结果
} catch (e) {
reject(false);
}
};
reader.readAsArrayBuffer(file);
});
}
async function checkFilesHeaders() {
const promises = files.value.map((f) => processFile(f));
const results = await Promise.all(promises);
const filteredFiles = files.value.filter((f, i) => results[i]);
const diff = files.value.length - filteredFiles.length
if (diff > 0) {
$q.notify({
type: "negative",
message: `${diff} 个文件未通过验证约束`,
});
}
files.value = filteredFiles;
}
return {
files,
checkFilesHeader,
};
},
};
注意:我的代码始终执行 resolve(false)
,模拟了如果处理结果需要筛选文件会发生什么。你的代码可能会有其他内容,例如 resolve(headerChecker(wb, source))
。```
英文:
isValid
is not initially defined in your code, i.e. using const
or let
, hence the error. Regardless, there are a few other things wrong.
Array.filter
does not work using asynchronous functions. This can be worked around by getting the asynchronous result first and filtering based on that:
const promises = files.map((f) => processFile(f)); // returns Promises resolving to true or false
const results = await Promise.all(promises); // [true, true, false, ...]
return files.filter((f, i) => results[i]); // filter using same index
- Regardless of the previous work-around, the prop
filter
on<q-file>
expects an array and nothing else. Returning a Promise, even one that resolves into an Array, will cause the file picker to fail. There's nothing that can be done about this because what you want to do requires an async function and async functions always return Promises.
Solution:
Drop the filter
prop and use @update:model-value
which runs every time a file is picked. You can then directly filter the v-model and not worry about any return value.
I started with the Quasar file-picker example code so not everything is the exact same, but should hopefully be close enough.
<q-file
style="max-width: 300px"
v-model="files"
filled
label="Pick file(s)"
multiple
@update:model-value="checkFilesHeader"
></q-file>
import { useQuasar } from "quasar";
import { ref } from "vue";
export default {
setup() {
const $q = useQuasar();
const files = ref(null);
async function processFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async function () {
try {
const data = new Uint8Array(reader.result);
resolve(false); // resolve the result of your processing
} catch (e) {
reject(false);
}
};
reader.readAsArrayBuffer(file);
});
}
async function checkFilesHeaders() {
const promises = files.value.map((f) => processFile(f));
const results = await Promise.all(promises);
const filteredFiles = files.value.filter((f, i) => results[i]);
const diff = files.value.length - filteredFiles.length
if (diff > 0) {
$q.notify({
type: "negative",
message: `${diff} file(s) did not pass validation constraints`,
});
}
files.value = filteredFiles;
}
return {
files,
checkFilesHeader,
};
},
};
Note: my code always does resolve(false)
which mocks what would happen if the processing resulted in a file that needed to be filtered. Your code would have something else, e.g. resolve(headerChecker(wb, source))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论