1
0
Go 4 лет назад
Родитель
Сommit
f0423ed446
45 измененных файлов с 1399 добавлено и 539 удалено
  1. 1 1
      front/project/Constant.js
  2. 1 2
      front/project/h5/routes/product/courseVs/page.js
  3. 2 2
      front/project/www/components/Filter/index.js
  4. 159 47
      front/project/www/components/Item/index.js
  5. 2 4
      front/project/www/components/Login/index.js
  6. 2 1
      front/project/www/components/Other/index.js
  7. 2 2
      front/project/www/local.json
  8. 196 83
      front/project/www/routes/course/data/page.js
  9. 2 2
      front/project/www/routes/course/dDetail/index.js
  10. 1 1
      front/project/www/routes/course/dDetail/index.less
  11. 196 0
      front/project/www/routes/course/dataDetail/page.js
  12. 11 0
      front/project/www/routes/course/detail/index.js
  13. 7 0
      front/project/www/routes/course/detail/index.less
  14. 0 0
      front/project/www/routes/course/detail/page.js
  15. 2 2
      front/project/www/routes/course/inside/index.js
  16. 1 1
      front/project/www/routes/course/inside/index.less
  17. 0 0
      front/project/www/routes/course/experience/page.js
  18. 2 2
      front/project/www/routes/course/iDetail/index.js
  19. 1 1
      front/project/www/routes/course/iDetail/index.less
  20. 0 0
      front/project/www/routes/course/experienceDetail/page.js
  21. 7 6
      front/project/www/routes/course/index.js
  22. 7 6
      front/project/www/routes/course/main/page.js
  23. 143 72
      front/project/www/routes/course/online/page.js
  24. 0 112
      front/project/www/routes/course/pDetail/page.js
  25. 2 2
      front/project/www/routes/course/pDetail/index.js
  26. 1 1
      front/project/www/routes/course/pDetail/index.less
  27. 97 0
      front/project/www/routes/course/packageDetail/page.js
  28. 0 171
      front/project/www/routes/course/single/page.js
  29. 2 2
      front/project/www/routes/course/single/index.js
  30. 1 1
      front/project/www/routes/course/single/index.less
  31. 247 0
      front/project/www/routes/course/vs/page.js
  32. 1 1
      front/project/www/routes/my/course/page.js
  33. 6 2
      front/project/www/stores/course.js
  34. 1 1
      front/project/www/stores/user.js
  35. 35 0
      server/data/src/main/java/com/qxgmat/data/dao/entity/Course.java
  36. 6 5
      server/data/src/main/java/com/qxgmat/data/dao/mapping/CourseMapper.xml
  37. 1 0
      server/data/src/main/resources/db/migration/V1__init_table.sql
  38. 29 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  39. 2 2
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  40. 1 0
      server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java
  41. 61 1
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseExtendDto.java
  42. 20 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseDataListDto.java
  43. 130 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CourseListDto.java
  44. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/CoursePackageListDto.java
  45. 1 1
      server/gateway-api/src/main/java/com/qxgmat/service/inline/CoursePackageService.java

+ 1 - 1
front/project/Constant.js

@@ -94,7 +94,7 @@ export const CourseModule = [{ label: '视频课程', value: 'video' }, { label:
 
 export const CourseModuleShow = [{ label: '在线课程', value: 'online', courseModules: ['video', 'online'] }, { label: '1V1私教', value: 'vs', courseModules: ['vs'] }];
 
-export const CourseVsType = [{ label: '新手辅导', value: 'novice', tips: '适合未参加过实战的考生' }, { label: '诊断辅导', value: 'coach', tips: '适合参加过实战的考生' }, { label: '系统授课', value: 'system' }, { label: '答疑课', value: 'answer' }];
+export const CourseVsType = [{ label: '新手辅导', value: 'novice', tips: '适合未参加过实战的考生', icon: 'xinshoufudao' }, { label: '诊断辅导', value: 'coach', tips: '适合参加过实战的考生', icon: 'zhenduanfudao' }, { label: '系统授课', value: 'system', icon: 'xitongshouke' }, { label: '答疑课', value: 'answer', icon: 'dayi' }];
 
 export const CourseVideoType = [{ label: '基础刷题', value: 'base' }, { label: '系统授课', value: 'system' }, { label: '思维提升', value: 'thinking' }];
 

+ 1 - 2
front/project/h5/routes/product/courseVs/page.js

@@ -21,7 +21,7 @@ export default class extends Page {
       this.promote = result.vs_list || [];
       Course.get(id).then(data => {
         this.setState({ data });
-        this.changeNumber(1);
+        this.changeNumber(data.minNumber || 1);
       });
     });
   }
@@ -31,7 +31,6 @@ export default class extends Page {
     let price = data.price * number;
     let max = 0;
     let maxIndex = -1;
-    this.promote = [{ number: 2, percent: 90 }];
     this.promote.forEach((row, i) => {
       if (row.number <= number) {
         if (row.number > max) {

+ 2 - 2
front/project/www/components/Filter/index.js

@@ -9,10 +9,10 @@ function Filter(props) {
         return (
           <div className="list">
             {item.children &&
-              item.children.map(child => {
+              item.children.map((child, index) => {
                 return (
                   <div
-                    className={`item ${filter[item.key] === child.key ? 'active' : ''}`}
+                    className={`item ${filter[item.key] === child.key || (!filter[item.key] && index === 0) ? 'active' : ''}`}
                     onClick={() => onFilter(item.key, child.key)}
                   >
                     {child.title}

+ 159 - 47
front/project/www/components/Item/index.js

@@ -1,85 +1,164 @@
 import React, { Component } from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
+import { getMap, formatSeconds } from '@src/services/Tools';
 import Button from '../Button';
+import { Order } from '../../stores/order';
+import { User } from '../../stores/user';
+import { ServiceKey, ServiceParamMap, CrowdList } from '../../../Constant';
+
+const CrowdMap = getMap(CrowdList, 'value', 'label');
 
 export class SingleItem extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {};
+  }
+
+  buy() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.speedPay({ productType: 'course', productId: data.id })
+        .then(result => {
+          User.needPay(result)
+            .then(() => {
+              linkTo('/my/course');
+            });
+        });
+    });
+  }
+
+  add() {
+    const { data } = this.props;
+    User.needLogin().thenn(() => {
+      Order.addCheckout({ productType: 'course', productId: data.id })
+        .then(() => {
+          this.setState({ add: true });
+        });
+    });
+  }
+
   render() {
-    const { show } = this.props;
+    const { data = {}, noAction } = this.props;
+    const { add } = this.state;
     return (
       <div className="single-item">
-        <div className="img">
+        <div className="img" onClick={() => linkTo(`/course/detail/${data.id}`)}>
           <div className="title">
-            <div className="tag">新手</div>OG20整合刷题-语法SC
+            <div className="tag">{CrowdMap[data.crowd]}</div>{data.title}
           </div>
           <div className="left">
-            <span>课时数: 20</span>
-            <span>133h32min</span>
+            <span>课时数: {data.noNumber}</span>
+            <span>{formatSeconds(data.time)}</span>
           </div>
           <div className="right">
-            <span>234234次观看</span>
+            <span>{data.trailNumber || 0}次观看</span>
           </div>
         </div>
         <div className="name">
-          <span>授课老师:李小小</span>
-          <span>优质问答: 999</span>
-          <span>有效期: 3个月</span>
+          <span>授课老师:{data.teacher}</span>
+          <span>优质问答: {data.askNumber}</span>
+          <span>有效期: {data.useExpireDays}天</span>
         </div>
-        <div className="money">¥ 15000</div>
-        {!show && (
-          <div className="action">
-            <Button radius size="lager">
-              立即购买
-            </Button>
-            <Button className="f-r" theme="default" radius size="lager">
-              查看课程
-            </Button>
-            <Button className="f-r" theme="default" radius size="lager">
-              <Assets name="add" />
-            </Button>
-          </div>
-        )}
-        {!show && <Assets className="buyed" width={75} height={75} name="Purchased" />}
+        <div className="money">¥ {data.price}</div>
+        {!noAction && <div className="action">
+          {!data.have && <Button radius size="lager" onClick={() => this.buy()}>
+            立即购买
+            </Button>}
+          {data.have && <Button className="f-r" theme="default" radius size="lager" onClick={() => linkTo(`/course/detail/${data.id}`)}>
+            查看课程
+            </Button>}
+          {!data.have && <Button className="f-r" theme="default" disabled={data.add || add} radius size="lager" onClick={() => this.add()} >
+            <Assets name="add" />
+          </Button>}
+        </div>}
+        {!noAction && data.have && <Assets className="buyed" width={75} height={75} name="Purchased" />}
       </div>
     );
   }
 }
 
 export class PackageItem extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {};
+  }
+
+  buy() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.speedPay({ productType: 'course_package', productId: data.id })
+        .then(result => {
+          User.needPay(result)
+            .then(() => {
+              linkTo('/my/course');
+            });
+        });
+    });
+  }
+
+  add() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.addCheckout({ productType: 'course_package', productId: data.id })
+        .then(() => {
+          this.setState({ add: true });
+        });
+    });
+  }
+
   render() {
+    const { data = {} } = this.props;
+    const { add } = this.state;
+    const originMoney = data.courses.reduce((a, y) => a + y.price, 0);
+    const novice = data.courses.filter(row => row.crowd !== 'novice').length === 0;
+    const teacherMap = {};
+    data.courses.forEach(row => {
+      teacherMap[row.teacher] = true;
+    });
+    const teachers = Object.keys(teacherMap);
     return (
       <div className="package-item">
-        <div className="block m-b-1">
+        <div className="block m-b-1" onClick={() => linkTo(`/course/package/detail/${data.id}`)}>
           <div className="title t-1 m-b-5 f-w-b">
-            <div className="tag f-w-d">新手</div>OG20整合刷题-语法SC
+            <div hidden={!novice} className="tag f-w-d">新手</div>{data.title}
           </div>
-          <div className="t-1 t-s-12 m-b-5">授课老师 李小小</div>
-          <div className="t-1 t-s-12 m-b-2">包含最新 XXXXXXX 的全部课程XXXXX;机经券×1+VIP×3 月+模考×1</div>
+          <div className="t-1 t-s-12 m-b-5">授课老师 {teachers.join(' ')}</div>
+          <div className="t-1 t-s-12 m-b-2">{data.description}</div>
           <div className="t-1 t-s-12">包含课程</div>
           <div className="p-t-5">
-            <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">OG20阅读刷题(7课时)</div>
+            {data.courses.map((course => {
+              return <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">{course.title}({course.noNumber}课时)</div>;
+            }))}
           </div>
           <div className="t-1 t-s-12">配套服务</div>
           <div className="p-t-5">
-            <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">OG20阅读刷题(7课时)</div>
+            <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">预习作业</div>
+            <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">课后答题</div>
           </div>
           <div className="t-1 t-s-12">赠送服务</div>
           <div className="p-t-5">
-            <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">OG20阅读刷题(7课时)</div>
+            {data.gift &&
+              ServiceKey.map(row => {
+                if (!data.gift[row.value]) return null;
+                const list = ServiceParamMap[row.value];
+                if (list) {
+                  const map = getMap(list, 'value', 'label');
+                  return <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">{row.label}×{map[data.gift[row.value]]}</div>;
+                }
+                return <div className="t m-b-1 t-4 m-r-5 t-s-12 d-i-b p-5">{row.label}×{data.gift[row.value]}</div>;
+              })}
           </div>
         </div>
         <div className="t-1 t-s-12">
-          原价: <span className="t-d-l-t">¥18888</span>
+          原价: <span className="t-d-l-t">¥{originMoney}</span>
         </div>
-        <div className="t-7 t-s-18 f-w-b m-b-1">套餐价: ¥8888</div>
+        <div className="t-7 t-s-18 f-w-b m-b-1">套餐价: ¥{data.price}</div>
         <div className="action">
-          <Button radius size="lager">
+          <Button radius size="lager" onClick={() => this.buy()}>
             立即购买
           </Button>
-          <Button className="f-r" theme="default" radius size="lager">
-            查看课程
-          </Button>
-          <Button className="f-r" theme="default" radius size="lager">
+          <Button className="f-r" theme="default" radius size="lager" disabled={data.add || add} onClick={() => this.add()}>
             <Assets name="add" />
           </Button>
         </div>
@@ -89,25 +168,58 @@ export class PackageItem extends Component {
 }
 
 export class DataItem extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {};
+  }
+
+  buy() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.speedPay({ productType: 'data', productId: data.id })
+        .then(result => {
+          User.needPay(result)
+            .then(() => {
+              linkTo('/my/tools?tab=data');
+            });
+        });
+    });
+  }
+
+  add() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.addCheckout({ productType: 'data', productId: data.id })
+        .then(() => {
+          this.setState({ add: true });
+        });
+    });
+  }
+
   render() {
+    const { data = {} } = this.props;
+    const { add } = this.state;
     return (
       <div className="data-item">
-        <Assets name="" />
+        <Assets width="309" height="264" name="" src={data.cover} onClick={() => linkTo(`/course/data/detail/${data.id}`)} />
         <div className="m-b-1">
-          <span className="t-7 t-s-24 f-w-b">¥8888</span>
-          <span className="f-r t-8 t-s-20">888人</span>
+          <span className="t-7 t-s-24 f-w-b">¥{data.price}</span>
+          <span className="f-r t-8 t-s-20">{data.saleNumber}人</span>
         </div>
         <div className="action">
-          <Button radius size="lager">
+          {!data.have && <Button radius size="lager" onClick={() => {
+            this.buy();
+          }}>
             立即购买
-          </Button>
-          <Button className="f-r" theme="default" radius size="lager">
+          </Button>}
+          {data.have && <Button className="f-r" theme="default" radius size="lager" onClick={() => openLink(data.resource)}>
             查看资料
-          </Button>
-          <Button className="f-r" theme="default" radius size="lager">
+          </Button>}
+          {!data.have && <Button className="f-r" theme="default" disabled={data.add || add} radius size="lager" onClick={() => this.add()}>
             <Assets name="add" />
-          </Button>
+          </Button>}
         </div>
+        {data.have && <Assets className="buyed" width={75} height={75} name="Purchased" />}
       </div>
     );
   }

