1
0
Преглед на файлове

feat(front): 答案详情页

Go преди 4 години
родител
ревизия
e79b1885f7

Файловите разлики са ограничени, защото са твърде много
+ 3 - 1
front/project/Constant.js


+ 3 - 3
front/project/www/components/Answer/index.js

@@ -94,17 +94,17 @@ export default class Answer extends Component {
   }
 
   renderTable() {
-    const { list = [] } = this.props;
+    const { list = [], first, second } = this.props;
     const { double = [] } = this.state;
     return (
       <table border="1" bordercolor="#d8d8d8">
         <thead>
           <tr className="bg">
             <th align="center" width="100" className="t-c">
-              Both acce
+              {first}
             </th>
             <th align="center" width="100" className="t-c">
-              Otherwise
+              {second}
             </th>
             <th />
           </tr>

+ 74 - 20
front/project/www/components/AnswerList/index.js

@@ -1,25 +1,79 @@
-import React from 'react';
+import React, { Component } from 'react';
 import './index.less';
+import { formatPercent } from '../../../../src/services/Tools';
 
-function getKey(index, selected, answer) {
-  return `${index === selected ? 'selected' : ''} ${index === answer ? 'true' : 'false'}`;
+function getKey(type, index, selected, answer) {
+  return `${(selected[type] || [])[index] ? 'selected' : ''} ${(answer[type] || [])[index] ? 'true' : 'false'}`;
 }
+function getDoubleKey(type, index, selected, answer, position) {
+  return `${(selected[type] || [])[index][position] ? 'selected' : ''} ${(answer[type] || [])[index][position] ? 'true' : 'false'}`;
+}
+
+export default class AnswerList extends Component {
+  render() {
+    return <div className="answer-list">{this.renderDetail()}</div>;
+  }
+
+  renderDetail() {
+    const { type } = this.props;
+    switch (type) {
+      case 'single':
+        return this.renderList();
+      case 'double':
+        return this.renderTable();
+      default:
+        return <div />;
+    }
+  }
+
+  renderList() {
+    const { selected = {}, answer = {}, show, list = [], distributed = [], type } = this.props;
+    const count = distributed.reduce((x, y) => x + y, 0);
+    return (
+      <div className="list">
+        {list.map((item, index) => {
+          return (
+            <div className={`item ${getKey(type, index, selected, answer)} ${show ? 'show' : ''}`}>
+              <div className="icon" />
+              <div className="text">{item}</div>
+              {show && <div className="total">{formatPercent(distributed[index], count, false)}用户选择该选项</div>}
+            </div>
+          );
+        })}
+      </div>
+    );
+  }
 
-function AnswerList(props) {
-  const { selected, answer, show, list = [] } = props;
-  return (
-    <div className="answer-list">
-      {list.map((item, index) => {
-        return (
-          <div className={`item ${getKey(index, selected, answer)} ${show ? 'show' : ''}`}>
-            <div className="icon" />
-            <div className="text">{item.text}</div>
-            {item.total && <div className="total">30%用户选择该选项</div>}
-          </div>
-        );
-      })}
-    </div>
-  );
+  renderTable() {
+    const { selected = {}, answer = {}, show, list = [], type, first, second } = this.props;
+    return (
+      <table border="1" bordercolor="#d8d8d8">
+        <thead>
+          <tr className="bg">
+            <th align="center" width="100" className="t-c">
+              {first}
+            </th>
+            <th align="center" width="100" className="t-c">
+              {second}
+            </th>
+            <th />
+          </tr>
+        </thead>
+        <tbody>
+          {list.map((item, index) => {
+            return (
+              <tr>
+                <td align="center" className="bg">
+                  <div className={`item ${getDoubleKey(type, index, selected, answer, 0)} ${show ? 'show' : ''}`}><div className="icon" /></div>
+                </td>
+                <td align="center" className="bg">
+                  <div className={`item ${getDoubleKey(type, index, selected, answer, 1)} ${show ? 'show' : ''}`}><div className="icon" /></div>
+                </td>
+                <td>{item}</td>
+              </tr>);
+          })}
+        </tbody>
+      </table>
+    );
+  }
 }
-AnswerList.propTypes = {};
-export default AnswerList;

+ 26 - 0
front/project/www/components/AnswerList/index.less

@@ -27,6 +27,32 @@
     }
   }
 
+  table {
+    .bg {
+      background: #f8f8f8;
+    }
+
+    tr {
+      th {
+        padding: 10px;
+      }
+
+      td {
+        padding: 10px;
+      }
+    }
+
+    .item {
+      margin: 0;
+
+      .icon {
+        position: relative;
+        left: 0;
+        top: 0;
+      }
+    }
+  }
+
   .item.selected {
     .icon {
       background-image: url('/assets/option_select.png');

+ 22 - 16
front/project/www/components/AnswerSelect/index.js

@@ -2,6 +2,10 @@ import React, { Component } from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
 
+function getKey(type, index, selected, answer) {
+  return `${(selected[type] || [])[index] ? 'selected' : ''} ${(answer[type] || [])[index] ? 'true' : 'false'}`;
+}
+
 export default class AnswerSelect extends Component {
   constructor(props) {
     super(props);
@@ -9,7 +13,14 @@ export default class AnswerSelect extends Component {
     for (let i = 0; i < props.list.length; i += 1) {
       select[i] = false;
     }
-    this.state = { selecting: false, select };
+    let index = -1;
+    const { selected = {}, answer = {}, type } = props;
+    let result = true;
+    (selected[type] || []).forEach((row, i) => {
+      if (row) index = i;
+      if (row !== answer[type][i]) result = false;
+    });
+    this.state = { selecting: false, select, index, result };
   }
 
   onChange(index) {
@@ -20,6 +31,8 @@ export default class AnswerSelect extends Component {
     }
     select[index] = true;
     if (onChange) onChange(select);
+    this.close();
+    this.setState({ index });
   }
 
   open() {
@@ -31,29 +44,22 @@ export default class AnswerSelect extends Component {
   }
 
   render() {
-    const { selecting } = this.state;
-    const { value, list = [] } = this.props;
-    let index = 0;
-    for (let i = 0; i < list.length; i += 1) {
-      if (list[i].key === value) {
-        index = i;
-        break;
-      }
-    }
-    const title = list.length > 0 ? list[index] : '';
+    const { selecting, index, result } = this.state;
+    const { list = [], show, selected = {}, answer = {}, type, fix } = this.props;
+    const title = list.length > 0 && index >= 0 ? list[index] : ' ';
     return (
       <div className="answer-select">
         <div hidden={!selecting} className="mask" onClick={() => this.close()} />
-        <div className="select-warp">
+        <div className={`select-warp ${show ? (result ? 'true' : 'false') : ''}`}>
           <div className="text" onClick={() => this.open()}>
-            {title}
+            {` ${title}`}
             <Assets name="chooser_icon" />
           </div>
           <div className={`select-body ${selecting ? 'select' : ''}`}>
-            {list.map(item => {
+            {list.map((item, i) => {
               return (
-                <div className="select-option" onClick={() => this.close()}>
-                  {item}
+                <div className={`item ${getKey(type, i, selected, answer)} ${show ? 'show' : ''}`} onClick={() => !fix && this.onChange(i)}>
+                  <div className="icon" />{item}
                 </div>
               );
             })}

+ 70 - 6
front/project/www/components/AnswerSelect/index.less

@@ -3,6 +3,8 @@
 .answer-select {
   position: relative;
   display: inline-block;
+  line-height: 20px;
+  height: 20px;
 
   .mask {
     position: fixed;
@@ -14,9 +16,23 @@
   }
 
   .select-warp {
-    line-height: 25px;
+    line-height: 20px;
+    height: 20px;
     display: inline-block;
     position: relative;
+    top: 0;
+
+    &.true {
+      .text {
+        background-color: rgba(23, 165, 27, 0.06);
+      }
+    }
+
+    &.false {
+      .text {
+        background-color: rgba(253, 247, 247, 1);
+      }
+    }
 
     .text {
       display: inline-block;
@@ -27,13 +43,20 @@
       min-width: 100px;
       padding-right: 20px;
       text-align: right;
+      line-height: 20px;
+      height: 20px;
 
       .assets {
         position: absolute;
         right: 5px;
-        top: 8px;
+        top: 5px;
       }
     }
+
+    .text::after {
+      display: inline-block;
+      content: " ";
+    }
   }
 
   .select-body {
@@ -50,21 +73,25 @@
     z-index: 2;
     transform: translateY(100%);
 
-    .select-option {
+    .item {
       height: 0;
       line-height: 25px;
       padding-left: 10px;
       font-size: 12px;
       cursor: pointer;
       transition: all 0.3s;
+
+      .icon {
+        display: none;
+      }
     }
 
-    .select-option:hover {
+    .item:hover {
       color: #fff;
       background: #006DAA;
     }
 
-    .select-option:last-child {
+    .item:last-child {
       border-bottom: none;
     }
   }
@@ -72,8 +99,45 @@
   .select-body.select {
     opacity: 1;
 
-    .select-option {
+    .item {
       height: auto;
     }
   }
+
+  .item.selected {
+    .icon {
+      background-image: url('/assets/option_select.png');
+    }
+  }
+
+  .item.show {
+    .icon {
+      display: inline-block;
+      width: 15px;
+      height: 15px;
+      margin-right: 5px;
+    }
+
+    .total {
+      display: block;
+    }
+  }
+
+  .item.show.true {
+    .icon {
+      background-image: url('/assets/option_right.png');
+    }
+  }
+
+  .item.show.false {
+    .text {
+      color: #686872;
+    }
+  }
+
+  .item.show.false.selected {
+    .icon {
+      background-image: url('/assets/option_wrong.png');
+    }
+  }
 }

+ 26 - 5
front/project/www/components/AnswerTable/index.js

@@ -5,19 +5,40 @@ import Select from '../Select';
 export default class AnswerTable extends Component {
   constructor(props) {
     super(props);
-    this.state = {};
+    this.state = { data: this.props.data, index: -1 };
   }
 
-  componentWillMount() {}
+  componentWillMount() { }
 
-  componentWillUnmount() {}
+  componentWillUnmount() { }
+
+  resort(index) {
+    const { data } = this.state;
+    let number = true;
+    const ii = data.map((row, i) => {
+      if (`${Number(row[index])}` !== row[index]) number = false;
+      return { value: row[index], index: i };
+    }).sort((x, y) => {
+      if (x.value === y.value) return 0;
+      return number ? Number(x.value) - Number(y.value) : x.value > y.value ? 1 : -1;
+    });
+    this.setState({
+      index,
+      data: ii.map((row) => {
+        return data[row.index];
+      }),
+    });
+  }
 
   render() {
-    const { columns = [], data = [], list = [] } = this.props;
+    const { columns = [], list = [] } = this.props;
+    const { data = [], index } = this.state;
     return (
       <div className="answer-table">
         <div className="select-line">
-          <Select size="basic" theme="default" list={list} />
+          <Select size="basic" theme="default" value={index} list={list} onChange={(item) => {
+            this.resort(item.key);
+          }} />
         </div>
         <table border="1" bordercolor="#d8d8d8">
           <thead>

+ 0 - 1
front/project/www/components/Calculator/index.js

@@ -72,7 +72,6 @@ export default class Calculator extends Component {
           this.typeList.push(text);
           this.change = false;
           if (this.valueList.length > 1) {
-            console.log(this);
             this.setState({ value: this.compute() });
           }
         }

+ 5 - 3
front/project/www/components/Navigation/index.js

@@ -2,12 +2,14 @@ import React from 'react';
 import './index.less';
 
 function Navigation(props) {
-  const { list = [], active = 0 } = props;
+  const { list = [], active = 0, theme, onChange } = props;
   return (
-    <div className="navigation">
+    <div className={`navigation ${theme}`}>
       {list.map((item, index) => {
         return (
-          <div className={`item ${index === active ? 'active' : ''}`}>
+          <div className={`item ${index === active ? 'active' : ''}`} onClick={() => {
+            if (onChange) onChange(index);
+          }}>
             <span className="text">{item.title}</span>
           </div>
         );

+ 81 - 52
front/project/www/components/Navigation/index.less

@@ -1,76 +1,105 @@
 @import '../../app.less';
 
 .navigation {
+
   text-align: center;
   display: flex;
-  height: 44px;
-  line-height: 44px;
-  color: #fff;
   overflow: hidden;
 
   .item {
     flex: 1;
     position: relative;
-    background: #006DAA;
     cursor: pointer;
     transition: all 0.3s;
+  }
+
+  &.process {
+    height: 44px;
+    line-height: 44px;
+    color: #fff;
 
-    .text {
-      margin-left: 5px;
+    .item {
+      background: #006DAA;
+
+      .text {
+        margin-left: 5px;
+      }
     }
-  }
 
-  .item:first-child:before {
-    display: none;
-  }
+    .item:first-child:before {
+      display: none;
+    }
 
-  .item:before {
-    content: '';
-    position: absolute;
-    height: 0;
-    width: 0;
-    top: -16px;
-    left: 0;
-    right: -34px;
-    border-width: 38px 19px;
-    z-index: 1;
-    border-style: solid;
-    border-color: transparent;
-    border-left-color: #fff;
-    transition: all 0.3s;
-  }
+    .item:before {
+      content: '';
+      position: absolute;
+      height: 0;
+      width: 0;
+      top: -16px;
+      left: 0;
+      right: -34px;
+      border-width: 38px 19px;
+      z-index: 1;
+      border-style: solid;
+      border-color: transparent;
+      border-left-color: #fff;
+      transition: all 0.3s;
+    }
 
-  .item:after {
-    content: '';
-    position: absolute;
-    height: 0;
-    width: 0;
-    right: -22px;
-    border-width: 22px 11px;
-    z-index: 2;
-    border-style: solid;
-    border-color: transparent;
-    border-left-color: #006DAA;
-    transition: all 0.3s;
-  }
+    .item:after {
+      content: '';
+      position: absolute;
+      height: 0;
+      width: 0;
+      right: -22px;
+      border-width: 22px 11px;
+      z-index: 2;
+      border-style: solid;
+      border-color: transparent;
+      border-left-color: #006DAA;
+      transition: all 0.3s;
+    }
 
-  .item.active,
-  .item.active:hover {
-    color: #fff;
-    background: #003366;
-  }
+    .item.active,
+    .item.active:hover {
+      color: #fff;
+      background: #003366;
+    }
 
-  .item.active:after,
-  .item.active:hover:after {
-    border-left-color: #003366;
-  }
+    .item.active:after,
+    .item.active:hover:after {
+      border-left-color: #003366;
+    }
 
-  .item:hover {
-    color: #fff;
-    background: darken(#006DAA, 5);
+    .item:hover {
+      color: #fff;
+      background: darken(#006DAA, 5);
+    }
+
+    .item:hover:after {
+      border-left-color: darken(#006DAA, 5);
+    }
   }
 
-  .item:hover:after {
-    border-left-color: darken(#006DAA, 5);
+  &.detail {
+    margin: 0 -2.5px;
+    width: 100%;
+    border-bottom: 1px solid #ECEDEE;
+    height: 32px;
+    line-height: 32px;
+    color: #A7A7B7;
+
+    .item {
+      background: #ECEDEE;
+      margin: 0 2.5px;
+    }
+
+    .item.active,
+    .item.active:hover,
+    .item:hover {
+      color: #fff;
+      background: #4299FF;
+    }
   }
+
 }

+ 4 - 5
front/project/www/components/OtherAnswer/index.js

@@ -23,19 +23,18 @@ export default class OtherAnswer extends Component {
   }
 
   render() {
+    const { data } = this.props;
     const { show, more } = this.state;
     return (
       <div className={`other-answer ${more ? 'more' : ''} ${!show ? 'hide' : ''}`}>
-        <div className="title">Q: Agreement;Rhetorical construction</div>
+        <div className="title">Q: {data.content}</div>
         <div
           ref={ref => {
             this.Text = ref;
           }}
           className="desc"
-        >
-          A: They are generally not good at taking care of themselves.and services to prevent worsening health and
-          increased health costs.They are worsening health and increased health costs..and services to
-        </div>
+          dangerouslySetInnerHTML={{ __html: `A: ${data.answer}` }}
+        />
         {more && show && <Icon name="up" onClick={() => this.setState({ show: false })} />}
         {more && !show && <Icon name="down" onClick={() => this.setState({ show: true })} />}
       </div>

+ 60 - 25
front/project/www/routes/paper/process/base/index.js

@@ -4,6 +4,7 @@ import './index.less';
 import { Checkbox } from 'antd';
 import Assets from '@src/components/Assets';
 import { formatSeconds, formatSecond, getMap } from '@src/services/Tools';
+import Icon from '../../../../components/Icon';
 import Button from '../../../../components/Button';
 import Navigation from '../../../../components/Navigation';
 import Answer from '../../../../components/Answer';
@@ -19,6 +20,8 @@ export default class extends Component {
   constructor(props) {
     super(props);
     this.state = {
+      showTime: true,
+      showNo: true,
       showCalculator: false,
       disorder: false,
       order: [],
@@ -29,8 +32,17 @@ export default class extends Component {
   }
 
   onChangeQuestion(index, value) {
-    const { question = {}, answer = {} } = this.state;
-    answer.questions[index] = { [question.type]: value };
+    const { question } = this.props;
+    const { content } = question;
+    const { answer = {} } = this.state;
+    const type = content.type === 'double' ? 'double' : 'single';
+    if (!answer.questions) {
+      answer.questions = content.questions.map(() => {
+        return {};
+      });
+    }
+    answer.questions[index] = { [type]: value };
+    console.log(answer);
     this.setState({ answer });
   }
 
@@ -53,9 +65,10 @@ export default class extends Component {
   }
 
   checkAnswer() {
-    const { question, answer } = this.state;
+    const { question } = this.props;
+    const { answer } = this.state;
     let result = null;
-    if (question.type === 'awa' && !answer.awa) result = 'Please answer the question first.';
+    if (question.questionType === 'awa' && !answer.awa) result = 'Please answer the question first.';
     if (result) return this.showToast(null, result);
     return true;
   }
@@ -70,7 +83,7 @@ export default class extends Component {
 
   formatStrem(text) {
     if (!text) return '';
-    const { question = { content: {} } } = this.state;
+    const { question = { content: {} } } = this.props;
     const { table = {}, questions = [] } = question.content;
     text = text.replace(/#select#/g, "<span class='#select#' />");
     text = text.replace(/#table#/g, "<span class='#table#' />");
@@ -78,13 +91,19 @@ export default class extends Component {
       const selectList = document.getElementsByClassName('#select#');
       const tableList = document.getElementsByClassName('#table#');
       for (let i = 0; i < selectList.length; i += 1) {
+        if (!questions[i]) break;
         ReactDOM.render(
-          <AnswerSelect list={questions[i].select} onChange={v => this.onChangeQuestion(i, v)} />,
+          <AnswerSelect list={questions[i].select} type={'single'} onChange={v => this.onChangeQuestion(i, v)} />,
           selectList[i],
         );
       }
-      for (let i = 0; i < tableList.length; i += 1) {
-        ReactDOM.render(<AnswerTable list={table.header} columns={table.header} data={table.data} />, tableList[i]);
+      if (table.row && table.col && table.header) {
+        const columns = table.header.map((title, index) => {
+          return { title, key: index };
+        });
+        for (let i = 0; i < tableList.length; i += 1) {
+          ReactDOM.render(<AnswerTable list={columns} columns={columns} data={table.data} />, tableList[i]);
+        }
       }
     }, 1);
     return text;
@@ -92,9 +111,12 @@ export default class extends Component {
 
   next() {
     const { flow } = this.props;
-
+    const { answer } = this.state;
     if (this.checkAnswer()) {
-      flow.next();
+      flow.submit(answer)
+        .then(() => {
+          flow.next();
+        });
     }
   }
 
@@ -125,8 +147,8 @@ export default class extends Component {
     const { steps = [] } = question.content;
     return (
       <div className="block block-content">
-        {steps.length > 0 && <Navigation list={question.content.steps} active={step} onChange={() => { }} />}
-        <div className="text">{this.formatStrem(steps.length > 0 ? steps[step].stem : question.stem)}</div>
+        {steps.length > 0 && <Navigation theme='process' list={question.content.steps} active={step} onChange={(v) => this.setState({ step: v })} />}
+        <div className="text" dangerouslySetInnerHTML={{ __html: this.formatStrem(steps.length > 0 ? steps[step].stem : question.stem) }} />
       </div>
     );
   }
@@ -145,7 +167,9 @@ export default class extends Component {
               <Answer
                 list={item.select}
                 type={type}
-                direction={question.direction}
+                first={item.first}
+                second={item.second}
+                direction={item.direction}
                 onChange={v => this.onChangeQuestion(index, v)}
               />
             </div>
@@ -156,37 +180,48 @@ export default class extends Component {
   }
 
   renderDetail() {
-    const { paper, userQuestion, question = { content: {} }, singleTime, stageTime, flow } = this.state;
-    const { showCalculator } = this.state;
+    const { paper, userQuestion, question = { content: {} }, singleTime, stageTime, flow } = this.props;
+    if (!userQuestion.id) return null;
+    const { showCalculator, showTime, showNo } = this.state;
     const { typeset = 'one' } = question.content;
     return (
       <div className="layout">
         <div className="fixed">
           {QuestionTypeMap[question.questionType].long}
-          <Assets
+          {question.questionType === 'ir' && <Assets
             className="calculator-icon"
             name="calculator_icon"
             onClick={() => this.setState({ showCalculator: !showCalculator })}
-          />
-          <Assets className="collect-icon" name="collect_icon" />
+          />}
+          {/* <Assets className="collect-icon" name="collect_icon" onClick={() => {
+            flow.toggleCollect();
+          }} /> */}
+          <div className="collect-icon"><Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} /></div>
         </div>
         <Calculator show={showCalculator} />
         <div className="layout-header">
           <div className="title">{paper.title}</div>
           <div className="right">
-            <div className="block">
+            <div className="block" onClick={() => {
+              this.setState({ showTime: !showTime });
+            }}>
               <Assets name="timeleft_icon" />
-              Time left {formatSecond(stageTime || singleTime)}
+              {showTime && stageTime && `Time left ${formatSecond(stageTime)}`}
+              {showTime && singleTime && `Time cost ${formatSecond(singleTime)}`}
             </div>
-            <div className="block">
+            <div className="block" onClick={() => {
+              this.setState({ showNo: !showNo });
+            }}>
               <Assets name="subjectnumber_icon" />
-              {userQuestion.no} of {paper.questionNumer}
+              {showNo && `${userQuestion.no} of ${paper.questionNumber}`}
             </div>
           </div>
         </div>
-        <div className={`layout-body ${typeset}`}>
-          {this.renderContent()}
-          {this.renderAnswer()}
+        <div className={'layout-body'}>
+          <div className={typeset}>
+            {this.renderContent()}
+            {this.renderAnswer()}
+          </div>
         </div>
         <div className="layout-footer">
           <div className="help">

+ 12 - 2
front/project/www/routes/paper/process/base/index.less

@@ -75,7 +75,7 @@
     .calculator {
       position: absolute;
       z-index: 9;
-      top: 60px;
+      top: 92px;
     }
 
     .fixed {
@@ -88,6 +88,7 @@
       background: #7EAFE0;
       color: #fff;
       padding: 0 25px;
+      z-index: 100;
 
       .calculator-icon {
         margin-left: 50px;
@@ -98,6 +99,8 @@
         float: right;
         cursor: pointer;
         transform: translateY(5px);
+        line-height: 16px;
+        height: 16px;
       }
     }
 
@@ -117,6 +120,7 @@
 
         .block {
           line-height: 30px;
+          cursor: pointer;
 
           .assets {
             margin-right: 10px;
@@ -186,10 +190,16 @@
       .block {
         padding: 60px 20px 20px;
       }
+
+      span.\#select\# {
+        line-height: 20px;
+        height: 20px;
+      }
     }
 
-    .layout-body.two {
+    .layout-body .two {
       display: flex;
+      height: 100%;
 
       .block {
         overflow: hidden;

+ 1 - 1
front/project/www/routes/paper/process/page.js

@@ -149,7 +149,7 @@ export default class extends Page {
       const { questions } = answer;
       if (questions) {
         // 还原乱序选项
-        answer.questions = questions.forEach((q, index) => {
+        questions.forEach((q, index) => {
           const order = questionSetting.questions[index];
           Object.keys(q).forEach((k) => {
             if (q[k]) q[k] = resortListWithOrder(q[k], order);

+ 1 - 0
front/project/www/routes/paper/question/index.less

@@ -53,6 +53,7 @@
           right: 0;
           text-align: left;
           padding: 0 10px;
+          white-space: nowrap;
 
           .menu-content {
             position: absolute;

+ 70 - 56
front/project/www/routes/paper/question/page.js

@@ -25,37 +25,38 @@ import Sentence from '../process/sentence';
 export default class extends Page {
   initState() {
     return {
+      step: 0,
       hideAnalysis: true,
       analysisTab: 'official',
       showAnswer: false,
       noteField: AskTarget[0].key,
       showIds: false,
 
-      question: {
-        content: {
-          typeset: 'one',
-        },
-        // questionType: 'awa',
-        answer: {
-          subject: [[{ text: 'like', uuid: 'hKyz' }]],
-          options: ['parallel'],
-        },
-        stem: "<p><span uuid='kBJe'>I</span> <span uuid='hKyz'>like</span> <span uuid='fQXh'>book</span></p>",
-      },
-      userQuestion: {
-        userAnswer: {
-          subject: [{ text: 'I', uuid: 'kBJe' }],
-          options: ['compare'],
-        },
-        no: 2,
-      },
-      paper: {
-        title: '长难句练习',
-        questionNumber: 20,
-      },
-      report: {
-        paperModule: 'sentence',
-      },
+      // question: {
+      //   content: {
+      //     typeset: 'one',
+      //   },
+      //   // questionType: 'awa',
+      //   answer: {
+      //     subject: [[{ text: 'like', uuid: 'hKyz' }]],
+      //     options: ['parallel'],
+      //   },
+      //   stem: "<p><span uuid='kBJe'>I</span> <span uuid='hKyz'>like</span> <span uuid='fQXh'>book</span></p>",
+      // },
+      // userQuestion: {
+      //   userAnswer: {
+      //     subject: [{ text: 'I', uuid: 'kBJe' }],
+      //     options: ['compare'],
+      //   },
+      //   no: 2,
+      // },
+      // paper: {
+      //   title: '长难句练习',
+      //   questionNumber: 20,
+      // },
+      // report: {
+      //   paperModule: 'sentence',
+      // },
 
     };
   }
@@ -63,27 +64,35 @@ export default class extends Page {
   initData() {
     const { id } = this.params;
     Question.getDetailById(id).then(userQuestion => {
-      const { question, questionNos, paper, note, report, answer, setting } = userQuestion;
+      const { question, questionNos, paper, note, report, setting } = userQuestion;
       let { questionNo } = userQuestion;
       if (!questionNo) ([questionNo] = questionNos);
+      if (!question.answer) question.answer = { questions: [] };
+      if (!question.answerDistributed) question.answerDistributed = { questions: [] };
+      if (!userQuestion.userAnswer) userQuestion.userAnswer = { questions: [] };
       if ((report.setting || {}).disorder) {
         const { content } = question;
         // 还原做题顺序
         content.questions.forEach((q, i) => {
           q.select = sortListWithOrder(question.select, setting.questions[i]);
         });
-        answer.questions.forEach((q, i) => {
+        question.answer.questions.forEach((q, i) => {
           Object.keys(q).forEach((k) => {
             if (q[k]) q[k] = sortListWithOrder(q[k], setting.questions[i]);
           });
         });
-        question.answerDistributed.forEach((q, i) => {
+        question.answerDistributed.questions.forEach((q, i) => {
+          Object.keys(q).forEach((k) => {
+            if (q[k]) q[k] = sortListWithOrder(q[k], setting.questions[i]);
+          });
+        });
+        userQuestion.userAnswer.questions.forEach((q, i) => {
           Object.keys(q).forEach((k) => {
             if (q[k]) q[k] = sortListWithOrder(q[k], setting.questions[i]);
           });
         });
       }
-      this.setState({ userQuestion, question, questionNo, note, paper });
+      this.setState({ userQuestion, question, questionNo, note, paper, questionNos });
     });
   }
 
@@ -154,7 +163,7 @@ export default class extends Page {
 
   formatStem(text) {
     if (!text) return '';
-    const { question = { content: {} } } = this.state;
+    const { showAnswer, question = { content: {} }, userQuestion } = this.state;
     const { table = {}, questions = [] } = question.content;
     text = text.replace(/#select#/g, "<span class='#select#' />");
     text = text.replace(/#table#/g, "<span class='#table#' />");
@@ -162,13 +171,25 @@ export default class extends Page {
       const selectList = document.getElementsByClassName('#select#');
       const tableList = document.getElementsByClassName('#table#');
       for (let i = 0; i < selectList.length; i += 1) {
+        if (!questions[i]) break;
         ReactDOM.render(
-          <AnswerSelect list={questions[i].select} onChange={v => this.onChangeQuestion(i, v)} />,
+          <AnswerSelect
+            list={questions[i].select}
+            type={'single'}
+            selected={(userQuestion.userAnswer || { questions: [] }).questions[i]}
+            answer={(question.answer || { questions: [] }).questions[i]}
+            fix
+            show={showAnswer} />,
           selectList[i],
         );
       }
-      for (let i = 0; i < tableList.length; i += 1) {
-        ReactDOM.render(<AnswerTable list={table.header} columns={table.header} data={table.data} />, tableList[i]);
+      if (table.row && table.col && table.header) {
+        const columns = table.header.map((title, index) => {
+          return { title, key: index };
+        });
+        for (let i = 0; i < tableList.length; i += 1) {
+          ReactDOM.render(<AnswerTable list={columns} columns={columns} data={table.data} />, tableList[i]);
+        }
       }
     }, 1);
     return text;
@@ -336,8 +357,8 @@ export default class extends Page {
         break;
       case 'qa':
         content = <div className="detail-block answer-block">
-          {asks.map(ask => {
-            return ask;
+          {asks.map((ask, index) => {
+            return <OtherAnswer key={index} data={ask} />;
           })}
         </div>;
         break;
@@ -347,33 +368,28 @@ export default class extends Page {
     return content;
   }
 
-  renderOtherAnswer() {
-    const { otherAnswer = [1, 2, 3, 4, 5] } = this.state;
-    return (
-      <div className="other">
-        {otherAnswer.map(() => {
-          return <OtherAnswer />;
-        })}
-      </div>
-    );
-  }
-
   renderAnswer() {
-    const { question = { content: {} }, showAnswer } = this.state;
-    const { questions = [] } = question.content;
-    const { typeset = 'one' } = question.content;
+    const { question = { content: {} }, showAnswer, userQuestion = {} } = this.state;
+    const { questions = [], type, typeset = 'one' } = question.content;
+    console.log(userQuestion);
     return <div className="block block-answer">
       {typeset === 'two' ? <Switch checked={showAnswer} onChange={(value) => {
         this.setState({ showAnswer: value });
       }}>{showAnswer ? '显示答案' : '关闭答案'}</Switch> : ''}
-      {questions.map((item) => {
+      {questions.map((item, index) => {
         return (
           <div>
             <div className="text m-b-2">{item.description}</div>
             <AnswerList
+              show={showAnswer}
+              selected={(userQuestion.userAnswer || { questions: [] }).questions[index]}
+              answer={(question.answer || { questions: [] }).questions[index]}
+              distributed={(question.answerDistributed || { questions: [] }).questions[index]}
               list={item.select}
-              type={question.type}
-              direction={question.direction}
+              type={type}
+              first={item.first}
+              second={item.second}
+              direction={item.direction}
             />
           </div>
         );
@@ -382,18 +398,16 @@ export default class extends Page {
   }
 
   renderContent() {
-    const { question = { content: {} }, userQuestion = {}, showAnswer, step } = this.state;
-    const { userAnswer } = userQuestion;
+    const { question = { content: {} }, showAnswer, step } = this.state;
     const { typeset = 'one' } = question.content;
     const { steps = [] } = question.content;
-    console.log(userAnswer);
     return (
       <div className="block block-content">
         {typeset === 'one' && question.questionType !== 'awa' ? <Switch checked={showAnswer} onChange={(value) => {
           this.setState({ showAnswer: value });
         }}>{showAnswer ? '显示答案' : '关闭答案'}</Switch> : ''}
         {question.questionType === 'awa' && <h2>Analytical Writing Assessment</h2>}
-        {steps.length > 0 && <Navigation list={question.content.steps} active={step} onChange={() => { }} />}
+        {steps.length > 0 && <Navigation theme='detail' list={question.content.steps} active={step} onChange={(v) => this.setState({ step: v })} />}
         <div className="text" style={{ height: 2000 }} dangerouslySetInnerHTML={{ __html: this.formatStem(steps.length > 0 ? steps[step].stem : question.stem) }} />
       </div>
     );

+ 20 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserQuestionBaseDto.java

@@ -13,6 +13,10 @@ public class UserQuestionBaseDto {
 
     private Integer no;
 
+    private String questionModule;
+
+    private String questionType;
+
     private Integer questionId;
 
     private Integer questionNoId;
@@ -88,4 +92,20 @@ public class UserQuestionBaseDto {
     public void setNo(Integer no) {
         this.no = no;
     }
+
+    public String getQuestionModule() {
+        return questionModule;
+    }
+
+    public void setQuestionModule(String questionModule) {
+        this.questionModule = questionModule;
+    }
+
+    public String getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(String questionType) {
+        this.questionType = questionType;
+    }
 }

+ 20 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserQuestionDetailDto.java

@@ -11,12 +11,16 @@ import java.util.List;
 public class UserQuestionDetailDto {
     private Integer id;
 
+    private Integer no;
+
     private UserPaperBaseExtendDto paper;
 
     private UserReportExtendDto report;
 
     private String questionModule;
 
+    private String questionType;
+
     private Integer questionId;
 
     private Integer questionNoId;
@@ -158,4 +162,20 @@ public class UserQuestionDetailDto {
     public void setUserAnswer(JSONObject userAnswer) {
         this.userAnswer = userAnswer;
     }
+
+    public Integer getNo() {
+        return no;
+    }
+
+    public void setNo(Integer no) {
+        this.no = no;
+    }
+
+    public String getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(String questionType) {
+        this.questionType = questionType;
+    }
 }

+ 11 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserReportBaseDto.java

@@ -1,5 +1,6 @@
 package com.qxgmat.dto.response;
 
+import com.alibaba.fastjson.JSONObject;
 import com.nuliji.tools.annotation.Dto;
 import com.qxgmat.data.dao.entity.UserReport;
 import com.qxgmat.dto.extend.UserPaperBaseExtendDto;
@@ -26,6 +27,8 @@ public class UserReportBaseDto {
 
     private Integer isFinish;
 
+    private JSONObject setting;
+
     private Date updateTime;
 
     public Integer getId() {
@@ -107,4 +110,12 @@ public class UserReportBaseDto {
     public void setIsFinish(Integer isFinish) {
         this.isFinish = isFinish;
     }
+
+    public JSONObject getSetting() {
+        return setting;
+    }
+
+    public void setSetting(JSONObject setting) {
+        this.setting = setting;
+    }
 }

+ 11 - 1
server/gateway-api/src/main/java/com/qxgmat/service/extend/QuestionFlowService.java

@@ -271,6 +271,16 @@ public class QuestionFlowService {
             // 判断答题情况
             JSONObject userAnswer = userQuestion.getUserAnswer();
             Question question = questionService.get(userQuestion.getQuestionId());
+            // 作文统计字数
+            if (question.getQuestionType().equals(QuestionType.AWA.key)){
+                JSONObject detail = new JSONObject();
+                String awa = userAnswer.getString("awa");
+                String base = awa.replaceAll("/<[^>]+>/g", "")
+                        .replaceAll("/[,.+-:;']+/g", "");
+                detail.put("words", base.split(" ").length);
+                userQuestion.setDetail(detail);
+                return false;
+            }
             JSONObject answer = question.getAnswer();
             return (boolean) this.baseAnswer(userAnswer, answer, question);
         });
@@ -313,7 +323,7 @@ public class QuestionFlowService {
 
         submitAfterCallback.put(QuestionModule.BASE, (userQuestion, question)->{
             // 作文不统计
-            if (!question.getQuestionType().equals(QuestionType.AWA.key)){
+            if (question.getQuestionType().equals(QuestionType.AWA.key)){
                 return;
             }
             // 更新题目及题目编号统计