소스 검색

feat(front): 题库

Go 5 년 전
부모
커밋
62cf220a9d

+ 112 - 0
front/project/www/routes/question/index.js

@@ -1,5 +1,117 @@
+import { getMap, formatTreeData } from '@src/services/Tools';
 import detail from './detail';
 import search from './search';
 import searchHistory from './searchHistory';
+import { CourseModule } from '../../../Constant';
+import { Main } from '../../stores/main';
 
 export default [detail, search, searchHistory];
+
+
+export function refreshQuestionType(compontent, subject, questionType, { all, needSentence, allSubject, excludeAwa }) {
+  return Main.getExercise().then(result => {
+    const list = result.filter(row => (needSentence ? true : row.isExamination)).map(row => {
+      row.title = `${row.titleZh}${row.titleEn}`;
+      row.key = row.extend;
+      return row;
+    });
+    const tree = formatTreeData(list, 'id', 'title', 'parentId');
+    compontent.questionSubjectMap = getMap(tree, 'key', 'children');
+    compontent.questionSubjectSelect = tree.filter(row => row.level === 1 && (allSubject ? true : row.children.length > 1) && (excludeAwa ? row.extend !== 'awa' : true));
+    if (all) {
+      compontent.questionSubjectSelect.forEach(row => {
+        row.children.unshift({
+          title: '全部',
+          key: '',
+        });
+      });
+      compontent.questionSubjectSelect.unshift({
+        title: '全部',
+        key: '',
+      });
+    }
+    compontent.setState({
+      questionSubjectSelect: compontent.questionSubjectSelect,
+      questionSubjectMap: compontent.questionSubjectMap,
+    });
+    return {
+      questionTypes: questionType || (subject ? compontent.questionSubjectMap[subject].map(row => row.key).filter(row => row) : null),
+    };
+  });
+}
+
+export function refreshStruct(compontent, module, one, two, { all }) {
+  switch (module) {
+    case 'exercise':
+      return Main.getExerciseAll().then(result => {
+        const tmp = result.filter(row => row.level > 2).map(row => {
+          row.title = `${row.titleZh}`;
+          row.key = row.titleEn;
+          return row;
+        });
+        const idsMap = getMap(tmp, 'id', 'key');
+        const map = {};
+        tmp.forEach(row => {
+          if (!map[row.key]) {
+            map[row.key] = {
+              title: row.title,
+              key: row.key,
+              structIds: [],
+              parentId: row.level > 3 ? idsMap[row.parentId] : null,
+              subject: [],
+              questionType: [],
+            };
+          }
+          const item = map[row.key];
+          item.structIds.push(row.id);
+          if (item.subject.indexOf(row.subject) < 0) {
+            item.subject.push(row.subject);
+          }
+          if (item.questionType.indexOf(row.questionType) < 0) {
+            item.questionType.push(row.questionType);
+          }
+        });
+        const list = Object.values(map);
+        let courseModules = null;
+        let structIds = null;
+        if (one === 'preview') {
+          if (!two) {
+            courseModules = CourseModule.map(row => row.value);
+          } else {
+            courseModules = [two];
+          }
+        } else if (one) {
+          const resultMap = getMap(list, 'key', 'structIds');
+          if (!two) {
+            structIds = resultMap[one];
+          } else {
+            structIds = resultMap[two];
+          }
+        }
+
+        const tree = formatTreeData(list, 'key', 'title', 'parentId');
+        const oneSelect = tree;
+        const twoSelectMap = getMap(tree, 'key', 'children');
+        if (all) {
+          oneSelect.forEach(row => {
+            row.children.unshift({
+              title: '全部',
+              key: '',
+            });
+          });
+          oneSelect.unshift({
+            title: '全部',
+            key: '',
+          });
+        }
+        compontent.setState({ oneSelect, twoSelectMap });
+
+        return {
+          structIds,
+          courseModules,
+        };
+      });
+    default:
+      return Promise.resolve({});
+  }
+}

+ 177 - 54
front/project/www/routes/question/search/page.js