+ 2 - 4
front/project/www/components/Login/index.js

@@ -181,7 +181,7 @@ export default class Login extends Component {
         <GIcon
           name="close"
           onClick={() => {
-            this.close();
+            User.closeLogin(new Error('no login'));
           }}
         />
       </Modal>
@@ -520,9 +520,7 @@ export class VerificationInput extends Component {
             <span className="g-input-right-verification" onClick={() => this.onSend()}>
               获取验证码
             </span>
-          ) : (
-            <span className="g-input-right-verification-loading">等待{loading}秒</span>
-          )
+          ) : (<span className="g-input-right-verification-loading">等待{loading}秒</span>)
         }
         value={value}
         error={error}

+ 2 - 1
front/project/www/components/Other/index.js

@@ -5,6 +5,7 @@ import { Carousel } from 'antd-mobile';
 import Assets from '@src/components/Assets';
 import { formatDate } from '@src/services/Tools';
 import Button from '../Button';
+import { User } from '../../stores/user';
 
 export class CommentFalls extends Component {
   createLayout() {
@@ -86,7 +87,7 @@ export class AnswerCarousel extends Component {
           <Assets name="footer_previous_highlight_1" className="prev" onClick={() => this.onPrev()} />
         </div>
         {!hideBtn && (
-          <Button size="lager" radius onClick={() => onFaq()}>
+          <Button size="lager" radius onClick={() => User.needLogin().then(() => onFaq())}>
             <Assets name="kf" className="m-r-5" />立即咨询
           </Button>
         )

+ 2 - 2
front/project/www/local.json

@@ -8,7 +8,7 @@
     ],
     "proxy": [
       {
-        "target": "http://qianxing.nuliji.com",
+        "target": "http://127.0.0.1:8888",
         "from": "/api",
         "to": "/api"
       }
@@ -30,4 +30,4 @@
       "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
     ]
   }
-}
+}

+ 196 - 83
front/project/www/routes/course/data/page.js

@@ -2,60 +2,150 @@ import React from 'react';
 import './index.less';
 import { Switch } from 'antd';
 import Page from '@src/containers/Page';
+import { getMap, formatDate } from '@src/services/Tools';
 import UserAction from '../../../components/UserAction';
 import Tabs from '../../../components/Tabs';
 import Filter from '../../../components/Filter';
 import Icon from '../../../components/Icon';
 import { DataItem } from '../../../components/Item';
 import UserTable from '../../../components/UserTable';
+import UserPagination from '../../../components/UserPagination';
+import { FeedbackErrorDataModal } from '../../../components/OtherModal';
+import { DataType } from '../../../../Constant';
+import { Main } from '../../../stores/main';
+import { Course } from '../../../stores/course';
+import { My } from '../../../stores/my';
+import { User } from '../../../stores/user';
 
