import React from 'react'; import { Form, Input, InputNumber, Tabs, Switch, Checkbox, Row, Col, Button, Tag, Dropdown, Menu } from 'antd'; import './index.less'; import Page from '@src/containers/Page'; import Block from '@src/components/Block'; import Editor from '@src/components/Editor'; // import ContextMenuLayout from '@src/layouts/ContextMenuLayout'; // import Select from '@src/components/Select'; // import FileUpload from '@src/components/FileUpload'; import { formatFormError, generateUUID } from '@src/services/Tools'; import { asyncSMessage } from '@src/services/AsyncTools'; import { SentenceOption } from '../../../../Constant'; // import { Preview } from '../../../stores/preview'; import { Exercise } from '../../../stores/exercise'; import { Sentence } from '../../../stores/sentence'; import config from './index'; const stopWords = [',', '.', ':', '!', ';', '?', '\\', '/', '`', '\'', '', '。', ',', '?', '!', '“', '”']; const htmlReg = /[.,!?。,!?“”]*<[^>]+>[.,!?。,!?“”]*/i; const sReg = /[.,!?。,!?“”]+/i; // const uuidReg = /@(.*)@(.*)/i; export default class extends Page { constructor(props) { super(props); this.targetWord = null; this.uuidMap = {}; this.uuidList = []; const { id } = this.params; if (id) { config.title = '编辑长难句题目'; } else { config.title = '添加长难句题目'; } } initState() { return { currentKey: 'subject' }; } init() { Exercise.allStruct().then(result => { result = result.filter(row => row.level === 2).map(row => { row.title = `${row.titleZh}/${row.titleEn}`; row.value = row.id; return row; }); this.setState({ exercise: result }); }); } initData() { const { id } = this.params; const { form } = this.props; this.uuidMap = {}; this.uuidList = []; this.targetWord = null; let handler; if (id) { handler = Sentence.getQuestion({ id }); this.setState({ mode: 'setting' }); } else { // "<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> <span uuid='Axff'></span> <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> <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> <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>" handler = Promise.resolve({ no: 1, question: { stem: '' } }); } handler .then(result => { result.isPaper = !!result.isPaper; result.isTrail = !!result.isTrail; result.question.questionType = 'sentence'; result.question.answer = result.question.answer || {}; form.getFieldDecorator('question.answer.subject'); form.getFieldDecorator('question.answer.predicate'); form.getFieldDecorator('question.answer.object'); form.getFieldDecorator('question.answer.options'); form.getFieldDecorator('question.qxContent'); form.getFieldDecorator('question.stem'); form.getFieldDecorator('question.questionType'); form.getFieldDecorator('question.place'); form.getFieldDecorator('question.id'); form.getFieldDecorator('questionId'); form.getFieldDecorator('chinese'); form.getFieldDecorator('title'); form.getFieldDecorator('isPaper'); form.getFieldDecorator('isTrail'); form.getFieldDecorator('no'); form.getFieldDecorator('id'); form.setFieldsValue(result); const { stem } = result.question; const { subject, predicate, object } = result.question.answer; this.setState({ data: result, subject, predicate, object, stem }); // 生成uuid列表 this.uuidList = (stem || '').replace(/<span\suuid=["']([^>]*)["']>([^<>]*)<\/span>/g, '@@$1_@_$2@@').split('@@').filter(row => row.indexOf('_@_') >= 0).map(row => { const [uuid, content] = row.split('_@_'); this.uuidMap[uuid] = content; return uuid; }); console.log(this.uuidList, this.uuidMap); }); } submit() { const { form } = this.props; const { mode } = this.state; if (mode !== 'setting') { asyncSMessage('请先退出编辑模式', 'warn'); return; } form.validateFields((err) => { if (!err) { const data = form.getFieldsValue(); data.isTrail = data.isTrail ? 1 : 0; data.isPaper = data.isPaper ? 1 : 0; data.question.stem = this.state.stem; data.question.description = data.question.stem.replace(/<[^>]+>/g, ''); data.question.answer = data.question.answer || {}; let handler; if (!data.id) { handler = Sentence.addQuestion(data); } else { handler = Sentence.editQuestion(data); } handler.then((result) => { asyncSMessage('保存成功'); if (data.id) { linkTo(`/subject/sentence/question/${data.id}`); } else { linkTo(`/subject/sentence/question/${result.id}`); } }).catch((e) => { if (e.result) form.setFields(formatFormError(data, e.result)); }); } }); } removeAnswer(key, index) { const { setFieldsValue, getFieldValue } = this.props.form; const data = getFieldValue(`question.answer.${key}`) || []; if (data.length > index) { data.splice(index, 1); } setFieldsValue({ [`question.answer.${key}`]: data }); this.setState({ [key]: data }); } addAnswer(key, uuid, text) { const { setFieldsValue, getFieldValue } = this.props.form; const data = getFieldValue(`question.answer.${key}`) || []; data.push([{ text, uuid }]); setFieldsValue({ [`question.answer.${key}`]: data }); this.setState({ [key]: data }); } appendAnswer(key, index, uuid, text) { const { setFieldsValue, getFieldValue } = this.props.form; const data = getFieldValue(`question.answer.${key}`) || []; data[index].push({ text, uuid }); setFieldsValue({ [`question.answer.${key}`]: data }); this.setState({ [key]: data }); } renderContextMenu() { const { getFieldValue } = this.props.form; const { currentKey } = this.state; const uuid = this.targetWord.getAttribute('uuid'); const text = this.targetWord.innerText; const data = getFieldValue(`question.answer.${currentKey}`) || []; const items = []; items.push({ title: '新答案', command: 'new' }); data.forEach((row, i) => { // 过滤已经在的答案 if (row.filter(r => r.uuid !== uuid).length !== row.length) return; items.push({ title: `插入: ${row.map(r => r.text).join(', ')}`, command: 'append', i }); }); return ( <Menu onClick={k => this.clickContentMenu(currentKey, k, uuid, text)}> {items.map((item, i) => { return <Menu.Item key={i} command={item.command} i={item.i}>{item.title || item.name}</Menu.Item>; })} </Menu> ); } clickContentMenu(key, k, uuid, text) { const item = k.item.props; switch (item.command) { case 'new': this.addAnswer(key, uuid, text); break; case 'append': this.appendAnswer(key, item.i, uuid, text); break; default: } } generateContent(content) { const { setFieldsValue } = this.props.form; // 处理编辑器未清除的标签: 主要是包含空格的标签属性 content = content.replace(/<[^>]+\s+[^>]+>([^<>]*)<\/[^>]+>/g, '$1'); this.index = 0; const list = ['</p><p>', ' ', ',', '.', '!', '?', ' ', ':', ';']; const result = this.splitList(list, 0, content); // console.log(this.uuidList, this.uuidMap); // 对比答案,是否uuid存在 let { subject = [], predicate = [], object = [] } = this.state; let flag = false; subject = subject.map(row => { return row.filter(r => { if (this.uuidMap[r.uuid] === r.text) return true; flag = true; return false; }); }).filter(row => row.length); predicate = predicate.map(row => { return row.filter(r => { if (this.uuidMap[r.uuid] === r.text) return true; flag = true; return false; }); }).filter(row => row.length); object = object.map(row => { return row.filter(r => { if (this.uuidMap[r.uuid] === r.text) return true; flag = true; return false; }); }).filter(row => row.length); if (flag) { asyncSMessage('修改影响答案,请再次确认答案', 'warn'); setFieldsValue({ 'question.answer.subject': subject, 'question.answer.predicate': predicate, 'question.answer.object': object }); // console.log(subject, predicate, object); this.setState({ subject, predicate, object }); } return result; } splitList(list, index, content) { if (list.length === index) { return this.generateText(content); } const result = content.split(list[index]).map(row => { return this.splitList(list, index + 1, row); }); if (result.length === 0) return content; if (result.length === 1) return result[0]; return result.join(list[index]); } generateText(content) { if (content.indexOf(stopWords) >= 0) return content; // 判断是否包含html标签 const r = htmlReg.exec(content); if (r === null) { const sr = sReg.exec(content); if (sr === null) { return this.generateTag(content); } // 句尾标点符号 return `${this.generateTag(content.replace(sr[0], ''))}${sr[0]}`; } if (r.index === 0) { // 头部html标签 return `${r[0]}${this.generateTag(content.replace(r[0], ''))}`; } // 尾部html标签 return `${this.generateTag(content.replace(r[0], ''))}${r[0]}`; } generateTag(content) { // return `<span>${content}</span>`; // 处理uuid信息 // const r = uuidReg.exec(content); // if (r === null) { let uuid; if (this.uuidList.length <= this.index) { do { uuid = generateUUID(4); } while (this.uuidMap[uuid]); this.uuidMap[uuid] = content; this.uuidList.push(uuid); } else { uuid = this.uuidList[this.index]; this.uuidMap[uuid] = content; } this.index += 1; return `<span uuid='${uuid}'>${content}</span>`; // } // uuidMap[r[1]] = `${r[2]}`; // return `<span uuid='${r[1]}'>${r[2]}</span>`; } renderTitle() { const { getFieldDecorator } = this.props.form; return <Block> <Form> {getFieldDecorator('id')(<input hidden />)} <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='题目序号'> {getFieldDecorator('no', { rules: [ { required: true, message: '请输入序号' }, // { // validator: (rule, value, callback) => { // if (this.partList.indexOf(value) >= 0) callback('该part已被使用'); // else callback(); // callback(); // }, // }, ], })( <InputNumber min={1} precision={0} formatter={(v) => parseInt(v, 10) || 1} />, )} </Form.Item> <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='名称'> {getFieldDecorator('title', { rules: [ { required: true, message: '请输入名称' }, ], })( <Input placeholder='请输入' />, )} </Form.Item> <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='开放试用'> {getFieldDecorator('isTrail', { valuePropName: 'checked', })( <Switch checkedChildren='on' unCheckedChildren='off' />, )} </Form.Item> {/* 不允许修改组卷逻辑 */} <Form.Item labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} label='组卷题目' > {getFieldDecorator('isPaper', { valuePropName: 'checked', })( <Switch checkedChildren='on' unCheckedChildren='off' />, )} </Form.Item> </Form> </Block>; } renderBase() { const { getFieldDecorator, getFieldValue, setFieldsValue } = this.props.form; const { stem, mode } = this.state; return <Block flex> <h1>题干信息: <Switch checked={mode !== 'setting'} checkedChildren='编辑模式' unCheckedChildren='设置模式' onChange={(value) => { if (value) { // console.log(stem); getFieldDecorator('question.stem'); setFieldsValue({ 'question.stem': stem }); this.setState({ mode: 'edit' }); } else { // 编辑器未修改会使用添加好uuid标签的结果 // 通过保留onChange获取到最后一次修改记录 this.setState({ mode: 'setting', stem: this.generateContent(this.stem || getFieldValue('question.stem') || '') }); } }} /></h1> <Form> {getFieldDecorator('question.id')(<input hidden />)} {<Form.Item style={{ display: (mode === 'edit' || !mode) ? 'block' : 'none' }}> {getFieldDecorator('question.stem')( <Editor // onChange={(content, delta, source, editor) => { // console.log(content, delta, source, editor); // setFieldsValue({ 'question.stem': content }); // }} onChange={(content) => { this.stem = content; // console.log(this.stem); }} placeholder='请输入内容' />, )} </Form.Item>} {mode === 'setting' && <Dropdown overlay={() => this.renderContextMenu()} trigger={['click']}><div className='stem-content' dangerouslySetInnerHTML={{ __html: stem }} onClick={(e) => { this.targetWord = e.target; }} /></Dropdown>} </Form> </Block>; } renderAnswer() { const { getFieldDecorator } = this.props.form; const { subject = [], predicate = [], object = [], currentKey } = this.state; return <Block flex> <h1>题目答案: <span>进入设置模式,选择主谓宾后,点击答案单词</span></h1> <table boarder cellSpacing className='answer'> <tr> <td width={30}><Checkbox checked={currentKey === 'subject'} onClick={(value) => value.target.checked && this.setState({ currentKey: 'subject' })} /> 主语</td> <td><Form.Item> {getFieldDecorator('question.answer.subject', { rules: [{ required: true, message: '请输入主语' }], })(<input hidden />)} {subject.map((row, index) => { return <Tag key={index} closable visible onClose={() => { this.removeAnswer('subject', index); }}>{row.map(r => r.text).join(', ')}</Tag>; })} </Form.Item></td> </tr> <tr> <td><Checkbox checked={currentKey === 'predicate'} onClick={(value) => value.target.checked && this.setState({ currentKey: 'predicate' })} /> 谓语</td> <td><Form.Item> {getFieldDecorator('question.answer.predicate', { rules: [{ required: true, message: '请输入谓语' }], })(<input hidden />)} {predicate.map((row, index) => { return <Tag key={index} closable visible onClose={() => { this.removeAnswer('predicate', index); }}>{row.map(r => r.text).join(', ')}</Tag>; })} </Form.Item></td> </tr> <tr> <td><Checkbox checked={currentKey === 'object'} onClick={(value) => value.target.checked && this.setState({ currentKey: 'object' })} /> 宾语</td> <td><Form.Item> {getFieldDecorator('question.answer.object', { rules: [{ required: true, message: '请输入宾语' }], })(<input hidden />)} {object.map((row, index) => { return <Tag key={index} closable visible onClose={() => { this.removeAnswer('object', index); }}>{row.map(r => r.text).join(', ')}</Tag>; })} </Form.Item></td> </tr> </table> </Block >; } renderOption() { const { getFieldDecorator } = this.props.form; return <Block flex> <h1>选项信息(长难句)</h1> <Form> <Form.Item> {getFieldDecorator('question.answer.options', { rules: [{ required: true, message: '请输入选项信息' }], })( <Checkbox.Group options={SentenceOption} />, )} </Form.Item> </Form> </Block>; } renderQX() { const { getFieldDecorator } = this.props.form; return <Block flex> <Form> <Form.Item label='千行解析'> {getFieldDecorator('question.qxContent', { })( <Editor placeholder='输入内容' />, )} </Form.Item> </Form> </Block>; } renderChinese() { const { getFieldDecorator } = this.props.form; return <Block flex> <Form> <Form.Item label='中文解析'> {getFieldDecorator('chinese', { })( <Editor placeholder='输入内容' />, )} </Form.Item> </Form> </Block>; } renderTab() { return <Tabs activeKey='sentence' onChange={(tab) => { switch (tab) { case 'textbook': linkTo('/subject/textbook/question'); break; case 'base': linkTo('/subject/question'); break; default: } }}> <Tabs.TabPane key='base' tab='考试题型' /> <Tabs.TabPane key='sentence' tab='长难句' /> <Tabs.TabPane key='textbook' tab='数学机经' /> </Tabs>; } renderView() { return <div flex > {this.renderTab()} {this.renderTitle()} {this.renderBase()} {this.renderAnswer()} {this.renderOption()} {this.renderQX()} {this.renderChinese()} <Row type="flex" justify="center"> <Col> <Button type="primary" onClick={() => { this.submit(); }}>保存</Button> </Col> </Row> </div>; } }