KaysonCui 4 anni fa
parent
commit
4b7707d485

+ 277 - 22
front/package-lock.json

@@ -293,6 +293,20 @@
         "@types/react": "16.8.7"
       }
     },
+    "@videojs/http-streaming": {
+      "version": "1.10.6",
+      "resolved": "https://registry.npm.taobao.org/@videojs/http-streaming/download/@videojs/http-streaming-1.10.6.tgz",
+      "integrity": "sha1-qRGbGCizVMXMF7QuoFHMe8zi3KA=",
+      "requires": {
+        "aes-decrypter": "3.0.0",
+        "global": "4.3.2",
+        "m3u8-parser": "4.4.0",
+        "mpd-parser": "0.8.1",
+        "mux.js": "5.2.1",
+        "url-toolkit": "2.1.6",
+        "video.js": "7.6.5"
+      }
+    },
     "accepts": {
       "version": "1.3.5",
       "resolved": "http://registry.npm.taobao.org/accepts/download/accepts-1.3.5.tgz",
@@ -340,6 +354,16 @@
         "object-assign": "4.1.1"
       }
     },
+    "aes-decrypter": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npm.taobao.org/aes-decrypter/download/aes-decrypter-3.0.0.tgz",
+      "integrity": "sha1-eEihwUW5/b9Xrj4rWxvHzwZEqPs=",
+      "requires": {
+        "commander": "2.15.1",
+        "global": "4.3.2",
+        "pkcs7": "1.0.2"
+      }
+    },
     "ajv": {
       "version": "4.11.8",
       "resolved": "http://registry.npm.taobao.org/ajv/download/ajv-4.11.8.tgz",
@@ -3099,8 +3123,7 @@
     "commander": {
       "version": "2.15.1",
       "resolved": "http://registry.npm.taobao.org/commander/download/commander-2.15.1.tgz",
-      "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=",
-      "dev": true
+      "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8="
     },
     "commondir": {
       "version": "1.0.1",
@@ -3808,8 +3831,7 @@
     "dom-walk": {
       "version": "0.1.1",
       "resolved": "http://registry.npm.taobao.org/dom-walk/download/dom-walk-0.1.1.tgz",
-      "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=",
-      "dev": true
+      "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg="
     },
     "domain-browser": {
       "version": "1.2.0",
@@ -5102,6 +5124,14 @@
         }
       }
     },
+    "for-each": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npm.taobao.org/for-each/download/for-each-0.3.3.tgz",
+      "integrity": "sha1-abRH6IoKXTLD5whPPxcQA0shN24=",
+      "requires": {
+        "is-callable": "1.1.3"
+      }
+    },
     "for-in": {
       "version": "1.0.2",
       "resolved": "http://registry.npm.taobao.org/for-in/download/for-in-1.0.2.tgz",
@@ -5179,8 +5209,7 @@
     "function-bind": {
       "version": "1.1.1",
       "resolved": "http://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz",
-      "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=",
-      "dev": true
+      "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0="
     },
     "functional-red-black-tree": {
       "version": "1.0.1",
@@ -5270,7 +5299,6 @@
       "version": "4.3.2",
       "resolved": "http://registry.npm.taobao.org/global/download/global-4.3.2.tgz",
       "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=",
-      "dev": true,
       "requires": {
         "min-document": "2.19.0",
         "process": "0.5.2"
@@ -5279,8 +5307,7 @@
         "process": {
           "version": "0.5.2",
           "resolved": "http://registry.npm.taobao.org/process/download/process-0.5.2.tgz",
-          "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=",
-          "dev": true
+          "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8="
         }
       }
     },
@@ -5347,7 +5374,6 @@
       "version": "1.0.3",
       "resolved": "http://registry.npm.taobao.org/has/download/has-1.0.3.tgz",
       "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=",
-      "dev": true,
       "requires": {
         "function-bind": "1.1.1"
       }
@@ -5370,8 +5396,7 @@
     "has-symbols": {
       "version": "1.0.0",
       "resolved": "http://registry.npm.taobao.org/has-symbols/download/has-symbols-1.0.0.tgz",
-      "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
-      "dev": true
+      "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
     },
     "has-value": {
       "version": "1.0.0",
@@ -5823,6 +5848,11 @@
       "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
       "dev": true
     },
+    "individual": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npm.taobao.org/individual/download/individual-2.0.0.tgz",
+      "integrity": "sha1-gzsJfa0jKU52EXqY+zjg2a1hu5c="
+    },
     "inflight": {
       "version": "1.0.6",
       "resolved": "http://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz",
@@ -6041,8 +6071,7 @@
     "is-callable": {
       "version": "1.1.3",
       "resolved": "http://registry.npm.taobao.org/is-callable/download/is-callable-1.1.3.tgz",
-      "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=",
-      "dev": true
+      "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI="
     },
     "is-data-descriptor": {
       "version": "0.1.4",
@@ -6056,8 +6085,7 @@
     "is-date-object": {
       "version": "1.0.1",
       "resolved": "http://registry.npm.taobao.org/is-date-object/download/is-date-object-1.0.1.tgz",
-      "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
-      "dev": true
+      "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
     },
     "is-descriptor": {
       "version": "0.1.6",
@@ -6108,6 +6136,11 @@
         "number-is-nan": "1.0.1"
       }
     },
+    "is-function": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npm.taobao.org/is-function/download/is-function-1.0.1.tgz",
+      "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU="
+    },
     "is-glob": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
@@ -6205,7 +6238,6 @@
       "version": "1.0.4",
       "resolved": "http://registry.npm.taobao.org/is-regex/download/is-regex-1.0.4.tgz",
       "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
-      "dev": true,
       "requires": {
         "has": "1.0.3"
       }
@@ -6446,6 +6478,11 @@
       "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
       "dev": true
     },
+    "keycode": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npm.taobao.org/keycode/download/keycode-2.2.0.tgz",
+      "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
+    },
     "killable": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -6796,6 +6833,14 @@
         "yallist": "2.1.2"
       }
     },
+    "m3u8-parser": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npm.taobao.org/m3u8-parser/download/m3u8-parser-4.4.0.tgz",
+      "integrity": "sha1-rfYGwK9tl/Z1AJWkIAbCrgPd4Xc=",
+      "requires": {
+        "global": "4.3.2"
+      }
+    },
     "map-cache": {
       "version": "0.2.2",
       "resolved": "http://registry.npm.taobao.org/map-cache/download/map-cache-0.2.2.tgz",
@@ -7017,7 +7062,6 @@
       "version": "2.19.0",
       "resolved": "http://registry.npm.taobao.org/min-document/download/min-document-2.19.0.tgz",
       "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
-      "dev": true,
       "requires": {
         "dom-walk": "0.1.1"
       }
@@ -7095,6 +7139,15 @@
       "resolved": "http://registry.npm.taobao.org/moment/download/moment-2.22.2.tgz",
       "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
     },
