270 lines
9.8 KiB
TypeScript
270 lines
9.8 KiB
TypeScript
|
|
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>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|