-const columns = [
-  {
-    key: '',
-    title: '更新时间',
-  },
-  {
-    key: '',
-    title: '位置',
-  },
-  {
-    key: '',
-    title: '原内容',
-  },
-  {
-    key: '',
-    title: '更改为',
-  },
-  {
-    key: '',
-    title: '更新至',
-  },
+
+const dataHistoryColumns = [
+  { title: '更新时间', key: 'time', width: 120 },
+  { title: '位置', key: 'position', width: 120 },
+  { title: '原内容', key: 'originContent', width: 120 },
+  { title: '更改为', key: 'content', width: 120 },
+  { title: '更新至', key: 'version', width: 90 },
 ];
 
 export default class extends Page {
   initState() {
+    const dataTypeSelect = DataType.map((row) => {
+      return {
+        title: row.label,
+        key: row.value,
+      };
+    });
+    dataTypeSelect.unshift({
+      title: '全部',
+      key: '',
+    });
     return {
-      tab: '2',
-      filterMap: { one: '1', two: '1' },
+      tab: '',
+      filterMap: {},
       sortMap: {},
       list: [],
-      type: [
-        { title: '长难句', key: '1', open: true, children: [{ key: '1', title: 'OG19 语法千行' }] },
-        { title: '语文 Verbal', key: '2', open: true, children: [{ key: '1', title: 'OG19 语法千行' }] },
-        { title: '数学 Quant', key: '3', open: true, children: [{ key: '1', title: 'OG19 语法千行' }] },
-      ],
+      dataStructSelect: [],
+      dataTypeSelect,
+      // type: [
+      //   { title: '长难句', key: '1', open: true, children: [{ key: '1', title: 'OG19 语法千行' }] },
+      //   { title: '语文 Verbal', key: '2', open: true, children: [{ key: '1', title: 'OG19 语法千行' }] },
+      //   { title: '数学 Quant', key: '3', open: true, children: [{ key: '1', title: 'OG19 语法千行' }] },
+      // ],
     };
   }
 
-  initData() {}
+  init() {
+    Main.dataStruct().then(result => {
+      const dataStructSelect = result.filter(row => row.level === 1).map(row => {
+        return {
+          title: `${row.titleZh}${row.titleEn}`,
+          key: `${row.id}`,
+        };
+      });
+      dataStructSelect.unshift({
+        title: '全部',
+        key: '',
+      });
+      const dataStructMap = getMap(dataStructSelect, 'key');
+      this.setState({ dataStructSelect, dataStructMap });
+    });
+  }
 
-  onChangeTab(tab) {
-    this.setState({ tab });
+  initData() {
+    const data = Object.assign(this.state, this.state.search);
+    if (data.order) {
+      data.sortMap = { [data.order]: data.direction };
+    }
+    data.filterMap = this.state.search;
+    this.setState(data);
+    switch (data.tab) {
+      case 'history':
+        this.refreshHistory();
+        break;
+      default:
+        this.refreshData();
+    }
   }
 
-  onFilter(value) {
-    this.search(value, false);
-    this.initData();
+  refreshData() {
+    Course.listData(Object.assign({}, this.state.search))
+      .then(result => {
+        this.setState({ list: result.list, total: result.total });
+      });
+  }
+
+  refreshHistory() {
+    let { all, dataId } = this.state;
+    dataId = Number(dataId);
+    if (!all) {
+      Course.listData({ page: 1, size: 1000 })
+        .then(result => {
+          this.dataMap = getMap(result.list, 'id');
+          const allIds = [];
+          result.list.forEach(row => {
+            if (allIds.indexOf(row.structId) >= 0) return;
+            allIds.push(row.structId);
+          });
+          all = allIds.map(row => {
+            return { id: row, children: [] };
+          });
+          result.list.forEach(row => {
+            const index = allIds.indexOf(row.structId);
+            all[index].children.push(row);
+            if (row.id === dataId) all[index].open = true;
+          });
+          if (!dataId) {
+            dataId = result.list[0].id;
+            all[0].open = true;
+          }
+
+          this.refreshHistoryList(dataId);
+          this.setState({ all });
+        });
+    } else if (dataId) {
+      this.refreshHistoryList(dataId);
+    }
+  }
+
+  refreshHistoryList(dataId) {
+    Course.historyData({ dataId, page: 1, size: 1000 })
+      .then(result => {
+        result.list = result.list.map(row => {
+          row.time = formatDate(row.time, 'YYYY-MM-DD\nHH:mm:ss');
+          return row;
+        });
+        this.setState({ item: this.dataMap[dataId], list: result.list, dataId });
+      });
+  }
+
+  onTabChange(tab) {
+    const data = { tab };
+    this.refreshQuery(data);
+  }
+
+  onFilter(key, value) {
+    const { filterMap } = this.state;
+    filterMap[key] = value;
+    this.search(filterMap);
   }
 
   onChangePage(page) {
@@ -63,10 +153,33 @@ export default class extends Page {
     this.initData();
   }
 
+  onSort(value) {
+    const keys = Object.keys(value);
+    // this.search({ order: keys.length ? keys.join('|') : null, direction: keys.length ? Object.values(value).join('|') : null });
+    const { sortMap } = this.state;
+    const index = keys.length > 1 && sortMap[keys[0]] ? 1 : 0;
+    this.search({ order: keys.length ? keys[index] : null, direction: keys.length ? value[keys[index]] : null });
+  }
+
   onOpen(index) {
-    const { type } = this.state;
-    type[index].open = !type[index].open;
-    this.setState({ type });
+    const { all } = this.state;
+    all[index].open = !all[index].open;
+    this.setState({ all });
+  }
+
+  onSelect(dataId) {
+    this.search({ dataId }, false);
+    this.initData();
+  }
+
+  subscribe(dataId, subscribe) {
+    User.needLogin()
+      .then(() => {
+        My.subscribeData(dataId, subscribe)
+          .then(() => {
+            this.refresh();
+          });
+      });
   }
 
   renderView() {
@@ -74,8 +187,8 @@ export default class extends Page {
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部课程 > OG20综合刷题 > 课时3 > <span className="t-1">资料列表</span>
-          <div className="f-r">我的资料</div>
+          千行课堂 > <span className="t-1">资料列表</span>
+          <div className="f-r"><a href="/my/tools?tab=data">我的资料</a></div>
         </div>
         <div className="center content">
           <Tabs
@@ -86,8 +199,8 @@ export default class extends Page {
             width={100}
             border
             active={tab}
-            tabs={[{ title: '全部资料', key: '1' }, { title: '更新日志', key: '2' }]}
-            onChange={key => this.onChangeTab(key)}
+            tabs={[{ title: '全部资料', key: '' }, { title: '更新日志', key: 'history' }]}
+            onChange={key => this.onTabChange(key)}
           />
           {this[`renderTab${tab}`]()}
         </div>
@@ -95,39 +208,31 @@ export default class extends Page {
     );
   }
 
-  renderTab1() {
-    const { list = [], sortMap, filterMap } = this.state;
+  renderTab() {
+    const { list = [], total, page, sortMap, filterMap, dataStructSelect, dataTypeSelect } = this.state;
     return [
       <Filter
         filter={filterMap}
         list={[
           {
-            key: 'one',
-            children: [
-              { key: '1', title: '全部' },
-              { key: '2', title: '长难句' },
-              { key: '3', title: '语文Verbal' },
-              { key: '4', title: '数学Quant' },
-            ],
+            key: 'structId',
+            placeholder: '学科',
+            children: dataStructSelect,
           },
           {
-            key: 'two',
-            children: [
-              { key: '1', title: '全部' },
-              { key: '2', title: '语法SC' },
-              { key: '3', title: '阅读RC' },
-              { key: '4', title: '逻辑RC' },
-            ],
+            placeholder: '形式',
+            key: 'dataType',
+            children: dataTypeSelect,
           },
         ]}
         onFilter={(key, value) => this.onFilter(key, value)}
       />,
       <UserAction
-        search
+        // search
         defaultSearch={filterMap.keyword}
         sortList={[
-          { label: '更新时间', key: 'ask_time', fixed: true },
-          { label: '销量', key: 'create_time', fixed: true },
+          { label: '更新时间', key: 'updateTime', fixed: true },
+          { label: '销量', key: 'saleNumber', fixed: true },
         ]}
         sortMap={sortMap}
         onSort={value => this.onSort(value)}
@@ -137,29 +242,33 @@ export default class extends Page {
           return <DataItem data={item} />;
         })}
       </div>,
+      total > 0 && list.length > 0 && (
+        <UserPagination total={total} current={page} pageSize={this.state.search.size} onChange={p => this.onChangePage(p)} />
+      ),
     ];
   }
 
-  renderTab2() {
-    const { type = [], list = [], filterMap } = this.state;
+  renderTabhistory() {
+    const { all = [], dataId, item = {}, list = [], dataStructMap = {}, showFeedbackError, feedbackError } = this.state;
     return [
       <div className="update-search">
-        <UserAction search defaultSearch={filterMap.keyword} />
+        {/* <UserAction search defaultSearch={filterMap.keyword} /> */}
       </div>,
       <div className="update-log">
         <div className="left">
-          {type.map((item, index) => {
+          {all.map((node, index) => {
+            const struct = dataStructMap[node.id] || {};
             return (
               <div className="block">
                 <div className="title" onClick={() => this.onOpen(index)}>
-                  {item.title}
+                  {struct.title}
                   <div className="f-r">
-                    {item.open ? <Icon name="arrow-up" noHover /> : <Icon name="arrow-down" noHover />}
+                    {node.open ? <Icon name="arrow-up" noHover /> : <Icon name="arrow-down" noHover />}
                   </div>
                 </div>
-                <div className={`list ${item.open ? 'open' : ''}`}>
-                  {item.children.map(child => {
-                    return <div className="item">{child.title}</div>;
+                <div className={`list ${node.open ? 'open' : ''}`}>
+                  {node.children.map(child => {
+                    return <div className={`item ${child.id === dataId ? 't-4' : ''}`} onClick={() => this.onSelect(child.id)}>{child.title}</div>;
                   })}
                 </div>
               </div>
@@ -167,23 +276,27 @@ export default class extends Page {
           })}
         </div>
         <div className="right">
-          {list.map(item => {
-            return (
-              <div className="item">
-                <div className="m-b-5">
-                  <span className="t-1 t-s-18 f-w-b">{item.title}</span>
-                  <div className="f-r">
-                    <span className="m-r-5">邮箱订阅</span>
-                    <Switch />
-                    <span className="m-l-5 t-4">纠错</span>
-                  </div>
-                </div>
-                <UserTable size="small" columns={columns} />
+          <div className="item">
+            <div className="m-b-5">
+              <span className="t-1 t-s-18 f-w-b">{item.title}</span>
+              <div className="f-r">
+                <span className="m-r-5">订阅</span>
+                <Switch checked={item.subscribe} onChange={() => {
+                  this.subscribe(item.id, !item.subscribe);
+                }} />
+                <a className="m-l-5 t-4" onClick={() => this.setState({ showFeedbackError: true, feedbackError: { dataId: item.id, title: item.title, position: ['', '', ''] } })}>纠错</a>
               </div>
-            );
-          })}
+            </div>
+            <UserTable size="small" columns={dataHistoryColumns} data={list} />
+          </div>
         </div>
       </div>,
+      <FeedbackErrorDataModal
+        show={showFeedbackError}
+        defaultData={feedbackError}
+        onConfirm={() => this.setState({ showFeedbackError: false })}
+        onCancel={() => this.setState({ showFeedbackError: false })}
+      />,
     ];
   }
 }

+ 2 - 2
front/project/www/routes/course/dDetail/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/course/d-detail',
-  key: 'course-d-detail',
+  path: '/course/data/detail/:id',
+  key: 'course-data-detail',
   title: '课堂-资料详情',
   needLogin: false,
   repeat: true,

+ 1 - 1
front/project/www/routes/course/dDetail/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#course-d-detail {
+#course-data-detail {
   background: #fff;
 
   .content {

+ 196 - 0
front/project/www/routes/course/dataDetail/page.js

@@ -0,0 +1,196 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+import Assets from '@src/components/Assets';
+import { getMap, formatDate } from '@src/services/Tools';
+import Footer from '../../../components/Footer';
+import { Contact } from '../../../components/Other';
+import Tabs from '../../../components/Tabs';
+import Button from '../../../components/Button';
+import { User } from '../../../stores/user';
+import { Course } from '../../../stores/course';
+import { Order } from '../../../stores/order';
+import { Main } from '../../../stores/main';
+
+export default class extends Page {
+  initState() {
+    return {
+      tab: '1',
+    };
+  }
+
+  init() {
+    Main.dataStruct().then(result => {
+      const dataStructSelect = result.map(row => {
+        return {
+          title: `${row.titleZh}${row.titleEn}`,
+          key: row.id,
+        };
+      });
+      const dataStructMap = getMap(dataStructSelect, 'key');
+      this.setState({ dataStructSelect, dataStructMap });
+    });
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+  }
+
+  initData() {
+    const { id } = this.params;
+    Course.getData(id)
+      .then(result => {
+        this.setState({ data: result });
+      });
+    Course.dataView(id);
+
+    Main.listFaq({ page: 1, size: 100, channel: 'course_data' }).then(result => {
+      this.faqs = result.list;
+      this.setState({ faqs: result.list });
+    });
+    Main.listComment({ page: 1, size: 100, channel: 'course_data' }).then(result => {
+      this.comments = result.list;
+      this.setState({ comments: result.list });
+    });
+  }
+
+  onChangeTab(tab) {
+    this.setState({ tab });
+  }
+
+  buy() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.speedPay({ productType: 'data', productId: data.id })
+        .then(result => {
+          User.needPay(result)
+            .then(() => {
+              linkTo('/my/tools?tab=data');
+            });
+        });
+    });
+  }
+
+  add() {
+    const { data } = this.props;
+    User.needLogin().then(() => {
+      Order.addCheckout({ productType: 'data', productId: data.id })
+        .then(() => {
+          this.setState({ add: true });
+        });
+    });
+  }
+
+  renderView() {
+    const { base = {}, data = {}, dataStructMap = {} } = this.state;
+    return (
+      <div>
+        <div className="top content t-8">
+          千行课堂 > 全部资料 > {data.parentStructId > 0 ? `${(dataStructMap[data.parentStructId] || {}).title} >` : ''} {(dataStructMap[data.structId] || {}).title} > {data.title} > <span className="t-1">资料详情</span>
+        </div>
+        {this.renderDetail()}
+        <Contact data={base.contact} />
+        <Footer />
+      </div>
+    );
+  }
+
+  renderDetail() {
+    const { tab, data = {} } = this.state;
+    return [
+      <div className="center">
+        <div className="content">
+          <div className="item-detail">
+            <div className="left">
+              <Assets name="" src={data.cover} />
+              <div className="tag-list">
+                {data.isNovice > 0 && <div className="tag">新手</div>}
+                {data.isOriginal > 0 && <div className="tag">原创</div>}
+              </div>
+            </div>
+            <div style={{ width: 760 }} className="right">
+              <div className="t-1 t-s-20">{data.title}</div>
+              <div className="t-7 m-b-2" dangerouslySetInnerHTML={{ __html: data.content }} />
+              <div className="">
+                <div className="d-i-b t-1">最近更新:</div>
+                <div className="d-i-b t-8">{data.latestTime && formatDate(data.latestTime, 'YYYY-MM-DD HH:mm:ss')}</div>
+              </div>
+              <div className="">
+                <div className="d-i-b t-1">页数:</div>
+                <div className="d-i-b t-8">{data.pages}页</div>
+              </div>
+              <div className="">
+                <div className="d-i-b t-1">格式:</div>
+                <div className="d-i-b t-8">{data.dataType === 'paper' ? '纸质' : 'PDF'}</div>
+              </div>
+              <div className="m-b-1">
+                <div style={{ marginTop: 12 }} className="d-i-b t-1 t-s-16 v-a-t">
+                  价格:
+                </div>
+                <div className="d-i-b t-7 t-s-28 f-w-b"> ¥ {data.price}</div>
+              </div>
+              <div className="action">
+                {!data.have && <Button className="m-r-1" radius size="lager" onClick={() => {
+                  if (data.dataType === 'paper') {
+                    openLink(data.link);
+                  } else {
+                    this.buy();
+                  }
+                }}>
+                  立即购买
+                </Button>}
+                {!data.have && <Button className="m-r-1" theme="default" radius size="lager" onClick={() => this.add()}>
+                  <Assets name="add" />
+                </Button>}
+                {!data.have && <Button theme="default" radius size="lager" onClick={() => openLink(data.resource)}>
+                  预览
+                </Button>}
+                {data.have && <Button className="f-r" theme="default" radius size="lager" onClick={() => openLink(data.resource)}>
+                  查看资料
+                </Button>}
+              </div>
+              {data.have && <Assets className="buyed" width={75} height={75} name="Purchased" />}
+            </div>
+          </div>
+        </div>
+      </div>,
+      <div className="bottom">
+        <div className="content">
+          <Tabs
+            type="full"
+            border
+            active={tab}
+            tabs={[
+              { title: '资料介绍', key: '1' },
+              { title: '作者介绍', key: '2' },
+              { title: '获取方式', key: '3' },
+              { title: 'FAQs', key: '4' },
+              { title: '学员评价', key: '5' },
+            ]}
+          />
+          {this[`renderTab${tab}`]()}
+        </div>
+      </div>,
+    ];
+  }
+
+  renderTab1() {
+    return <div />;
+  }
+
+  renderTab2() {
+    return <div />;
+  }
+
+  renderTab3() {
+    return <div />;
+  }
+
+  renderTab4() {
+    return <div />;
+  }
+
+  renderTab5() {
+    return <div />;
+  }
+}

+ 11 - 0
front/project/www/routes/course/detail/index.js

@@ -0,0 +1,11 @@
+export default {
+  path: '/course/detail/:id',
+  key: 'course-detail',
+  title: '课堂-课程详情',
+  needLogin: false,
+  repeat: true,
+  tab: 'course',
+  component() {
+    return import('./page');
+  },
+};

+ 7 - 0
front/project/www/routes/course/detail/index.less

@@ -0,0 +1,7 @@
+@charset "utf-8";
+
+#course-detail {
+  background: #fff;
+
+
+}

front/project/www/routes/course/dDetail/page.js → front/project/www/routes/course/detail/page.js


+ 2 - 2
front/project/www/routes/course/inside/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/course/inside',
-  key: 'course-inside',
+  path: '/course/experience',
+  key: 'course-experience',
   title: '课堂-心经',
   needLogin: false,
   repeat: true,

+ 1 - 1
front/project/www/routes/course/inside/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#course-inside {
+#course-experience {
   background: #fff;
 
   .content {

front/project/www/routes/course/inside/page.js → front/project/www/routes/course/experience/page.js


+ 2 - 2
front/project/www/routes/course/iDetail/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/course/i-detail',
-  key: 'course-i-detail',
+  path: '/course/experience/detail/:id',
+  key: 'course-experience-detail',
   title: '课堂-心经详情',
   needLogin: false,
   repeat: true,

+ 1 - 1
front/project/www/routes/course/iDetail/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#course-i-detail {
+#course-experience-detail {
   background: #fff;
   min-height: 100%;
 

front/project/www/routes/course/iDetail/page.js → front/project/www/routes/course/experienceDetail/page.js


+ 7 - 6
front/project/www/routes/course/index.js

@@ -1,12 +1,13 @@
 import main from './main';
 import online from './online';
+import detail from './detail';
 import answer from './answer';
 import note from './note';
 import data from './data';
-import single from './single';
-import pDetail from './pDetail';
-import dDetail from './dDetail';
-import inside from './inside';
-import iDetail from './iDetail';
+import vs from './vs';
+import packageDetail from './packageDetail';
+import dataDetail from './dataDetail';
+import experience from './experience';
+import experienceDetail from './experienceDetail';
 
-export default [main, online, answer, note, data, single, pDetail, dDetail, inside, iDetail];
+export default [main, online, detail, answer, note, data, vs, packageDetail, dataDetail, experience, experienceDetail];

+ 7 - 6
front/project/www/routes/course/main/page.js

@@ -7,6 +7,7 @@ import Footer from '../../../components/Footer';
 import { FaqModal } from '../../../components/OtherModal';
 import { CommentFalls, AnswerCarousel, Consultation, Contact } from '../../../components/Other';
 import Button from '../../../components/Button';
+import { User } from '../../../stores/user';
 import { Main } from '../../../stores/main';
 import { Course } from '../../../stores/course';
 import { ServiceKey, ServiceParamMap } from '../../../../Constant';
@@ -70,11 +71,11 @@ export default class extends Page {
           </div>
           <div className="class-list">
             {packages.map(data => {
-              const originMoney = data.courses.reduce((a, y) => a + y.money, 0);
+              const originMoney = data.courses.reduce((a, y) => a + y.price, 0);
               const novice = data.courses.filter(row => row.crowd !== 'novice').length === 0;
               return <div className="class-item">
                 {novice && <Assets width={55} height={52} className="new" name="noviciate" />}
-                <div className="t-s-22 t-4 m-b-5 f-w-b">{data.title}</div>
+                <div className="t-s-22 t-4 m-b-5 f-w-b"><a href={`/course/package/detail/${data.id}`}>{data.title}</a></div>
                 <div className="t-8 m-b-1">{data.description}</div>
                 <div className="t-1 t-s-12">包含课程</div>
                 <div className="m-b-5">
@@ -103,9 +104,9 @@ export default class extends Page {
                 <div className="t-8">
                   总价值: <span className="t-d-l-t">¥{originMoney}</span>
                 </div>
-                <div className="t-1 t-s-18 f-w-b m-b-1">套餐价: ¥{data.money}</div>
+                <div className="t-1 t-s-18 f-w-b m-b-1">套餐价: ¥{data.price}</div>
                 <div className="m-b-5">
-                  <Button size="lager" onClick={() => Order.addCheckout({ productType: 'course_package', productId: data.id }).then(() => linkTo('/cart'))}>立即购买</Button>
+                  <Button size="lager" onClick={() => User.needLogin().then(() => Order.addCheckout({ productType: 'course_package', productId: data.id }).then(() => linkTo('/cart')))}>立即购买</Button>
                 </div>
               </div>;
             })}
@@ -143,8 +144,8 @@ export default class extends Page {
         <AnswerCarousel list={faqs} onFaq={() => this.setState({ showFaq: true, faq: { channel: 'course-index' } })} />
         <Contact data={base.contact} />
         <Footer />
-        <FaqModal show={showFaq} defaultData={faq} />
-      </div>
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false })} />
+      </div >
     );
   }
 }

+ 143 - 72
front/project/www/routes/course/online/page.js

@@ -2,41 +2,135 @@ import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
+import { getMap, formatTreeData } from '@src/services/Tools';
 import Footer from '../../../components/Footer';
+import { FaqModal } from '../../../components/OtherModal';
 import { CommentFalls, AnswerCarousel, Consultation, Contact } from '../../../components/Other';
 import Tabs from '../../../components/Tabs';
 import Filter from '../../../components/Filter';
 import { SingleItem, PackageItem } from '../../../components/Item';
+import { Main } from '../../../stores/main';
+import { Course } from '../../../stores/course';
 
 export default class extends Page {
   initState() {
     return {
-      list: [{}, {}],
-      tab: '1',
-      tab1Filter: { one: '1', two: '1' },
-      tab2Filter: { one: '1' },
+      list: [],
+      tab: 'single',
     };
   }
 
-  onChangeTab(tab) {
-    this.setState({ tab });
+  init() {
+    Main.getPromote()
+      .then(result => {
+        this.setState({ promote: result });
+      });
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+
+    Main.courseStruct().then(result => {
+      const courseStruct = result.map(row => {
+        return {
+          level: row.level,
+          title: `${row.titleZh}${row.titleEn}`,
+          key: `${row.id}`,
+          parentId: row.parentId,
+          isExamination: row.isExamination,
+        };
+      });
+      const courseStructSelect = courseStruct.filter(row => row.level === 1);
+      const packageStructSelect = courseStructSelect.filter(row => row.isExamination);
+      courseStructSelect.unshift({
+        title: '全部',
+        key: '',
+      });
+      packageStructSelect.unshift({
+        title: '全部',
+        key: '',
+      });
+
+      const courseStructTree = formatTreeData(courseStruct, 'key', 'title', 'parentId');
+      const courseStructTreeMap = getMap(courseStructTree, 'key', 'children');
+      this.setState({ courseStructSelect, courseStructTreeMap, packageStructSelect });
+    });
+  }
+
+  initData() {
+    const data = Object.assign(this.state, this.state.search);
+    if (data.order) {
+      data.sortMap = { [data.order]: data.direction };
+    }
+    data.filterMap = this.state.search;
+    this.setState(data);
+
+    const { tab } = this.state;
+    switch (tab) {
+      case 'single':
+        this.refreshSingle();
+        break;
+      case 'package':
+        this.refreshPackage();
+        break;
+      default:
+        break;
+    }
+  }
+
+  refreshSingle() {
+    Main.listFaq({ page: 1, size: 100, channel: 'course-video_index' }).then(result => {
+      this.setState({ faqs: result.list });
+    });
+    Main.listComment({ page: 1, size: 100, channel: 'course-video_index' }).then(result => {
+      this.setState({ coments: result.list });
+    });
+    Course.listVideo(Object.assign({ structId: this.state.search.parentStructId }, this.state.search))
+      .then(result => {
+        this.setState({ list: result.list, total: result.total });
+      });
   }
 
-  onFilter(type, key, value) {
-    this.state[type][key] = value;
-    this.setState(this.state);
+  refreshPackage() {
+    Main.listFaq({ page: 1, size: 100, channel: 'course-package_index' }).then(result => {
+      this.setState({ faqs: result.list });
+    });
+    Main.listComment({ page: 1, size: 100, channel: 'course-package_index' }).then(result => {
+      this.setState({ coments: result.list });
+    });
+    Course.listPackage(Object.assign({}, this.state.search))
+      .then(result => {
+        this.setState({ list: result.list, total: result.total });
+      });
+  }
+
+  onTabChange(tab) {
+    const data = { tab };
+    this.refreshQuery(data);
+  }
+
+  onFilter(key, value) {
+    const { filterMap } = this.state;
+    filterMap[key] = value;
+    this.search(filterMap);
+  }
+
+  onChangePage(page) {
+    this.search({ page }, false);
+    this.initData();
   }
 
   renderView() {
-    const { tab } = this.state;
+    const { number } = this.props.order;
+    const { tab, promote = {}, base = {}, comments, faqs, showFaq, faq } = this.state;
     return (
       <div>
         <div className="top content">
-          <Tabs type="text" active={'1'} tabs={[{ title: '在线课程', key: '1' }, { title: '1v1私教', key: '2' }]} />
+          <Tabs type="text" active={'online'} tabs={[{ title: '在线课程', key: 'online', path: '/course/online' }, { title: '1v1私教', key: 'vs', path: '/course/vs' }]} />
           <div className="f-r">
-            <span className="t-2 m-r-1">优惠活动:2门9折,3门88折,4门及以上85折。</span>
-            <Assets name="cart" />
-            <span className="t-2">( 1 )</span>
+            <span className="t-2 m-r-1">{(promote.video || {}).text ? `优惠活动:${promote.video.text}` : ''}</span>
+            <Assets name="cart" onClick={() => linkTo('cart')} />
+            <span className="t-2">( {number || 0} )</span>
           </div>
         </div>
         <div className="center">
@@ -49,96 +143,73 @@ export default class extends Page {
               width={100}
               border
               active={tab}
-              tabs={[{ title: '单项购买', key: '1' }, { title: '套餐购买', key: '2' }]}
-              onChange={key => this.onChangeTab(key)}
+              tabs={[{ title: '单项购买', key: 'single' }, { title: '套餐购买', key: 'package' }]}
+              onChange={key => this.onTabChange(key)}
             />
             {this[`renderTab${tab}`]()}
           </div>
         </div>
-        <Consultation />
-        <CommentFalls />
-        <AnswerCarousel
-          list={[
-            {
-              question: '如果视频课程到期了却没有听完,没听的课程可以退款么',
-              answer: '不可以的,视频课程为虚拟商品,购买成功后不接受退换。',
-            },
-            {
-              question: '学习过程中可以申请停课么?',
-              answer: '每个商品均有1次申请停课的机会,最长停课30天,停课时间不计入使用有效期内。',
-            },
-            {
-              question: '我需要一个整体的GMAT备考计划,报课程的话可以提供么?',
-              answer:
-                '报语文全科“系统授课”的同学会赠送价值900元课前辅导,老师语音一对一与同学交流沟通,为同学提供针对性建议和详细的备考计划。',
-            },
-            {
-              question: '如果视频课程到期了却没有听完,没听的课程可以退款么',
-              answer: '不可以的,视频课程为虚拟商品,购买成功后不接受退换。',
-            },
-          ]}
-        />
-        <Contact />
+        <Consultation data={base.contact} />
+        <CommentFalls list={comments} />
+        <AnswerCarousel list={faqs} onFaq={() => this.setState({ showFaq: true, faq: { channel: tab === 'single' ? 'course-video_index' : 'course-package_index' } })} />
+        <Contact data={base.contact} />
         <Footer />
+        <FaqModal show={showFaq} defaultData={faq} onCancel={() => this.setState({ showFaq: false })} onConfirm={() => this.setState({ showFaq: false })} />
       </div>
     );
   }
 
-  renderTab1() {
-    const { tab1Filter, list = [] } = this.state;
+  renderTabsingle() {
+    const { filterMap, list = [], courseStructSelect, courseStructTreeMap = {} } = this.state;
+    const child = (courseStructTreeMap[filterMap.parentStructId] || []).map(row => {
+      return {
+        title: row.title,
+        key: row.key,
+      };
+    });
+    child.unshift({
+      title: '全部',
+      key: '',
+    });
     return [
       <Filter
-        filter={tab1Filter}
+        filter={filterMap}
         list={[
           {
-            key: 'one',
-            children: [
-              { key: '1', title: '全部' },
-              { key: '2', title: '长难句' },
-              { key: '3', title: '语文Verbal' },
-              { key: '4', title: '数学Quant' },
-            ],
+            key: 'parentStructId',
+            children: courseStructSelect,
           },
           {
-            key: 'two',
-            children: [
-              { key: '1', title: '全部' },
-              { key: '2', title: '语法SC' },
-              { key: '3', title: '阅读RC' },
-              { key: '4', title: '逻辑RC' },
-            ],
+            key: 'structId',
+            children: child,
           },
         ]}
-        onFilter={(key, value) => this.onFilter('tab1Filter', key, value)}
+        onFilter={(key, value) => this.onFilter(key, value)}
       />,
       <div className="tab-1-list">
-        {list.map(() => {
-          return <SingleItem />;
+        {list.map((data) => {
+          return <SingleItem data={data} />;
         })}
       </div>,
     ];
   }
 
-  renderTab2() {
-    const { tab2Filter, list = [] } = this.state;
+  renderTabpackage() {
+    const { filterMap, list = [], packageStructSelect } = this.state;
     return [
       <Filter
-        filter={tab2Filter}
+        filter={filterMap}
         list={[
           {
-            key: 'one',
-            children: [
-              { key: '1', title: '全部' },
-              { key: '2', title: '语文Verbal' },
-              { key: '3', title: '数学Quant' },
-            ],
+            key: 'structId',
+            children: packageStructSelect,
           },
         ]}
-        onFilter={(key, value) => this.onFilter('tab2Filter', key, value)}
+        onFilter={(key, value) => this.onFilter(key, value)}
       />,
       <div className="tab-2-list">
-        {list.map(() => {
-          return <PackageItem />;
+        {list.map((data) => {
+          return <PackageItem data={data} />;
         })}
       </div>,
     ];

+ 0 - 112
front/project/www/routes/course/pDetail/page.js

@@ -1,112 +0,0 @@
-import React from 'react';
-import './index.less';
-import Assets from '@src/components/Assets';
-import Page from '@src/containers/Page';
-import Button from '../../../components/Button';
-import { SingleItem } from '../../../components/Item';
-import Footer from '../../../components/Footer';
-import { Contact } from '../../../components/Other';
-
-export default class extends Page {
-  constructor(props) {
-    props.size = 10;
-    super(props);
-  }
-
-  initState() {
-    return {
-      filterMap: {},
-      list: [],
-      tab: '1',
-    };
-  }
-
-  onTabChange(tab) {
-    const data = { tab };
-    this.refreshQuery(data);
-  }
-
-  onFilter(value) {
-    this.search(value, false);
-    this.initData();
-  }
-
-  onSearch(value) {
-    this.search({ keyword: value }, false);
-    this.initData();
-  }
-
-  onAction() {}
-
-  delAsk(id) {
-    console.log(id);
-  }
-
-  renderView() {
-    return (
-      <div>
-        <div className="top content t-8">
-          千行课堂 > 全部课程 > OG20综合刷题 > 课时3 > <span className="t-1">套餐详情</span>
-        </div>
-        <div className="center content">
-          <div className="t-1 t-s-20 m-b-2">
-            OG20综合刷题——第3课时:XXXXXXX
-            <div className="action f-r">
-              <Button className="m-r-1" radius size="lager">
-                立即购买
-              </Button>
-              <Button theme="default" radius size="lager">
-                <Assets name="add" />
-              </Button>
-            </div>
-          </div>
-          <div className="t-7 t-s-18 f-w-b m-b-1">套餐价: ¥ 15000</div>
-          <div className="t-1 t-s-16 desc">
-            针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练。针对 0G20 的集中训练针对
-            0G20 的集中训练针对 0G20 的集,中训练针对 0G20 的集中训练针对 0G20 的集中训练针对。 0G20 的集中训练针对
-            0,G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训。练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20
-            的集中训练。针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练。
-          </div>
-          <div className="main-title">包含课程</div>
-          <div className="list">
-            <SingleItem show />
-            <SingleItem show />
-            <SingleItem show />
-          </div>
-          <div className="main-title">配套服务</div>
-          <div className="list">
-            <div className="other-item d-i-b">
-              <Assets name="" />
-            </div>
-            <div className="other-item d-i-b">
-              <Assets name="" />
-            </div>
-            <div className="other-item d-i-b">
-              <Assets name="" />
-            </div>
-          </div>
-          <div className="main-title">赠送服务</div>
-          <div className="list">
-            <div className="service-item d-i-b">
-              <Assets name="mokao" />
-              <div className="t t-13 t-s-20 f-w-b">机经</div>
-              <div className="t-13">1个月</div>
-            </div>
-            <div className="service-item d-i-b">
-              <Assets name="VIP_1" />
-              <div className="t t-13 t-s-20 f-w-b">VIP</div>
-              <div className="t-13">3个月</div>
-            </div>
-            <div className="service-item d-i-b">
-              <Assets name="jijing" />
-              <div className="t t-13 t-s-20 f-w-b">模考</div>
-              <div className="t-13">1个月</div>
-            </div>
-          </div>
-        </div>
-        <Contact />
-        <Footer />
-      </div>
-    );
-  }
-}

+ 2 - 2
front/project/www/routes/course/pDetail/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/course/p-detail',
-  key: 'course-p-detail',
+  path: '/course/package/detail/:id',
+  key: 'course-package-detail',
   title: '课堂-套餐',
   needLogin: false,
   repeat: true,

+ 1 - 1
front/project/www/routes/course/pDetail/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#course-p-detail {
+#course-package-detail {
   background: #fff;
   min-height: 100%;
 

+ 97 - 0
front/project/www/routes/course/packageDetail/page.js

@@ -0,0 +1,97 @@
+import React from 'react';
+import './index.less';
+import Assets from '@src/components/Assets';
+import Page from '@src/containers/Page';
+import { getMap } from '@src/services/Tools';
+import Button from '../../../components/Button';
+import { SingleItem } from '../../../components/Item';
+import Footer from '../../../components/Footer';
+import { Contact } from '../../../components/Other';
+import { Main } from '../../../stores/main';
+import { Course } from '../../../stores/course';
+import { ServiceKey, ServiceParamMap } from '../../../../Constant';
+
+const serviceIconMap = {
+  textbook: 'mokao',
+  vip: 'VIP_1',
+  qx_cat: 'jijing',
+};
+
+export default class extends Page {
+  constructor(props) {
+    props.size = 10;
+    super(props);
+  }
+
+  init() {
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+  }
+
+  initData() {
+    const { id } = this.params;
+    Course.getPackage(id)
+      .then(result => {
+        this.setState({ data: result });
+      });
+  }
+
+  renderView() {
+    const { data = {}, base = {} } = this.state;
+    return (
+      <div>
+        <div className="top content t-8">
+          千行课堂 > 全部套餐 > {data.title} > <span className="t-1">套餐详情</span>
+        </div>
+        <div className="center content">
+          <div className="t-1 t-s-20 m-b-2">
+            {data.title}
+            <div className="action f-r">
+              <Button className="m-r-1" radius size="lager" onClick={() => this.buy()}>
+                立即购买
+              </Button>
+              <Button theme="default" radius size="lager" onClick={() => this.add()}>
+                <Assets name="add" />
+              </Button>
+            </div>
+          </div>
+          <div className="t-7 t-s-18 f-w-b m-b-1">套餐价: ¥ {data.price}</div>
+          <div className="t-1 t-s-16 desc">{data.description}</div>
+          <div className="main-title">包含课程</div>
+          <div className="list">
+            {(data.courses || []).map(item => {
+              return <SingleItem noAction data={item} />;
+            })}
+          </div>
+          <div className="main-title">配套服务</div>
+          <div className="list">
+            <div className="other-item d-i-b">
+              <Assets name="" />
+            </div>
+            <div className="other-item d-i-b">
+              <Assets name="" />
+            </div>
+          </div>
+          <div className="main-title">赠送服务</div>
+          <div className="list">
+            {data.gift &&
+              ServiceKey.map(row => {
+                if (!data.gift[row.value]) return null;
+                const list = ServiceParamMap[row.value];
+                const map = getMap(list || [], 'value', 'label');
+                return <div className="service-item d-i-b">
+                  <Assets name={serviceIconMap[row.value]} />
+                  <div className="t t-13 t-s-20 f-w-b">{row.label}</div>
+                  <div className="t-13">{list ? map[data.gift[row.value]] : data.gift[row.value]}</div>
+                </div>;
+              })}
+          </div>
+        </div>
+        <Contact data={base.contact} />
+        <Footer />
+      </div>
+    );
+  }
+}

+ 0 - 171
front/project/www/routes/course/single/page.js

@@ -1,171 +0,0 @@
-import React from 'react';
-import './index.less';
-import { Icon } from 'antd';
-import Page from '@src/containers/Page';
-import Assets from '@src/components/Assets';
-import Footer from '../../../components/Footer';
-import { Contact } from '../../../components/Other';
-import Tabs from '../../../components/Tabs';
-import Button from '../../../components/Button';
-
-export default class extends Page {
-  initState() {
-    return {
-      tab: '1',
-      key: '1',
-    };
-  }
-
-  onChangeTab(tab) {
-    this.setState({ tab });
-  }
-
-  onChangeItem(key) {
-    this.setState({ key });
-  }
-
-  renderView() {
-    return (
-      <div>
-        <div className="top content">
-          <Tabs type="text" active={'2'} tabs={[{ title: '在线课程', key: '1' }, { title: '1v1私教', key: '2' }]} />
-          <div className="f-r">
-            <span className="t-2 m-r-1">优惠活动:2门9折,3门88折,4门及以上85折。</span>
-            <Assets name="cart" />
-            <span className="t-2">( 1 )</span>
-          </div>
-        </div>
-        {this.renderDetail()}
-        <Contact />
-        <Footer />
-      </div>
-    );
-  }
-
-  renderDetail() {
-    const { tab, key } = this.state;
-    return [
-      <div className="center">
-        <div className="content">
-          <Assets className="m-b-2" width={1140} name="s" />
-          <div className="item-list">
-            <div className={`item ${key === '1' ? 'active' : ''}`} onClick={() => this.onChangeItem('1')}>
-              <Assets name="xinshoufudao" />
-              <div className="t-1 t-s-20 f-w-b">新手辅导</div>
-              <div className="t-2">GMAT 全面了解,定制学习计划</div>
-            </div>
-            <div className={`item ${key === '2' ? 'active' : ''}`} onClick={() => this.onChangeItem('2')}>
-              <Assets name="zhenduanfudao" />
-              <div className="t-1 t-s-20 f-w-b">诊断辅导</div>
-              <div className="t-2">复习效果不理想,制定突破计划</div>
-            </div>
-            <div className={`item ${key === '3' ? 'active' : ''}`} onClick={() => this.onChangeItem('3')}>
-              <Assets name="xitongshouke" />
-              <div className="t-1 t-s-20 f-w-b">系统授课</div>
-              <div className="t-2">全面知识体系讲解,提升实战能力</div>
-            </div>
-            <div className={`item ${key === '4' ? 'active' : ''}`} onClick={() => this.onChangeItem('4')}>
-              <Assets name="dayi" />
-              <div className="t-1 t-s-20 f-w-b">答疑课</div>
-              <div className="t-2">GMAT 全面了解,定制学习计划</div>
-            </div>
-          </div>
-          <div className="item-detail">
-            <div className="left">
-              <Assets name="" />
-            </div>
-            <div className="right">
-              <div className="t-1 t-s-16 m-b-2">
-                针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练。针对 0G20
-                的集中训练针对 0G20 的集中训练针对 0G20 的集,中训练针对 0G20 的集中训练针 对 0G20 的集中训练针对。 0G20
-                的集中训练针对 0,G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训。练针对 0G20 的集中训练针对 0G20
-                的集中训练针对 0G20 的集中训练。针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20
-                的集中训练。
-              </div>
-              <div className="m-b-5">
-                <div style={{ width: 120 }} className="d-i-b t-2">
-                  适合人群
-                </div>
-                <div className="d-i-b t-1">刚接触GMAT考试的考 ⽣生或需要科学备考计划 的考⽣生</div>
-              </div>
-              <div className="m-b-5">
-                <div style={{ width: 120 }} className="d-i-b t-2">
-                  课时数
-                </div>
-                <div className="d-i-b t-1">1课时,时⻓长1-1.5⼩小时</div>
-              </div>
-              <div className="m-b-5">
-                <div style={{ width: 120 }} className="d-i-b t-2">
-                  授课流程
-                </div>
-                <div className="d-i-b t-1">购买课时 → 收集信息 → 预约时间 → 正式辅导</div>
-              </div>
-              <div className="m-b-5">
-                <div style={{ width: 120 }} className="d-i-b t-2">
-                  课程有效期
-                </div>
-                <div className="d-i-b t-1">30天</div>
-              </div>
-              <div className="m-b-5 input">
-                <div style={{ width: 120 }} className="d-i-b t-2">
-                  购买课时
-                </div>
-                <div className="d-i-b t-1">
-                  <input style={{ width: 32 }} className="m-l-5 t-c" />
-                  <Icon className="up" type="caret-up" />
-                  <Icon className="down" type="caret-down" />
-                </div>
-              </div>
-              <div className="m-b-5">
-                <div style={{ width: 120 }} className="d-i-b t-2">
-                  课程总价
-                </div>
-                <div className="d-i-b t-7 t-s-20 f-w-b"> ¥ 15000</div>
-              </div>
-              <div className="action">
-                <Button className="m-r-1" radius size="lager">
-                  立即购买
-                </Button>
-                <Button theme="default" radius size="lager">
-                  <Assets name="add" />
-                </Button>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>,
-      <div className="bottom">
-        <div className="content">
-          <Tabs
-            type="full"
-            border
-            active={tab}
-            tabs={[
-              { title: '老师资料', key: '1' },
-              { title: '咨询方式', key: '2' },
-              { title: 'FAQs', key: '3' },
-              { title: '学员评价', key: '4' },
-            ]}
-          />
-          {this[`renderTab${tab}`]()}
-        </div>
-      </div>,
-    ];
-  }
-
-  renderTab1() {
-    return <div />;
-  }
-
-  renderTab2() {
-    return <div />;
-  }
-
-  renderTab3() {
-    return <div />;
-  }
-
-  renderTab4() {
-    return <div />;
-  }
-}

+ 2 - 2
front/project/www/routes/course/single/index.js

@@ -1,6 +1,6 @@
 export default {
-  path: '/course/single',
-  key: 'course-single',
+  path: '/course/vs',
+  key: 'course-vs',
   title: '课堂-1v1',
   needLogin: false,
   repeat: true,

+ 1 - 1
front/project/www/routes/course/single/index.less

@@ -1,6 +1,6 @@
 @charset "utf-8";
 
-#course-single {
+#course-vs {
   .content {
     width: 1200px !important;
   }

+ 247 - 0
front/project/www/routes/course/vs/page.js

@@ -0,0 +1,247 @@
+import React from 'react';
+import './index.less';
+import { Icon } from 'antd';
+import Page from '@src/containers/Page';
+import Assets from '@src/components/Assets';
+import Footer from '../../../components/Footer';
+import { Contact } from '../../../components/Other';
+import Tabs from '../../../components/Tabs';
+import Button from '../../../components/Button';
+import { Main } from '../../../stores/main';
+import { Order } from '../../../stores/order';
+import { User } from '../../../stores/user';
+import { Course } from '../../../stores/course';
+import { CourseVsType } from '../../../../Constant';
+
+export default class extends Page {
+  initState() {
+    return {
+      info: '1',
+      number: 0,
+    };
+  }
+
+  init() {
+    this.courseVsMap = {};
+    this.faqMap = {};
+    this.faqs = [];
+    this.commentMap = {};
+    this.comments = [];
+    this.promote = [];
+    this.teacherMap = {};
+    Main.getPromote()
+      .then(result => {
+        this.promote = result.vs_list || [];
+        this.setState({ promote: result });
+      });
+    Main.getBase()
+      .then(result => {
+        this.setState({ base: result });
+      });
+  }
+
+  initData() {
+    Course.allVs()
+      .then(result => {
+        result.forEach((row) => {
+          this.courseVsMap[row.vsType] = row;
+        });
+        this.onChangeItem(CourseVsType[0].value);
+      });
+  }
+
+  onChangeTab(tab) {
+    this.setState({ tab });
+  }
+
+  onChangeItem(key) {
+    const item = this.courseVsMap[key];
+    if (!this.faqs) {
+      Main.listFaq({ page: 1, size: 100, channel: 'course-vs_index' }).then(result => {
+        this.faqs = result.list;
+        this.setState({ faqs: result.list });
+      });
+    }
+    if (!this.commentMap[key]) {
+      Main.listComment({ page: 1, size: 100, channel: 'course-vs', position: item.id }).then(result => {
+        this.commentMap[key] = result.list;
+        this.setState({ comments: result.list });
+      });
+    }
+    if (!this.teacherMap[key]) {
+      Course.allTeacher(item.id).then(result => {
+        this.teacherMap[key] = result.list;
+        this.setState({ teachers: result.list });
+      });
+    }
+    this.setState({ key, data: item, faqs: this.faqs, comments: this.commentMap[key], teachers: this.teacherMap[key] });
+    this.changeNumber(item.minNumber || 1);
+  }
+
+  changeNumber(number) {
+    const { data } = this.state;
+    let price = data.price * number;
+    let max = 0;
+    let maxIndex = -1;
+    this.promote.forEach((row, i) => {
+      if (row.number <= number) {
+        if (row.number > max) {
+          max = number;
+          maxIndex = i;
+        }
+      }
+    });
+    if (maxIndex >= 0) {
+      price *= this.promote[maxIndex].percent / 100;
+    }
+    this.setState({ number, price });
+  }
+
+  buy() {
+    const { data } = this.props;
+    Order.speedPay({ productType: 'course', productId: data.id })
+      .then(result => {
+        User.needPay(result)
+          .then(() => {
+            linkTo('/my/course');
+          });
+      });
+  }
+
+  add() {
+    const { data } = this.props;
+    Order.addCheckout({ productType: 'course', productId: data.id })
+      .then(() => {
+        this.setState({ add: true });
+      });
+  }
+
+  renderView() {
+    const { number } = this.props.order;
+    const { promote = {}, base = {} } = this.state;
+    return (
+      <div>
+        <div className="top content">
+          <Tabs type="text" active={'vs'} tabs={[{ title: '在线课程', key: 'online', path: '/course/online' }, { title: '1v1私教', key: 'vs', path: '/course/vs' }]} />
+          <div className="f-r">
+            <span className="t-2 m-r-1">{(promote.vs || {}).text ? `优惠活动:${promote.vs.text}` : ''}</span>
+            <Assets name="cart" onClick={() => linkTo('/cart')} />
+            <span className="t-2">( {number || 0} )</span>
+          </div>
+        </div>
+        {this.renderDetail()}
+        <Contact data={base.contact} />
+        <Footer />
+      </div>
+    );
+  }
+
+  renderDetail() {
+    const { info, key, data, number, price } = this.state;
+    return [
+      <div className="center">
+        <div className="content">
+          <Assets className="m-b-2" width={1140} name="s" />
+          <div className="item-list">
+            {CourseVsType.map(t => {
+              const course = this.courseVsMap[t.value] || {};
+              return <div className={`item ${key === t.value ? 'active' : ''}`} onClick={() => this.onChangeItem(t.value)}>
+                <Assets name={t.icon} />
+                <div className="t-1 t-s-20 f-w-b">{course.title}</div>
+                <div className="t-2">{course.comment}</div>
+              </div>;
+            })}
+          </div>
+          <div className="item-detail">
+            <div className="left">
+              <Assets name={data.cover} />
+            </div>
+            <div className="right">
+              <div className="t-1 t-s-16 m-b-2" dangerouslySetInnerHTML={{ __html: data.serviceContent }} />
+              <div className="m-b-5">
+                <div style={{ width: 120 }} className="d-i-b t-2">
+                  适合人群
+                </div>
+                <div className="d-i-b t-1" dangerouslySetInnerHTML={{ __html: data.crowdContent }} />
+              </div>
+              <div className="m-b-5">
+                <div style={{ width: 120 }} className="d-i-b t-2">
+                  课时数
+                </div>
+                <div className="d-i-b t-1" dangerouslySetInnerHTML={{ __html: data.courseNoContent }} />
+              </div>
+              <div className="m-b-5">
+                <div style={{ width: 120 }} className="d-i-b t-2">
+                  授课流程
+                </div>
+                <div className="d-i-b t-1" dangerouslySetInnerHTML={{ __html: data.processContent }} />
+              </div>
+              <div className="m-b-5">
+                <div style={{ width: 120 }} className="d-i-b t-2">
+                  课程有效期
+                </div>
+                <div className="d-i-b t-1">{data.expirePreDays}天/10课时</div>
+              </div>
+              <div className="m-b-5 input">
+                <div style={{ width: 120 }} className="d-i-b t-2">
+                  购买课时
+                </div>
+                <div className="d-i-b t-1">
+                  <input value={number} style={{ width: 32 }} className="m-l-5 t-c" />
+                  <Icon className="up" type="caret-up" onClick={() => number < data.maxNumber && this.setState({ number: number + 1 })} />
+                  <Icon className="down" type="caret-down" onClick={() => number !== 1 && number > data.minNumber && this.setState({ number: number - 1 })} />
+                </div>
+              </div>
+              <div className="m-b-5">
+                <div style={{ width: 120 }} className="d-i-b t-2">
+                  课程总价
+                </div>
+                <div className="d-i-b t-7 t-s-20 f-w-b"> ¥ {price}</div>
+              </div>
+              <div className="action">
+                <Button className="m-r-1" radius size="lager" onClick={() => this.buy()}>
+                  立即购买
+                </Button>
+                <Button theme="default" radius size="lager" onClick={() => this.add()}>
+                  <Assets name="add" />
+                </Button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>,
+      <div className="bottom">
+        <div className="content">
+          <Tabs
+            type="full"
+            border
+            active={info}
+            tabs={[
+              { title: '老师资料', key: '1' },
+              { title: '咨询方式', key: '2' },
+              { title: 'FAQs', key: '3' },
+              { title: '学员评价', key: '4' },
+            ]}
+          />
+          {this[`renderTab${info}`]()}
+        </div>
+      </div>,
+    ];
+  }
+
+  renderTab1() {
+    return <div />;
+  }
+
+  renderTab2() {
+    return <div />;
+  }
+
+  renderTab3() {
+    return <div />;
+  }
+
+  renderTab4() {
+    return <div />;
+  }
+}

+ 1 - 1
front/project/www/routes/my/course/page.js

@@ -833,7 +833,7 @@ class CourseOnline extends Component {
                 </div>
                 <div className="d-i-b">
                   <div className="t-2">总时长</div>
-                  <div className="t-1 t-s-16">{formatSeconds(data.courseTime)}</div>
+                  <div className="t-1 t-s-16">{formatSeconds(data.time || data.courseTime)}</div>
                 </div>
               </div>
             </div>

+ 6 - 2
front/project/www/stores/course.js

@@ -52,8 +52,12 @@ export default class CourseStore extends BaseStore {
     return this.apiPost('/course/data/view', { id });
   }
 
-  historyData(dataId) {
-    return this.apiGet('/course/data/history', { dataId });
+  historyData({ page, size, dataId }) {
+    return this.apiGet('/course/data/history', { page, size, dataId });
+  }
+
+  allTeacher(courseId) {
+    return this.apiGet('/course/teacher/all', { courseId });
   }
 
   listExperience({ page, size, perpareStatus, experienceDay, experienceScore, experiencePercent, order, direction }) {

+ 1 - 1
front/project/www/stores/user.js

@@ -123,7 +123,7 @@ export default class UserStore extends BaseStore {
   }
 
   closeLogin(err) {
-    this.setState({ needLogin: !!err });
+    this.setState({ needLogin: false });
     if (err) {
       if (this.failCB) this.failCB();
     } else if (this.successCB) this.successCB();

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

@@ -37,6 +37,12 @@ public class Course implements Serializable {
     private Integer noNumber;
 
     /**
+     * 课时时长
+     */
+    @Column(name = "`time`")
+    private Integer time;
+
+    /**
      * 1vs1课程类型
      */
     @Column(name = "`vs_type`")
@@ -311,6 +317,24 @@ public class Course implements Serializable {
     }
 
     /**
+     * 获取课时时长
+     *
+     * @return time - 课时时长
+     */
+    public Integer getTime() {
+        return time;
+    }
+
+    /**
+     * 设置课时时长
+     *
+     * @param time 课时时长
+     */
+    public void setTime(Integer time) {
+        this.time = time;
+    }
+
+    /**
      * 获取1vs1课程类型
      *
      * @return vs_type - 1vs1课程类型
@@ -889,6 +913,7 @@ public class Course implements Serializable {
         sb.append(", parentStructId=").append(parentStructId);
         sb.append(", courseModule=").append(courseModule);
         sb.append(", noNumber=").append(noNumber);
+        sb.append(", time=").append(time);
         sb.append(", vsType=").append(vsType);
         sb.append(", videoType=").append(videoType);
         sb.append(", extend=").append(extend);
@@ -985,6 +1010,16 @@ public class Course implements Serializable {
         }
 
         /**
+         * 设置课时时长
+         *
+         * @param time 课时时长
+         */
+        public Builder time(Integer time) {
+            obj.setTime(time);
+            return this;
+        }
+
+        /**
          * 设置1vs1课程类型
          *
          * @param vsType 1vs1课程类型

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

@@ -10,6 +10,7 @@
     <result column="parent_struct_id" jdbcType="INTEGER" property="parentStructId" />
     <result column="course_module" jdbcType="VARCHAR" property="courseModule" />
     <result column="no_number" jdbcType="INTEGER" property="noNumber" />
+    <result column="time" jdbcType="INTEGER" property="time" />
     <result column="vs_type" jdbcType="VARCHAR" property="vsType" />
     <result column="video_type" jdbcType="VARCHAR" property="videoType" />
     <result column="extend" jdbcType="VARCHAR" property="extend" />
@@ -52,11 +53,11 @@
     <!--
       WARNING - @mbg.generated
     -->
-    `id`, `struct_id`, `parent_struct_id`, `course_module`, `no_number`, `vs_type`, `video_type`, 
-    `extend`, `title`, `comment`, `crowd`, `price`, `teacher`, `cover`, `min_number`, 
-    `max_number`, `expire_pre_days`, `expire_days`, `ask_extend_days`, `use_expire_days`, 
-    `wechat_avatar`, `trail_number`, `sale_number`, `package_sale_number`, `create_time`, 
-    `update_time`
+    `id`, `struct_id`, `parent_struct_id`, `course_module`, `no_number`, `time`, `vs_type`, 
+    `video_type`, `extend`, `title`, `comment`, `crowd`, `price`, `teacher`, `cover`, 
+    `min_number`, `max_number`, `expire_pre_days`, `expire_days`, `ask_extend_days`, 
+    `use_expire_days`, `wechat_avatar`, `trail_number`, `sale_number`, `package_sale_number`, 
+    `create_time`, `update_time`
   </sql>
   <sql id="Blob_Column_List">
     <!--

+ 1 - 0
server/data/src/main/resources/db/migration/V1__init_table.sql

@@ -52,6 +52,7 @@ CREATE TABLE course (
   parent_struct_id int(11) unsigned NOT NULL DEFAULT '0' COMMENT '上级节点id,0为无上级',
   course_module varchar(20) NOT NULL DEFAULT '' COMMENT '课程模块',
   no_number int(11) unsigned NOT NULL DEFAULT '0' COMMENT '课时数',
+  time int(11) unsigned NOT NULL DEFAULT '0' COMMENT '课时时长',
   vs_type varchar(20) NOT NULL DEFAULT '' COMMENT '1vs1课程类型',
   video_type varchar(20) NOT NULL DEFAULT '' COMMENT '视频课程类型',
   extend varchar(20) NOT NULL DEFAULT '' COMMENT '从struct上继承extend',

+ 29 - 2
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -128,6 +128,14 @@ public class CourseController {
         // 评论
         Map<Object, Collection<Comment>> commentMap = commentService.groupByPosition("course-video", ids, 1);
         Transform.combine(pr, commentMap, CourseListDto.class, "id", "comments", CommentExtendDto.class);
+        // 已购买
+
+
+        // 添加购物车
+
+
+        // 优质问答
+
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
@@ -157,6 +165,11 @@ public class CourseController {
         List<Faq> faqList = faqService.list(1, 10, "course-"+course.getCourseModule(), course.getId().toString());
         dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
 
+        // 已购买
+
+        // 购物车
+
+        // 优质问答
         return ResponseHelp.success(dto);
     }
 
@@ -256,6 +269,8 @@ public class CourseController {
         Map<Object, Collection<Comment>> commentMap = commentService.groupByPosition("course-package", ids, 1);
         Transform.combine(pr, commentMap, CourseListDto.class, "id", "comments", CommentExtendDto.class);
 
+        // 购物车
+
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
@@ -280,6 +295,8 @@ public class CourseController {
         List<Faq> faqList = faqService.list(1, 10, "course-package", coursePackage.getId().toString());
         dto.setFaqs(Transform.convert(faqList, FaqExtendDto.class));
 
+        // 购物车
+
         return ResponseHelp.success(dto);
     }
 
@@ -304,6 +321,11 @@ public class CourseController {
         Map<Object, Collection<Comment>> commentMap = commentService.groupByPosition("course_data", ids, 1);
         Transform.combine(pr, commentMap, CourseListDto.class, "id", "comments", CommentExtendDto.class);
 
+        // 已购买
+
+
+        // 添加购物车
+
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
 
@@ -335,6 +357,11 @@ public class CourseController {
             dto.setSubscribe(subscribe != null);
         }
 
+        // 已购买
+
+
+        // 添加购物车
+
         return ResponseHelp.success(dto);
     }
 
@@ -359,9 +386,9 @@ public class CourseController {
         return ResponseHelp.success(p, page, size, p.getTotal());
     }
 
-    @RequestMapping(value = "/teacher/list", method = RequestMethod.GET)
+    @RequestMapping(value = "/teacher/all", method = RequestMethod.GET)
     @ApiOperation(value = "资料列表", httpMethod = "GET")
-    public Response<List<CourseTeacher>> listTeacher(
+    public Response<List<CourseTeacher>> allTeacher(
             @RequestParam(required = false) Integer courseId,
             HttpSession session) {
 

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

@@ -1551,8 +1551,8 @@ public class MyController {
         return ResponseHelp.success(true);
     }
 
-    @RequestMapping(value = "/data/subscribe", method = RequestMethod.GET)
-    @ApiOperation(value = "资料订阅", httpMethod = "GET")
+    @RequestMapping(value = "/data/subscribe", method = RequestMethod.POST)
+    @ApiOperation(value = "资料订阅", httpMethod = "POST")
     public Response<Boolean> subscribeData(@RequestBody @Validated DataSubscribeDto dto) {
         User user = (User) shiroHelp.getLoginUser();
 

+ 1 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java

@@ -103,6 +103,7 @@ public class OrderController {
     public Response<Integer> addCheckout(@RequestBody @Validated RecordAddDto dto, HttpServletRequest request)  {
         User user = (User) shiroHelp.getLoginUser();
         UserOrderCheckout checkout = Transform.dtoToEntity(dto);
+        checkout.setUserId(user.getId());
         int number = orderFlowService.addCheckout(user.getId(), checkout);
         return ResponseHelp.success(number);
     }

+ 61 - 1
server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseExtendDto.java

@@ -21,14 +21,26 @@ public class CourseExtendDto {
 
     private String teacher;
 
+    private Integer noNumber;
+
+    private Integer time;
+
     private String wechatAvatar;
 
+    private String cover;
+
+    private String crowd;
+
     private BigDecimal price;
 
-    private Integer noNumber;
+    private Integer askNumber;
 
     private Integer expireDays;
 
+    private Integer useExpireDays;
+
+    private Integer trailNumber;
+
     public Integer getId() {
         return id;
     }
@@ -116,4 +128,52 @@ public class CourseExtendDto {
     public void setVsType(String vsType) {
         this.vsType = vsType;
     }
+
+    public Integer getTrailNumber() {
+        return trailNumber;
+    }
+
+    public void setTrailNumber(Integer trailNumber) {
+        this.trailNumber = trailNumber;
+    }
+
+    public String getCover() {
+        return cover;
+    }
+
+    public void setCover(String cover) {
+        this.cover = cover;
+    }
+
+    public String getCrowd() {
+        return crowd;
+    }
+
+    public void setCrowd(String crowd) {
+        this.crowd = crowd;
+    }
+
+    public Integer getTime() {
+        return time;
+    }
+
+    public void setTime(Integer time) {
+        this.time = time;
+    }
+
+    public Integer getAskNumber() {
+        return askNumber;
+    }
+
+    public void setAskNumber(Integer askNumber) {
+        this.askNumber = askNumber;
+    }
+
+    public Integer getUseExpireDays() {
+        return useExpireDays;
+    }
+
+    public void setUseExpireDays(Integer useExpireDays) {
+        this.useExpireDays = useExpireDays;
+    }
 }

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

@@ -35,6 +35,10 @@ public class CourseDataListDto {
 
     private Integer saleNumber;
 
+    private Boolean have;
+
+    private Boolean add;
+
     private Collection<CommentExtendDto> comments;
 
     public Integer getId() {
@@ -148,4 +152,20 @@ public class CourseDataListDto {
     public void setComments(Collection<CommentExtendDto> comments) {
         this.comments = comments;
     }
+
+    public Boolean getHave() {
+        return have;
+    }
+
+    public void setHave(Boolean have) {
+        this.have = have;
+    }
+
+    public Boolean getAdd() {
+        return add;
+    }
+
+    public void setAdd(Boolean add) {
+        this.add = add;
+    }
 }

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

@@ -25,6 +25,10 @@ public class CourseListDto {
 
     private String title;
 
+    private Integer noNumber;
+
+    private Integer time;
+
     private String crowd;
 
     private BigDecimal price;
@@ -39,6 +43,28 @@ public class CourseListDto {
 
     private Integer useExpireDays;
 
+    private Integer minNumber;
+
+    private Integer maxNumber;
+
+    private String serviceContent;
+
+    private String courseNoContent;
+
+    private String crowdContent;
+
+    private String messageContent;
+
+    private String processContent;
+
+    private String wechatAvatar;
+
+    private Boolean have;
+
+    private Boolean add;
+
+    private Integer askNumber;
+
     private Collection<CommentExtendDto> comments;
 
     public Integer getId() {
@@ -168,4 +194,108 @@ public class CourseListDto {
     public void setExpirePreDays(Integer expirePreDays) {
         this.expirePreDays = expirePreDays;
     }
+
+    public Boolean getHave() {
+        return have;
+    }
+
+    public void setHave(Boolean have) {
+        this.have = have;
+    }
+
+    public Boolean getAdd() {
+        return add;
+    }
+
+    public void setAdd(Boolean add) {
+        this.add = add;
+    }
+
+    public Integer getAskNumber() {
+        return askNumber;
+    }
+
+    public void setAskNumber(Integer askNumber) {
+        this.askNumber = askNumber;
+    }
+
+    public Integer getMinNumber() {
+        return minNumber;
+    }
+
+    public void setMinNumber(Integer minNumber) {
+        this.minNumber = minNumber;
+    }
+
+    public Integer getMaxNumber() {
+        return maxNumber;
+    }
+
+    public void setMaxNumber(Integer maxNumber) {
+        this.maxNumber = maxNumber;
+    }
+
+    public String getServiceContent() {
+        return serviceContent;
+    }
+
+    public void setServiceContent(String serviceContent) {
+        this.serviceContent = serviceContent;
+    }
+
+    public String getCourseNoContent() {
+        return courseNoContent;
+    }
+
+    public void setCourseNoContent(String courseNoContent) {
+        this.courseNoContent = courseNoContent;
+    }
+
+    public String getCrowdContent() {
+        return crowdContent;
+    }
+
+    public void setCrowdContent(String crowdContent) {
+        this.crowdContent = crowdContent;
+    }
+
+    public String getMessageContent() {
+        return messageContent;
+    }
+
+    public void setMessageContent(String messageContent) {
+        this.messageContent = messageContent;
+    }
+
+    public String getProcessContent() {
+        return processContent;
+    }
+
+    public void setProcessContent(String processContent) {
+        this.processContent = processContent;
+    }
+
+    public String getWechatAvatar() {
+        return wechatAvatar;
+    }
+
+    public void setWechatAvatar(String wechatAvatar) {
+        this.wechatAvatar = wechatAvatar;
+    }
+
+    public Integer getNoNumber() {
+        return noNumber;
+    }
+
+    public void setNoNumber(Integer noNumber) {
+        this.noNumber = noNumber;
+    }
+
+    public Integer getTime() {
+        return time;
+    }
+
+    public void setTime(Integer time) {
+        this.time = time;
+    }
 }

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/CoursePackageListDto.java

@@ -34,6 +34,8 @@ public class CoursePackageListDto {
 
     private Date updateTime;
 
+    private Boolean add;
+
     public Integer getId() {
         return id;
     }
@@ -121,4 +123,12 @@ public class CoursePackageListDto {
     public void setComments(Collection<CommentExtendDto> comments) {
         this.comments = comments;
     }
+
+    public Boolean getAdd() {
+        return add;
+    }
+
+    public void setAdd(Boolean add) {
+        this.add = add;
+    }
 }

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

@@ -54,7 +54,7 @@ public class CoursePackageService extends AbstractService {
     }
 
     public Page<CoursePackage> list(int page, int size, Integer structId, Boolean isSpecial, String order, DirectionStatus direction){
-        Example example = new Example(Course.class);
+        Example example = new Example(CoursePackage.class);
         if(structId != null){
             example.and(
                     example.createCriteria()