+    "mpd-parser": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npm.taobao.org/mpd-parser/download/mpd-parser-0.8.1.tgz",
+      "integrity": "sha1-2ymdvsM3mZ+7us6YnSJ8ewPcjqc=",
+      "requires": {
+        "global": "4.3.2",
+        "url-toolkit": "2.1.6"
+      }
+    },
     "ms": {
       "version": "2.0.0",
       "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz",
@@ -7127,6 +7180,11 @@
       "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
       "dev": true
     },
+    "mux.js": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npm.taobao.org/mux.js/download/mux.js-5.2.1.tgz",
+      "integrity": "sha1-Zph2H8iNpazs6gdYrCXxHToIvug="
+    },
     "nanomatch": {
       "version": "1.2.9",
       "resolved": "http://registry.npm.taobao.org/nanomatch/download/nanomatch-1.2.9.tgz",
@@ -7375,11 +7433,15 @@
       "integrity": "sha1-dtm6b/ETz478DZlhAoUf5nI5Y+I=",
       "dev": true
     },
+    "object-inspect": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npm.taobao.org/object-inspect/download/object-inspect-1.6.0.tgz",
+      "integrity": "sha1-xwtsv3LydKq0w0wMgvUWe/gs8Vs="
+    },
     "object-keys": {
       "version": "1.0.12",
       "resolved": "http://registry.npm.taobao.org/object-keys/download/object-keys-1.0.12.tgz",
-      "integrity": "sha1-CcU4VTd1dTEMymL1W7M0q/97PtI=",
-      "dev": true
+      "integrity": "sha1-CcU4VTd1dTEMymL1W7M0q/97PtI="
     },
     "object-visit": {
       "version": "1.0.1",
@@ -7626,6 +7688,15 @@
         "safe-buffer": "5.1.2"
       }
     },
+    "parse-headers": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npm.taobao.org/parse-headers/download/parse-headers-2.0.2.tgz",
+      "integrity": "sha1-lUXopMGuXq6n0kmSvKiQKB7SbjQ=",
+      "requires": {
+        "for-each": "0.3.3",
+        "string.prototype.trim": "1.2.0"
+      }
+    },
     "parse-json": {
       "version": "2.2.0",
       "resolved": "http://registry.npm.taobao.org/parse-json/download/parse-json-2.2.0.tgz",
@@ -7746,6 +7817,11 @@
         "pinkie": "2.0.4"
       }
     },
+    "pkcs7": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npm.taobao.org/pkcs7/download/pkcs7-1.0.2.tgz",
+      "integrity": "sha1-ttulJ1KMKUK/wSLOLa/NteWQdOc="
+    },
     "pkg-dir": {
       "version": "1.0.0",
       "resolved": "http://registry.npm.taobao.org/pkg-dir/download/pkg-dir-1.0.0.tgz",
@@ -10077,6 +10153,14 @@
         "is-promise": "2.1.0"
       }
     },
+    "rust-result": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npm.taobao.org/rust-result/download/rust-result-1.0.0.tgz",
+      "integrity": "sha1-NMdbLm3Dn+WHXlveyFteD5FTb3I=",
+      "requires": {
+        "individual": "2.0.0"
+      }
+    },
     "rxjs": {
       "version": "6.4.0",
       "resolved": "http://registry.npm.taobao.org/rxjs/download/rxjs-6.4.0.tgz",
@@ -10091,6 +10175,14 @@
       "resolved": "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz",
       "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0="
     },
+    "safe-json-parse": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npm.taobao.org/safe-json-parse/download/safe-json-parse-4.0.0.tgz",
+      "integrity": "sha1-fA9XjPzNEtM6ccDgVBPi7KFx6qw=",
+      "requires": {
+        "rust-result": "1.0.0"
+      }
+    },
     "safe-regex": {
       "version": "1.1.0",
       "resolved": "http://registry.npm.taobao.org/safe-regex/download/safe-regex-1.1.0.tgz",
@@ -10783,6 +10875,111 @@
         "strip-ansi": "3.0.1"
       }
     },
+    "string.prototype.trim": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npm.taobao.org/string.prototype.trim/download/string.prototype.trim-1.2.0.tgz",
+      "integrity": "sha1-dacpsQz8G+Q5VD2uRCEpRZzmHj0=",
+      "requires": {
+        "define-properties": "1.1.3",
+        "es-abstract": "1.14.2",
+        "function-bind": "1.1.1"
+      },
+      "dependencies": {
+        "define-properties": {
+          "version": "1.1.3",
+          "resolved": "http://registry.npm.taobao.org/define-properties/download/define-properties-1.1.3.tgz",
+          "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=",
+          "requires": {
+            "object-keys": "1.0.12"
+          }
+        },
+        "es-abstract": {
+          "version": "1.14.2",
+          "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.14.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.14.2.tgz",
+          "integrity": "sha1-fOEI+tgwaMh4PDzfYuUE4ITYxJc=",
+          "requires": {
+            "es-to-primitive": "1.2.0",
+            "function-bind": "1.1.1",
+            "has": "1.0.3",
+            "has-symbols": "1.0.0",
+            "is-callable": "1.1.4",
+            "is-regex": "1.0.4",
+            "object-inspect": "1.6.0",
+            "object-keys": "1.1.1",
+            "string.prototype.trimleft": "2.1.0",
+            "string.prototype.trimright": "2.1.0"
+          },
+          "dependencies": {
+            "object-keys": {
+              "version": "1.1.1",
+              "resolved": "https://registry.npm.taobao.org/object-keys/download/object-keys-1.1.1.tgz",
+              "integrity": "sha1-HEfyct8nfzsdrwYWd9nILiMixg4="
+            }
+          }
+        },
+        "es-to-primitive": {
+          "version": "1.2.0",
+          "resolved": "http://registry.npm.taobao.org/es-to-primitive/download/es-to-primitive-1.2.0.tgz",
+          "integrity": "sha1-7fckeAM0VujdqO8J4ArZZQcH83c=",
+          "requires": {
+            "is-callable": "1.1.4",
+            "is-date-object": "1.0.1",
+            "is-symbol": "1.0.2"
+          }
+        },
+        "is-callable": {
+          "version": "1.1.4",
+          "resolved": "http://registry.npm.taobao.org/is-callable/download/is-callable-1.1.4.tgz",
+          "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU="
+        },
+        "is-symbol": {
+          "version": "1.0.2",
+          "resolved": "http://registry.npm.taobao.org/is-symbol/download/is-symbol-1.0.2.tgz",
+          "integrity": "sha1-oFX2rlcZLK7jKeeoYBGLSXqVDzg=",
+          "requires": {
+            "has-symbols": "1.0.0"
+          }
+        }
+      }
+    },
+    "string.prototype.trimleft": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npm.taobao.org/string.prototype.trimleft/download/string.prototype.trimleft-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring.prototype.trimleft%2Fdownload%2Fstring.prototype.trimleft-2.1.0.tgz",
+      "integrity": "sha1-bMR/DX641isPNwFhFxWjlUWR1jQ=",
+      "requires": {
+        "define-properties": "1.1.3",
+        "function-bind": "1.1.1"
+      },
+      "dependencies": {
+        "define-properties": {
+          "version": "1.1.3",
+          "resolved": "http://registry.npm.taobao.org/define-properties/download/define-properties-1.1.3.tgz",
+          "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=",
+          "requires": {
+            "object-keys": "1.0.12"
+          }
+        }
+      }
+    },
+    "string.prototype.trimright": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npm.taobao.org/string.prototype.trimright/download/string.prototype.trimright-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring.prototype.trimright%2Fdownload%2Fstring.prototype.trimright-2.1.0.tgz",
+      "integrity": "sha1-Zp0WS+nfm291WfqOiZRbFopabFg=",
+      "requires": {
+        "define-properties": "1.1.3",
+        "function-bind": "1.1.1"
+      },
+      "dependencies": {
+        "define-properties": {
+          "version": "1.1.3",
+          "resolved": "http://registry.npm.taobao.org/define-properties/download/define-properties-1.1.3.tgz",
+          "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=",
+          "requires": {
+            "object-keys": "1.0.12"
+          }
+        }
+      }
+    },
     "string_decoder": {
       "version": "1.1.1",
       "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz",
@@ -11440,6 +11637,11 @@
         "requires-port": "1.0.0"
       }
     },
