Files
alab-amis-fix-exam/src/renderer/ExamImport.tsx

270 lines
9.8 KiB
TypeScript
Raw Normal View History

2024-11-13 09:05:45 +08:00
import { FormControlProps, FormItem, getVariable } from 'amis';
import mammoth from 'mammoth/mammoth.browser';
import React from 'react';
@FormItem({
test: /(^|\/)examimport$/,
name: 'examimport',
})
export class ExamImportRenderer extends React.Component<
FormControlProps,
{
exam: Array<any>;
file: any;
}
> {
constructor(props: FormControlProps) {
super(props);
this.state = {
exam: [],
file: '',
};
}
// word 文件解析方法
parseWord(event: any, organization_id: any, category: any, onChange: any) {
const reader = new FileReader();
reader.onload = (e: any) => {
const arrayBuffer = e.target.result;
const options = {
convertImage: mammoth.images.imgElement((image: any) => {
return image.read().then((res: any) => {
let file = new Blob([res], {
type: image.contentType,
});
let formData = new FormData();
formData.set('file', file);
return this.props.env
.fetcher({
url: 'storage/v1/object/exam',
method: 'post',
dataType: 'form-data',
data: formData,
})
.then((res) => {
return {
src: res.data.data.value,
};
});
});
}),
ignoreEmptyParagraphs: false,
};
mammoth
.convertToHtml({ arrayBuffer: arrayBuffer }, options)
.then((resultObject: any) => {
let temp: any[] = [];
let local: any[] = [];
// 清除粗体、软回车,按空行或题号分割题目,并做 forEach 循环
resultObject.value
.replace(/<strong>|<\/strong>/g, '')
.replace(/<br \/><\/p>/g, '</p>')
.replace(/<br \/>/g, '</p><p>')
.split(/<p>\s*<\/p>|(?<!^|<p>\s*<\/p>)<p>\d+/g)
.forEach((item: any, q_index: any) => {
if (item) {
// 清除开头的 . 或 、
if (!item.match(/^<p>/g)) {
item = item.replace(/^(\.|、)/g, '<p>');
}
// 建立当前题目的分割序列
let split_series: any[] = [];
if (item.search(/<p>答案:|<p>答案:/g) > 0) {
split_series.push({
pattern: /(<p>答案:|<p>答案:)/g,
num: item.search(/<p>答案:|<p>答案:/g),
dict: 'answer',
});
}
if (item.search(/<p>答案解析:|<p>答案解析:/g) > 0) {
split_series.push({
pattern: /(<p>答案解析:|<p>答案解析:)/g,
num: item.search(/<p>答案解析:|<p>答案解析:/g),
dict: 'analysis',
});
}
if (item.search(/<p>难易程度:|<p>难易程度:/g) > 0) {
split_series.push({
pattern: /(<p>难易程度:|<p>难易程度:)/g,
num: item.search(/<p>难易程度:|<p>难易程度:/g),
dict: 'difficulty',
});
}
if (item.search(/<p>题型:|<p>题型:/g) > 0) {
split_series.push({
pattern: /(<p>题型:|<p>题型:)/g,
num: item.search(/<p>题型:|<p>题型:/g),
dict: 'type',
});
}
// 按分割序列各项在题目中的位置排序,从前到后
split_series = split_series.sort((a, b) => a.num - b.num);
let exam = item;
let data_item: any = {};
let last_split_item: any = {};
// 按分割序列逐项分割当前题目
split_series.forEach((split_item, index) => {
let result = exam.split(split_item.pattern);
// 如果是第一项,必定是题干和选项(如有),其他项则为其他信息
if (!index) {
// 提取选项
let options = result[0].match(
/<p>[A-Za-z](\.|、).+?<\/p>(?=<p>[A-Za-z](\.|、)|$)/g
);
// 如果有选项则继续分割题干和选项,否则即为题干
if (options) {
let title = result[0]
.replace(/\n/g, '')
.match(/^.+?(?=<p>[A-Za-z](\.|、))/g)[0]
.replace(
/(<p>\d+(\.|、)\{<\/p>)|(?<=<p>)\d+(\.|、)|(<p>}<\/p>)/g,
''
)
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
options = options.map((item: any) => {
return {
key: item.match(/(?<=<p>)[A-Za-z]/g)[0],
label: '<p>'
.concat(
item
.match(/(?<=\.|、).+<\/p>/g)[0]
.replace(/(<p>{<\/p>)|(<p>}<\/p>)/g, '')
)
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, ''),
is_fixed: false,
};
});
// 判断是否有相同的选项
let key_set = new Set();
let key_count = 0;
options.forEach((item: any) => {
key_set.add(item.key);
key_count++;
});
if (key_set.size !== key_count) local.push(q_index + 1);
data_item.options = options;
data_item.title = title;
} else {
let title = result[0]
.replace(
/(<p>\d+(\.|、)\{<\/p>)|(?<=<p>)\d+(\.|、)|(<p>}<\/p>)/g,
''
)
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
data_item.title = title;
}
} else {
// 清除 { 、} 和 分割项
data_item[last_split_item.dict] = result[0]
.replace(/(<p>{<\/p>)|(<p>}<\/p>)/g, '')
.replace(last_split_item.pattern, '<p>')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
// 如果是答案、题型、难易程度,则清除 DOM 标签,并清除空格
if (
last_split_item.dict === 'answer' ||
last_split_item.dict === 'type' ||
last_split_item.dict === 'difficulty'
) {
data_item[last_split_item.dict] = data_item[
last_split_item.dict
]
.replace(/<p>|<\/p>/g, '')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
}
}
// 最后一项
if (index + 1 === split_series.length) {
data_item[split_item.dict] = result[1]
.concat(result[2])
.replace(/(<p>{<\/p>)|(<p>}<\/p>)/g, '')
.replace(split_item.pattern, '<p>')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
// 如果是答案、题型、难易程度,则清除 DOM 标签,并清除空格
if (
split_item.dict === 'answer' ||
split_item.dict === 'type' ||
split_item.dict === 'difficulty'
) {
data_item[split_item.dict] = data_item[split_item.dict]
.replace(/<p>|<\/p>/g, '')
.replace(/(?<=<p>)\s+|\s+(?=<\/p>)/g, '');
}
}
// 重组分割项和剩余项
exam = result[1].concat(result[2]);
// 保留上一次分割的分割项
last_split_item = split_item;
});
if (data_item.type === '判断题') {
data_item.options = [
{ key: '正确', label: '正确', is_fixed: false },
{ key: '错误', label: '错误', is_fixed: false },
];
}
data_item.organization_id = organization_id;
data_item.category = category;
temp.push(data_item);
}
});
this.setState({ exam: temp, file: '' });
if (!local.length) {
onChange(this.state.exam);
} else {
this.props.env.notify(
'error',
`${local.toString()} 题中有重复选项,请修改后重新上传`
);
}
});
};
if (event.target.files.length !== 0) {
reader.readAsArrayBuffer(event.target.files[0]);
}
}
updateValue(event: any) {
this.setState({ file: event.target.value });
}
render() {
const { data, onChange } = this.props;
const { exam } = this.state;
const organization_id = getVariable(data, 'centre_id');
const category = getVariable(data, 'tree');
return (
<form action="">
<input
type="file"
accept=".docx"
value={this.state.file}
onChange={(event) => {
this.parseWord(event, organization_id, category, onChange);
this.updateValue(event);
}}
/>
</form>
);
}
}