Browse Source

feat(h5): h5交互

Go 5 years ago
parent
commit
e0d6fd352b
53 changed files with 1326 additions and 452 deletions
  1. 52 48
      front/project/admin/routes/setting/service/page.js
  2. 1 1
      front/project/admin/routes/setting/struct/page.js
  3. 0 0
      front/project/h5/assets/qx_cat.png
  4. 0 0
      front/project/h5/assets/textbook.png
  5. 33 21
      front/project/h5/components/Block/index.js
  6. 3 3
      front/project/h5/components/Radio/index.js
  7. 34 16
      front/project/h5/routes/page/message/page.js
  8. 0 3
      front/project/h5/routes/product/courseSingle/index.less
  9. 1 1
      front/project/h5/routes/product/course/index.js
  10. 1 1
      front/project/h5/routes/product/course/index.less
  11. 0 0
      front/project/h5/routes/product/courseVideo/page.js
  12. 2 2
      front/project/h5/routes/product/courseSingle/index.js
  13. 3 0
      front/project/h5/routes/product/courseVs/index.less
  14. 0 0
      front/project/h5/routes/product/courseVs/page.js
  15. 3 3
      front/project/h5/routes/product/data/index.js
  16. 126 1
      front/project/h5/routes/product/data/index.less
  17. 159 1
      front/project/h5/routes/product/data/page.js
  18. 8 1
      front/project/h5/routes/product/dataDetail/page.js
  19. 0 9
      front/project/h5/routes/product/list/index.js
  20. 0 124
      front/project/h5/routes/product/list/index.less
  21. 0 101
      front/project/h5/routes/product/list/page.js
  22. 20 1
      front/project/h5/routes/product/main/index.less
  23. 80 33
      front/project/h5/routes/product/main/page.js
  24. 1 1
      front/project/h5/routes/product/serviceDetail/index.js
  25. 44 8
      front/project/h5/routes/product/serviceDetail/page.js
  26. 1 1
      front/project/h5/routes/textbook/detail/index.js
  27. 29 5
      front/project/h5/routes/textbook/detail/index.less
  28. 15 9
      front/project/h5/routes/textbook/detail/page.js
  29. 19 4
      front/project/h5/routes/textbook/library/page.js
  30. 3 3
      front/project/h5/routes/textbook/main/page.js
  31. 24 0
      front/project/h5/stores/course.js
  32. 2 1
      front/project/h5/stores/index.js
  33. 23 3
      front/project/h5/stores/main.js
  34. 1 1
      front/project/h5/stores/my.js
  35. 3 3
      front/project/www/stores/main.js
  36. 3 2
      front/src/stores/base.js
  37. 70 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Course.java
  38. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/CourseData.java
  39. 16 16
      server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookTopic.java
  40. 4 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataMapper.xml
  41. 5 3
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseMapper.xml
  42. 2 2
      server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookTopicMapper.xml
  43. 23 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java
  44. 79 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  45. 7 4
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  46. 149 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataListDto.java
  47. 169 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseListDto.java
  48. 19 9
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookInfoDto.java
  49. 39 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseDataService.java
  50. 9 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseService.java
  51. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookLibraryService.java
  52. 3 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookTopicService.java
  53. 2 2
      server/tools/src/main/java/com/nuliji/tools/Tools.java

+ 52 - 48
front/project/admin/routes/setting/service/page.js

@@ -36,24 +36,28 @@ export default class extends Page {
     return System.getServiceQxCat().then(result => {
       this.setState({ qx_cat: result || {} });
       const { form } = this.props;
-      form.setFieldsValue(flattenObject(result, 'sentence'));
+      form.setFieldsValue(flattenObject(result, 'qx_cat'));
     });
   }
 
   refreshTextbook() {
     return System.getServiceTextbook().then(result => {
       this.setState({ textbook: result || {} });
+      const { form } = this.props;
+      form.setFieldsValue(flattenObject(result, 'textbook'));
     });
   }
 
   refreshVip() {
     return System.getServiceVip().then(result => {
       this.setState({ vip: result || {} });
+      const { form } = this.props;
+      form.setFieldsValue(flattenObject(result, 'vip'));
     });
   }
 