+    "url-toolkit": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npm.taobao.org/url-toolkit/download/url-toolkit-2.1.6.tgz",
+      "integrity": "sha1-bQMkZJnlGarSJMRARKSuIFRBVPI="
+    },
     "use": {
       "version": "3.1.0",
       "resolved": "http://registry.npm.taobao.org/use/download/use-3.1.0.tgz",
@@ -11537,6 +11739,49 @@
         }
       }
     },
+    "video.js": {
+      "version": "7.6.5",
+      "resolved": "https://registry.npm.taobao.org/video.js/download/video.js-7.6.5.tgz",
+      "integrity": "sha1-r2anG8Bf15xYHBZzrFp4prMbyDE=",
+      "requires": {
+        "@babel/runtime": "7.6.2",
+        "@videojs/http-streaming": "1.10.6",
+        "global": "4.3.2",
+        "keycode": "2.2.0",
+        "safe-json-parse": "4.0.0",
+        "videojs-font": "3.2.0",
+        "videojs-vtt.js": "0.14.1",
+        "xhr": "2.4.0"
+      },
+      "dependencies": {
+        "@babel/runtime": {
+          "version": "7.6.2",
+          "resolved": "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.6.2.tgz?cache=0&sync_timestamp=1569273822239&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.6.2.tgz",
+          "integrity": "sha1-w9bkGzBO8Q3PE3d6M+dpTsSppt0=",
+          "requires": {
+            "regenerator-runtime": "0.13.3"
+          }
+        },
+        "regenerator-runtime": {
+          "version": "0.13.3",
+          "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.3.tgz",
+          "integrity": "sha1-fPanfY9cb2Drc8X8GVWyzrAea/U="
+        }
+      }
+    },
+    "videojs-font": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npm.taobao.org/videojs-font/download/videojs-font-3.2.0.tgz",
+      "integrity": "sha1-ISydP05Ow/pzRRZ9ZDFq3TXpIjI="
+    },
+    "videojs-vtt.js": {
+      "version": "0.14.1",
+      "resolved": "https://registry.npm.taobao.org/videojs-vtt.js/download/videojs-vtt.js-0.14.1.tgz",
+      "integrity": "sha1-2lg+sfycgcgmqUMrcGBA6N6kmRE=",
+      "requires": {
+        "global": "4.3.2"
+      }
+    },
     "vm-browserify": {
       "version": "0.0.4",
       "resolved": "http://registry.npm.taobao.org/vm-browserify/download/vm-browserify-0.0.4.tgz",
@@ -11991,11 +12236,21 @@
         "mkdirp": "0.5.1"
       }
     },
+    "xhr": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npm.taobao.org/xhr/download/xhr-2.4.0.tgz",
+      "integrity": "sha1-4W5mpF+GmGHu76tBbV7/ci3ECZM=",
+      "requires": {
+        "global": "4.3.2",
+        "is-function": "1.0.1",
+        "parse-headers": "2.0.2",
+        "xtend": "4.0.1"
+      }
+    },
     "xtend": {
       "version": "4.0.1",
       "resolved": "http://registry.npm.taobao.org/xtend/download/xtend-4.0.1.tgz",
-      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
-      "dev": true
+      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
     },
     "y18n": {
       "version": "3.2.1",

+ 2 - 1
front/package.json

@@ -94,7 +94,8 @@
     "redux-thunk": "^2.0.0",
     "sortablejs": "^1.10.0-rc2",
     "superagent": "^3.4.1",
-    "url": "^0.11.0"
+    "url": "^0.11.0",
+    "video.js": "^7.6.5"
   },
   "devDependencies": {
     "@hot-loader/react-dom": "^16.8.4",

+ 20 - 3
front/project/www/components/Other/index.js

@@ -88,10 +88,10 @@ export class AnswerCarousel extends Component {
         </div>
         {!hideBtn && (
           <Button size="lager" radius onClick={() => User.needLogin().then(() => onFaq())}>
-            <Assets name="kf" className="m-r-5" />立即咨询
+            <Assets name="kf" className="m-r-5" />
+            立即咨询
           </Button>
-        )
-        }
+        )}
       </div>
     );
   }
@@ -168,3 +168,20 @@ export class Contact extends Component {
     );
   }
 }
+
+export class Comment extends Component {
+  render() {
+    return (
+      <div className="comment-item">
+        <Assets className="m-r-1" src="" />
+        <div className="d-i-b">
+          <div className="t-1 t-s-18">王大锤</div>
+          <div className="t-3">2018-10-20</div>
+        </div>
+        <div className="t-1 t-s-18 m-t-1">
+          掌握了学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关了。掌握了学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关了掌握了学习方法以后,再加上刻苦勤奋的练习,第一次考试就过关了。
+        </div>
+      </div>
+    );
+  }
+}

+ 12 - 0
front/project/www/components/Other/index.less

@@ -128,6 +128,7 @@
       left: 0;
       bottom: 0;
       right: 0;
+      cursor: pointer;
     }
 
     .next {
@@ -172,4 +173,15 @@
       }
     }
   }
+}
+
+.comment-item {
+  padding: 20px 0;
+  position: relative;
+  border-bottom: 1px solid #eee;
+  .assets {
+    border-radius: 50%;
+    width: 60px;
+    height: 60px;
+  }
 }

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

