page.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. import React from 'react';
  2. import { Form, Input, InputNumber, Tabs, Switch, Checkbox, Row, Col, Button, Tag, Dropdown, Menu } from 'antd';
  3. import './index.less';
  4. import Page from '@src/containers/Page';
  5. import Block from '@src/components/Block';
  6. import Editor from '@src/components/Editor';
  7. // import ContextMenuLayout from '@src/layouts/ContextMenuLayout';
  8. // import Select from '@src/components/Select';
  9. // import FileUpload from '@src/components/FileUpload';
  10. import { formatFormError, generateUUID } from '@src/services/Tools';
  11. import { asyncSMessage } from '@src/services/AsyncTools';
  12. import { SentenceOption } from '../../../../Constant';
  13. // import { Preview } from '../../../stores/preview';
  14. import { Exercise } from '../../../stores/exercise';
  15. import { Sentence } from '../../../stores/sentence';
  16. import { System } from '../../../stores/system';
  17. import config from './index';
  18. const stopWords = [',', '.', ':', '!', ';', '?', '\\', '/', '`', '\'', '', '。', ',', '?', '!', '“', '”'];
  19. const htmlReg = /[.,!?。,!?“”]*<[^>]+>[.,!?。,!?“”]*/i;
  20. const sReg = /[.,!?。,!?“”]+/i;
  21. // const uuidReg = /@(.*)@(.*)/i;
  22. export default class extends Page {
  23. constructor(props) {
  24. super(props);
  25. this.targetWord = null;
  26. this.uuidMap = {};
  27. this.uuidList = [];
  28. const { id } = this.params;
  29. if (id) {
  30. config.title = '编辑长难句题目';
  31. } else {
  32. config.title = '添加长难句题目';
  33. }
  34. }
  35. initState() {
  36. return { currentKey: 'subject' };
  37. }
  38. init() {
  39. Exercise.allStruct().then(result => {
  40. result = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; });
  41. this.setState({ exercise: result });
  42. });
  43. }
  44. initData() {
  45. const { id } = this.params;
  46. const { form } = this.props;
  47. this.uuidMap = {};
  48. this.uuidList = [];
  49. this.targetWord = null;
  50. let handler;
  51. if (id) {
  52. handler = Sentence.getQuestion({ id });
  53. this.setState({ mode: 'setting' });
  54. } else {
  55. // "<p><span uuid='CmS0'>The</span> <span uuid='2BKs'>furthest</span> <span uuid='3JxJ'>distance</span> <span uuid='hVsF'>in</span> <span uuid='OlAJ'>the</span> <span uuid='RrCt'>world</span>&nbsp;<span uuid='Axff'></span>&nbsp;<span uuid='wovI'></span></p><p><span uuid='odI3'>Is</span> <span uuid='HHXm'>not</span> <span uuid='ld8s'>between</span> <span uuid='wuh2'>life</span> <span uuid='o29m'>and</span> <span uuid='NLCc'>death</span>&nbsp;<span uuid='GCaB'></span></p><p><span uuid='WENt'>But</span> <span uuid='kHpB'>when</span> <span uuid='nMb4'>I</span> <span uuid='CNoW'>stand</span> <span uuid='EwJh'>in</span> <span uuid='MOL4'>front</span> <span uuid='Vg1e'>of</span> <span uuid='aZAW'>you</span>&nbsp;<span uuid='z7zD'></span></p><p><span uuid='hLr7'>Yet</span> <span uuid='s7lJ'>you</span> <span uuid='xhmd'>don\"t</span> <span uuid='uKQk'>know</span> <span uuid='mDus'>that</span> <span uuid='neve'>I</span> <span uuid='yWSc'>love</span> <span uuid='7JRW'>you</span></p>"
  56. handler = Promise.resolve({ no: 1, question: { stem: '' } });
  57. }
  58. handler
  59. .then(result => {
  60. result.isPaper = !!result.isPaper;
  61. result.isTrail = !!result.isTrail;
  62. result.question.questionType = 'sentence';
  63. result.question.answer = result.question.answer || {};
  64. form.getFieldDecorator('question.answer.subject');
  65. form.getFieldDecorator('question.answer.predicate');
  66. form.getFieldDecorator('question.answer.object');
  67. form.getFieldDecorator('question.answer.options');
  68. form.getFieldDecorator('question.qxContent');
  69. form.getFieldDecorator('question.stem');
  70. form.getFieldDecorator('question.questionType');
  71. form.getFieldDecorator('question.place');
  72. form.getFieldDecorator('question.chineseContent');
  73. form.getFieldDecorator('question.id');
  74. form.getFieldDecorator('questionId');
  75. form.getFieldDecorator('title');
  76. form.getFieldDecorator('isPaper');
  77. form.getFieldDecorator('isTrail');
  78. form.getFieldDecorator('no');
  79. form.getFieldDecorator('id');
  80. form.setFieldsValue(result);
  81. const { stem } = result.question;
  82. const { subject, predicate, object } = result.question.answer;
  83. this.setState({ data: result, subject, predicate, object, stem });
  84. // 生成uuid列表
  85. this.uuidList = (stem || '').replace(/<span\suuid=["']([^>]*)["']>([^<>]*)<\/span>/g, '@@$1_@_$2@@').split('@@').filter(row => row.indexOf('_@_') >= 0).map(row => {
  86. const [uuid, content] = row.split('_@_');
  87. this.uuidMap[uuid] = content;
  88. return uuid;
  89. });
  90. console.log(this.uuidList, this.uuidMap);
  91. });
  92. }
  93. submit() {
  94. const { form } = this.props;
  95. const { mode } = this.state;
  96. if (mode !== 'setting') {
  97. asyncSMessage('请先退出编辑模式', 'warn');
  98. return;
  99. }
  100. form.validateFields((err) => {
  101. if (!err) {
  102. const data = form.getFieldsValue();
  103. data.isTrail = data.isTrail ? 1 : 0;
  104. data.isPaper = data.isPaper ? 1 : 0;
  105. data.question.stem = this.state.stem;
  106. data.question.description = data.question.stem.replace(/<[^>]+>/g, '');
  107. data.question.answer = data.question.answer || {};
  108. let handler;
  109. if (!data.id) {
  110. handler = Sentence.addQuestion(data);
  111. } else {
  112. handler = Sentence.editQuestion(data);
  113. }
  114. handler.then((result) => {
  115. asyncSMessage('保存成功');
  116. if (data.id) {
  117. linkTo(`/subject/sentence/question/${data.id}`);
  118. } else {
  119. linkTo(`/subject/sentence/question/${result.id}`);
  120. }
  121. }).catch((e) => {
  122. if (e.result) form.setFields(formatFormError(data, e.result));
  123. else {
  124. asyncSMessage(e.message, 'error');
  125. }
  126. });
  127. }
  128. });
  129. }
  130. removeAnswer(key, index) {
  131. const { setFieldsValue, getFieldValue } = this.props.form;
  132. const data = getFieldValue(`question.answer.${key}`) || [];
  133. if (data.length > index) {
  134. data.splice(index, 1);
  135. }
  136. setFieldsValue({ [`question.answer.${key}`]: data });
  137. this.setState({ [key]: data });
  138. }
  139. addAnswer(key, uuid, text) {
  140. const { setFieldsValue, getFieldValue } = this.props.form;
  141. const data = getFieldValue(`question.answer.${key}`) || [];
  142. data.push([{ text, uuid }]);
  143. setFieldsValue({ [`question.answer.${key}`]: data });
  144. this.setState({ [key]: data });
  145. }
  146. appendAnswer(key, index, uuid, text) {
  147. const { setFieldsValue, getFieldValue } = this.props.form;
  148. const data = getFieldValue(`question.answer.${key}`) || [];
  149. data[index].push({ text, uuid });
  150. setFieldsValue({ [`question.answer.${key}`]: data });
  151. this.setState({ [key]: data });
  152. }
  153. renderContextMenu() {
  154. const { getFieldValue } = this.props.form;
  155. const { currentKey } = this.state;
  156. const uuid = this.targetWord.getAttribute('uuid');
  157. const text = this.targetWord.innerText;
  158. const data = getFieldValue(`question.answer.${currentKey}`) || [];
  159. const items = [];
  160. items.push({ title: '新答案', command: 'new' });
  161. data.forEach((row, i) => {
  162. // 过滤已经在的答案
  163. if (row.filter(r => r.uuid !== uuid).length !== row.length) return;
  164. items.push({ title: `插入: ${row.map(r => r.text).join(', ')}`, command: 'append', i });
  165. });
  166. return (
  167. <Menu onClick={k => this.clickContentMenu(currentKey, k, uuid, text)}>
  168. {items.map((item, i) => {
  169. return <Menu.Item key={i} command={item.command} i={item.i}>{item.title || item.name}</Menu.Item>;
  170. })}
  171. </Menu>
  172. );
  173. }
  174. clickContentMenu(key, k, uuid, text) {
  175. const item = k.item.props;
  176. switch (item.command) {
  177. case 'new':
  178. this.addAnswer(key, uuid, text);
  179. break;
  180. case 'append':
  181. this.appendAnswer(key, item.i, uuid, text);
  182. break;
  183. default:
  184. }
  185. }
  186. generateContent(content) {
  187. const { setFieldsValue } = this.props.form;
  188. // 处理编辑器未清除的标签: 主要是包含空格的标签属性
  189. content = content.replace(/<[^>]+\s+[^>]+>([^<>]*)<\/[^>]+>/g, '$1');
  190. this.index = 0;
  191. const list = ['</p><p>', ' ', ',', '.', '!', '?', '&nbsp;', ':', ';'];
  192. const result = this.splitList(list, 0, content);
  193. // console.log(this.uuidList, this.uuidMap);
  194. // 对比答案,是否uuid存在
  195. let { subject = [], predicate = [], object = [] } = this.state;
  196. let flag = false;
  197. subject = subject.map(row => {
  198. return row.filter(r => {
  199. if (this.uuidMap[r.uuid] === r.text) return true;
  200. flag = true;
  201. return false;
  202. });
  203. }).filter(row => row.length);
  204. predicate = predicate.map(row => {
  205. return row.filter(r => {
  206. if (this.uuidMap[r.uuid] === r.text) return true;
  207. flag = true;
  208. return false;
  209. });
  210. }).filter(row => row.length);
  211. object = object.map(row => {
  212. return row.filter(r => {
  213. if (this.uuidMap[r.uuid] === r.text) return true;
  214. flag = true;
  215. return false;
  216. });
  217. }).filter(row => row.length);
  218. if (flag) {
  219. asyncSMessage('修改影响答案,请再次确认答案', 'warn');
  220. setFieldsValue({ 'question.answer.subject': subject, 'question.answer.predicate': predicate, 'question.answer.object': object });
  221. // console.log(subject, predicate, object);
  222. this.setState({ subject, predicate, object });
  223. }
  224. return result;
  225. }
  226. splitList(list, index, content) {
  227. if (list.length === index) {
  228. return this.generateText(content);
  229. }
  230. const result = content.split(list[index]).map(row => {
  231. return this.splitList(list, index + 1, row);
  232. });
  233. if (result.length === 0) return content;
  234. if (result.length === 1) return result[0];
  235. return result.join(list[index]);
  236. }
  237. generateText(content) {
  238. if (content.indexOf(stopWords) >= 0) return content;
  239. // 判断是否包含html标签
  240. const r = htmlReg.exec(content);
  241. if (r === null) {
  242. const sr = sReg.exec(content);
  243. if (sr === null) {
  244. return this.generateTag(content);
  245. }
  246. // 句尾标点符号
  247. return `${this.generateTag(content.replace(sr[0], ''))}${sr[0]}`;
  248. }
  249. if (r.index === 0) {
  250. // 头部html标签
  251. return `${r[0]}${this.generateTag(content.replace(r[0], ''))}`;
  252. }
  253. // 尾部html标签
  254. return `${this.generateTag(content.replace(r[0], ''))}${r[0]}`;
  255. }
  256. generateTag(content) {
  257. // return `<span>${content}</span>`;
  258. // 处理uuid信息
  259. // const r = uuidReg.exec(content);
  260. // if (r === null) {
  261. let uuid;
  262. if (this.uuidList.length <= this.index) {
  263. do {
  264. uuid = generateUUID(4);
  265. } while (this.uuidMap[uuid]);
  266. this.uuidMap[uuid] = content;
  267. this.uuidList.push(uuid);
  268. } else {
  269. uuid = this.uuidList[this.index];
  270. this.uuidMap[uuid] = content;
  271. }
  272. this.index += 1;
  273. return `<span uuid='${uuid}'>${content}</span>`;
  274. // }
  275. // uuidMap[r[1]] = `${r[2]}`;
  276. // return `<span uuid='${r[1]}'>${r[2]}</span>`;
  277. }
  278. renderTitle() {
  279. const { getFieldDecorator } = this.props.form;
  280. return <Block>
  281. <Form>
  282. {getFieldDecorator('id')(<input hidden />)}
  283. <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目序号'>
  284. {getFieldDecorator('no', {
  285. rules: [
  286. { required: true, message: '请输入序号' },
  287. // {
  288. // validator: (rule, value, callback) => {
  289. // if (this.partList.indexOf(value) >= 0) callback('该part已被使用');
  290. // else callback();
  291. // callback();
  292. // },
  293. // },
  294. ],
  295. })(
  296. <InputNumber min={1} precision={0} formatter={(v) => parseInt(v, 10) || 1} />,
  297. )}
  298. </Form.Item>
  299. <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='名称'>
  300. {getFieldDecorator('title', {
  301. rules: [
  302. { required: true, message: '请输入名称' },
  303. ],
  304. })(
  305. <Input placeholder='请输入' />,
  306. )}
  307. </Form.Item>
  308. <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='开放试用'>
  309. {getFieldDecorator('isTrail', {
  310. valuePropName: 'checked',
  311. })(
  312. <Switch checkedChildren='on' unCheckedChildren='off' />,
  313. )}
  314. </Form.Item>
  315. {/* 不允许修改组卷逻辑 */}
  316. <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='组卷题目' >
  317. {getFieldDecorator('isPaper', {
  318. valuePropName: 'checked',
  319. })(
  320. <Switch checkedChildren='on' unCheckedChildren='off' />,
  321. )}
  322. </Form.Item>
  323. </Form>
  324. </Block>;
  325. }
  326. renderBase() {
  327. const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form;
  328. const { stem, mode } = this.state;
  329. return <Block flex>
  330. <h1>题干信息:
  331. <Switch checked={mode !== 'setting'} checkedChildren='编辑模式' unCheckedChildren='设置模式' onChange={(value) => {
  332. if (value) {
  333. // console.log(stem);
  334. getFieldDecorator('question.stem');
  335. setFieldsValue({ 'question.stem': stem });
  336. this.setState({ mode: 'edit' });
  337. } else {
  338. // 编辑器未修改会使用添加好uuid标签的结果
  339. // 通过保留onChange获取到最后一次修改记录
  340. this.setState({ mode: 'setting', stem: this.generateContent(this.stem || getFieldValue('question.stem') || '') });
  341. }
  342. }} /></h1>
  343. <Form>
  344. {getFieldDecorator('question.id')(<input hidden />)}
  345. {<Form.Item style={{ display: (mode === 'edit' || !mode) ? 'block' : 'none' }}>
  346. {getFieldDecorator('question.stem')(
  347. <Editor
  348. // onChange={(content, delta, source, editor) => {
  349. // console.log(content, delta, source, editor);
  350. // setFieldsValue({ 'question.stem': content });
  351. // }}
  352. onChange={(content) => {
  353. this.stem = content;
  354. // console.log(this.stem);
  355. }}
  356. placeholder='请输入内容' />,
  357. )}
  358. </Form.Item>}
  359. {mode === 'setting' && <Dropdown overlay={() => this.renderContextMenu()} trigger={['click']}><div className='stem-content' dangerouslySetInnerHTML={{ __html: stem }} onClick={(e) => {
  360. this.targetWord = e.target;
  361. }} /></Dropdown>}
  362. </Form>
  363. </Block>;
  364. }
  365. renderAnswer() {
  366. const { getFieldDecorator } = this.props.form;
  367. const { subject = [], predicate = [], object = [], currentKey } = this.state;
  368. return <Block flex>
  369. <h1>题目答案: <span>进入设置模式,选择主谓宾后,点击答案单词</span></h1>
  370. <table boarder cellSpacing className='answer'>
  371. <tr>
  372. <td width={30}><Checkbox checked={currentKey === 'subject'} onClick={(value) => value.target.checked && this.setState({ currentKey: 'subject' })} /> 主语</td>
  373. <td><Form.Item>
  374. {getFieldDecorator('question.answer.subject', {
  375. rules: [{ required: true, message: '请输入主语' }],
  376. })(<input hidden />)}
  377. {subject.map((row, index) => {
  378. return <Tag key={index} closable visible onClose={() => {
  379. this.removeAnswer('subject', index);
  380. }}>{row.map(r => r.text).join(', ')}</Tag>;
  381. })}
  382. </Form.Item></td>
  383. </tr>
  384. <tr>
  385. <td><Checkbox checked={currentKey === 'predicate'} onClick={(value) => value.target.checked && this.setState({ currentKey: 'predicate' })} /> 谓语</td>
  386. <td><Form.Item>
  387. {getFieldDecorator('question.answer.predicate', {
  388. rules: [{ required: true, message: '请输入谓语' }],
  389. })(<input hidden />)}
  390. {predicate.map((row, index) => {
  391. return <Tag key={index} closable visible onClose={() => {
  392. this.removeAnswer('predicate', index);
  393. }}>{row.map(r => r.text).join(', ')}</Tag>;
  394. })}
  395. </Form.Item></td>
  396. </tr>
  397. <tr>
  398. <td><Checkbox checked={currentKey === 'object'} onClick={(value) => value.target.checked && this.setState({ currentKey: 'object' })} /> 宾语</td>
  399. <td><Form.Item>
  400. {getFieldDecorator('question.answer.object', {
  401. rules: [{ required: true, message: '请输入宾语' }],
  402. })(<input hidden />)}
  403. {object.map((row, index) => {
  404. return <Tag key={index} closable visible onClose={() => {
  405. this.removeAnswer('object', index);
  406. }}>{row.map(r => r.text).join(', ')}</Tag>;
  407. })}
  408. </Form.Item></td>
  409. </tr>
  410. </table>
  411. </Block >;
  412. }
  413. renderOption() {
  414. const { getFieldDecorator } = this.props.form;
  415. return <Block flex>
  416. <h1>选项信息(长难句)</h1>
  417. <Form>
  418. <Form.Item>
  419. {getFieldDecorator('question.answer.options', {
  420. rules: [{ required: true, message: '请输入选项信息' }],
  421. })(
  422. <Checkbox.Group options={SentenceOption} />,
  423. )}
  424. </Form.Item>
  425. </Form>
  426. </Block>;
  427. }
  428. renderQX() {
  429. const { getFieldDecorator } = this.props.form;
  430. return <Block flex>
  431. <Form>
  432. <Form.Item label='千行解析'>
  433. {getFieldDecorator('question.qxContent', {
  434. })(
  435. <Editor placeholder='输入内容' onUpload={(file) => System.uploadImage(file)} />,
  436. )}
  437. </Form.Item>
  438. </Form>
  439. </Block>;
  440. }
  441. renderChinese() {
  442. const { getFieldDecorator } = this.props.form;
  443. return <Block flex>
  444. <Form>
  445. <Form.Item label='中文解析'>
  446. {getFieldDecorator('question.chineseContent', {
  447. })(
  448. <Editor placeholder='输入内容' onUpload={(file) => System.uploadImage(file)} />,
  449. )}
  450. </Form.Item>
  451. </Form>
  452. </Block>;
  453. }
  454. renderTab() {
  455. return <Tabs activeKey='sentence' onChange={(tab) => {
  456. switch (tab) {
  457. case 'textbook':
  458. linkTo('/subject/textbook/question');
  459. break;
  460. case 'base':
  461. linkTo('/subject/question');
  462. break;
  463. default:
  464. }
  465. }}>
  466. <Tabs.TabPane key='base' tab='考试题型' />
  467. <Tabs.TabPane key='sentence' tab='长难句' />
  468. <Tabs.TabPane key='textbook' tab='数学机经' />
  469. </Tabs>;
  470. }
  471. renderView() {
  472. return <div flex >
  473. {this.renderTab()}
  474. {this.renderTitle()}
  475. {this.renderBase()}
  476. {this.renderAnswer()}
  477. {this.renderOption()}
  478. {this.renderQX()}
  479. {this.renderChinese()}
  480. <Row type="flex" justify="center">
  481. <Col>
  482. <Button type="primary" onClick={() => {
  483. this.submit();
  484. }}>保存</Button>
  485. </Col>
  486. </Row>
  487. </div>;
  488. }
  489. }