-  changeMapValue(field, index, key, value) {
-    const data = this.state[field] || {};
+  changeMapValue(field, second, index, key, value) {
+    const data = this.state[field] ? this.state[field][second] || {} : {};
     data[index] = data[index] || {};
     data[index][key] = value;
     this.setState({ [field]: data });
@@ -98,78 +102,78 @@ export default class extends Page {
 
   renderQxCat() {
     const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-    const image = getFieldValue('qx_cat_image') || null;
+    const image = getFieldValue('qx_cat.image') || null;
     return <Form>
       <Row>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品价格'>
-          {getFieldDecorator('qx_cat[0].price', {
+          {getFieldDecorator('qx_cat.package[0].price', {
             rules: [
               { required: true, message: '输入千行Cat价格' },
             ],
           })(
             <InputNumber placeholder='请输入千行Cat价格' onChange={(value) => {
-              this.changeMapValue('qx_cat', 0, 'price', value);
+              this.changeMapValue('qx_cat', 'package', 0, 'price', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务名称'>
-          {getFieldDecorator('qx_cat[0].title', {
+          {getFieldDecorator('qx_cat.package[0].title', {
             rules: [
               { required: true, message: '输入千行Cat名称' },
             ],
           })(
             <Input placeholder='请输入千行Cat名称' onChange={(value) => {
-              this.changeMapValue('qx_cat', 0, 'title', value);
+              this.changeMapValue('qx_cat', 'package', 0, 'title', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务简介'>
-          {getFieldDecorator('qx_cat[0].description', {
+          {getFieldDecorator('qx_cat.package[0].description', {
             rules: [
               { required: true, message: '输入千行Cat服务简介' },
             ],
           })(
             <Input placeholder='请输入千行Cat服务简介' onChange={(value) => {
-              this.changeMapValue('qx_cat', 0, 'description', value);
+              this.changeMapValue('qx_cat', 'package', 0, 'description', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='有效期说明'>
-          {getFieldDecorator('qx_cat[0].expire_info', {
+          {getFieldDecorator('qx_cat.package[0].expire_info', {
             rules: [
               { required: true, message: '输入千行Cat有效期说明' },
             ],
           })(
             <Input placeholder='请输入千行Cat有效期说明' onChange={(value) => {
-              this.changeMapValue('qx_cat', 0, 'expire_info', value);
+              this.changeMapValue('qx_cat', 'package', 0, 'expire_info', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='退款政策'>
-          {getFieldDecorator('qx_cat[0].refund_policy', {
+          {getFieldDecorator('qx_cat.package[0].refund_policy', {
             rules: [
               { required: true, message: '输入千行Cat退款政策' },
             ],
           })(
             <Input placeholder='请输入千行Cat退款政策' onChange={(value) => {
-              this.changeMapValue('qx_cat', 0, 'refund_policy', value);
+              this.changeMapValue('qx_cat', 'package', 0, 'refund_policy', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='版权说明'>
-          {getFieldDecorator('qx_cat[0].copyright_notes', {
+          {getFieldDecorator('qx_cat.package[0].copyright_notes', {
             rules: [
               { required: true, message: '输入千行Cat版权说明' },
             ],
           })(
             <Input placeholder='请输入千行Cat版权说明' onChange={(value) => {
-              this.changeMapValue('qx_cat', 0, 'copyright_notes', value);
+              this.changeMapValue('qx_cat', 'package', 0, 'copyright_notes', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
 
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品图片'>
-          {getFieldDecorator('qx_cat_image', {
+          {getFieldDecorator('qx_cat.image', {
             rules: [
               { required: true, message: '上传图片' },
             ],
@@ -178,7 +182,7 @@ export default class extends Page {
               listType="picture-card"
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                setFieldsValue({ qx_cat_image: result.url });
+                setFieldsValue({ 'qx_cat.image': result.url });
                 return Promise.reject();
               })}
             >
@@ -195,11 +199,11 @@ export default class extends Page {
 
   renderTextbook() {
     const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-    const image = getFieldValue('textbook_image') || null;
+    const image = getFieldValue('textbook.image') || null;
     return <Form>
       <Row>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品图片'>
-          {getFieldDecorator('textbook_image', {
+          {getFieldDecorator('textbook.image', {
             rules: [
               { required: true, message: '上传图片' },
             ],
@@ -208,7 +212,7 @@ export default class extends Page {
               listType="picture-card"
               showUploadList={false}
               beforeUpload={(file) => System.uploadImage(file).then((result) => {
-                setFieldsValue({ textbook_image: result.url });
+                setFieldsValue({ 'textbook.image': result.url });
                 return Promise.reject();
               })}
             >
@@ -220,68 +224,68 @@ export default class extends Page {
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='商品价格'>
-          {getFieldDecorator('textbook[0].price', {
+          {getFieldDecorator('textbook.package[0].price', {
             rules: [
               { required: true, message: '输入数学机经价格' },
             ],
           })(
             <InputNumber placeholder='请输入数学机经价格' onChange={(value) => {
-              this.changeMapValue('textbook', 0, 'price', value);
+              this.changeMapValue('textbook', 'package', 0, 'price', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务名称'>
-          {getFieldDecorator('textbook[0].title', {
+          {getFieldDecorator('textbook.package[0].title', {
             rules: [
               { required: true, message: '输入数学机经名称' },
             ],
           })(
             <Input placeholder='请输入数学机经名称' onChange={(value) => {
-              this.changeMapValue('textbook', 0, 'title', value);
+              this.changeMapValue('textbook', 'package', 0, 'title', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='服务简介'>
-          {getFieldDecorator('textbook[0].description', {
+          {getFieldDecorator('textbook.package[0].description', {
             rules: [
               { required: true, message: '输入数学机经服务简介' },
             ],
           })(
             <Input placeholder='请输入数学机经服务简介' onChange={(value) => {
-              this.changeMapValue('textbook', 0, 'description', value);
+              this.changeMapValue('textbook', 'package', 0, 'description', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='有效期说明'>
-          {getFieldDecorator('textbook[0].expire_info', {
+          {getFieldDecorator('textbook.package[0].expire_info', {
             rules: [
               { required: true, message: '输入数学机经有效期说明' },
             ],
           })(
             <Input placeholder='请输入数学机经有效期说明' onChange={(value) => {
-              this.changeMapValue('textbook', 0, 'expire_info', value);
+              this.changeMapValue('textbook', 'package', 0, 'expire_info', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='退款政策'>
-          {getFieldDecorator('textbook[0].refund_policy', {
+          {getFieldDecorator('textbook.package[0].refund_policy', {
             rules: [
               { required: true, message: '输入数学机经退款政策' },
             ],
           })(
             <Input placeholder='请输入数学机经退款政策' onChange={(value) => {
-              this.changeMapValue('textbook', 0, 'refund_policy', value);
+              this.changeMapValue('textbook', 'package', 0, 'refund_policy', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
         <Form.Item labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} label='版权说明'>
-          {getFieldDecorator('textbook[0].copyright_notes', {
+          {getFieldDecorator('textbook.package[0].copyright_notes', {
             rules: [
               { required: true, message: '输入数学机经版权说明' },
             ],
           })(
             <Input placeholder='请输入数学机经版权说明' onChange={(value) => {
-              this.changeMapValue('textbook', 0, 'copyright_notes', value);
+              this.changeMapValue('textbook', 'package', 0, 'copyright_notes', value);
             }} style={{ width: '200px' }} />,
           )}
         </Form.Item>
@@ -291,10 +295,10 @@ export default class extends Page {
 
   renderVip() {
     const { getFieldDecorator, setFieldsValue, getFieldValue } = this.props.form;
-    const image = getFieldValue('vip_image') || null;
+    const image = getFieldValue('vip.image') || null;
     return <Form>
       <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='商品图片'>
-        {getFieldDecorator('vip_image', {
+        {getFieldDecorator('vip.image', {
           rules: [
             { required: true, message: '上传图片' },
           ],
@@ -303,7 +307,7 @@ export default class extends Page {
             listType="picture-card"
             showUploadList={false}
             beforeUpload={(file) => System.uploadImage(file).then((result) => {
-              setFieldsValue({ vip_image: result.url });
+              setFieldsValue({ 'vip.image': result.url });
               return Promise.reject();
             })}
           >
@@ -319,68 +323,68 @@ export default class extends Page {
           return <Col span={12}>
             <h1>{row.label}</h1>
             <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='商品价格'>
-              {getFieldDecorator(`vip[${index}].price`, {
+              {getFieldDecorator(`vip.package[${index}].price`, {
                 rules: [
                   { required: true, message: '输入价格' },
                 ],
               })(
                 <InputNumber placeholder={'输入价格'} onChange={(value) => {
-                  this.changeMapValue('vip', index, 'price', value);
+                  this.changeMapValue('vip', 'package', index, 'price', value);
                 }} style={{ width: '200px' }} />,
               )}
             </Form.Item>
             <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='服务名称'>
-              {getFieldDecorator(`vip[${index}].title`, {
+              {getFieldDecorator(`vip.package[${index}].title`, {
                 rules: [
                   { required: true, message: '输入名称' },
                 ],
               })(
                 <Input placeholder={'输入名称'} onChange={(value) => {
-                  this.changeMapValue('vip', index, 'title', value);
+                  this.changeMapValue('vip', 'package', index, 'title', value);
                 }} style={{ width: '200px' }} />,
               )}
             </Form.Item>
             <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='服务简介'>
-              {getFieldDecorator(`vip[${index}].description`, {
+              {getFieldDecorator(`vip.package[${index}].description`, {
                 rules: [
                   { required: true, message: '输入服务简介' },
                 ],
               })(
                 <Input placeholder='请输入服务简介' onChange={(value) => {
-                  this.changeMapValue('vip', index, 'description', value);
+                  this.changeMapValue('vip', 'package', index, 'description', value);
                 }} style={{ width: '200px' }} />,
               )}
             </Form.Item>
             <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='有效期说明'>
-              {getFieldDecorator(`vip[${index}].expire_info`, {
+              {getFieldDecorator(`vip.package[${index}].expire_info`, {
                 rules: [
                   { required: true, message: '输入有效期说明' },
                 ],
               })(
                 <Input placeholder='请输入有效期说明' onChange={(value) => {
-                  this.changeMapValue('vip', index, 'expire_info', value);
+                  this.changeMapValue('vip', 'package', index, 'expire_info', value);
                 }} style={{ width: '200px' }} />,
               )}
             </Form.Item>
             <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='退款政策'>
-              {getFieldDecorator(`vip[${index}].refund_policy`, {
+              {getFieldDecorator(`vip.package[${index}].refund_policy`, {
                 rules: [
                   { required: true, message: '输入退款政策' },
                 ],
               })(
                 <Input placeholder='请输入退款政策' onChange={(value) => {
-                  this.changeMapValue('vip', index, 'refund_policy', value);
+                  this.changeMapValue('vip', 'package', index, 'refund_policy', value);
                 }} style={{ width: '200px' }} />,
               )}
             </Form.Item>
             <Form.Item labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} label='版权说明'>
-              {getFieldDecorator(`vip[${index}].copyright_notes`, {
+              {getFieldDecorator(`vip.package[${index}].copyright_notes`, {
                 rules: [
                   { required: true, message: '输入版权说明' },
                 ],
               })(
                 <Input placeholder='请输入版权说明' onChange={(value) => {
-                  this.changeMapValue('vip', index, 'copyright_notes', value);
+                  this.changeMapValue('vip', 'package', index, 'copyright_notes', value);
                 }} style={{ width: '200px' }} />,
               )}
             </Form.Item>

+ 1 - 1
front/project/admin/routes/setting/struct/page.js

@@ -337,7 +337,7 @@ export default class extends Page {
       if (node.level <= 2) {
         itemList = this.exerciseItemListLimit;
       }
-      if (node.extend) {
+      if (node.level <= 2) {
         asyncSMessage('该节点不允许编辑', 'warn');
         return;
       }

front/project/h5/assets/CAT.png → front/project/h5/assets/qx_cat.png


front/project/h5/assets/JiJing.png → front/project/h5/assets/textbook.png


+ 33 - 21
front/project/h5/components/Block/index.js

@@ -2,10 +2,14 @@ import React, { Component } from 'react';
 import './index.less';
 import { Icon } from 'antd-mobile';
 import Assets from '@src/components/Assets';
+import { getMap } from '@src/services/Tools';
+import { CrowdList } from '../../../Constant';
 import Tag from '../Tag';
 import Money from '../Money';
 import Button from '../Button';
 
+const CrowdMap = getMap(CrowdList, 'value', 'label');
+
 export class Block extends Component {
   render() {
     const { className = '', children } = this.props;
@@ -33,9 +37,9 @@ export class TagBlock extends Component {
 
 export class LinkBlock extends Component {
   render() {
-    const { className = '', theme = 'default', title, sub } = this.props;
+    const { className = '', theme = 'default', title, sub, onClick } = this.props;
     return (
-      <div className={`g-link-block ${className} ${theme}`}>
+      <div className={`g-link-block ${className} ${theme}`} onClick={() => onClick && onClick()}>
         <div className="g-link-block-title">{title}</div>
         <div className="g-link-block-sub">{sub}</div>
         <div className="g-link-block-icon">
@@ -48,23 +52,26 @@ export class LinkBlock extends Component {
 
 export class CourseBlock extends Component {
   render() {
+    const { data } = this.props;
     return (
-      <Block className="course-block">
-        <div className="title">OG20整合刷题-语法SC</div>
+      <Block className="course-block" onClick={() => {
+        linkTo(`/product/course/detail/${data.id}`);
+      }}>
+        <div className="title">{data.title}</div>
         <div className="block-body">
-          <Assets name="c_b" />
+          <Assets name="c_b" src={data.cover} />
           <div className="info">
             <div className="teacher">
-              授课老师<span>李小小</span>
+              授课老师<span>{data.teacher}</span>
             </div>
             <div className="desc">
-              老师特别特别特别好,上课特别认真…老师特别特别特别好,上课特别认真…老师特别特别特别好,上课特别认真…老师特别特别特别好,上课特别认真…
+              {data.comment}
             </div>
             <div className="division" />
             <div className="data">
-              <Tag size="small">适合新手</Tag>
+              {CrowdMap[data.crowd] && <Tag size="small">适合{CrowdMap[data.crowd]}</Tag>}
               <div className="result">
-                优质回答<span>89</span>
+                优质回答<span>{data.askNumber}</span>
               </div>
             </div>
           </div>
@@ -76,18 +83,20 @@ export class CourseBlock extends Component {
 
 export class CourseCoBlock extends Component {
   render() {
-    const { theme } = this.props;
+    const { theme, data } = this.props;
     return (
-      <TagBlock className="course-co-block" theme={theme} tag="语文Verbal">
-        <div className="title">OG20基础刷题套餐</div>
+      <TagBlock className="course-co-block" theme={theme} tag={''} onClick={() => {
+        linkTo(`/product/course/detail/${data.id}`);
+      }}>
+        <div className="title">{data.title}</div>
         <div className="info">
           <div className="teacher">
-            授课老师<span>李小小</span>
+            授课老师<span>{data.teacher}</span>
           </div>
-          <Tag size="small">适合新手</Tag>
+          {CrowdMap[data.crowd] && <Tag size="small">适合{CrowdMap[data.crowd]}</Tag>}
         </div>
         <div className="desc">
-          老师特别特别特别好,上课特别认真…老师特别特别特别好,上课特别认真…老师特别特别特别好,上课特别认真…老师特别特别特别好,上课特别认真…
+          {data.comment}
         </div>
       </TagBlock>
     );
@@ -96,16 +105,19 @@ export class CourseCoBlock extends Component {
 
 export class DataBlock extends Component {
   render() {
+    const { data } = this.props;
     return (
-      <Block className="data-block">
-        <Assets name="d_b" />
+      <Block className="data-block" onClick={() => {
+        linkTo(`/product/data/detail/${data.id}`);
+      }}>
+        <Assets name="d_b" src={data.cover} />
         <div className="info">
-          <div className="title">OG16/17/18/19语法千行</div>
-          <div className="desc">资料非常好,帮助复习。</div>
+          <div className="title">{data.title}</div>
+          <div className="desc">{data.comment}</div>
           <div className="division" />
           <div className="data">
-            <div className="people">888人已购</div>
-            <Money value="200" />
+            <div className="people">{data.saleNumber}人已购</div>
+            <Money value={data.price} />
           </div>
         </div>
       </Block>

+ 3 - 3
front/project/h5/components/Radio/index.js

@@ -15,9 +15,9 @@ export class SpecialRadio extends Component {
 }
 
 export class SpecialRadioGroup extends Component {
-  onClickItem(key) {
+  onClickItem(value) {
     const { onChange } = this.props;
-    if (onChange) onChange(key);
+    if (onChange) onChange(value);
   }
 
   render() {
@@ -26,7 +26,7 @@ export class SpecialRadioGroup extends Component {
       <div className="g-special-radio-group">
         {list.map(item => {
           return (
-            <SpecialRadio checked={value === item.key || values.indexOf(item.key) >= 0} onClick={() => this.onClickItem(item.key)}>
+            <SpecialRadio checked={value === item.value || values.indexOf(item.value) >= 0} onClick={() => this.onClickItem(item.value)}>
               {item.label}
             </SpecialRadio>
           );

+ 34 - 16
front/project/h5/routes/page/message/page.js

@@ -1,35 +1,53 @@
 import React from 'react';
-import { Link } from 'react-router-dom';
 import { Badge } from 'antd-mobile';
 import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
+import { formatDate } from '@src/services/Tools';
+import { My } from '../../../stores/my';
 
 export default class extends Page {
-  init() {}
+  initData() {
+    My.message(this.state.search)
+      .then(result => {
+        this.setTableData(result.list, result.total);
+      });
+  }
+
+  readAll() {
+    My.readAllMessage()
+      .then(() => {
+        this.refresh();
+      });
+  }
 
   renderView() {
+    const { list = [] } = this.state;
     return (
       <div>
-        <div className="all">
+        <div className="all" onClick={() => {
+          this.readAll();
+        }}>
           <Assets name="clean" />
           全部已读
         </div>
         <div className="list">
-          <div className="item">
-            <div className="detail">
-              <div className="title">
-                请尽快完成作业
-                <Badge dot />
+          {list.map(row => {
+            return <div className="item">
+              <div className="detail">
+                <div className="title">
+                  {row.title}
+                  <Badge dot />
+                </div>
+                <div className="desc">{row.description}</div>
+                {row.link && <a href={row.link}>查看详情</a>}
+              </div>
+              <div className="date-time">
+                <div className="date">{formatDate(row.createTime, 'YYYY-MM-DD')}</div>
+                <div className="time">{formatDate(row.createTime, 'HH:mm:ss')}</div>
               </div>
-              <div className="desc">消息详情消息详情消息详情消息详情</div>
-              <Link to="">请在千行官网查看详情</Link>
-            </div>
-            <div className="date-time">
-              <div className="date">2019-05-20</div>
-              <div className="time">10:22:21</div>
-            </div>
-          </div>
+            </div>;
+          })}
         </div>
       </div>
     );

+ 0 - 3
front/project/h5/routes/product/courseSingle/index.less

@@ -1,3 +0,0 @@
-@charset "utf-8";
-
-#product-course-detail {}

+ 1 - 1
front/project/h5/routes/product/course/index.js

@@ -1,5 +1,5 @@
 export default {
-  path: '/product/course',
+  path: '/product/course/video',
   key: 'product-course',
   title: '在线课程',
   needLogin: false,

+ 1 - 1
front/project/h5/routes/product/course/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#product-course {
+#product-course-video {
   .top {
     position: absolute;
     top: 0;

front/project/h5/routes/product/course/page.js → front/project/h5/routes/product/courseVideo/page.js


+ 2 - 2
front/project/h5/routes/product/courseSingle/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/product/course/single',
-  key: 'product-course-single',
+  path: '/product/course/vs/:id',
+  key: 'product-course-vs',
   title: '1vs1课程',
   needLogin: false,
   component() {

+ 3 - 0
front/project/h5/routes/product/courseVs/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#product-course-vs {}

front/project/h5/routes/product/courseSingle/page.js → front/project/h5/routes/product/courseVs/page.js


+ 3 - 3
front/project/h5/routes/product/data/index.js

@@ -1,7 +1,7 @@
 export default {
-  path: '/product/bought',
-  key: 'product-bought',
-  title: '已购买',
+  path: '/product/data',
+  key: 'product-data',
+  title: '全部资料',
   needLogin: false,
   component() {
     return import('./page');

+ 126 - 1
front/project/h5/routes/product/data/index.less

@@ -1,3 +1,128 @@
 @charset "utf-8";
 
-#product-data {}
+#product-data {
+  .top {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    border-bottom: 1px solid #eee;
+    padding: 10px 15px;
+    line-height: 24px;
+    color: #686872;
+
+    .tab {
+      display: inline-block;
+      padding: 0 15px;
+    }
+
+    .tab.active {
+      color: #41A6F3;
+    }
+
+    .right {
+      float: right;
+      border-left: 1px solid #eee;
+      padding: 0 10px;
+
+      .assets {
+        margin-left: 5px;
+        width: 20px;
+        height: 20px;
+      }
+    }
+  }
+
+  .list {
+    padding: 15px;
+    padding-top: 59px;
+  }
+
+  .am-drawer {
+    .am-drawer-sidebar {
+      width: 240px;
+      background: #fff;
+
+      .filter {
+        .body {
+
+          .item {
+            line-height: 30px;
+            padding: 15px;
+            border-bottom: 1px solid #eee;
+
+            .label {
+              color: #303036;
+              font-size: 12px;
+            }
+
+            .value {
+              border-bottom: 1px solid #eee;
+              padding-bottom: 10px;
+
+              .g-special-radio-wrapper {
+                margin: 0 4px;
+              }
+            }
+
+            .left {
+              display: inline-block;
+            }
+
+            .right {
+              float: right;
+              text-align: right;
+              border: none;
+              padding: 0;
+
+              .g-checkbox-wrapper {
+                padding-top: 3px;
+              }
+
+              .g-special-radio-wrapper {
+                margin: 0 5px;
+              }
+            }
+
+            .arrow {
+              width: 15px;
+              height: 15px;
+            }
+          }
+
+          .sub {
+            padding: 0 15px;
+            border-bottom: 1px solid #eee;
+
+            .item {
+              padding: 10px 0;
+
+              .label {
+                color: #686872;
+              }
+
+              .value {
+                border: none;
+                padding: 0;
+              }
+            }
+
+            .title {
+              color: #303036;
+              font-size: 12px;
+              line-height: 40px;
+            }
+          }
+        }
+
+        .footer {
+          position: absolute;
+          bottom: 20px;
+          left: 0;
+          right: 0;
+          text-align: center;
+        }
+      }
+    }
+  }
+}

+ 159 - 1
front/project/h5/routes/product/data/page.js

@@ -1,12 +1,170 @@
 import React from 'react';
 import './index.less';
+import { Drawer } from 'antd-mobile';
 import Page from '@src/containers/Page';
+import Assets from '@src/components/Assets';
+import { formatTreeData } from '@src/services/Tools';
+import { DataBlock } from '../../../components/Block';
+import { SpecialRadioGroup } from '../../../components/Radio';
+import Switch from '../../../components/Switch';
+import Button from '../../../components/Button';
+import Checkbox from '../../../components/CheckBox';
+import { Course } from '../../../stores/course';
+import { Main } from '../../../stores/main';
+import { DataType } from '../../../../Constant';
 
 export default class extends Page {
   init() {
+    Main.dataStruct().then((list) => {
+      const structTree = formatTreeData(list.map(row => {
+        row.title = `${row.titleZh} ${row.titleEn}`;
+        row.label = row.title;
+        row.key = row.id;
+        return row;
+      }), 'id', 'title', 'parentId');
+      this.setState({ structTree });
+    });
+
+    const { search } = this.state;
+    if (!search.order) search.order = 'saleNumber';
+    this.setState({ search });
+  }
+
+  initData() {
+    Course.listData(this.state.search).then(result => {
+      this.setTableData(result.list, result.total);
+    });
+  }
+
+  changeOrder(order, direction) {
+    const { search = {} } = this.state;
+    search.order = order;
+    search.direction = direction;
+
+    this.setState({ search });
+    this.refresh();
+  }
+
+  changeNovice() {
+    const { search = {} } = this.state;
+    search.isNovice = !search.isNovice;
+
+    this.setState({ search });
+  }
+
+  changeOriginal() {
+    const { search = {} } = this.state;
+    search.isOriginal = !search.isOriginal;
+
+    this.setState({ search });
+  }
+
+  changeDataType(value) {
+    const { search = {} } = this.state;
+    search.dataType = value;
+
+    this.setState({ search });
+  }
+
+  changeStruct(value) {
+    const { search = {} } = this.state;
+    search.structId = value;
+    this.setState({ search });
   }
 
   renderView() {
-    return <div />;
+    const { filter, list = [], search = {} } = this.state;
+    return (<Drawer
+      style={{ minHeight: document.documentElement.clientHeight }}
+      position="right"
+      open={filter}
+      sidebar={this.renderFilter()}
+      onOpenChange={isOpen => this.setState({ filter: isOpen })}
+    >
+      <div className="top">
+        <div className={search.order === 'saleNumber' ? 'tab active' : 'tab'} onClick={() => {
+          if (search.order !== 'saleNumber') this.changeOrder('saleNumber', 'desc');
+        }}>销量</div>
+        <div className={search.order === 'updateTime' ? 'tab active' : 'tab'} onClick={() => {
+          if (search.order !== 'updateTime') this.changeOrder('updateTime', 'desc');
+        }}>更新时间</div>
+        <div className="right" onClick={() => this.setState({ filter: true })}>
+          筛选
+            <Assets name="screen" />
+        </div>
+      </div>
+      <div className="list">
+        {list.map(row => <DataBlock data={row} />)}
+      </div>
+    </Drawer>
+    );
+  }
+
+  renderFilter() {
+    const { search, structTree = [] } = this.state;
+    return (
+      <div className="filter">
+        <div className="body">
+          <div className="item">
+            <div className="label left">适合新手</div>
+            <div className="value right">
+              <Switch checked={search.isNovice} onClick={() => {
+                this.changeNovice();
+              }} />
+            </div>
+          </div>
+          <div className="item">
+            <div className="label left">原创资料</div>
+            <div className="value right">
+              <Switch checked={search.isOriginal} onClick={() => {
+                this.changeOriginal();
+              }} />
+            </div>
+          </div>
+          <div className="item">
+            <div className="label left">资料形式</div>
+            <div className="value right">
+              <SpecialRadioGroup
+                list={DataType}
+                value={search.dataType}
+                onChange={(value) => {
+                  this.changeDataType(value);
+                }}
+              />
+            </div>
+          </div>
+          <div className="sub">
+            <div className="title">筛选学科</div>
+            {structTree.map((row) => {
+              return <div className="item">
+                <div className={row.children.length > 0 ? 'label' : 'label left'}>{row.title}</div>
+                {row.children.length > 0 && <div className="value">
+                  <SpecialRadioGroup
+                    list={row.children}
+                    value={search.structId}
+                    onChange={(value) => {
+                      this.changeStruct(value);
+                    }}
+                  />
+                </div>}
+                {row.children.length === 0 && <div className="value right">
+                  <Checkbox checked={search.structId === row.id} onClick={() => {
+                    this.changeStruct(row.id);
+                  }} />
+                </div>}
+
+              </div>;
+            })}
+          </div>
+        </div>
+        <div className="footer">
+          <Button radius width={90} onClick={() => {
+            this.refresh();
+          }}>
+            确定
+          </Button>
+        </div>
+      </div>
+    );
   }
 }

+ 8 - 1
front/project/h5/routes/product/dataDetail/page.js

@@ -6,9 +6,16 @@ import Assets from '@src/components/Assets';
 import Money from '../../../components/Money';
 import Button from '../../../components/Button';
 import Tag from '../../../components/Tag';
+import { Course } from '../../../stores/course';
 
 export default class extends Page {
-  init() {}
+  initData() {
+    const { id } = this.state;
+    Course.getData(id)
+      .then((data) => {
+        this.setState({ data });
+      });
+  }
 
   renderView() {
     return (

+ 0 - 9
front/project/h5/routes/product/list/index.js

@@ -1,9 +0,0 @@
-export default {
-  path: '/product/list',
-  key: 'product-list',
-  title: '全部资料',
-  needLogin: false,
-  component() {
-    return import('./page');
-  },
-};

+ 0 - 124
front/project/h5/routes/product/list/index.less

@@ -1,124 +0,0 @@
-@charset "utf-8";
-
-#product-list {
-  .top {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    border-bottom: 1px solid #eee;
-    padding: 10px 15px;
-    line-height: 24px;
-    color: #686872;
-
-    .tab {
-      display: inline-block;
-      padding: 0 15px;
-    }
-
-    .tab.active {
-      color: #41A6F3;
-    }
-
-    .right {
-      float: right;
-      border-left: 1px solid #eee;
-      padding: 0 10px;
-
-      .assets {
-        margin-left: 5px;
-        width: 20px;
-        height: 20px;
-      }
-    }
-  }
-
-  .list {
-    padding: 15px;
-    padding-top: 59px;
-  }
-
-  .am-drawer {
-    .am-drawer-sidebar {
-      width: 240px;
-      background: #fff;
-
-      .filter {
-        .body {
-
-          .item {
-            line-height: 30px;
-            padding: 15px;
-            border-bottom: 1px solid #eee;
-
-            .label {
-              color: #303036;
-              font-size: 12px;
-            }
-
-            .value {
-              border-bottom: 1px solid #eee;
-              padding-bottom: 10px;
-            }
-
-            .left {
-              display: inline-block;
-            }
-
-            .right {
-              float: right;
-              text-align: right;
-              border: none;
-              padding: 0;
-
-              .g-checkbox-wrapper {
-                padding-top: 3px;
-              }
-
-              .g-special-radio-wrapper {
-                margin: 0 5px;
-              }
-            }
-
-            .arrow {
-              width: 15px;
-              height: 15px;
-            }
-          }
-
-          .sub {
-            padding: 0 15px;
-            border-bottom: 1px solid #eee;
-
-            .item {
-              padding: 10px 0;
-
-              .label {
-                color: #686872;
-              }
-
-              .value {
-                border: none;
-                padding: 0;
-              }
-            }
-
-            .title {
-              color: #303036;
-              font-size: 12px;
-              line-height: 40px;
-            }
-          }
-        }
-
-        .footer {
-          position: absolute;
-          bottom: 20px;
-          left: 0;
-          right: 0;
-          text-align: center;
-        }
-      }
-    }
-  }
-}

+ 0 - 101
front/project/h5/routes/product/list/page.js

@@ -1,101 +0,0 @@
-import React from 'react';
-import './index.less';
-import { Drawer } from 'antd-mobile';
-import Page from '@src/containers/Page';
-import Assets from '@src/components/Assets';
-import { DataBlock } from '../../../components/Block';
-import { SpecialRadioGroup } from '../../../components/Radio';
-import Switch from '../../../components/Switch';
-import Button from '../../../components/Button';
-import Checkbox from '../../../components/CheckBox';
-
-export default class extends Page {
-  init() {}
-
-  renderView() {
-    const { filter } = this.state;
-    return (
-      <Drawer
-        style={{ minHeight: document.documentElement.clientHeight }}
-        position="right"
-        open={filter}
-        sidebar={this.renderFilter()}
-        onOpenChange={isOpen => this.setState({ filter: isOpen })}
-      >
-        <div className="top">
-          <div className="tab active">销量</div>
-          <div className="tab">更新时间</div>
-          <div className="right" onClick={() => this.setState({ filter: true })}>
-            筛选
-            <Assets name="screen" />
-          </div>
-        </div>
-        <div className="list">
-          <DataBlock />
-          <DataBlock />
-          <DataBlock />
-          <DataBlock />
-          <DataBlock />
-          <DataBlock />
-          <DataBlock />
-        </div>
-      </Drawer>
-    );
-  }
-
-  renderFilter() {
-    return (
-      <div className="filter">
-        <div className="body">
-          <div className="item">
-            <div className="label left">适合新手</div>
-            <div className="value right">
-              <Switch />
-            </div>
-          </div>
-          <div className="item">
-            <div className="label left">原创资料</div>
-            <div className="value right">
-              <Switch />
-            </div>
-          </div>
-          <div className="item">
-            <div className="label left">资料形式</div>
-            <div className="value right">
-              <SpecialRadioGroup list={[{ key: '1', label: '纸质' }, { key: '2', label: '电子' }]} />
-            </div>
-          </div>
-          <div className="sub">
-            <div className="title">筛选学科</div>
-            <div className="item">
-              <div className="label left">长难句</div>
-              <div className="value right">
-                <Checkbox />
-              </div>
-            </div>
-            <div className="item">
-              <div className="label">语文 Verbal</div>
-              <div className="value">
-                <SpecialRadioGroup
-                  list={[
-                    { key: '1', label: '语法 SC' },
-                    { key: '2', label: '阅读 RC' },
-                    { key: '3', label: '逻辑 CR' },
-                  ]}
-                />
-              </div>
-            </div>
-            <div className="item">
-              <div className="label">数学 Quant</div>
-            </div>
-          </div>
-        </div>
-        <div className="footer">
-          <Button radius width={90}>
-            确定
-          </Button>
-        </div>
-      </div>
-    );
-  }
-}

+ 20 - 1
front/project/h5/routes/product/main/index.less

@@ -1,12 +1,31 @@
 @charset "utf-8";
 
 #product {
-  padding: 0 15px;
+  height: 100%;
+  position: relative;
+  height: 100%;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+
+  .ant-anchor-link {
+    padding: 0;
+  }
 
   .am-tabs {
     margin-bottom: 15px;
   }
 
+  .list {
+    padding: 0px 15px 20px 15px;
+    overflow: hidden;
+    flex: 1;
+
+    .body {
+      overflow-y: auto;
+    }
+  }
+
   .title {
     font-size: 16px;
     color: #1A1A1F;

+ 80 - 33
front/project/h5/routes/product/main/page.js

@@ -1,58 +1,105 @@
 import React from 'react';
 import './index.less';
+import { Anchor } from 'antd';
 import { Tabs } from 'antd-mobile';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
+import { getMap } from '@src/services/Tools';
 import Money from '../../../components/Money';
 import { LinkBlock, CourseBlock, DataBlock } from '../../../components/Block';
+import { Main } from '../../../stores/main';
+import { Course } from '../../../stores/course';
+import { ServiceKey, CourseVsType } from '../../../../Constant';
 
 export default class extends Page {
-  init() { }
+  init() {
+    this.courseVsMap = {};
+  }
+
+  initData() {
+    Promise.all(ServiceKey.map(service => {
+      return Main.getService(service).then(result => {
+        this.setState({ [service]: result });
+      });
+    }));
+    Course.allVs().then(list => {
+      this.courseVsMap = getMap(list, 'vsType');
+      this.setState({ vsList: list });
+    });
+    Course.listVideo({ page: 1, size: 3 })
+      .then(result => {
+        this.setState({ courseTop: result.list });
+      });
+    Course.listData({ page: 1, size: 3 })
+      .then((result) => {
+        this.setState({ dataTop: result.list });
+      });
+  }
 
   renderView() {
-    return (
-      <div>
-        <Tabs
-          tabs={[
-            { title: '服务', key: 'service' },
-            { title: '1V1私教', key: 'vs' },
-            { title: '在线课程', key: 'video' },
-            { title: '资料', key: 'data' },
-          ]}
-        />
+    const { courseTop = [], dataTop = [] } = this.state;
+    return [<Anchor>
+      <Tabs
+        tabs={[
+          { title: '服务', key: 'service' },
+          { title: '1V1私教', key: 'vs' },
+          { title: '在线课程', key: 'video' },
+          { title: '资料', key: 'data' },
+        ]}
+        renderTab={(tab) => {
+          return <Anchor.Link href={`#${tab.key}`} title={tab.title} />;
+        }}
+      />
+    </Anchor>, <div className="list">
+      <div className="body">
+        <a name="service" />
         <div className="title">服务</div>
         <div className="service">
-          <div className="service-item">
-            <div className="service-item-t">123342</div>
-            <Money size="small" value="100" />
-            <Assets name="VIP" />
-          </div>
-          <div className="service-item">
-            <div className="service-item-t">123342</div>
-            <Money size="small" value="100" />
-            <Assets name="CAT" />
-          </div>
-          <div className="service-item">
-            <div className="service-item-t">123342</div>
-            <Money size="small" value="100" />
-            <Assets name="JiJing" />
-          </div>
+          {ServiceKey.map(service => {
+            const s = this.state[service.value] || {};
+            const money = s.package ? s.package[0].price : 0;
+            return <div className="service-item" onClick={() => {
+              linkTo(`/product/service/${service.value}`);
+            }}>
+              <div className="service-item-t">{service.label}</div>
+              <Money size="small" value={money} />
+              <Assets name={service.value} />
+            </div>;
+          })}
         </div>
+        <a name="vs" />
         <div className="title">1V1私教</div>
         <Assets name="banner" className="banner" />
-        <LinkBlock title="新手辅导" sub="GMAT 全面了解,定制学习计划" />
+        {CourseVsType.map((t, index) => {
+          const course = this.courseVsMap[t.value] || {};
+          return <LinkBlock title={course.title} sub={course.comment} theme={index % 2 > 0 ? 'not' : ''} onClick={() => {
+            linkTo(`/product/course/vs/${course.id}`);
+          }} />;
+        })}
+        {/* <LinkBlock title="新手辅导" sub="GMAT 全面了解,定制学习计划" />
         <LinkBlock title="诊断辅导" sub="复习效果不理想,制定突破计划" theme="not" />
         <LinkBlock title="系统授课" sub="全面知识体系讲解,提升实战能力" />
-        <LinkBlock title="答疑课" sub="一对一解答疑问,破解所有疑团" theme="not" />
+        <LinkBlock title="答疑课" sub="一对一解答疑问,破解所有疑团" theme="not" /> */}
+        <a name="video" />
         <div className="title">在线课程</div>
         <Assets name="banner" className="banner" />
-        <CourseBlock />
-        <div className="more t-r m-b-2">全部课程 ></div>
+        {courseTop.map(row => {
+          return <CourseBlock data={row} />;
+        })}
+        <div className="more t-r m-b-2" onClick={() => {
+          linkTo('/product/course/video');
+        }} >全部课程 ></div>
+        <a name="data" />
         <div className="title">资料</div>
         <Assets name="banner" className="banner" />
-        <DataBlock />
-        <div className="more t-r m-b-2">全部资料 ></div>
+        {dataTop.map(row => {
+          return <DataBlock data={row} />;
+        })}
+        <div className="more t-r m-b-2" onClick={() => {
+          linkTo('/product/data');
+        }}>全部资料 ></div>
       </div>
-    );
+    </div>,
+    ];
   }
 }

+ 1 - 1
front/project/h5/routes/product/serviceDetail/index.js

@@ -1,5 +1,5 @@
 export default {
-  path: '/product/service/:id',
+  path: '/product/service/:service',
   key: 'product-service-detail',
   title: '服务详情',
   needLogin: false,

+ 44 - 8
front/project/h5/routes/product/serviceDetail/page.js

@@ -4,27 +4,63 @@ import Page from '@src/containers/Page';
 import Money from '../../../components/Money';
 import Button from '../../../components/Button';
 import { SpecialRadioGroup } from '../../../components/Radio';
+import { Main } from '../../../stores/main';
 
 export default class extends Page {
-  init() {}
+  initState() {
+    return {
+      index: 0,
+    };
+  }
+
+  initData() {
+    const { service } = this.params;
+    Main.getService(service)
+      .then(result => {
+        result.package = (result.package || []).map((row, index) => {
+          row.label = row.title;
+          row.value = index;
+          return row;
+        });
+        this.setState({ data: result });
+      });
+  }
 
+  buy() {
+    const { data, index } = this.state;
+    const item = data.package[index] || {};
+    console.log(item);
+  }
+
+  //   expire_info: '',
   renderView() {
+    const { data = {}, index } = this.state;
+    const item = data.package[index] || {};
     return (
       <div>
-        <div className="b-g" style={{ backgroundImage: 'url(/assets/p_bg.png)' }}>
-          <div className="title">OG20整合刷题-语法SC</div>
+        <div className="b-g" style={{ backgroundImage: `url(${data.image})` }}>
+          <div className="title">{item.title}</div>
         </div>
         <div className="detail">
-          <div className="title">可选套餐</div>
-          <SpecialRadioGroup list={[{ key: '1', label: 1 }, { key: '2', label: 2 }]} value="1" onChange={() => {}} />
-          <div className="division" />
+          {data.package.length > 1 && <div className="title">可选套餐</div>}
+
+          {data.package.length > 1 && <SpecialRadioGroup list={data.package} value={index} onChange={(value) => this.setState({ index: value })} />}
+          {data.package.length > 1 && <div className="division" />}
           <div className="title">服务介绍</div>
+          <h2>服务</h2>
+          <p>{item.description} {item.expire_info}</p>
+          <h2>退款政策</h2>
+          <p>{item.refund_policy}</p>
+          <h2>版权说明</h2>
+          <p>{item.copyright_nnotes}</p>
         </div>
         <div className="fixed">
           <div className="fee">
-            总额: <Money value="1200" size="lager" />
+            总额: <Money value={item.price} size="lager" />
           </div>
-          <Button width={110} className="f-r" radius>
+          <Button width={110} className="f-r" radius onClick={() => {
+            this.buy();
+          }}>
             立即购买
           </Button>
         </div>

+ 1 - 1
front/project/h5/routes/textbook/detail/index.js

@@ -1,5 +1,5 @@
 export default {
-  path: '/textbook/detail',
+  path: '/textbook/detail/:subject',
   key: 'textbook-detail',
   title: '机经详情',
   needLogin: false,

+ 29 - 5
front/project/h5/routes/textbook/detail/index.less

@@ -92,12 +92,26 @@
         margin-left: 5px;
         width: 15px;
         height: 15px;
-        margin-top: 14px;
       }
 
-      .ant-list-item {
+      .am-list-item {
         padding: 0;
+        width: 100%;
+
+        .am-list-extra {
+          display: none;
+        }
+
+        .am-list-line {
+          padding: 0;
+
+          .am-list-content {
+            font-size: 12px;
+            padding: 0px;
+          }
+        }
       }
+
     }
   }
 
@@ -156,17 +170,27 @@
             .arrow {
               width: 15px;
               height: 15px;
-              margin-top: 8px;
             }
           }
         }
 
-        .ant-list-item {
+        .am-list-item {
           padding: 0px;
 
-          .ant-list-item-extra {
+          .am-list-extra {
             display: none;
           }
+
+          .am-list-line {
+            padding: 0;
+            line-height: 30px;
+            height: 30px;
+
+            .am-list-content {
+              font-size: 12px;
+              padding: 0px;
+            }
+          }
         }
 
         .footer {

+ 15 - 9
front/project/h5/routes/textbook/detail/page.js

@@ -1,16 +1,18 @@
 import React, { Component } from 'react';
 import './index.less';
-import { Drawer, Picker } from 'antd-mobile';
+import { Drawer, Picker, List } from 'antd-mobile';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
-import { List } from 'antd';
+import { getMap, formatDate } from '@src/services/Tools';
 import Switch from '../../../components/Switch';
 import Icon from '../../../components/Icon';
 import { SpecialRadioGroup } from '../../../components/Radio';
 import Button from '../../../components/Button';
-import { TextbookQuality } from '../../../../Constant';
+import { TextbookQuality, TextbookSubject } from '../../../../Constant';
 import { Textbook } from '../../../stores/textbook';
 
+const TextbookSubjectMap = getMap(TextbookSubject, 'value', 'label');
+
 class Detail extends Component {
   constructor(props) {
     super(props);
@@ -42,13 +44,17 @@ export default class extends Page {
     const { search } = this.state;
     search.isOld = false;
     search.qualitys = [];
-    search.order = '';
     this.setState({ search });
   }
 
   initData() {
+    Textbook.getInfo()
+      .then(result => {
+        this.setState(result);
+      });
+    const { subject } = this.params;
     this.setState({ filter: false });
-    Textbook.listTopic(Object.assign({ latest: true }, this.state.search))
+    Textbook.listTopic(Object.assign({ latest: true, subject, order: 'updateTime' }, this.state.search))
       .then(result => {
         this.setTableData(result.list, result.total);
         const pageData = [];
@@ -123,7 +129,8 @@ export default class extends Page {
   }
 
   renderView() {
-    const { filter, search, pageData } = this.state;
+    const { subject } = this.params;
+    const { filter, search, pageData, list, latest = {} } = this.state;
     return (
       <Drawer
         style={{ minHeight: document.documentElement.clientHeight }}
@@ -132,10 +139,9 @@ export default class extends Page {
         sidebar={this.renderFilter()}
         onOpenChange={isOpen => this.setState({ filter: isOpen })}
       >
-        <div className="title">【逻辑】0515 起逻辑机经整理</div>
+        <div className="title">【{TextbookSubjectMap[subject]}】{latest.startDate ? formatDate(latest.startDate, 'MMDD') : ''} 起{TextbookSubjectMap[subject]}机经整理</div>
         <div className="detail-list">
-          <Detail />
-          <Detail />
+          {list.map(row => <Detail data={row} />)}
         </div>
         <div className="fixed">
           <div className="prev" onClick={() => {

+ 19 - 4
front/project/h5/routes/textbook/library/page.js

@@ -24,17 +24,32 @@ export default class extends Page {
   refreshYear(date) {
     this.setState({ date });
     Textbook.listYear(date.getFullYear())
-      .then(() => {
-        this.setState({ months: [] });
+      .then((list) => {
+        const map = {};
+        list.forEach((row) => {
+          const d = new Date(row.startDate);
+          const month = d.getMonth() + 1;
+          if (!map[month]) {
+            map[month] = [];
+          }
+          map[month].push(d.getDate());
+        });
+        const months = Object.keys(map).map(key => {
+          return {
+            value: key,
+            list: map[key],
+          };
+        });
+        this.setState({ months });
       });
   }
 
   renderView() {
-    const { library = {}, date, months = [] } = this.state;
+    const { latest = {}, date, months = [] } = this.state;
     return (
       <div>
         <div className="title">最新换库</div>
-        <div className="main">{library.startDate ? formatDate(library.startDate, 'YYYY年MM月DD日') : ''}</div>
+        <div className="main">{latest.startDate ? formatDate(latest.startDate, 'YYYY年MM月DD日') : ''}</div>
         <div className="title">
           历史记录
           <div className="date">

+ 3 - 3
front/project/h5/routes/textbook/main/page.js

@@ -24,7 +24,7 @@ export default class extends Page {
   initData() {
     Textbook.getInfo()
       .then(result => {
-        result.day = parseInt((new Date().getTime() - new Date(result.library.startDate).getTime()) / 86400000, 10);
+        result.day = parseInt((new Date().getTime() - new Date(result.latest.startDate).getTime()) / 86400000, 10);
         result.hasService = true;
         this.setState(result);
       });
@@ -39,10 +39,10 @@ export default class extends Page {
   }
 
   renderView() {
-    const { library = {}, hasService, tab, day } = this.state;
+    const { latest = {}, hasService, tab, day } = this.state;
     return (
       <div>
-        <div className="tip">最近换库时间:{library.startDate ? formatDate(library.startDate, 'YYYY-MM-DD') : ''},已换库{day}天。</div>
+        <div className="tip">最近换库时间:{latest.startDate ? formatDate(latest.startDate, 'YYYY-MM-DD') : ''},已换库{day}天。</div>
         <Tabs page={tab.key} tabs={TextbookSubjectTabs} onChange={(v) => {
           linkTo('/textbook/detail');
           this.refreshTab(v);

+ 24 - 0
front/project/h5/stores/course.js

@@ -0,0 +1,24 @@
+import BaseStore from '@src/stores/base';
+
+export default class CourseStore extends BaseStore {
+  /**
+   * 所有vs课程
+   */
+  allVs() {
+    return this.apiGet('/course/vs');
+  }
+
+  listVideo(params) {
+    return this.apiGet('/course/video/list', params);
+  }
+
+  listPackage(params) {
+    return this.apiGet('/course/package/list', params);
+  }
+
+  listData(params) {
+    return this.apiGet('/course/data/list', params);
+  }
+}
+
+export const Course = new CourseStore({ key: 'course' });

+ 2 - 1
front/project/h5/stores/index.js

@@ -2,6 +2,7 @@ import { User } from './user';
 import { Common } from './common';
 import { Main } from './main';
 import { Textbook } from './textbook';
+import { Course } from './course';
 import { My } from './my';
 
-export default [User, Common, Main, Textbook, My];
+export default [User, Common, Main, Textbook, Course, My];

+ 23 - 3
front/project/h5/stores/main.js

@@ -1,6 +1,18 @@
 import BaseStore from '@src/stores/base';
 
 export default class MainStore extends BaseStore {
+  courseStruct() {
+    return this.getExercise().then((result) => {
+      return result.filter(row => row.isCourse);
+    });
+  }
+
+  dataStruct() {
+    return this.getExercise().then((result) => {
+      return result.filter(row => row.isData);
+    });
+  }
+
   /**
    * 获取首页配置
    */
@@ -36,7 +48,7 @@ export default class MainStore extends BaseStore {
   getExercise() {
     return this.getApiCache('API:main:getExercise', () => {
       return this.apiGet('/base/exercise/main');
-    });
+    }, 3600);
   }
 
   /**
@@ -62,7 +74,7 @@ export default class MainStore extends BaseStore {
   getExamination() {
     return this.getApiCache('API:main:getExamination', () => {
       return this.apiGet('/base/examination/main');
-    });
+    }, 3600);
   }
 
   /**
@@ -88,7 +100,15 @@ export default class MainStore extends BaseStore {
   getExaminationNumber() {
     return this.getApiCache('API:main:getExaminationNumber', () => {
       return this.apiGet('/base/examination/number');
-    });
+    }, 3600);
+  }
+
+  /**
+   * 获取服务信息
+   * @param {*} service
+   */
+  getService(service) {
+    return this.apiGet('/base/service', { service });
   }
 }
 

+ 1 - 1
front/project/h5/stores/my.js

@@ -48,7 +48,7 @@ export default class MyStore extends BaseStore {
    * @param {*} type
    * @param {*} read
    */
-  message(page, size, type, read) {
+  message({ page, size, type, read }) {
     return this.apiGet('/my/message', { page, size, type, read });
   }
 

+ 3 - 3
front/project/www/stores/main.js

@@ -36,7 +36,7 @@ export default class MainStore extends BaseStore {
   getExercise() {
     return this.getApiCache('API:main:getExercise', () => {
       return this.apiGet('/base/exercise/main');
-    });
+    }, 3600);
   }
 
   /**
@@ -62,7 +62,7 @@ export default class MainStore extends BaseStore {
   getExamination() {
     return this.getApiCache('API:main:getExamination', () => {
       return this.apiGet('/base/examination/main');
-    });
+    }, 3600);
   }
 
   /**
@@ -88,7 +88,7 @@ export default class MainStore extends BaseStore {
   getExaminationNumber() {
     return this.getApiCache('API:main:getExaminationNumber', () => {
       return this.apiGet('/base/examination/number');
-    });
+    }, 3600);
   }
 
   listFaq(page, size, channel, position) {

+ 3 - 2
front/src/stores/base.js

@@ -137,11 +137,12 @@ export default class BaseStore {
     this.Cache.removeCache(this.getCacheKey(key), true);
   }
 
-  getApiCache(key, api) {
+  getApiCache(key, api, expireTime = 0) {
     const result = this.Cache.getCache(key);
     if (result) return Promise.resolve(result);
     return api().then(data => {
-      this.Cache.setCache(key, data);
+      this.Cache.setCache(key, data, expireTime);
+      return data;
     });
   }
 

+ 70 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/Course.java

@@ -55,6 +55,12 @@ public class Course implements Serializable {
     private String title;
 
     /**
+     * 评价推荐
+     */
+    @Column(name = "`comment`")
+    private String comment;
+
+    /**
      * 适合人群
      */
     @Column(name = "`crowd`")
@@ -73,6 +79,12 @@ public class Course implements Serializable {
     private String teacher;
 
     /**
+     * 课程封面
+     */
+    @Column(name = "`cover`")
+    private String cover;
+
+    /**
      * 最小购买数量
      */
     @Column(name = "`min_number`")
@@ -341,6 +353,24 @@ public class Course implements Serializable {
     }
 
     /**
+     * 获取评价推荐
+     *
+     * @return comment - 评价推荐
+     */
+    public String getComment() {
+        return comment;
+    }
+
+    /**
+     * 设置评价推荐
+     *
+     * @param comment 评价推荐
+     */
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    /**
      * 获取适合人群
      *
      * @return crowd - 适合人群
@@ -395,6 +425,24 @@ public class Course implements Serializable {
     }
 
     /**
+     * 获取课程封面
+     *
+     * @return cover - 课程封面
+     */
+    public String getCover() {
+        return cover;
+    }
+
+    /**
+     * 设置课程封面
+     *
+     * @param cover 课程封面
+     */
+    public void setCover(String cover) {
+        this.cover = cover;
+    }
+
+    /**
      * 获取最小购买数量
      *
      * @return min_number - 最小购买数量
@@ -796,9 +844,11 @@ public class Course implements Serializable {
         sb.append(", videoType=").append(videoType);
         sb.append(", extend=").append(extend);
         sb.append(", title=").append(title);
+        sb.append(", comment=").append(comment);
         sb.append(", crowd=").append(crowd);
         sb.append(", price=").append(price);
         sb.append(", teacher=").append(teacher);
+        sb.append(", cover=").append(cover);
         sb.append(", minNumber=").append(minNumber);
         sb.append(", maxNumber=").append(maxNumber);
         sb.append(", expireDays=").append(expireDays);
@@ -915,6 +965,16 @@ public class Course implements Serializable {
         }
 
         /**
+         * 设置评价推荐
+         *
+         * @param comment 评价推荐
+         */
+        public Builder comment(String comment) {
+            obj.setComment(comment);
+            return this;
+        }
+
+        /**
          * 设置适合人群
          *
          * @param crowd 适合人群
@@ -965,6 +1025,16 @@ public class Course implements Serializable {
         }
 
         /**
+         * 设置课程封面
+         *
+         * @param cover 课程封面
+         */
+        public Builder cover(String cover) {
+            obj.setCover(cover);
+            return this;
+        }
+
+        /**
          * 设置最小购买数量
          *
          * @param minNumber 最小购买数量

+ 35 - 0
server/data/src/main/java/com/qxgmat/data/dao/entity/CourseData.java

@@ -19,6 +19,12 @@ public class CourseData implements Serializable {
     private String title;
 
     /**
+     * 评价推荐
+     */
+    @Column(name = "`comment`")
+    private String comment;
+
+    /**
      * 学科:节点id
      */
     @Column(name = "`struct_id`")
@@ -173,6 +179,24 @@ public class CourseData implements Serializable {
     }
 
     /**
+     * 获取评价推荐
+     *
+     * @return comment - 评价推荐
+     */
+    public String getComment() {
+        return comment;
+    }
+
+    /**
+     * 设置评价推荐
+     *
+     * @param comment 评价推荐
+     */
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    /**
      * 获取学科:节点id
      *
      * @return struct_id - 学科:节点id
@@ -550,6 +574,7 @@ public class CourseData implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", title=").append(title);
+        sb.append(", comment=").append(comment);
         sb.append(", structId=").append(structId);
         sb.append(", parentStructId=").append(parentStructId);
         sb.append(", dataType=").append(dataType);
@@ -605,6 +630,16 @@ public class CourseData implements Serializable {
         }
 
         /**
+         * 设置评价推荐
+         *
+         * @param comment 评价推荐
+         */
+        public Builder comment(String comment) {
+            obj.setComment(comment);
+            return this;
+        }
+
+        /**
          * 设置学科:节点id
          *
          * @param structId 学科:节点id

+ 16 - 16
server/data/src/main/java/com/qxgmat/data/dao/entity/TextbookTopic.java

@@ -18,10 +18,10 @@ public class TextbookTopic implements Serializable {
     private Integer libraryId;
 
     /**
-     * 单项-题型
+     * 学科
      */
-    @Column(name = "`question_type`")
-    private String questionType;
+    @Column(name = "`question_subject`")
+    private String questionSubject;
 
     /**
      * 题目序号
@@ -100,21 +100,21 @@ public class TextbookTopic implements Serializable {
     }
 
     /**
-     * 获取单项-题型
+     * 获取学科
      *
-     * @return question_type - 单项-题型
+     * @return question_subject - 学科
      */
-    public String getQuestionType() {
-        return questionType;
+    public String getQuestionSubject() {
+        return questionSubject;
     }
 
     /**
-     * 设置单项-题型
+     * 设置学科
      *
-     * @param questionType 单项-题型
+     * @param questionSubject 学科
      */
-    public void setQuestionType(String questionType) {
-        this.questionType = questionType;
+    public void setQuestionSubject(String questionSubject) {
+        this.questionSubject = questionSubject;
     }
 
     /**
@@ -261,7 +261,7 @@ public class TextbookTopic implements Serializable {
         sb.append("Hash = ").append(hashCode());
         sb.append(", id=").append(id);
         sb.append(", libraryId=").append(libraryId);
-        sb.append(", questionType=").append(questionType);
+        sb.append(", questionSubject=").append(questionSubject);
         sb.append(", no=").append(no);
         sb.append(", keyword=").append(keyword);
         sb.append(", quality=").append(quality);
@@ -304,12 +304,12 @@ public class TextbookTopic implements Serializable {
         }
 
         /**
-         * 设置单项-题型
+         * 设置学科
          *
-         * @param questionType 单项-题型
+         * @param questionSubject 学科
          */
-        public Builder questionType(String questionType) {
-            obj.setQuestionType(questionType);
+        public Builder questionSubject(String questionSubject) {
+            obj.setQuestionSubject(questionSubject);
             return this;
         }
 

+ 4 - 3
server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseDataMapper.xml

@@ -7,6 +7,7 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="comment" jdbcType="VARCHAR" property="comment" />
     <result column="struct_id" jdbcType="INTEGER" property="structId" />
     <result column="parent_struct_id" jdbcType="INTEGER" property="parentStructId" />
     <result column="data_type" jdbcType="VARCHAR" property="dataType" />
@@ -38,9 +39,9 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `title`, `struct_id`, `parent_struct_id`, `data_type`, `is_novice`, `is_original`, 
-    `price`, `pages`, `link`, `cover`, `resource`, `trail_resource`, `trail_start`, `trail_end`, 
-    `view_number`, `sale_number`, `create_time`, `update_time`
+    `id`, `title`, `comment`, `struct_id`, `parent_struct_id`, `data_type`, `is_novice`, 
+    `is_original`, `price`, `pages`, `link`, `cover`, `resource`, `trail_resource`, `trail_start`, 
+    `trail_end`, `view_number`, `sale_number`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 5 - 3
server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseMapper.xml

@@ -13,9 +13,11 @@
     <result column="video_type" jdbcType="VARCHAR" property="videoType" />
     <result column="extend" jdbcType="VARCHAR" property="extend" />
     <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="comment" jdbcType="VARCHAR" property="comment" />
     <result column="crowd" jdbcType="VARCHAR" property="crowd" />
     <result column="price" jdbcType="DECIMAL" property="price" />
     <result column="teacher" jdbcType="VARCHAR" property="teacher" />
+    <result column="cover" jdbcType="VARCHAR" property="cover" />
     <result column="min_number" jdbcType="INTEGER" property="minNumber" />
     <result column="max_number" jdbcType="INTEGER" property="maxNumber" />
     <result column="expire_days" jdbcType="INTEGER" property="expireDays" />
@@ -49,9 +51,9 @@
       WARNING - @mbg.generated
     -->
     `id`, `struct_id`, `parent_struct_id`, `course_module`, `vs_type`, `video_type`, 
-    `extend`, `title`, `crowd`, `price`, `teacher`, `min_number`, `max_number`, `expire_days`, 
-    `expire_time`, `use_expire_time`, `wechat_avatar`, `trail_number`, `sale_number`, 
-    `package_sale_number`, `create_time`, `update_time`
+    `extend`, `title`, `comment`, `crowd`, `price`, `teacher`, `cover`, `min_number`, 
+    `max_number`, `expire_days`, `expire_time`, `use_expire_time`, `wechat_avatar`, `trail_number`, 
+    `sale_number`, `package_sale_number`, `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 2 - 2
server/data/src/main/java/com/qxgmat/data/dao/mapping/TextbookTopicMapper.xml

@@ -7,7 +7,7 @@
     -->
     <id column="id" jdbcType="INTEGER" property="id" />
     <result column="library_id" jdbcType="INTEGER" property="libraryId" />
-    <result column="question_type" jdbcType="VARCHAR" property="questionType" />
+    <result column="question_subject" jdbcType="VARCHAR" property="questionSubject" />
     <result column="no" jdbcType="INTEGER" property="no" />
     <result column="keyword" jdbcType="VARCHAR" property="keyword" />
     <result column="quality" jdbcType="VARCHAR" property="quality" />
@@ -26,7 +26,7 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `library_id`, `question_type`, `no`, `keyword`, `quality`, `is_old`, `create_time`, 
+    `id`, `library_id`, `question_subject`, `no`, `keyword`, `quality`, `is_old`, `create_time`, 
     `update_time`
   </sql>
   <sql id="Blob_Column_List">

+ 23 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java

@@ -6,6 +6,8 @@ import com.nuliji.tools.PageMessage;
 import com.nuliji.tools.Response;
 import com.nuliji.tools.ResponseHelp;
 import com.nuliji.tools.Transform;
+import com.nuliji.tools.exception.ParameterException;
+import com.qxgmat.data.constants.enums.ServiceKey;
 import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.module.ChannelModule;
 import com.qxgmat.data.dao.entity.*;
@@ -155,6 +157,27 @@ public class BaseController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/service", method = RequestMethod.GET)
+    @ApiOperation(value = "获取服务信息", notes = "获取服务信息", httpMethod = "GET")
+    public Response<JSONObject> service(@RequestParam(required = true) String service)  {
+        SettingKey key;
+        switch(ServiceKey.ValueOf(service)){
+            case TEXTBOOK:
+                key = SettingKey.SERVICE_TEXTBOOK;
+                break;
+            case VIP:
+                key = SettingKey.SERVICE_VIP;
+                break;
+            case QX_CAT:
+                key = SettingKey.SERVICE_QX_CAT;
+                break;
+            default:
+                throw new ParameterException("服务信息错误");
+        }
+        Setting setting = settingService.getByKey(key);
+        return ResponseHelp.success(setting.getValue());
+    }
+
     @RequestMapping(value = "/faq/list", method = RequestMethod.GET)
     @ApiOperation(value = "faq列表", httpMethod = "GET")
     public Response<PageMessage<FaqDto>> listFaq(

+ 79 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -1,8 +1,12 @@
 package com.qxgmat.controller.api;
 
 
+import com.github.pagehelper.Page;
 import com.nuliji.tools.*;
+import com.qxgmat.data.constants.enums.module.CourseModule;
 import com.qxgmat.data.constants.enums.module.ProductType;
+import com.qxgmat.data.constants.enums.status.DirectionStatus;
+import com.qxgmat.data.constants.enums.user.DataType;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.data.relation.entity.UserPreviewPaperRelation;
 import com.qxgmat.dto.extend.UserPreviewPaperExtendDto;
@@ -15,6 +19,7 @@ import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpSession;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -29,6 +34,18 @@ public class CourseController {
     private ShiroHelp shiroHelp;
 
     @Autowired
+    private CourseService courseService;
+
+    @Autowired
+    private CourseDataService courseDataService;
+
+    @Autowired
+    private CoursePackageService coursePackageService;
+
+    @Autowired
+    private CourseTeacherService courseTeacherService;
+
+    @Autowired
     private PreviewService previewService;
 
     @Autowired
@@ -47,6 +64,68 @@ public class CourseController {
     private UserOrderRecordService userOrderRecordService;
 
 
+    @RequestMapping(value = "/vs", method = RequestMethod.GET)
+    @ApiOperation(value = "获取1v1课程信息", notes = "获取1v1课程信息", httpMethod = "GET")
+    public Response<List<CourseListDto>> vs()  {
+        List<Course> p = courseService.all(CourseModule.VS);
+        List<CourseListDto> pr = Transform.convert(p, CourseListDto.class);
+        return ResponseHelp.success(pr);
+    }
+
+//    @RequestMapping(value = "/video/list", method = RequestMethod.GET)
+//    @ApiOperation(value = "在线课程列表", httpMethod = "GET")
+//    public Response<PageMessage<CourseListDto>> listVideo(
+//            @RequestParam(required = false, defaultValue = "1") int page,
+//            @RequestParam(required = false, defaultValue = "100") int size,
+//            @RequestParam(required = true) String subject,
+//            @RequestParam(required = false) String[] qualitys,
+//            @RequestParam(required = false) Boolean isOld,
+//            @RequestParam(required = false, defaultValue = "id") String order,
+//            @RequestParam(required = false, defaultValue = "desc") String direction,
+//            HttpSession session) {
+//
+//        Page<Course> p = courseService.list(page, size, library.getId(), QuestionSubject.ValueOf(subject), qualitys, isOld, order, DirectionStatus.ValueOf(direction));
+//
+//        List<CourseListDto> pr = Transform.convert(p, CourseListDto.class);
+//        return ResponseHelp.success(pr, page, size, p.getTotal());
+//    }
+
+//    @RequestMapping(value = "/package/list", method = RequestMethod.GET)
+//    @ApiOperation(value = "套餐列表", httpMethod = "GET")
+//    public Response<PageMessage<CourseListDto>> listPackage(
+//            @RequestParam(required = false, defaultValue = "1") int page,
+//            @RequestParam(required = false, defaultValue = "100") int size,
+//            @RequestParam(required = true) String subject,
+//            @RequestParam(required = false) String[] qualitys,
+//            @RequestParam(required = false) Boolean isOld,
+//            @RequestParam(required = false, defaultValue = "id") String order,
+//            @RequestParam(required = false, defaultValue = "desc") String direction,
+//            HttpSession session) {
+//
+//        Page<Course> p = courseService.list(page, size, library.getId(), QuestionSubject.ValueOf(subject), qualitys, isOld, order, DirectionStatus.ValueOf(direction));
+//
+//        return ResponseHelp.success(p, page, size, p.getTotal());
+//    }
+
+    @RequestMapping(value = "/data/list", method = RequestMethod.GET)
+    @ApiOperation(value = "资料列表", httpMethod = "GET")
+    public Response<PageMessage<CourseDataListDto>> listData(
+            @RequestParam(required = false, defaultValue = "1") int page,
+            @RequestParam(required = false, defaultValue = "100") int size,
+            @RequestParam(required = false) Integer structId,
+            @RequestParam(required = false) String dataType,
+            @RequestParam(required = false) Boolean isNovice,
+            @RequestParam(required = false) Boolean isOriginal,
+            @RequestParam(required = false, defaultValue = "id") String order,
+            @RequestParam(required = false, defaultValue = "desc") String direction,
+            HttpSession session) {
+
+        Page<CourseData> p = courseDataService.list(page, size, structId, DataType.ValueOf(dataType), isNovice, isOriginal, order, DirectionStatus.ValueOf(direction));
+        List<CourseDataListDto> pr = Transform.convert(p, CourseDataListDto.class);
+
+        return ResponseHelp.success(pr, page, size, p.getTotal());
+    }
+
     @RequestMapping(value = "/progress", method = RequestMethod.GET)
     @ApiOperation(value = "获取课程进度", notes = "获取所有课程及状态进度", httpMethod = "GET")
     public Response<List<UserCourseDetailDto>> progress()  {

+ 7 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java

@@ -71,14 +71,16 @@ public class TextbookController
     @ApiOperation(value = "机经信息", httpMethod = "GET")
     public Response<UserTextbookInfoDto> info(HttpSession session) {
         User user = (User) shiroHelp.getLoginUser();
-
-        TextbookLibrary library = textbookLibraryService.getLatest();
         UserTextbookInfoDto dto = new UserTextbookInfoDto();
-        dto.setLibrary(library);
+
+        TextbookLibrary latest = textbookLibraryService.getLatest();
+        dto.setLatest(latest);
         if (user != null){
             dto.setHasService(userServiceService.hasService(user.getId(), ServiceKey.TEXTBOOK));
             dto.setUnUseRecord(userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK));
         }
+        TextbookLibrary second = textbookLibraryService.getSecond();
+        dto.setSecond(second);
 
         return ResponseHelp.success(dto);
     }
@@ -127,6 +129,7 @@ public class TextbookController
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = true) boolean latest,
+            @RequestParam(required = true) String subject,
             @RequestParam(required = false) String[] qualitys,
             @RequestParam(required = false) Boolean isOld,
             @RequestParam(required = false, defaultValue = "id") String order,
@@ -147,7 +150,7 @@ public class TextbookController
             library = textbookLibraryService.getSecond();
         }
 
-        Page<TextbookTopic> p = textbookTopicService.list(page, size, library.getId(), qualitys, isOld, order, DirectionStatus.ValueOf(direction));
+        Page<TextbookTopic> p = textbookTopicService.list(page, size, library.getId(), QuestionSubject.ValueOf(subject), qualitys, isOld, order, DirectionStatus.ValueOf(direction));
 
         return ResponseHelp.success(p, page, size, p.getTotal());
     }

+ 149 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataListDto.java

@@ -0,0 +1,149 @@
+package com.qxgmat.dto.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.CourseData;
+
+import java.math.BigDecimal;
+
+@Dto(entity = CourseData.class)
+public class CourseDataListDto {
+    private Integer id;
+
+    private String title;
+
+    private String comment;
+
+    private Integer structId;
+
+    private Integer parentStructId;
+
+    private String dataType;
+
+    private Integer isNovice;
+
+    private Integer isOriginal;
+
+    private BigDecimal price;
+
+    private Integer pages;
+
+    private String link;
+
+    private String cover;
+
+    private Integer viewNumber;
+
+    private Integer saleNumber;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public Integer getStructId() {
+        return structId;
+    }
+
+    public void setStructId(Integer structId) {
+        this.structId = structId;
+    }
+
+    public Integer getParentStructId() {
+        return parentStructId;
+    }
+
+    public void setParentStructId(Integer parentStructId) {
+        this.parentStructId = parentStructId;
+    }
+
+    public String getDataType() {
+        return dataType;
+    }
+
+    public void setDataType(String dataType) {
+        this.dataType = dataType;
+    }
+
+    public Integer getIsNovice() {
+        return isNovice;
+    }
+
+    public void setIsNovice(Integer isNovice) {
+        this.isNovice = isNovice;
+    }
+
+    public Integer getIsOriginal() {
+        return isOriginal;
+    }
+
+    public void setIsOriginal(Integer isOriginal) {
+        this.isOriginal = isOriginal;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public Integer getPages() {
+        return pages;
+    }
+
+    public void setPages(Integer pages) {
+        this.pages = pages;
+    }
+
+    public String getLink() {
+        return link;
+    }
+
+    public void setLink(String link) {
+        this.link = link;
+    }
+
+    public String getCover() {
+        return cover;
+    }
+
+    public void setCover(String cover) {
+        this.cover = cover;
+    }
+
+    public Integer getViewNumber() {
+        return viewNumber;
+    }
+
+    public void setViewNumber(Integer viewNumber) {
+        this.viewNumber = viewNumber;
+    }
+
+    public Integer getSaleNumber() {
+        return saleNumber;
+    }
+
+    public void setSaleNumber(Integer saleNumber) {
+        this.saleNumber = saleNumber;
+    }
+}

+ 169 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseListDto.java

@@ -0,0 +1,169 @@
+package com.qxgmat.dto.response;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.Course;
+
+import java.math.BigDecimal;
+
+@Dto(entity = Course.class)
+public class CourseListDto {
+    private Integer id;
+
+    private Integer structId;
+
+    private Integer parentStructId;
+
+    private String courseModule;
+
+    private String vsType;
+
+    private String videoType;
+
+    private String extend;
+
+    private String title;
+
+    private String comment;
+
+    private String crowd;
+
+    private BigDecimal price;
+
+    private String teacher;
+
+    private String cover;
+
+    private Integer expireDays;
+
+    private Integer expireTime;
+
+    private Integer useExpireTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getStructId() {
+        return structId;
+    }
+
+    public void setStructId(Integer structId) {
+        this.structId = structId;
+    }
+
+    public Integer getParentStructId() {
+        return parentStructId;
+    }
+
+    public void setParentStructId(Integer parentStructId) {
+        this.parentStructId = parentStructId;
+    }
+
+    public String getCourseModule() {
+        return courseModule;
+    }
+
+    public void setCourseModule(String courseModule) {
+        this.courseModule = courseModule;
+    }
+
+    public String getVsType() {
+        return vsType;
+    }
+
+    public void setVsType(String vsType) {
+        this.vsType = vsType;
+    }
+
+    public String getVideoType() {
+        return videoType;
+    }
+
+    public void setVideoType(String videoType) {
+        this.videoType = videoType;
+    }
+
+    public String getExtend() {
+        return extend;
+    }
+
+    public void setExtend(String extend) {
+        this.extend = extend;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getCrowd() {
+        return crowd;
+    }
+
+    public void setCrowd(String crowd) {
+        this.crowd = crowd;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public String getTeacher() {
+        return teacher;
+    }
+
+    public void setTeacher(String teacher) {
+        this.teacher = teacher;
+    }
+
+    public Integer getExpireDays() {
+        return expireDays;
+    }
+
+    public void setExpireDays(Integer expireDays) {
+        this.expireDays = expireDays;
+    }
+
+    public Integer getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Integer expireTime) {
+        this.expireTime = expireTime;
+    }
+
+    public Integer getUseExpireTime() {
+        return useExpireTime;
+    }
+
+    public void setUseExpireTime(Integer useExpireTime) {
+        this.useExpireTime = useExpireTime;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getCover() {
+        return cover;
+    }
+
+    public void setCover(String cover) {
+        this.cover = cover;
+    }
+}

+ 19 - 9
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserTextbookInfoDto.java

@@ -3,20 +3,14 @@ package com.qxgmat.dto.response;
 import com.qxgmat.data.dao.entity.TextbookLibrary;
 
 public class UserTextbookInfoDto {
-    private TextbookLibrary library;
+    private TextbookLibrary latest;
+
+    private TextbookLibrary second;
 
     private Boolean hasService;
 
     private Integer unUseRecord;
 
-    public TextbookLibrary getLibrary() {
-        return library;
-    }
-
-    public void setLibrary(TextbookLibrary library) {
-        this.library = library;
-    }
-
     public Boolean getHasService() {
         return hasService;
     }
@@ -32,4 +26,20 @@ public class UserTextbookInfoDto {
     public void setUnUseRecord(Integer unUseRecord) {
         this.unUseRecord = unUseRecord;
     }
+
+    public TextbookLibrary getLatest() {
+        return latest;
+    }
+
+    public void setLatest(TextbookLibrary latest) {
+        this.latest = latest;
+    }
+
+    public TextbookLibrary getSecond() {
+        return second;
+    }
+
+    public void setSecond(TextbookLibrary second) {
+        this.second = second;
+    }
 }

+ 39 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseDataService.java

@@ -53,6 +53,45 @@ public class CourseDataService extends AbstractService {
         return select(courseDataMapper, example, page, size);
     }
 
+    public Page<CourseData> list(int page, int size, Integer structId, DataType dataType, Boolean isNovice, Boolean isOriginal, String order, DirectionStatus direction){
+        Example example = new Example(CourseData.class);
+        if(structId != null){
+            example.and(
+                    example.createCriteria()
+                            .orEqualTo("structId", structId)
+                            .orEqualTo("parentStructId", structId)
+            );
+        }
+        if (isNovice != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("isNovice", isNovice ? 1 : 0)
+            );
+        }
+        if (isOriginal != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("isOriginal", isOriginal ? 1 : 0)
+            );
+        }
+        if(dataType != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("dataType", dataType.key)
+            );
+        }
+        if(order.isEmpty()) order = "id";
+        switch(direction){
+            case ASC:
+                example.orderBy(order).asc();
+                break;
+            case DESC:
+            default:
+                example.orderBy(order).desc();
+        }
+        return select(courseDataMapper, example, page, size);
+    }
+
     public CourseData add(CourseData courseData){
         int result = insert(courseDataMapper, courseData);
         courseData = one(courseDataMapper, courseData.getId());

+ 9 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/CourseService.java

@@ -53,6 +53,15 @@ public class CourseService extends AbstractService {
         return select(courseMapper, example, page, size);
     }
 
+    public List<Course> all(CourseModule module){
+        Example example = new Example(Course.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("courseModule", module.key)
+        );
+        return select(courseMapper, example);
+    }
+
     public Course add(Course course){
         int result = insert(courseMapper, course);
         course = one(courseMapper, course.getId());

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

@@ -81,7 +81,7 @@ public class TextbookLibraryService extends AbstractService {
         example.and(
                 example.createCriteria()
                 .andGreaterThanOrEqualTo("startDate", startTime)
-                .andLessThan("startDate", startTime)
+                .andLessThan("startDate", endTime)
         );
         return select(textbookLibraryMapper, example);
     }

+ 3 - 1
server/gateway-api/src/main/java/com/qxgmat/service/inline/TextbookTopicService.java

@@ -5,6 +5,7 @@ import com.nuliji.tools.AbstractService;
 import com.nuliji.tools.exception.ParameterException;
 import com.nuliji.tools.exception.SystemException;
 import com.nuliji.tools.mybatis.Example;
+import com.qxgmat.data.constants.enums.QuestionSubject;
 import com.qxgmat.data.constants.enums.TopicQuality;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.dao.TextbookTopicMapper;
@@ -28,11 +29,12 @@ public class TextbookTopicService extends AbstractService {
     @Resource
     private TextbookTopicMapper textbookTopicMapper;
 
-    public Page<TextbookTopic> list(int page, int size, Integer libraryId, String[] qualitys, Boolean isOld, String order, DirectionStatus direction){
+    public Page<TextbookTopic> list(int page, int size, Integer libraryId, QuestionSubject subject, String[] qualitys, Boolean isOld, String order, DirectionStatus direction){
         Example example = new Example(TextbookTopic.class);
         example.and(
                 example.createCriteria()
                 .andEqualTo("libraryId", libraryId)
+                .andEqualTo("questionSubject", subject.key)
         );
         if (qualitys != null){
             example.and(

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

@@ -256,14 +256,14 @@ public class Tools {
         Calendar c = Calendar.getInstance();
         c.setTime(date);
         c.add(Calendar.DAY_OF_YEAR, day);
-        return date;
+        return c.getTime();
     }
 
     public static Date addYear(Date date, int year){
         Calendar c = Calendar.getInstance();
         c.setTime(date);
         c.add(Calendar.YEAR, year);
-        return date;
+        return c.getTime();
     }
 
     public static TreeMap<String, String> parseXml(String xml) {