@@ -3,9 +3,9 @@ import './index.less';
 import Progress from '../Progress';
 
 function ProgressText(props) {
-  const { progress, width, times, unit = '遍', size } = props;
+  const { className = '', progress, width, times, unit = '遍', size } = props;
   return (
-    <div className="progress-text">
+    <div className={`progress-text ${className}`}>
       <div style={{ width }} className="p">
         <Progress progress={progress} size={size} />
       </div>

+ 101 - 0
front/project/www/components/Video/index.js

@@ -0,0 +1,101 @@
+import React, { Component } from 'react';
+import './index.less';
+import videojs from 'video.js';
+import Assets from '@src/components/Assets';
+
+export default class Video extends Component {
+  constructor(props) {
+    super(props);
+    this.ready = false;
+    this.state = { playing: false, fulling: false };
+  }
+
+  componentDidMount() {
+    this.player = videojs(
+      this.videoNode,
+      {
+        controls: false,
+        sources: [
+          {
+            src: this.props.src,
+            type: 'video/mp4',
+          },
+        ],
+      },
+      () => {
+        this.ready = true;
+      },
+    );
+  }
+
+  componentWillUnmount() {
+    if (this.player) {
+      this.player.dispose();
+    }
+  }
+
+  onPlay() {
+    if (!this.ready) return;
+    this.player.play();
+    this.setState({ playing: true });
+  }
+
+  onPuase() {
+    if (!this.ready) return;
+    this.player.pause();
+    this.setState({ playing: false });
+  }
+
+  onSpeed(speed) {
+    this.player.playbackRate(speed);
+  }
+
+  render() {
+    const { action = true, btnList = [], onAction } = this.props;
+    const { playing, fulling } = this.state;
+    return (
+      <div className={`video-item ${action ? 'action' : ''}`}>
+        <div className="video-wrapper">
+          <video
+            ref={node => {
+              this.videoNode = node;
+            }}
+          />
+          {!playing && <Assets className="play" name="play" onClick={() => this.onPlay()} />}
+          {playing && <Assets className="stop" name="stop" onClick={() => this.onPuase()} />}
+        </div>
+        <div className="video-bottom">
+          <div className="progress" />
+          {action && (
+            <div className="action-bar">
+              <div className="d-i-b m-r-1">
+                {!playing && <Assets name="play2" onClick={() => this.onPlay()} />}
+                {playing && <Assets name="stop2" onClick={() => this.onPuase()} />}
+              </div>
+              <div className="d-i-b m-r-1">
+                <Assets name="next2" />
+              </div>
+              <div className="flex-block" />
+              {btnList.map(btn => {
+                return (
+                  <div className="d-i-b m-r-1">
+                    <div className="btn" onClick={() => onAction && onAction(btn.key)}>
+                      {btn.title}
+                    </div>
+                  </div>
+                );
+              })}
+              <div className="d-i-b m-r-1">
+                <div className="btn">倍速</div>
+              </div>
+              <div className="d-i-b">
+                {!fulling && <Assets name="full2" />}
+                {fulling && <Assets name="reduction2" />}
+              </div>
+            </div>
+          )}
+        </div>
+      </div>
+    );
+  }
+}

+ 201 - 0
front/project/www/components/Video/index.less

@@ -0,0 +1,201 @@
+.video-item {
+  width: 100%;
+  height: 100%;
+  padding-bottom: 3px;
+  position: relative;
+
+  .video-bottom {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+  }
+
+  .progress {
+    height: 3px;
+    background: #616161FF;
+  }
+
+  .action-bar {
+    height: 50px;
+    line-height: 50px;
+    padding: 0 20px;
+    display: flex;
+
+    .assets {
+      cursor: pointer;
+    }
+
+    .btn {
+      display: inline-block;
+      font-size: 12px;
+      line-height: 12px;
+      padding: 6px 10px;
+      color: #fff;
+      background: #696969FF;
+      text-align: center;
+      cursor: pointer;
+      border-radius: 12px;
+    }
+
+    .btn:hover {
+      background: darken(#696969, 10);
+    }
+  }
+
+  .video-wrapper {
+    width: 100%;
+    height: 100%;
+    position: relative;
+
+    .vjs_video_3-dimensions {
+      width: 100% !important;
+      height: 100% !important;
+
+      .vjs-text-track-display {
+        display: none;
+      }
+
+      .vjs-loading-spinner {
+        display: none;
+      }
+
+      .vjs-big-play-button {
+        display: none;
+      }
+
+      .vjs-error-display {
+        display: none;
+      }
+
+      .vjs-modal-dialog {
+        display: none;
+      }
+
+      .vjs-control-bar {
+        position: absolute;
+        bottom: -53px;
+        left: 0;
+        right: 0;
+        height: 70px;
+        padding-top: 20px;
+        line-height: 50px;
+        padding-left: 90px;
+        overflow: hidden;
+
+        button {
+          display: none;
+        }
+
+        .vjs-volume-panel {
+          display: none;
+        }
+
+        .vjs-control-text {
+          display: none;
+        }
+
+        .vjs-current-time {
+          display: inline-block;
+          color: #fff;
+          font-size: 12px;
+        }
+
+        .vjs-time-divider {
+          display: inline-block;
+          color: #fff;
+          font-size: 12px;
+          margin: 0 5px;
+        }
+
+        .vjs-duration {
+          display: inline-block;
+          color: #fff;
+          font-size: 12px;
+        }
+
+        .vjs-progress-control:hover {
+          .vjs-mouse-display {
+            opacity: 1;
+          }
+        }
+
+        .vjs-progress-control {
+          position: absolute;
+          height: 3px;
+          top: 17px;
+          left: 0;
+          right: 0;
+
+          .vjs-load-progress {
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgb(168, 168, 168);
+            z-index: 1;
+          }
+
+          .vjs-mouse-display {
+            position: absolute;
+            width: 8px;
+            height: 8px;
+            background: #fff;
+            border-radius: 4px;
+            top: -2px;
+            z-index: 2;
+            opacity: 0;
+
+            .vjs-time-tooltip {
+              color: #fff;
+              position: absolute;
+              top: -35px;
+              font-size: 12px;
+            }
+          }
+
+          .vjs-play-progress {
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: #4292f0;
+            z-index: 1;
+
+            .vjs-time-tooltip {
+              display: none;
+            }
+          }
+        }
+      }
+    }
+
+    .assets {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+    }
+
+    .stop {
+      display: none;
+    }
+  }
+
+  .video-wrapper:hover {
+    .stop {
+      display: block;
+    }
+  }
+
+  video {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.video-item.action {
+  padding-bottom: 53px;
+}

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

@@ -12,7 +12,7 @@
     border-radius: 14px;
     padding: 20px;
 
-    .answer-layout {
+    .answer-item {
       border-bottom: 1px solid #eee;
       padding-top: 20px;
       padding-left: 15px;

+ 54 - 62
front/project/www/routes/course/answer/page.js

@@ -21,43 +21,38 @@ export default class extends Page {
       filterMap: {},
       list: [],
       tab: 'special',
-      answerSelect: [
-        { title: '全部', key: '' },
-        { title: '已回答', key: '1' },
-        { title: '未回答', key: '0' },
-      ],
+      answerSelect: [{ title: '全部', key: '' }, { title: '已回答', key: '1' }, { title: '未回答', key: '0' }],
     };
   }
 
   init() {
     const { id } = this.params;
-    Course.get(id)
-      .then(result => {
-        const courseNoSelect = result.courseNos.map(row => {
-          return {
-            title: row.title,
-            key: `${row.id}`,
-          };
-        });
-        courseNoSelect.unshift({
-          title: '全部课时',
-          key: '',
-        });
-        const courseNoMap = getMap(result.courseNos, 'id');
-        const timelineSelect = [{ title: '全部区间', value: '' }];
-        const max = Math.max(result.courseNos.map(row => row.time));
-        let start = 0;
-        let end = start + 5;
-        while (start < max) {
-          timelineSelect.push({
-            title: `${start}:00~${end}:00`,
-            key: `${start}`,
-          });
-          start += 5;
-          end = Math.min(start + 5, max);
-        }
-        this.setState({ course: result, courseNoMap, courseNoSelect, timelineSelect });
+    Course.get(id).then(result => {
+      const courseNoSelect = result.courseNos.map(row => {
+        return {
+          title: row.title,
+          key: `${row.id}`,
+        };
+      });
+      courseNoSelect.unshift({
+        title: '全部课时',
+        key: '',
       });
+      const courseNoMap = getMap(result.courseNos, 'id');
+      const timelineSelect = [{ title: '全部区间', value: '' }];
+      const max = Math.max(result.courseNos.map(row => row.time));
+      let start = 0;
+      let end = start + 5;
+      while (start < max) {
+        timelineSelect.push({
+          title: `${start}:00~${end}:00`,
+          key: `${start}`,
+        });
+        start += 5;
+        end = Math.min(start + 5, max);
+      }
+      this.setState({ course: result, courseNoMap, courseNoSelect, timelineSelect });
+    });
   }
 
   initData() {
@@ -92,10 +87,9 @@ export default class extends Page {
       ],
     });
     // paixu
-    Course.listAsk(Object.assign({ courseId: id }, this.state.search))
-      .then(result => {
-        this.setState({ list: result.list, total: result.total });
-      });
+    Course.listAsk(Object.assign({ courseId: id }, this.state.search)).then(result => {
+      this.setState({ list: result.list, total: result.total });
+    });
   }
 
   refreshMy() {
@@ -107,10 +101,9 @@ export default class extends Page {
         { title: '更新时间', key: 'updateTime' },
       ],
     });
-    My.listCourseAsk(Object.assign({ courseId: id }, this.state.search))
-      .then(result => {
-        this.setState({ list: result.list, total: result.total });
-      });
+    My.listCourseAsk(Object.assign({ courseId: id }, this.state.search)).then(result => {
+      this.setState({ list: result.list, total: result.total });
+    });
   }
 
   onTabChange(tab) {
@@ -128,13 +121,12 @@ export default class extends Page {
     this.initData();
   }
 
-  onAction() { }
+  onAction() {}
 
   delAsk(id) {
-    My.delCourseAsk(id)
-      .then(() => {
-        this.refresh();
-      });
+    My.delCourseAsk(id).then(() => {
+      this.refresh();
+    });
   }
 
   viewAsk(id) {
@@ -143,21 +135,15 @@ export default class extends Page {
 
   renderView() {
     const { course = {}, courseNoMap = {} } = this.state;
-    const {
-      tab,
-      courseNoSelect,
-      answerSelect,
-      timelineSelect,
-      orderSelect,
-      filterMap = {},
-      list = [],
-    } = this.state;
+    const { tab, courseNoSelect, answerSelect, timelineSelect, orderSelect, filterMap = {}, list = [] } = this.state;
     const { total, page } = this.state;
-    const selectActionList = [{
-      key: 'courseNoId',
-      placeholder: '全部课时',
-      select: courseNoSelect,
-    }];
+    const selectActionList = [
+      {
+        key: 'courseNoId',
+        placeholder: '全部课时',
+        select: courseNoSelect,
+      },
+    ];
     selectActionList.push({
       key: 'order',
       placeholder: '排序',
@@ -183,7 +169,9 @@ export default class extends Page {
       <div>
         <div className="top content t-8">
           千行课堂 > 全部课程 > {course.title} > <span className="t-1">全部问答</span>
-          <div className="f-r" onClick={() => linkTo(`/course/detail/${course.id}`)}>返回课程</div>
+          <div className="f-r" onClick={() => linkTo(`/course/detail/${course.id}`)}>
+            返回课程
+          </div>
         </div>
         <div className="center content">
           <div className="t-1 t-s-20 m-b-2">{course.title}</div>
@@ -208,8 +196,10 @@ export default class extends Page {
           />
           {list.map(item => {
             return (
-              <div className="answer-layout">
-                <div className="t-2">课时{(courseNoMap[item.key] || {}).no} {item.position}:00~{item.position + 5}:00</div>
+              <div className="answer-item">
+                <div className="t-2">
+                  课时{(courseNoMap[item.key] || {}).no} {item.position}:00~{item.position + 5}:00
+                </div>
                 <div className="t-2">课程内容: {(courseNoMap[item.key] || {}).title}</div>
 
                 {tab === 'my' && item.answerStatus === 0 && (
@@ -234,7 +224,9 @@ export default class extends Page {
                 )}
                 {item.answerStatus > 0 && (
                   <div className="desc">
-                    <OpenText onOpen={() => tab !== 'my' && this.viewAsk(item.id)}>{item.answerStatus === 2 ? '与课程内容无关,老师无法作出回答,敬请谅解。' : item.answer}</OpenText>
+                    <OpenText onOpen={() => tab !== 'my' && this.viewAsk(item.id)}>
+                      {item.answerStatus === 2 ? '与课程内容无关,老师无法作出回答,敬请谅解。' : item.answer}
+                    </OpenText>
                   </div>
                 )}
               </div>

+ 29 - 3
front/project/www/routes/course/dataDetail/index.less

@@ -4,11 +4,11 @@
   background: #fff;
 
   .content {
-    width: 1200px !important;
+    width: 1140px !important;
   }
 
   .top {
-    padding: 8px 30px;
+    padding: 8px 0;
     height: 60px;
     line-height: 44px;
   }
@@ -17,7 +17,6 @@
     background: #fff;
 
     .content {
-      width: 1140px !important;
 
       .item-detail {
         overflow: hidden;
@@ -75,6 +74,33 @@
 
     .content {
       padding: 20px 0;
+
+      .tab-layout {
+        padding-top: 20px;
+
+        .tab-title {
+          border-left: 6px solid #7876FCFF;
+          padding-left: 5px;
+          color: #303036FF;
+          font-size: 18px;
+          margin-bottom: 5px;
+          font-weight: bold;
+        }
+
+        .tab-desc {
+          margin-bottom: 30px;
+          font-size: 18x;
+        }
+
+        .qr-layout {
+          text-align: center;
+          padding: 20px;
+        }
+
+        .other-answer-carousel {
+          padding: 50px 0;
+        }
+      }
     }
   }
 }

+ 112 - 43
front/project/www/routes/course/dataDetail/page.js

@@ -4,7 +4,7 @@ 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 { Contact, AnswerCarousel, Comment } from '../../../components/Other';
 import Tabs from '../../../components/Tabs';
 import Button from '../../../components/Button';
 import { User } from '../../../stores/user';
@@ -30,18 +30,16 @@ export default class extends Page {
       const dataStructMap = getMap(dataStructSelect, 'key');
       this.setState({ dataStructSelect, dataStructMap });
     });
-    Main.getBase()
-      .then(result => {
-        this.setState({ base: result });
-      });
+    Main.getBase().then(result => {
+      this.setState({ base: result });
+    });
   }
 
   initData() {
     const { id } = this.params;
-    Course.getData(id)
-      .then(result => {
-        this.setState({ data: result });
-      });
+    Course.getData(id).then(result => {
+      this.setState({ data: result });
+    });
     this.view(id);
     Main.listFaq({ page: 1, size: 100, channel: 'course_data' }).then(result => {
       this.faqs = result.list;
@@ -64,23 +62,20 @@ export default class extends Page {
   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');
-            });
+      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 });
-        });
+      Order.addCheckout({ productType: 'data', productId: data.id }).then(() => {
+        this.setState({ add: true });
+      });
     });
   }
 
@@ -89,7 +84,8 @@ export default class extends Page {
     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>
+          千行课堂 > 全部资料 > {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} />
@@ -133,24 +129,44 @@ export default class extends Page {
                 <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" disabled={data.add || add} 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>}
+                {!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"
+                    disabled={data.add || add}
+                    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>
@@ -170,6 +186,7 @@ export default class extends Page {
               { title: 'FAQs', key: '4' },
               { title: '学员评价', key: '5' },
             ]}
+            onChange={key => this.onChangeTab(key)}
           />
           {this[`renderTab${tab}`]()}
         </div>
@@ -178,22 +195,74 @@ export default class extends Page {
   }
 
   renderTab1() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+      </div>
+    );
   }
 
   renderTab2() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+      </div>
+    );
   }
 
   renderTab3() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+      </div>
+    );
   }
 
   renderTab4() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <AnswerCarousel
+          hideBtn
+          list={[
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+          ]}
+        />
+      </div>
+    );
   }
 
   renderTab5() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="m-b-1 t-r">
+          <Button width={100} radius>
+            写评论
+          </Button>
+        </div>
+        <Comment />
+        <Comment />
+        <Comment />
+        <Comment />
+        <Comment />
+      </div>
+    );
   }
 }

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

@@ -2,6 +2,167 @@
 
 #course-detail {
   background: #fff;
+  min-height: 100%;
 
+  .content {
+    width: 1140px !important;
+  }
 
+  .top {
+    padding: 8px 0;
+    height: 60px;
+    line-height: 44px;
+  }
+
+  .center {
+    padding-top: 20px;
+    padding-bottom: 30px;
+    position: relative;
+    overflow: hidden;
+
+    .detail {
+      margin-bottom: 30px;
+
+      .left {
+        float: left;
+        width: 750px;
+
+        .left-top {
+          background: #F3F3F3FF;
+          line-height: 28px;
+          padding: 11px 15px;
+          margin-bottom: 20px;
+
+          span {
+            vertical-align: top;
+          }
+
+          .progress-text {
+            margin-top: 2px;
+          }
+        }
+
+        .video-layout {
+          width: 100%;
+          height: 520px;
+          background: #3A3A3AFF;
+        }
+      }
+
+      .right {
+        padding-left: 790px;
+
+        .tabs {
+          margin-bottom: 5px;
+          line-height: 36px;
+          height: 36px;
+        }
+
+        .all-answer {
+          line-height: 20px;
+          height: 40px;
+          padding: 10px;
+          background: #F3F3F3FF;
+          font-size: 12px;
+          margin-bottom: 10px;
+
+          .b {
+            vertical-align: top;
+            margin-top: 2.5px;
+            width: 15px;
+            height: 15px;
+            background: #D8D8D8FF;
+          }
+        }
+
+        .answer-layout {
+          height: 430px;
+          overflow-y: auto;
+
+          .answer-item {
+            border-bottom: 1px solid #eee;
+            margin-bottom: 10px;
+
+            .small-tag {
+              display: inline-block;
+              height: 16px;
+              background: rgba(163, 207, 255, 1);
+              border-radius: 2px;
+              color: #fff;
+              line-height: 16px;
+              font-size: 10px;
+              padding: 0 9px;
+            }
+
+            .desc {
+              color: #303139;
+              margin-bottom: 15px;
+            }
+          }
+        }
+
+        .item-layout {
+          height: 480px;
+          overflow-y: auto;
+
+          .item {
+            line-height: 20px;
+            height: 60px;
+            padding: 20px;
+            margin-bottom: 10px;
+            cursor: pointer;
+          }
+
+          .item.active {
+            background: #F3F3F3FF;
+          }
+        }
+      }
+
+      .right.progress {
+        .answer-layout {
+          height: 500px;
+        }
+
+        .item-layout {
+          height: 550px;
+        }
+      }
+    }
+  }
+
+  .bottom {
+    background: #FAFAFAFF;
+
+    .content {
+      padding: 20px 0;
+
+      .tab-layout {
+        padding-top: 20px;
+
+        .tab-title {
+          border-left: 6px solid #7876FCFF;
+          padding-left: 5px;
+          color: #303036FF;
+          font-size: 18px;
+          margin-bottom: 5px;
+          font-weight: bold;
+        }
+
+        .tab-desc {
+          margin-bottom: 30px;
+          font-size: 18x;
+        }
+
+        .qr-layout {
+          text-align: center;
+          padding: 20px;
+        }
+
+        .other-answer-carousel {
+          padding: 50px 0;
+        }
+      }
+    }
+  }
 }