@@ -1,79 +1,186 @@
 import React, { Component } from 'react';
 import './index.less';
 import { Icon } from 'antd';
+import { Link } from 'react-router-dom';
 import Page from '@src/containers/Page';
+import { getMap, formatPercent, formatSeconds } from '@src/services/Tools';
 import Button from '../../../components/Button';
 import Tabs from '../../../components/Tabs';
 import UserAction from '../../../components/UserAction';
 import UserPagination from '../../../components/UserPagination';
 
+import { refreshQuestionType, refreshStruct } from '../index';
+import { User } from '../../../stores/user';
+import { Question } from '../../../stores/question';
+
+import { QuestionType, QuestionDifficult } from '../../../../Constant';
+import { My } from '../../../stores/my';
+
+const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
+// const QuestionDifficultMap = getMap(QuestionDifficult, 'value', 'label');
+
 export default class extends Page {
   initState() {
+    this.searchNo = 0;
     return {
-      list: [{}, {}, {}],
-      searchList: [123, 123, 123],
-      historyList: [321, 321, 321],
-      searchValue: '',
-      search: false,
-      tab: '1',
+      list: [],
+      searchList: [],
+      keyword: '',
+      subject: 'verbal',
       filterMap: {},
       sortMap: {},
       focus: false,
+      difficultSelect: QuestionDifficult.map(row => {
+        return {
+          title: row.label,
+          key: row.value,
+        };
+      }),
     };
   }
 
-  onTabChange(tab) {
-    this.setState({ tab });
+  initData() {
+    const data = Object.assign(this.state, this.state.search);
+    data.filterMap = this.state.search;
+    if (data.order) {
+      data.sortMap = { [data.order]: data.direction };
+    }
+    if (data.subject) {
+      data.filterMap.subject = data.subject;
+    }
+    this.setState(data);
+    refreshQuestionType(this, data.subject, data.questionType, { needSentence: false, allSubject: true, excludeAwa: true })
+      .then(({ questionTypes }) => {
+        return refreshStruct(this, 'exercise', data.one, data.two, {
+          all: true,
+        }).then(({ structIds }) => {
+          Question.searchStem(
+            Object.assign(
+              { questionTypes, structIds, module: 'exercise' },
+              this.state.search,
+              {
+                order: Object.keys(data.sortMap)
+                  .map(key => {
+                    return `${key} ${data.sortMap[key]}`;
+                  })
+                  .join(','),
+              },
+            ),
+          ).then(result => {
+            this.setState({ list: result.list, total: result.total, page: data.page, searchResult: !!data.keyword });
+          });
+        });
+      });
+  }
+
+  onRefreshFilter(query) {
+    // this.changeQuery(query);
+    // this.setState(query);
+    this.refreshQuery(query);
+    // this.initData();
+  }
+
+  onFilter(value) {
+    this.search(value);
+    // this.initData();
+  }
+
+  onSearch() {
+    const { keyword } = this.state;
+    User.addSearch(keyword);
+    // this.search({ keyword }, false);
+    // this.changeQuery({ keyword });
+    // this.setState({ keyword });
+    this.refreshQuery({ keyword });
+    // this.initData();
+  }
+
+  onSort(value) {
+    const keys = Object.keys(value);
+    // this.search({ order: keys.length ? keys.join('|') : null, direction: keys.length ? Object.values(value).join('|') : null });
+    const { sortMap } = this.state;
+    const index = keys.length > 1 && sortMap[keys[0]] ? 1 : 0;
+    this.search({ order: keys.length ? keys[index] : null, direction: keys.length ? value[keys[index]] : null }, false);
+    this.initData();
+  }
+
+  onChangePage(page) {
+    this.search({ page }, false);
+    this.initData();
   }
 
-  onChangeSearch(value) {
-    this.setState({ searchValue: value });
+  onChangeSearch(keyword, force = false) {
+    if (!force) {
+      this.searchNo += 1;
+      const no = this.searchNo;
+      Question.searchNo({ keyword, module: 'exercise', page: 1, size: 5 })
+        .then((result) => {
+          if (no !== this.searchNo) return;
+          this.setState({ searchList: result.list.map(row => row.title) });
+        });
+    }
+    this.setState({ keyword });
+  }
+
+  addSearchHistory(id) {
+    My.addSearchHistory(id);
   }
 
   renderView() {
-    const { search } = this.state;
+    const { searchResult } = this.state;
     return (
       <div>
         {this.renderSearch()}
-        {search ? this.renderResult() : this.renderFilter()}
+        {searchResult ? this.renderResult() : this.renderFilter()}
       </div>
     );
   }
 
   renderSearch() {
-    const { searchList, searchValue, historyList, focus } = this.state;
+    const { searchHistoryList = [] } = this.props.user;
+    const { searchList = [], keyword, focus, tip } = this.state;
+    // console.log(focus, tip, searchHistoryList);
     return (
       <div className="search-layout">
         <div className="search-wrapper">
           <input
-            value={searchValue}
+            value={keyword}
             onChange={e => this.onChangeSearch(e.target.value)}
             onFocus={() => this.setState({ focus: true })}
             onBlur={() => this.setState({ focus: false })}
           />
-          <Button width={150}>
+          <Button width={150} onClick={() => this.onSearch()}>
             <Icon className="m-r-5" type="search" />
             搜索题目
           </Button>
-          {focus && (
-            <div hidden={!searchValue} className="search-tip-wrapper">
+          {(focus || tip) && (
+            <div hidden={!keyword || searchList.length === 0} className="search-tip-wrapper" onMouseEnter={() => this.setState({ tip: true })} onMouseLeave={() => this.setState({ tip: false })}>
               {searchList.map(item => {
-                return <div className="t-2 t-s-16">{item}</div>;
+                return <div className="t-2 t-s-16" onClick={() => {
+                  this.onChangeSearch(item, true);
+                  this.onSearch();
+                }}>{item}</div>;
               })}
             </div>
           )}
-          {focus && (
-            <div hidden={searchValue} className="search-tip-wrapper">
-              {historyList.map(item => {
+          {(focus || tip) && (
+            <div hidden={keyword || searchHistoryList.length === 0} className="search-tip-wrapper" onMouseEnter={() => this.setState({ tip: true })} onMouseLeave={() => this.setState({ tip: false })}>
+              {searchHistoryList.map((item, index) => {
                 return (
-                  <div className="t-2 t-s-16">
+                  <div className="t-2 t-s-16" onClick={() => {
+                    this.onChangeSearch(item, true);
+                    this.onSearch();
+                  }}>
                     {item}
-                    <div className="f-r t-4 t-s-12 c-p">删除</div>
+                    <div className="f-r t-4 t-s-12 c-p" onClick={(e) => {
+                      e.stopPropagation();
+                      User.removeSearchIndex(index);
+                    }}>删除</div>
                   </div>
                 );
               })}
               <div className="all-del t-r">
-                <span className="t-4 t-s-12 c-p">删除历史</span>
+                <span className="t-4 t-s-12 c-p" onClick={() => User.clearSearch()}>删除历史</span>
               </div>
             </div>
           )}
@@ -83,7 +190,20 @@ export default class extends Page {
   }
 
   renderFilter() {
-    const { tab, filterMap, sortMap } = this.state;
+    const { filterMap, sortMap } = this.state;
+    const {
+      subject,
+      questionSubjectSelect,
+      questionSubjectMap = {},
+      difficultSelect,
+      oneSelect,
+      twoSelectMap = {},
+      list = [],
+      total,
+      page,
+    } = this.state;
+    const { login } = this.props.user;
+    console.log(this.props.user);
     return (
       <div className="filter-layout">
         <div className="content">
@@ -94,30 +214,31 @@ export default class extends Page {
             size="small"
             space={5}
             width={220}
-            active={tab}
-            tabs={[{ key: '1', title: 'Verval' }, { key: '2', title: 'Quant' }, { key: '3', title: 'IR' }]}
-            onChange={key => this.onTabChange(key)}
+            active={subject}
+            tabs={questionSubjectSelect}
+            onChange={key => this.onRefreshFilter({ subject: key })}
           />
+          <div hidden={!login}><Link to="/question/search/history">浏览历史</Link></div>
           <UserAction
             selectList={[
               {
                 key: 'questionType',
                 placeholder: '题型',
-                select: [],
+                select: questionSubjectMap[subject] || [],
               },
               {
                 label: '范围',
                 children: [
                   {
-                    key: 'rang1',
+                    key: 'one',
                     placeholder: '全部',
-                    select: [],
+                    select: oneSelect,
                   },
                   {
                     placeholder: '全部',
-                    key: 'rang2',
-                    be: 'rang1',
-                    selectMap: {},
+                    key: 'two',
+                    be: 'one',
+                    selectMap: twoSelectMap,
                   },
                 ],
               },
@@ -125,35 +246,41 @@ export default class extends Page {
                 right: true,
                 placeholder: '难度',
                 key: 'level',
-                select: [],
+                select: difficultSelect,
               },
             ]}
             sortList={[
-              { key: '1', label: '全站用时', fixed: true, right: true },
-              { key: '2', label: '平均正确率', fixed: true, right: true },
-              { key: '3', label: '收藏人数', fixed: true, right: true },
+              { key: 'time', label: '全站用时', fixed: true, right: true },
+              { key: 'correct', label: '平均正确率', fixed: true, right: true },
+              { key: 'collect_number', label: '收藏人数', fixed: true, right: true },
             ]}
             filterMap={filterMap}
             sortMap={sortMap}
+            onSort={value => this.onSort(value)}
             onFilter={value => this.onFilter(value)}
           />
           {this.renderList()}
-          <UserPagination />
+          {total > 0 && list.length > 0 && (
+            <UserPagination total={total} current={page} pageSize={this.state.search.size} onChange={p => this.onChangePage(p)} />
+          )}
         </div>
       </div>
     );
   }
 
   renderResult() {
+    const { total, list, page } = this.state;
     return (
       <div className="result-layout">
         <div className="content">
           <div className="m-b-1">
             <span className="t-1 t-s-24">搜索结果:</span>
-            <span className="t-2 t-s-18">共12条</span>
+            <span className="t-2 t-s-18">共{total}条</span>
           </div>
           {this.renderList()}
-          <UserPagination jump />
+          {total > 0 && list.length > 0 && (
+            <UserPagination total={total} current={page} pageSize={this.state.search.size} onChange={p => this.onChangePage(p)} />
+          )}
         </div>
       </div>
     );
@@ -162,31 +289,27 @@ export default class extends Page {
   renderList() {
     const { list } = this.state;
     return list.map(item => {
-      return <SearchItem data={item} />;
+      return <SearchItem data={item} onClick={() => this.addSearchHistory(item.id)} />;
     });
   }
 }
 
 class SearchItem extends Component {
   render() {
+    const { data = {}, onClick } = this.props;
     return (
       <div className="search-item">
         <div className="search-item-head">
-          <span className="t-15 t-s-16 m-r-1">阅读RC</span>
-          <span className="t-1 t-s-16">PREP07#124、PREP08#332、PREP07#124、PREP08#332、PREP07#124</span>
+          <span className="t-15 t-s-16 m-r-1">{QuestionTypeMap[data.question.questionType]}</span>
+          <a className="t-1 t-s-16" href={`/question/detail/${data.id}`} target="_blank" onClick={() => onClick()}>{data.title}</a>
           <div className="f-r t-15 t-s-14">
-            <span className="m-r-1">Medium</span>
-            <span className="m-r-1">用时: 1m 39s</span>
-            <span className="m-r-1">80% </span>
-            <span>收藏 20313</span>
+            <span className="m-r-1">{data.question.difficult}</span>
+            <span className="m-r-1">用时: {formatSeconds(data.totalTime / data.totalNumer)}</span>
+            <span className="m-r-1">{formatPercent(data.totalCorrect, data.totalNumber, false)}</span>
+            <span>收藏 {data.collectNumber}</span>
           </div>
         </div>
-        <div className="t-1 p-20">
-          Margaret Mead, the best-known anthropologist of the twentieth century, helped shape public opinion on
-          fundamentally important areas like attitudes toward children and families, along with the relative merits of
-          competition and cooperation. A. shape public opinion on fundamentally important areas like attitudes toward
-          children and families, along with
-        </div>
+        <div className="t-1 p-20">{data.question.description}</div>
       </div>
     );
   }

+ 2 - 2
front/project/www/routes/question/searchHistory/index.js

@@ -1,8 +1,8 @@
 export default {
   path: '/question/search/history',
   key: 'question-search-history',
-  title: '题库搜索历史',
-  needLogin: false,
+  title: '题库-浏览记录',
+  needLogin: true,
   repeat: true,
   tab: 'question',
   component() {

+ 49 - 13
front/project/www/routes/question/searchHistory/page.js

@@ -2,16 +2,52 @@ import React, { Component } from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
+import { getMap, formatDate } from '@src/services/Tools';
+
+import { QuestionType } from '../../../../Constant';
+import { My } from '../../../stores/my';
+
+const QuestionTypeMap = getMap(QuestionType, 'value', 'label');
 
 export default class extends Page {
   initState() {
+    this.today = formatDate(new Date(), 'YYYY-MM-DD');
     return {
-      list: [[{}, {}, {}], [{}, {}, {}], [{}, {}, {}]],
+      list: [],
     };
   }
 
+  initData() {
+    My.listSearchHistory()
+      .then(result => {
+        const map = {};
+        const dateList = [];
+        result.forEach((row) => {
+          const date = formatDate(row.createTime, 'YYYY-MM-DD');
+          if (!map[date]) {
+            map[date] = { date, list: [] };
+            dateList.push(date);
+          }
+          const item = map[date];
+          item.list.push(row);
+        });
+        const list = dateList.map(row => {
+          return map[row];
+        });
+
+        this.setState({ list });
+      });
+  }
+
+  clearHistory(date) {
+    My.clearSearchHistory(date)
+      .then(() => {
+        this.refresh();
+      });
+  }
+
   renderView() {
-    const { list } = this.state;
+    const { list = [] } = this.state;
     return (
       <div>
         <div className="top content t-8">
@@ -19,7 +55,8 @@ export default class extends Page {
         </div>
         <div className="center content">
           {list.map(item => {
-            return <DayList list={item} />;
+            if (item.date === this.today) item.today = true;
+            return <DayList data={item} onClear={() => this.clearHistory(item.date)} />;
           })}
         </div>
       </div>
@@ -29,17 +66,17 @@ export default class extends Page {
 
 class DayList extends Component {
   render() {
-    const { list = [] } = this.props;
+    const { data = {}, onClear } = this.props;
     return (
       <div className="day-list">
         <div className="m-b-1">
-          <span className="t-1 t-s-18 m-r-1 f-w-b">2019-03-23</span>
-          <span className="c-p t-2 t-s-12">
+          <span className="t-1 t-s-18 m-r-1 f-w-b">{data.today ? '今天' : data.date}</span>
+          <span className="c-p t-2 t-s-12" onClick={() => onClear()}>
             <Assets name="ico_24_restart" svg /> 清空记录
           </span>
         </div>
-        {list.map(item => {
-          return <LogItem data={item} />;
+        {data.list.map(item => {
+          return <LogItem data={item} onClick={() => { }} />;
         })}
       </div>
     );
@@ -48,13 +85,12 @@ class DayList extends Component {
 
 class LogItem extends Component {
   render() {
+    const { data, onClick } = this.props;
     return (
       <div className="log-item">
-        <span className="t-6 m-r-1 tag">阅读SC</span>
-        <span className="t-1 m-r-2 f-w-b">OG 19 #678</span>
-        <span className="p-l-1 t-2">
-          GMAT考试由分析写作、推理、数学和语文四部分组成。分别为: a)分析性写作评价(Analytical Writing Assessment)
-        </span>
+        <span className="t-6 m-r-1 tag">{QuestionTypeMap[data.question.questionType]}</span>
+        <a className="t-1 m-r-2 f-w-b" href={`/question/detail/${data.questionNo.id}`} target="_blank" onClick={() => onClick()}>{data.questionNo.title}</a>
+        <span className="p-l-1 t-2">{data.question.description}</span>
       </div>
     );
   }

+ 4 - 0
front/project/www/stores/my.js

@@ -567,6 +567,10 @@ export default class MyStore extends BaseStore {
     return this.apiPost('/my/search/history', { questionNoId });
   }
 
+  clearSearchHistory(date) {
+    return this.apiPost('/my/search/history/clear', { date });
+  }
+
   listSearchHistory() {
     return this.apiGet('/my/search/history/list', {});
   }

+ 17 - 0
front/project/www/stores/user.js

@@ -153,6 +153,23 @@ export default class UserStore extends BaseStore {
     });
   }
 
+  addSearch(keyword) {
+    const { searchHistoryList = [] } = this.state;
+    searchHistoryList.unshift(keyword);
+    searchHistoryList.splice(5);
+    this.setState({ searchHistoryList });
+  }
+
+  removeSearchIndex(index) {
+    const { searchHistoryList = [] } = this.state;
+    searchHistoryList.splice(index, 1);
+    this.setState({ searchHistoryList });
+  }
+
+  clearSearch() {
+    this.setState({ searchHistoryList: [] });
+  }
+
   /**
    * 设置长难句试用
    */

+ 1 - 0
server/data/src/main/java/com/qxgmat/data/relation/QuestionNoRelationMapper.java

@@ -44,6 +44,7 @@ public interface QuestionNoRelationMapper {
 
     List<QuestionNo> searchNoFulltext(
             @Param("keyword") String keyword,
+            @Param("module") String module,
             @Param("qxCatId") Integer qxCatId
     );
 

+ 11 - 11
server/data/src/main/java/com/qxgmat/data/relation/mapping/QuestionNoRelationMapper.xml

@@ -61,11 +61,10 @@
   <select id="searchStemFulltext" resultMap="IdMap">
     select
     <include refid="Id_Column_List" />,
+    <if test="keyword">
     if(qn.`title` = #{keyword, jdbcType=VARCHAR}, 1, MATCH (q.`description`) AGAINST (#{keyword, jdbcType=VARCHAR} IN NATURAL LANGUAGE MODE)) as `relation_score`,
-    <if test="qxCatId != null">
-      if(qn.module='examination', find_in_set(#{qxCatId}, qn.`module_struct`), 0) as `select`,
     </if>
-    q.`collect_number` as `collect_number`,
+    qn.`collect_number` as `collect_number`,
     qn.`total_correct` / qn.`total_number` as `correct`,
     qn.`total_time` / qn.`total_number` as `time`
     from `question_no` qn
@@ -84,10 +83,12 @@
       </foreach>
     </if>
     where
-    (qn.`title` = #{keyword, jdbcType=VARCHAR} or MATCH (q.`description`) AGAINST (#{keyword, jdbcType=VARCHAR} IN NATURAL LANGUAGE MODE))
-    and q.id &gt; 0
+    q.id &gt; 0
+    <if test="keyword">
+      and (qn.`title` = #{keyword, jdbcType=VARCHAR} or MATCH (q.`description`) AGAINST (#{keyword, jdbcType=VARCHAR} IN NATURAL LANGUAGE MODE))
+    </if>
     <if test="qxCatId != null">
-      and `select` = 0
+      and (qn.module='examination' and find_in_set(#{qxCatId}, qn.`module_struct`) = 0)
     </if>
     <if test="module != null">
       and qn.`module` = #{module,jdbcType=VARCHAR}
@@ -108,15 +109,14 @@
     select
     <include refid="Id_Column_List" />,
     MATCH (qn.`title`) AGAINST (#{keyword, jdbcType=VARCHAR} IN NATURAL LANGUAGE MODE) as `relation_score`
-    <if test="qxCatId != null">
-      ,
-      if(qn.module='examination', find_in_set(#{qxCatId}, qn.`module_struct`), 0) as `select`
-    </if>
     from `question_no` qn
     where
     MATCH (qn.`title`) AGAINST (#{keyword, jdbcType=VARCHAR} IN NATURAL LANGUAGE MODE)
+    <if test="module != null">
+      and qn.`module` = #{module,jdbcType=VARCHAR}
+    </if>
     <if test="qxCatId != null">
-      and `select` = 0
+      and (qn.module='examination' and find_in_set(#{qxCatId}, qn.`module_struct`) = 0)
     </if>
     ORDER BY `relation_score` DESC
   </select>

+ 21 - 2
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -45,6 +45,7 @@ import java.io.File;
 import java.io.IOException;
 import java.text.DateFormat;
 import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -2108,6 +2109,24 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
+    @RequestMapping(value = "/search/history/clear", method = RequestMethod.POST)
+    @ApiOperation(value = "清除搜索记录", notes = "清除搜索记录", httpMethod = "POST")
+    public Response<Boolean> addSearchHistory(@RequestBody @Validated SearchHistoryClearDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+
+        Date end;
+        Date start;
+        try{
+            SimpleDateFormat sdf =   new SimpleDateFormat("yyyy-MM-dd");
+            start = sdf.parse(dto.getDate());
+            end = Tools.addDate(start, 1);
+        }catch (Exception e){
+            throw new ParameterException("日期格式错误");
+        }
+        userSearchHistoryService.clearDate(user.getId(), start, end);
+        return ResponseHelp.success(true);
+    }
+
     @RequestMapping(value = "/search/history/list", method = RequestMethod.GET)
     @ApiOperation(value = "搜索历史记录", httpMethod = "GET")
     public Response<List<UserSearchHistoryDto>> listSearchHistory(
@@ -2121,12 +2140,12 @@ public class MyController {
         Date start = Tools.addDate(now, -1 * (day + week * 7));
         Date end = Tools.addDate(start, 7);
 
-        List<UserSearchHistory> p = userSearchHistoryService.listByUser(user.getId(), start.toString(), end.toString());
+        List<UserSearchHistory> p = userSearchHistoryService.listByUser(user.getId(), start, end);
         List<UserSearchHistoryDto> pr = Transform.convert(p, UserSearchHistoryDto.class);
 
         Collection questionIds = Transform.getIds(p, UserSearchHistory.class, "questionId");
         List<Question> questionList = questionService.select(questionIds);
-        Transform.combine(pr, questionList, UserSearchHistoryDto.class, "questionId", "question", QuestionNo.class, "id", QuestionExtendDto.class);
+        Transform.combine(pr, questionList, UserSearchHistoryDto.class, "questionId", "question", Question.class, "id", QuestionExtendDto.class);
 
         Collection questionNoIds = Transform.getIds(p, UserSearchHistory.class, "questionNoId");
         List<QuestionNo> questionNoList = questionNoService.select(questionNoIds);

+ 7 - 6
server/gateway-api/src/main/java/com/qxgmat/controller/api/QuestionController.java

@@ -137,10 +137,10 @@ public class QuestionController {
         User user = (User) shiroHelp.getLoginUser();
 
         // 过滤千行cat的数据
-        List<ExaminationStruct> structs = examinationStructService.main();
-        ExaminationStruct qxCatStruct = structs.stream().filter((row)-> row.getExtend().equals(ServiceKey.QX_CAT.key)).findFirst().get();
+//        List<ExaminationStruct> structs = examinationStructService.main();
+//        ExaminationStruct qxCatStruct = structs.stream().filter((row)-> row.getExtend().equals(ServiceKey.QX_CAT.key)).findFirst().get();
 
-        Page<QuestionNoRelation> p = questionNoService.searchStemFulltext(page, size, keyword, questionTypes, module, structIds, place, difficult, qxCatStruct.getId(), order, DirectionStatus.ValueOf(direction));
+        Page<QuestionNoRelation> p = questionNoService.searchStemFulltext(page, size, keyword, questionTypes, module, structIds, place, difficult, null, order, DirectionStatus.ValueOf(direction));
         List<QuestionNoDetailDto> pr = Transform.convert(p, QuestionNoDetailDto.class);
 
         if(user!= null){
@@ -167,12 +167,13 @@ public class QuestionController {
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "5") int size,
             @RequestParam(required = true) String keyword,
+            @RequestParam(required = false) String module,
             HttpSession session) {
         // 过滤千行cat的数据
-        List<ExaminationStruct> structs = examinationStructService.main();
-        ExaminationStruct qxCatStruct = structs.stream().filter((row)-> row.getExtend().equals(ServiceKey.QX_CAT.key)).findFirst().get();
+//        List<ExaminationStruct> structs = examinationStructService.main();
+//        ExaminationStruct qxCatStruct = structs.stream().filter((row)-> row.getExtend().equals(ServiceKey.QX_CAT.key)).findFirst().get();
 
-        Page<QuestionNo> p = questionNoService.searchNoFulltext(page, size, keyword, qxCatStruct.getId());
+        Page<QuestionNo> p = questionNoService.searchNoFulltext(page, size, keyword, module, null);
         List<QuestionNoDto> pr = Transform.convert(p, QuestionNoDto.class);
 
         return ResponseHelp.success(pr, page, size, p.getTotal());

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/SearchHistoryClearDto.java

@@ -0,0 +1,13 @@
+package com.qxgmat.dto.request;
+
+public class SearchHistoryClearDto {
+    private String date;
+
+    public String getDate() {
+        return date;
+    }
+
+    public void setDate(String date) {
+        this.date = date;
+    }
+}

+ 2 - 2
server/gateway-api/src/main/java/com/qxgmat/service/inline/QuestionNoService.java

@@ -122,9 +122,9 @@ public class QuestionNoService extends AbstractService {
      * @param keyword
      * @return
      */
-    public Page<QuestionNo> searchNoFulltext(int page, int size, String keyword, Integer qxCatId){
+    public Page<QuestionNo> searchNoFulltext(int page, int size, String keyword, String module, Integer qxCatId){
         Page<QuestionNo> p = page(()->{
-            questionNoRelationMapper.searchNoFulltext(keyword, qxCatId);
+            questionNoRelationMapper.searchNoFulltext(keyword, module, qxCatId);
         }, page, size);
 
         Collection ids = Transform.getIds(p, QuestionNo.class, "id");

+ 14 - 1
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserSearchHistoryService.java

@@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 @Service
@@ -25,7 +26,7 @@ public class UserSearchHistoryService extends AbstractService {
     @Resource
     private UserSearchHistoryMapper userSearchHistoryMapper;
 
-    public List<UserSearchHistory> listByUser(Integer userId, String startTime, String endTime){
+    public List<UserSearchHistory> listByUser(Integer userId, Date startTime, Date endTime){
         Example example = new Example(UserExport.class);
         example.and(
                 example.createCriteria()
@@ -33,9 +34,21 @@ public class UserSearchHistoryService extends AbstractService {
                         .andGreaterThanOrEqualTo("createTime", startTime)
                         .andLessThan("createTime", endTime)
         );
+        example.orderBy("id").desc();
         return select(userSearchHistoryMapper, example);
     }
 
+    public void clearDate(Integer userId, Date startTime, Date endTime){
+        Example example = new Example(UserExport.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andGreaterThanOrEqualTo("createTime", startTime)
+                        .andLessThan("createTime", endTime)
+        );
+        delete(userSearchHistoryMapper, example);
+    }
+
     public UserSearchHistory add(UserSearchHistory entity){
         int result = insert(userSearchHistoryMapper, entity);
         entity = one(userSearchHistoryMapper, entity.getId());

+ 6 - 0
server/tools/src/main/java/com/nuliji/tools/Tools.java

@@ -16,6 +16,7 @@ import java.io.UnsupportedEncodingException;
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -247,6 +248,11 @@ public class Tools {
         return ipAddress;
     }
 
+    public static String baseDate(Date date){
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return sdf.format(date);
+    }
+
     public static Date today(){
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(new Date());