+ 237 - 62
front/project/www/routes/course/detail/page.js

@@ -3,18 +3,57 @@ import './index.less';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
 import Footer from '../../../components/Footer';
-import { Contact } from '../../../components/Other';
+import { Contact, AnswerCarousel, Comment } from '../../../components/Other';
 import Tabs from '../../../components/Tabs';
 import Button from '../../../components/Button';
+import UserTable from '../../../components/UserTable';
+import ProgressText from '../../../components/ProgressText';
+import { OpenText } from '../../../components/Open';
+import Video from '../../../components/Video';
 
 export default class extends Page {
   initState() {
+    this.columns = [
+      {
+        key: '1',
+        title: '学习内容',
+      },
+      {
+        key: '2',
+        title: '预习作业',
+      },
+      {
+        key: '3',
+        title: '进展',
+      },
+      {
+        key: '4',
+        title: '最近学习',
+      },
+      {
+        key: '5',
+        title: '笔记',
+      },
+      {
+        key: '6',
+        title: '问答',
+      },
+    ];
     return {
       tab: '1',
+      rightTab: '1',
       key: '1',
+      add: false,
+      list: [{ key: '1' }, { key: '2' }, { key: '3' }],
+      progress: 0,
+      data: { title: '语法SC系统授课—课时10:逻辑语义解题专题讲解(1)' },
     };
   }
 
+  onChangeRightTab(rightTab) {
+    this.setState({ rightTab });
+  }
+
   onChangeTab(tab) {
     this.setState({ tab });
   }
@@ -24,73 +63,117 @@ export default class extends Page {
   }
 
   renderView() {
+    const { base = {}, data = {}, add, progress, rightTab } = this.state;
     return (
       <div>
         <div className="top content t-8">
-          千行课堂 > 全部课程 > OG20综合刷题 > 课时3 > <span className="t-1">套餐详情</span>
+          千行课堂 > 全部套餐 > {data.title} > <span className="t-1">课程详情</span>
+        </div>
+        <div className="center content">
+          <div className="t-1 t-s-20">
+            {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" disabled={data.add || add} onClick={() => this.add()}>
+                <Assets name="add" />
+              </Button>
+            </div>
+          </div>
+          <div className="t-2 m-b-1">授课老师:李奕都</div>
+          <div className="detail">
+            <div className="left">
+              <div hidden={progress === 0} className="left-top">
+                <span className="d-i-b m-r-1">预习作业</span>
+                <span className="d-i-b m-r-2">
+                  <ProgressText width={480} size="small" progress={progress} />
+                </span>
+                <Button className="f-r" radius>
+                  做题
+                </Button>
+              </div>
+              <div className="video-layout">
+                <Video src="/01.mp4" btnList={[{ title: '提问', key: 'answer' }, { title: '笔记', key: 'note' }]} />
+              </div>
+            </div>
+            <div className={`right ${progress > 0 ? 'progress' : ''}`}>
+              <Tabs
+                type="division"
+                theme="gray"
+                space={2.5}
+                active={rightTab}
+                tabs={[{ title: '精选问答', key: '1' }, { title: '课时列表', key: '2' }]}
+                onChange={key => this.onChangeRightTab(key)}
+              />
+              {this[`renderRightTab${rightTab}`]()}
+            </div>
+          </div>
+          <UserTable columns={this.columns} />
+        </div>
+        <div className="bottom">
+          <div className="content">{this.renderTab()}</div>
         </div>
-        {this.renderDetail()}
-        <Contact />
+        <Contact data={base.contact} />
         <Footer />
       </div>
     );
   }
 
-  renderDetail() {
-    const { tab } = this.state;
+  renderRightTab1() {
+    const { list = [] } = this.state;
     return [
-      <div className="center">
-        <div className="content">
-          <div className="item-detail">
-            <div className="left">
-              <Assets name="" />
-              <div className="tag-list">
-                <div className="tag">新手</div>
-                <div className="tag">原创</div>
-              </div>
-            </div>
-            <div style={{ width: 760 }} className="right">
-              <div className="t-1 t-s-20">OG20基础刷题套餐 </div>
-              <div className="t-7 m-b-2">
-                对“忽略有效考点”的解析进行补充,同时讲解OG不够精准的解析,帮助考生明确重点、避开误区,节省大量的时间和精力,刷OG必备。
-              </div>
-              <div className="">
-                <div className="d-i-b t-1">最近更新:</div>
-                <div className="d-i-b t-8">2019-06-20 10:21:04</div>
-              </div>
-              <div className="">
-                <div className="d-i-b t-1">页数:</div>
-                <div className="d-i-b t-8">30页</div>
-              </div>
-              <div className="">
-                <div className="d-i-b t-1">格式:</div>
-                <div className="d-i-b t-8">PDF</div>
+      <div className="all-answer">
+        <span className="d-i-b b m-r-5" />
+        <span className="d-i-b t-6">35:00 ~ 40:00</span>
+        <span className="f-r d-i-b t-4 c-p">全部问答 ></span>
+      </div>,
+      <div className="answer-layout">
+        {list.map(item => {
+          return (
+            <div className="answer-item">
+              <div>
+                <div className="small-tag">提问</div>
               </div>
-              <div className="m-b-1">
-                <div className="d-i-b t-1">获取方式:</div>
-                <div className="d-i-b t-8">发送至邮箱</div>
+              <div className="desc">
+                <OpenText>{item.content}</OpenText>
               </div>
-              <div className="m-b-1">
-                <div style={{ marginTop: 12 }} className="d-i-b t-1 t-s-16 v-a-t">
-                  价格:
+              {item.answerStatus > 0 && (
+                <div>
+                  <div className="small-tag">回答</div>
                 </div>
-                <div className="d-i-b t-7 t-s-28 f-w-b"> ¥ 15000</div>
-              </div>
-              <div className="action">
-                <Button className="m-r-1" radius size="lager">
-                  立即购买
-                </Button>
-                <Button className="m-r-1" theme="default" radius size="lager">
-                  <Assets name="add" />
-                </Button>
-                <Button theme="default" radius size="lager">
-                  预览
-                </Button>
-              </div>
+              )}
+              {item.answerStatus > 0 && (
+                <div className="desc">
+                  <OpenText>{item.answer}</OpenText>
+                </div>
+              )}
             </div>
-          </div>
-        </div>
+          );
+        })}
       </div>,
+    ];
+  }
+
+  renderRightTab2() {
+    const { list = [], key } = this.state;
+    return (
+      <div className="item-layout">
+        {list.map(item => {
+          return (
+            <div className={`item ${item.key === key ? 'active' : ''}`} onClick={() => this.onChangeItem(item.key)}>
+              <span className="t-1">课时1</span>
+              <span className="t-2">解读句子结构</span>
+            </div>
+          );
+        })}
+      </div>
+    );
+  }
+
+  renderTab() {
+    const { tab } = this.state;
+    return [
       <div className="bottom">
         <div className="content">
           <Tabs
@@ -98,12 +181,14 @@ export default class extends Page {
             border
             active={tab}
             tabs={[
-              { title: '资料介绍', key: '1' },
-              { title: '作者介绍', key: '2' },
-              { title: '获取方式', key: '3' },
+              { title: '课程介绍', key: '1' },
+              { title: '授课大纲', key: '2' },
+              { title: '小助手', key: '3' },
               { title: 'FAQs', key: '4' },
-              { title: '学员评价', key: '5' },
+              { title: '优惠信息', key: '5' },
+              { title: '学员评价', key: '6' },
             ]}
+            onChange={key => this.onChangeTab(key)}
           />
           {this[`renderTab${tab}`]()}
         </div>
@@ -112,22 +197,112 @@ export default class extends Page {
   }
 
   renderTab1() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="tab-title">老师资历</div>
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+        <div className="tab-title">基本参数</div>
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+        <div className="tab-title">授课重点</div>
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+        <div className="tab-title">适合人群</div>
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+      </div>
+    );
   }
 
   renderTab2() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+      </div>
+    );
   }
 
   renderTab3() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="qr-layout">
+          <Assets name="qrcode" className="m-r-2 v-a-t" />
+          <div className="p-l-1 d-i-b t-l">
+            <div className="t-1">千行小助手:</div>
+            <div className="t-1 m-b-2">1232104-310431</div>
+            <div className="t-2 t-s-12">微信扫码添加千行小助手为好友,</div>
+            <div className="t-2 t-s-12">咨询课程,了解更多信息</div>
+          </div>
+        </div>
+      </div>
+    );
   }
 
   renderTab4() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <AnswerCarousel
+          hideBtn
+          list={[
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+          ]}
+        />
+      </div>
+    );
   }
 
   renderTab5() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="tab-desc">
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+          已经参加过GMAT基础班,对GMAT考试内容已经有全面认识,已经掌握了GMAT考试所需要的所有语言能力,希望学习具体的做题方法和应试技巧,提高实战做题能力的学员;
+          住宿管理, 让学生高效利用时间在最短的时间内攻破GMAT考试。
+        </div>
+      </div>
+    );
+  }
+
+  renderTab6() {
+    return (
+      <div className="tab-layout">
+        <div className="m-b-1 t-r">
+          <Button width={100} radius>
+            写评论
+          </Button>
+        </div>
+        <Comment />
+        <Comment />
+        <Comment />
+        <Comment />
+        <Comment />
+      </div>
+    );
   }
 }

+ 2 - 3
front/project/www/routes/course/experience/index.less

@@ -4,11 +4,11 @@
   background: #fff;
 
   .content {
-    width: 1200px !important;
+    width: 1140px !important;
   }
 
   .top {
-    padding: 8px 30px;
+    padding: 8px 0;
     height: 60px;
     line-height: 44px;
   }
@@ -18,7 +18,6 @@
     padding-top: 20px;
 
     .content {
-      width: 1140px !important;
 
       .total-list {
         margin: 0 -20px;

+ 2 - 3
front/project/www/routes/course/experienceDetail/index.less

@@ -5,11 +5,11 @@
   min-height: 100%;
 
   .content {
-    width: 1200px !important;
+    width: 1140px !important;
   }
 
   .top {
-    padding: 8px 30px;
+    padding: 8px 0;
     height: 60px;
     line-height: 44px;
   }
@@ -19,7 +19,6 @@
     padding-top: 60px;
 
     .content {
-      width: 1140px !important;
 
       .detail {
         padding-top: 60px;

+ 2 - 2
front/project/www/routes/course/online/index.less

@@ -2,11 +2,11 @@
 
 #course-online {
   .content {
-    width: 1200px !important;
+    width: 1140px !important;
   }
 
   .top {
-    padding: 8px 30px;
+    padding: 8px 0;
     height: 60px;
 
     .tabs.text {

+ 2 - 3
front/project/www/routes/course/packageDetail/index.less

@@ -5,11 +5,11 @@
   min-height: 100%;
 
   .content {
-    width: 1200px !important;
+    width: 1140px !important;
   }
 
   .top {
-    padding: 8px 30px;
+    padding: 8px 0px;
     height: 60px;
     line-height: 44px;
   }
@@ -18,7 +18,6 @@
     padding-top: 20px;
     position: relative;
     overflow: hidden;
-    width: 1140px !important;
 
     .desc {
       margin-bottom: 60px;

+ 44 - 3
front/project/www/routes/course/vs/index.less

@@ -2,11 +2,11 @@
 
 #course-vs {
   .content {
-    width: 1200px !important;
+    width: 1140px !important;
   }
 
   .top {
-    padding: 8px 30px;
+    padding: 8px 0;
     height: 60px;
 
     .tabs.text {
@@ -25,7 +25,6 @@
     background: #fff;
 
     .content {
-      width: 1140px !important;
 
       .item-list {
         margin: 0 -20px;
@@ -120,6 +119,48 @@
 
     .content {
       padding: 20px 0;
+
+      .tab-layout {
+        padding-top: 20px;
+
+        .tab-title {
+          border-left: 6px solid #7876FCFF;
+          padding-left: 5px;
+          color: #303036FF;
+          font-size: 18px;
+          margin-bottom: 5px;
+          font-weight: bold;
+        }
+
+        .tab-desc {
+          margin-bottom: 30px;
+          font-size: 18x;
+        }
+
+        .qr-layout {
+          text-align: center;
+          padding: 20px;
+        }
+
+        .other-answer-carousel {
+          padding: 50px 0;
+        }
+
+        .teach-item {
+          position: relative;
+          overflow: hidden;
+          padding: 40px 0;
+
+          .left {
+            float: left;
+            width: 240px;
+          }
+
+          .right {
+            padding-left: 280px;
+          }
+        }
+      }
     }
   }
 }

+ 96 - 40
front/project/www/routes/course/vs/page.js

@@ -4,7 +4,7 @@ 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 { Contact, Comment, Consultation, AnswerCarousel } from '../../../components/Other';
 import Tabs from '../../../components/Tabs';
 import Button from '../../../components/Button';
 import { Main } from '../../../stores/main';
@@ -29,29 +29,26 @@ export default class extends Page {
     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 });
-      });
+    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);
+    Course.allVs().then(result => {
+      result.forEach(row => {
+        this.courseVsMap[row.vsType] = row;
       });
+      this.onChangeItem(CourseVsType[0].value);
+    });
   }
 
-  onChangeTab(tab) {
-    this.setState({ tab });
+  onChangeTab(info) {
+    this.setState({ info });
   }
 
   onChangeItem(key) {
@@ -99,21 +96,18 @@ export default class extends Page {
 
   buy() {
     const { data } = this.props;
-    Order.speedPay({ productType: 'course', productId: data.id })
-      .then(result => {
-        User.needPay(result)
-          .then(() => {
-            linkTo('/my/course');
-          });
+    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 });
-      });
+    Order.addCheckout({ productType: 'course', productId: data.id }).then(() => {
+      this.setState({ add: true });
+    });
   }
 
   renderView() {
@@ -122,7 +116,14 @@ export default class extends Page {
     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' }]} />
+          <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')} />
@@ -145,11 +146,13 @@ export default class extends Page {
           <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>;
+              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">
@@ -188,8 +191,16 @@ export default class extends Page {
                 </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 })} />
+                  <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">
@@ -222,6 +233,7 @@ export default class extends Page {
               { title: 'FAQs', key: '3' },
               { title: '学员评价', key: '4' },
             ]}
+            onChange={tab => this.onChangeTab(tab)}
           />
           {this[`renderTab${info}`]()}
         </div>
@@ -230,18 +242,62 @@ export default class extends Page {
   }
 
   renderTab1() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="teach-item">
+          <div className="left t-c">
+            <Assets className="m-b-1" />
+            <div className="t-1 t-s-20">李奕都(DUKB24)</div>
+          </div>
+          <div className="right t-1 t-s-16">
+            针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练。针对 0G20 的集中训练针对
+            0G20 的集中训练针对 0G20 的集,中训练针对 0G20 的集中训练针对 0G20 的集中训练针对。 针对 0G20
+            的集中训。练针对 0G20 的集中训练针对 0G20 的集中训练针对 0G20 的集中训练。 针对 0G20 的集中训练针对 0G20
+            的集中训练针对 0G。
+          </div>
+        </div>
+      </div>
+    );
   }
 
   renderTab2() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <Consultation />
+      </div>
+    );
   }
 
   renderTab3() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <AnswerCarousel
+          hideBtn
+          list={[
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+            { answer: '123123', content: '12312312' },
+          ]}
+        />
+      </div>
+    );
   }
 
   renderTab4() {
-    return <div />;
+    return (
+      <div className="tab-layout">
+        <div className="m-b-1 t-r">
+          <Button width={100} radius>
+            写评论
+          </Button>
+        </div>
+        <Comment />
+        <Comment />
+        <Comment />
+        <Comment />
+        <Comment />
+      </div>
+    );
   }
 }