├── .eslintrc ├── .gitignore ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── static │ └── a.js ├── scripts ├── build.js ├── start.js └── test.js └── src ├── App.js ├── api ├── http.js └── index.js ├── assets ├── style │ ├── reset.scss │ └── theme.scss └── wechat.jpg ├── components ├── BackHome │ └── index.jsx ├── Banner │ ├── index.jsx │ └── style.scss ├── Countdown │ ├── index.jsx │ └── style.scss ├── CourseList │ ├── index.jsx │ └── style.scss ├── FooterBar │ ├── index.jsx │ └── style.scss ├── Loading │ ├── index.jsx │ └── style.scss ├── LoadingM │ ├── index.jsx │ ├── index.scss │ └── loading.gif ├── StatusBtn │ ├── index.jsx │ └── style.scss └── VideoAlert │ ├── index.jsx │ └── style.scss ├── containers ├── CourseDetail │ ├── DrawerList │ │ ├── index.jsx │ │ └── style.scss │ ├── FooterBar │ │ ├── index.jsx │ │ └── style.scss │ ├── GroupList │ │ ├── index.jsx │ │ └── style.scss │ ├── Header │ │ ├── index.jsx │ │ └── style.scss │ ├── Tab │ │ ├── index.jsx │ │ └── style.scss │ └── index.jsx ├── Home │ ├── CourseTabs │ │ ├── index.jsx │ │ └── style.scss │ ├── Lists │ │ ├── index.jsx │ │ └── style.scss │ ├── NullCourse │ │ ├── index.jsx │ │ └── style.scss │ └── index.jsx ├── My │ ├── List │ │ ├── index.jsx │ │ ├── listConfig.js │ │ └── style.scss │ ├── Top │ │ ├── index.jsx │ │ └── style.scss │ └── index.jsx ├── OtherCourse │ ├── Header │ │ ├── index.jsx │ │ └── style.scss │ ├── List │ │ └── index.jsx │ └── index.jsx ├── Test │ ├── Wrapper │ │ └── index.js │ ├── hook.js │ ├── hooks.js │ ├── index.jsx │ ├── main.js │ └── style.css ├── UserInfo │ └── index.jsx ├── WorkDetail │ ├── FooterBtn │ │ ├── index.jsx │ │ └── style.scss │ ├── FooterInput │ │ ├── index.jsx │ │ └── style.scss │ ├── Header │ │ ├── index.jsx │ │ └── style.scss │ ├── List_tpl │ │ ├── index.jsx │ │ └── style.scss │ ├── TeachList │ │ └── index.jsx │ ├── TopicList │ │ ├── index.jsx │ │ └── style.scss │ └── index.jsx └── Works │ ├── List │ ├── index.jsx │ └── style.scss │ ├── Lists │ ├── index.jsx │ └── style.scss │ ├── Topbar │ └── index.jsx │ └── index.jsx ├── index.js ├── router ├── index.jsx └── routes.js ├── store ├── clearState.js ├── courseDetail.js ├── global.js ├── home.js ├── index.js ├── otherCourse.js ├── userInfos.js ├── workDetail.js └── works.js └── utils ├── rem.js └── utils.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "rules": { 4 | "no-multi-spaces": 1, 5 | "react/jsx-space-before-closing": 0, // 总是在自动关闭的标签前加一个空格,正常情况下也不需要换行 6 | "jsx-quotes": 1, 7 | "react/jsx-closing-bracket-location": 1, // 遵循JSX语法缩进/格式 8 | "react/jsx-boolean-value": 1, // 如果属性值为 true, 可以直接省略 9 | "react/no-string-refs": 1, // 总是在Refs里使用回调函数 10 | "react/self-closing-comp": 1, // 对于没有子元素的标签来说总是自己关闭标签 11 | "react/jsx-no-bind": 0, // 当在 render() 里使用事件处理方法时,提前在构造函数里把 this 绑定上去 12 | "react/sort-comp": 1, // 按照具体规范的React.createClass 的生命周期函数书写代码 13 | "react/jsx-pascal-case": 1 // React模块名使用帕斯卡命名,实例使用骆驼式命名 14 | } 15 | } 16 | 17 | //0 : off 1 : warning 2 error -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | .package-lock.json 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 第一个react项目。 2 | 3 | npm intall; 4 | 5 | npm start; -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebookincubator/create-react-app/issues/253. 46 | // It Works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/static/a.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-My/live/10badc021aaf6c269ec89bee9135803f91943ca8/public/static/a.js -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const path = require('path'); 18 | const chalk = require('chalk'); 19 | const fs = require('fs-extra'); 20 | const webpack = require('webpack'); 21 | const config = require('../config/webpack.config.prod'); 22 | const paths = require('../config/paths'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 25 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 26 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 27 | const printBuildError = require('react-dev-utils/printBuildError'); 28 | 29 | const measureFileSizesBeforeBuild = 30 | FileSizeReporter.measureFileSizesBeforeBuild; 31 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 32 | const useYarn = fs.existsSync(paths.yarnLockFile); 33 | 34 | // These sizes are pretty large. We'll warn for bundles exceeding them. 35 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 36 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 37 | 38 | // Warn and crash if required files are missing 39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 40 | process.exit(1); 41 | } 42 | 43 | // First, read the current file sizes in build directory. 44 | // This lets us display how much they changed later. 45 | measureFileSizesBeforeBuild(paths.appBuild) 46 | .then(previousFileSizes => { 47 | // Remove all content but keep the directory so that 48 | // if you're in it, you don't end up in Trash 49 | fs.emptyDirSync(paths.appBuild); 50 | // Merge with the public folder 51 | copyPublicFolder(); 52 | // Start the webpack build 53 | return build(previousFileSizes); 54 | }) 55 | .then( 56 | ({ stats, previousFileSizes, warnings }) => { 57 | if (warnings.length) { 58 | console.log(chalk.yellow('Compiled with warnings.\n')); 59 | console.log(warnings.join('\n\n')); 60 | console.log( 61 | '\nSearch for the ' + 62 | chalk.underline(chalk.yellow('keywords')) + 63 | ' to learn more about each warning.' 64 | ); 65 | console.log( 66 | 'To ignore, add ' + 67 | chalk.cyan('// eslint-disable-next-line') + 68 | ' to the line before.\n' 69 | ); 70 | } else { 71 | console.log(chalk.green('Compiled successfully.\n')); 72 | } 73 | 74 | console.log('File sizes after gzip:\n'); 75 | printFileSizesAfterBuild( 76 | stats, 77 | previousFileSizes, 78 | paths.appBuild, 79 | WARN_AFTER_BUNDLE_GZIP_SIZE, 80 | WARN_AFTER_CHUNK_GZIP_SIZE 81 | ); 82 | console.log(); 83 | 84 | const appPackage = require(paths.appPackageJson); 85 | const publicUrl = paths.publicUrl; 86 | const publicPath = config.output.publicPath; 87 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 88 | printHostingInstructions( 89 | appPackage, 90 | publicUrl, 91 | publicPath, 92 | buildFolder, 93 | useYarn 94 | ); 95 | }, 96 | err => { 97 | console.log(chalk.red('Failed to compile.\n')); 98 | printBuildError(err); 99 | process.exit(1); 100 | } 101 | ); 102 | 103 | // Create the production build and print the deployment instructions. 104 | function build(previousFileSizes) { 105 | console.log('Creating an optimized production build...'); 106 | 107 | let compiler = webpack(config); 108 | return new Promise((resolve, reject) => { 109 | compiler.run((err, stats) => { 110 | if (err) { 111 | return reject(err); 112 | } 113 | const messages = formatWebpackMessages(stats.toJson({}, true)); 114 | if (messages.errors.length) { 115 | // Only keep the first error. Others are often indicative 116 | // of the same problem, but confuse the reader with noise. 117 | if (messages.errors.length > 1) { 118 | messages.errors.length = 1; 119 | } 120 | return reject(new Error(messages.errors.join('\n\n'))); 121 | } 122 | if ( 123 | process.env.CI && 124 | (typeof process.env.CI !== 'string' || 125 | process.env.CI.toLowerCase() !== 'false') && 126 | messages.warnings.length 127 | ) { 128 | console.log( 129 | chalk.yellow( 130 | '\nTreating warnings as errors because process.env.CI = true.\n' + 131 | 'Most CI servers set it automatically.\n' 132 | ) 133 | ); 134 | return reject(new Error(messages.warnings.join('\n\n'))); 135 | } 136 | return resolve({ 137 | stats, 138 | previousFileSizes, 139 | warnings: messages.warnings, 140 | }); 141 | }); 142 | }); 143 | } 144 | 145 | function copyPublicFolder() { 146 | fs.copySync(paths.appPublic, paths.appBuild, { 147 | dereference: true, 148 | filter: file => file !== paths.appHtml, 149 | }); 150 | } 151 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const fs = require('fs'); 18 | const chalk = require('chalk'); 19 | const webpack = require('webpack'); 20 | const WebpackDevServer = require('webpack-dev-server'); 21 | const clearConsole = require('react-dev-utils/clearConsole'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const { 24 | choosePort, 25 | createCompiler, 26 | prepareProxy, 27 | prepareUrls, 28 | } = require('react-dev-utils/WebpackDevServerUtils'); 29 | const openBrowser = require('react-dev-utils/openBrowser'); 30 | const paths = require('../config/paths'); 31 | const config = require('../config/webpack.config.dev'); 32 | const createDevServerConfig = require('../config/webpackDevServer.config'); 33 | 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | const isInteractive = process.stdout.isTTY; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // Tools like Cloud9 rely on this. 43 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 44 | const HOST = process.env.HOST || '0.0.0.0'; 45 | 46 | 47 | if (process.env.HOST) { 48 | console.log( 49 | chalk.cyan( 50 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 51 | chalk.bold(process.env.HOST) 52 | )}` 53 | ) 54 | ); 55 | console.log( 56 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 57 | ); 58 | console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`); 59 | console.log(); 60 | } 61 | 62 | // We attempt to use the default port but if it is busy, we offer the user to 63 | // run on a different port. `choosePort()` Promise resolves to the next free port. 64 | choosePort(HOST, DEFAULT_PORT) 65 | .then(port => { 66 | if (port == null) { 67 | // We have not found a port. 68 | return; 69 | } 70 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 71 | const appName = require(paths.appPackageJson).name; 72 | const urls = prepareUrls(protocol, HOST, port); 73 | // Create a webpack compiler that is configured with custom messages. 74 | const compiler = createCompiler(webpack, config, appName, urls, useYarn); 75 | // Load proxy config 76 | const proxySetting = require(paths.appPackageJson).proxy; 77 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 78 | // Serve webpack assets generated by the compiler over a web sever. 79 | const serverConfig = createDevServerConfig( 80 | proxyConfig, 81 | urls.lanUrlForConfig 82 | ); 83 | const devServer = new WebpackDevServer(compiler, serverConfig); 84 | // Launch WebpackDevServer. 85 | devServer.listen(port, HOST, err => { 86 | if (err) { 87 | return console.log(err); 88 | } 89 | if (isInteractive) { 90 | clearConsole(); 91 | } 92 | console.log(chalk.cyan('Starting the development server...\n')); 93 | 94 | openBrowser(urls.localUrlForBrowser); 95 | }); 96 | 97 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 98 | process.on(sig, function() { 99 | devServer.close(); 100 | process.exit(); 101 | }); 102 | }); 103 | 104 | }) 105 | .catch(err => { 106 | if (err && err.message) { 107 | console.log(err.message); 108 | } 109 | process.exit(1); 110 | 111 | 112 | }); 113 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Provider } from "react-redux"; 3 | import "./assets/style/reset.scss"; 4 | import "./utils/rem"; 5 | import store from "./store"; 6 | import { getUserInfos } from "./api"; 7 | import Router from "./router"; 8 | import axios from "axios"; 9 | 10 | 11 | class App extends Component { 12 | componentDidMount() { 13 | /*** 14 | * https://www.hihiworld.com 测试环境地址,自行微信登陆获取cookie. 15 | * 即可访问到所有接口信息 16 | * */ 17 | let cookie = "nykakdst23i04exaavx2p2cx"; 18 | //let testCookie = "wv4kfu2cq24afhpw1jhtihbb"; 19 | document.cookie = "ASP.NET_SessionId=" + cookie; 20 | //document.cookie = null; 21 | getUserInfos().then(res => { 22 | console.log(res); 23 | //if() 24 | // top.location.href = userServer + "/account/loginwx?rurl=" + encodeURIComponent(location.href.split("#")[1]) 25 | }); 26 | 27 | // this.onGetUser(); 28 | 29 | // fetch('http://192.168.1.105:8800/v2').then(response => { 30 | // return response.json(); 31 | // }).then(res =>{ 32 | // console.log(res) 33 | // }) 34 | } 35 | 36 | 37 | render() { 38 | return ( 39 | 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /src/api/http.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { Toast } from "antd-mobile"; 3 | 4 | const config = { 5 | server: "//www.hihiworld.com/Live", 6 | host: "www.hihiworld.com", 7 | upload: "//upload.hihiworld.com", 8 | ws: "wss://socket.hihiworld.com/ws" 9 | }; 10 | const successCode = 0; 11 | let httpNum = 0; 12 | const instance = axios.create({ 13 | baseURL: "/Live", 14 | withCredentials: true // 跨域类型时是否在请求中协带cookie, 15 | }); 16 | 17 | instance.interceptors.request.use(function(config) { 18 | if (httpNum === 0) { 19 | // Toast.loading("Loading...", 0); 20 | } 21 | httpNum++; 22 | return config; 23 | }); 24 | instance.interceptors.response.use(function(config) { 25 | if (httpNum <= 0) return; 26 | httpNum--; 27 | if (httpNum === 0) { 28 | Toast.hide(); 29 | } 30 | return config; 31 | }); 32 | 33 | export default class Https { 34 | static get(url, params = {}) { 35 | return new Promise((resolve, reject) => { 36 | instance.get(url, { 37 | params 38 | }).then(({ data }) => { 39 | if (data.Code === successCode) { 40 | resolve(data); 41 | } else if (data.Code === -102) { 42 | //window.location.href = config.server + "/account/loginwx?rurl=" + window.location.href.split("#")[1] 43 | const result = data.Message || data; 44 | resolve({ data: result }); 45 | } else { 46 | console.log(data); 47 | reject({ err: data.Message, name: data.name || "" }); 48 | } 49 | }).catch((err) => { 50 | console.log(err); 51 | reject({ err: JSON.stringify(err) }); 52 | }); 53 | }); 54 | } 55 | 56 | static post(url, params = {}) { 57 | return new Promise((resolve, reject) => { 58 | const data = params !== null ? params : null; 59 | instance.post(url, data).then(({ data }) => { 60 | if (data.status === successCode || data.status === undefined) { 61 | const result = data.data || data; 62 | resolve({ data: result }); 63 | } else if (data.status === -2) { 64 | const result = data.data || data; 65 | resolve({ data: result }); 66 | } else { 67 | reject({ err: data.errmsg, name: data.name || "" }); 68 | } 69 | }).catch((err) => { 70 | reject({ err: JSON.stringify(err) }); 71 | }); 72 | }); 73 | } 74 | } -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import Https from "./http"; 2 | 3 | export const getUserInfos = (params) => { 4 | return Https.get("/My/GetUserProfile?", params); 5 | }; 6 | 7 | export const getQueryList = (params) => { 8 | return Https.get("/News/QueryList", params); 9 | }; 10 | 11 | //首页课程列表 12 | export const getSumGetChannelCourseGroup = (params) => { 13 | return Https.get("/Sum/GetChannelCourseGroup", params); 14 | }; 15 | 16 | export const getCourseChannelQueryList = (params) => { 17 | return Https.get("/CourseChannel/QueryList", params); 18 | }; 19 | export const getCourseGroupQueryList = (params) => { 20 | return Https.get("/CourseGroup/QueryList", params); 21 | }; 22 | export const getCourseWork = (params) => { 23 | return Https.get("/CourseWork/QueryList", params); 24 | }; 25 | // export const getCourseWork = (params) => { 26 | // return axios.get('/CourseWork/QueryList', {params:params}) 27 | // 28 | // }; 29 | 30 | export const getEnterCourseGroup = (params) => { 31 | return Https.get("/My/EnterCourseGroup", params); 32 | }; 33 | //课程详情团列表 34 | export const getTuanQueryList = (params) => { 35 | return Https.get("/Tuan/QueryList", params); 36 | }; 37 | 38 | export const getCourseWorkQueryById = (params) => { 39 | return Https.get("/CourseWork/QueryById", params); 40 | }; 41 | 42 | 43 | //评论列表 44 | export const getCommentQueryList = (params) => { 45 | return Https.get("/Comment/QueryList", params); 46 | }; 47 | 48 | export const getIsLike = (params) => { 49 | return Https.get("/My/IsLike", params); 50 | }; 51 | //点赞成功 52 | export const getAddLike = (params) => { 53 | return Https.get("/My/AddLike", params); 54 | }; 55 | //取消成功 56 | export const getRemoveLike = (params) => { 57 | return Https.get("/My/RemoveLike", params); 58 | }; 59 | //添加评论 60 | export const getAddComment = (params) => { 61 | return Https.get("/My/AddComment", params); 62 | }; -------------------------------------------------------------------------------- /src/assets/style/reset.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 3 | * http://cssreset.com 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | table, 60 | caption, 61 | tbody, 62 | tfoot, 63 | thead, 64 | tr, 65 | th, 66 | td, 67 | article, 68 | aside, 69 | canvas, 70 | details, 71 | embed, 72 | figure, 73 | figcaption, 74 | footer, 75 | header, 76 | menu, 77 | nav, 78 | output, 79 | ruby, 80 | section, 81 | summary, 82 | time, 83 | mark, 84 | audio, 85 | video, 86 | input { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font-size: 100%; 91 | font-weight: normal; 92 | vertical-align: baseline; 93 | } 94 | 95 | /* HTML5 display-role reset for older browsers */ 96 | 97 | article, 98 | aside, 99 | details, 100 | figcaption, 101 | figure, 102 | footer, 103 | header, 104 | menu, 105 | nav, 106 | section { 107 | display: block; 108 | } 109 | 110 | body { 111 | line-height: 1; 112 | background: #d6f1ff !important; 113 | } 114 | 115 | blockquote, 116 | q { 117 | quotes: none; 118 | } 119 | 120 | blockquote:before, 121 | blockquote:after, 122 | q:before, 123 | q:after { 124 | content: none; 125 | } 126 | 127 | table { 128 | border-collapse: collapse; 129 | border-spacing: 0; 130 | } 131 | 132 | /* custom */ 133 | 134 | a { 135 | color: #818181; 136 | text-decoration: none; 137 | -webkit-backface-visibility: hidden; 138 | } 139 | 140 | html, 141 | body { 142 | width: 100%; 143 | background: #d6f1ff; 144 | color: #444444; 145 | font-size: 14px; 146 | font-weight: 200; 147 | font-family: 'PingFang SC', 'STHeitiSC-Light', 'Arial, Helvetica, sans-serif'; 148 | } 149 | 150 | @media all and (min-width: 768px) { 151 | body { 152 | max-width: 768px !important; 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /src/assets/style/theme.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | /*常用样式封装*/ 4 | 5 | .head-img { 6 | width: .8rem; 7 | height: .8rem; 8 | border-radius: 50%; 9 | overflow: hidden; 10 | float: left; 11 | } 12 | 13 | .head-img>img { 14 | width: .8rem; 15 | height: .8rem; 16 | } 17 | 18 | .stroke { 19 | text-decoration: line-through; 20 | } 21 | 22 | /*自定义样式封装*/ 23 | 24 | .back { 25 | width: 1.6rem; 26 | height: .56rem; 27 | line-height: .56rem; 28 | background: rgba(3, 43, 80, 0.8); 29 | border-radius: 0.11rem; 30 | position: fixed; 31 | top: .4rem; 32 | left: .4rem; 33 | z-index: 99; 34 | color: white; 35 | font-size: .32rem; 36 | text-align: center; 37 | } 38 | 39 | .bold { 40 | font-weight: bold; 41 | } 42 | 43 | .white-bg { 44 | background: white; 45 | } 46 | 47 | .t-c { 48 | text-align: center; 49 | } 50 | 51 | .t-r { 52 | text-align: right; 53 | } 54 | 55 | .f-left { 56 | float: left; 57 | } 58 | 59 | .f-right { 60 | float: right; 61 | } 62 | 63 | .over-hid { 64 | overflow: hidden; 65 | } 66 | 67 | .l-h-53 { 68 | line-height: .53rem; 69 | } 70 | 71 | .p-t-2 { 72 | padding-top: .2rem; 73 | overflow: hidden; 74 | } 75 | 76 | .p-t-4 { 77 | padding-top: .4rem; 78 | overflow: hidden; 79 | } 80 | 81 | .p-r-02 { 82 | padding-right: .2rem; 83 | } 84 | 85 | .p-r-16 { 86 | padding-right: 1.6rem; 87 | } 88 | 89 | .p-lan-4 { 90 | /*padding: 0 .4rem; */ 91 | padding-left: .4rem; 92 | padding-right: .4rem; 93 | } 94 | 95 | .p-lon-4 { 96 | padding: .4rem 0; 97 | } 98 | 99 | .p-lon-2 { 100 | padding: .2rem 0; 101 | } 102 | 103 | .m-lon-2 { 104 | margin: .2rem 0; 105 | } 106 | 107 | .m-b-2 { 108 | margin-bottom: .2rem; 109 | } 110 | 111 | .t-n-1 { 112 | position: relative; 113 | top: -.1rem; 114 | } 115 | 116 | .t-n-2 { 117 | position: relative; 118 | top: -.2rem; 119 | } 120 | 121 | .t-j-1 { 122 | position: relative; 123 | top: .1rem; 124 | } 125 | 126 | .sign-before { 127 | padding-left: .25rem; 128 | position: relative; 129 | } 130 | 131 | .sign-before:before { 132 | display: block; 133 | content: " "; 134 | width: .04rem; 135 | height: .32rem; 136 | background: #87DBFF; 137 | position: absolute; 138 | left: 0; 139 | top: .1rem; 140 | } 141 | 142 | .pro-bg { 143 | display: inline-block; 144 | /*width: .85rem;*/ 145 | height: .43rem; 146 | line-height: .4rem; 147 | text-align: center; 148 | background: #87DBFF; 149 | } 150 | 151 | .head-back { 152 | width: 10rem; 153 | height: 1.1rem; 154 | padding: .2rem .3rem; 155 | background: white; 156 | } 157 | 158 | .head-left { 159 | height: .7rem; 160 | line-height: .7rem; 161 | float: left; 162 | display: -webkit-box; 163 | display: -ms-flexbox; 164 | display: flex; 165 | -webkit-box-align: center; 166 | -ms-flex-align: center; 167 | align-items: center; 168 | } 169 | 170 | .head-left span { 171 | position: relative; 172 | top: .04rem; 173 | } 174 | 175 | .head-back img { 176 | width: .54rem; 177 | height: .54rem; 178 | } 179 | 180 | .head-right { 181 | height: .7rem; 182 | margin-top: .08rem; 183 | float: right; 184 | } 185 | 186 | .upload { 187 | display: block; 188 | width: 100%; 189 | height: 100%; 190 | position: absolute; 191 | top: 0; 192 | left: 0; 193 | z-index: 10; 194 | opacity: 0; 195 | } 196 | 197 | /*字体大小*/ 198 | 199 | .f-s-27 { 200 | font-size: .27rem; 201 | } 202 | 203 | .f-s-29 { 204 | font-size: .29rem; 205 | } 206 | 207 | .f-s-32 { 208 | font-size: .32rem; 209 | } 210 | 211 | .f-s-37 { 212 | font-size: .37rem; 213 | } 214 | 215 | .f-s-43 { 216 | font-size: .43rem; 217 | } 218 | 219 | .f-s-53 { 220 | font-size: .53rem; 221 | } 222 | 223 | .f-s-96 { 224 | font-size: .96rem; 225 | 226 | } 227 | 228 | /*字体颜色*/ 229 | 230 | .f-c-00 { 231 | color: #009DFF; 232 | } 233 | 234 | .f-c-009 { 235 | color: #009DFF; 236 | } 237 | 238 | .f-c-03 { 239 | color: #032B50; 240 | } 241 | 242 | .f-c-36 { 243 | color: #36F0FF; 244 | } 245 | 246 | .f-c-44 { 247 | color: #444444; 248 | } 249 | 250 | .f-c-6f { 251 | color: #6F9CB9; 252 | } 253 | 254 | .f-c-10 { 255 | color: #1088FF; 256 | } 257 | 258 | .f-c-9b { 259 | color: #9BC1D4; 260 | } 261 | 262 | .f-c-fb { 263 | color: #FBFF05; 264 | } 265 | 266 | .f-c-ff { 267 | color: #FF476F; 268 | } 269 | 270 | .f-c-w { 271 | color: white; 272 | } 273 | 274 | /*按钮样式*/ 275 | 276 | .full-danger-btn { 277 | width: 10rem; 278 | height: 1.3rem; 279 | line-height: 1.3rem; 280 | text-align: center; 281 | background: #FF476F; 282 | color: white; 283 | font-size: .43rem; 284 | position: fixed; 285 | bottom: 0; 286 | left: 0; 287 | z-index: 12; 288 | } 289 | 290 | .half-pri-btn { 291 | width: 5.87rem; 292 | height: 1rem; 293 | line-height: 1rem; 294 | text-align: center; 295 | background: #0383D3; 296 | border-radius: 0.53rem; 297 | font-weight: bold; 298 | color: white; 299 | font-size: .43rem; 300 | } 301 | 302 | .half-danger-btn { 303 | width: 5.87rem; 304 | height: 1rem; 305 | line-height: 1rem; 306 | text-align: center; 307 | background: #FF476F; 308 | border-radius: 0.53rem; 309 | font-weight: bold; 310 | color: white; 311 | font-size: .43rem; 312 | } 313 | 314 | .third-pri-btn { 315 | width: 3.2rem; 316 | height: 1rem; 317 | line-height: 1rem; 318 | text-align: center; 319 | background: #0383D3; 320 | border-radius: 0.53rem; 321 | font-weight: bold; 322 | color: white; 323 | font-size: .43rem; 324 | } 325 | 326 | .third-info-btn { 327 | width: 3.2rem; 328 | height: 1rem; 329 | line-height: 1rem; 330 | text-align: center; 331 | background: #009DFF; 332 | border-radius: 0.53rem; 333 | font-weight: bold; 334 | font-size: .43rem; 335 | color: white; 336 | } 337 | 338 | .third-danger-btn { 339 | width: 3.2rem; 340 | height: 1rem; 341 | line-height: 1rem; 342 | text-align: center; 343 | background: #FF476F; 344 | border-radius: 0.53rem; 345 | font-weight: bold; 346 | color: white; 347 | font-size: .43rem; 348 | } 349 | 350 | .quarter-pri-btn { 351 | width: 2.4rem; 352 | height: 1rem; 353 | line-height: 1rem; 354 | text-align: center; 355 | background: #0383D3; 356 | border-radius: 0.53rem; 357 | font-weight: bold; 358 | font-size: .43rem; 359 | color: white; 360 | } 361 | 362 | .mini-danger-btn { 363 | font-size: 0.32rem; 364 | padding: .06rem .1rem; 365 | border-radius: .11rem; 366 | background: #FF476F; 367 | color: white; 368 | } 369 | 370 | .mini-info-btn { 371 | font-size: .32rem; 372 | padding: .06rem .1rem; 373 | border-radius: .11rem; 374 | background: #009DFF; 375 | color: white; 376 | } 377 | 378 | .mini-disable-btn { 379 | font-size: .32rem; 380 | padding: .06rem .1rem; 381 | border-radius: .11rem; 382 | background: #9BC1D4; 383 | color: white; 384 | } 385 | 386 | .mini-ghost-btn { 387 | display: inline-block; 388 | font-size: 0.32rem; 389 | padding: 0 .06rem; 390 | border-radius: .11rem; 391 | border: 1px solid #FF476F; 392 | color: #FF476F; 393 | font-size: .26rem; 394 | } 395 | 396 | .mini-brown-btn { 397 | font-size: .32rem; 398 | padding: .06rem .1rem; 399 | border-radius: .11rem; 400 | background: #032B50; 401 | color: white; 402 | } 403 | 404 | .mini-black-btn { 405 | font-size: .27rem; 406 | padding: .06rem .16rem; 407 | border-radius: .11rem; 408 | background: #000000; 409 | /*color: white; */ 410 | } 411 | 412 | .limit-2-rows { 413 | height: 1.1rem; 414 | line-height: .54rem; 415 | display: -webkit-box; 416 | -webkit-box-orient: vertical; 417 | -webkit-line-clamp: 2; 418 | overflow: hidden; 419 | } 420 | 421 | .limit-1-rows { 422 | line-height: .54rem; 423 | display: -webkit-box; 424 | -webkit-box-orient: vertical; 425 | -webkit-line-clamp: 1; 426 | overflow: hidden; 427 | } 428 | -------------------------------------------------------------------------------- /src/assets/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-My/live/10badc021aaf6c269ec89bee9135803f91943ca8/src/assets/wechat.jpg -------------------------------------------------------------------------------- /src/components/BackHome/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const style = { 5 | width: "1.13rem", 6 | height: ".4rem", 7 | background: "rgba(3, 43, 80, .8)", 8 | borderRadius: ".1rem", 9 | lineHeight: ".4rem", 10 | textAlign: "center", 11 | fontSize: "12px", 12 | position: "fixed", 13 | top: ".3rem", 14 | left: ".3rem", 15 | zIndex: "1" 16 | }; 17 | const a = { 18 | color: "#ffffff" 19 | }; 20 | export default class BackHome extends React.Component { 21 | render() { 22 | return ( 23 |
24 | 返回首页 25 |
26 | ); 27 | } 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /src/components/Banner/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Carousel, WingBlank } from "antd-mobile"; 3 | import CSSModules from "react-css-modules"; 4 | import styles from "./style.scss"; 5 | 6 | /** 7 | * @constructor 8 | * @description 轮播图 9 | */ 10 | 11 | @CSSModules(styles) 12 | export default class Banner extends React.Component { 13 | state = { 14 | imgHeight: 150 15 | }; 16 | 17 | render() { 18 | return ( 19 | 20 | 24 | {this.props.data.map((item, index) => ( 25 | 30 | { 35 | this.setState({ imgHeight: "auto" }); 36 | }} 37 | /> 38 | 39 | ))} 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/components/Banner/style.scss: -------------------------------------------------------------------------------- 1 | 2 | :global { 3 | .am-wingblank{ 4 | margin: 0 !important; 5 | } 6 | .slider-list{ 7 | height: 3rem !important; 8 | } 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/Countdown/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import styles from "./style.scss"; 3 | 4 | let add0 = m => { 5 | return m < 10 ? "0" + m : m; 6 | }; 7 | 8 | class Countdown extends Component { 9 | state = { // 通过state来定义当前组件内部自己的数据 10 | clocker: "", 11 | timeobj: { 12 | } 13 | }; 14 | componentDidMount = () => { 15 | let date = Math.round(new Date() / 1000); 16 | let timeLag = parseInt(this.props.endTime, 10) - parseInt(date, 10); 17 | let seconds = timeLag; 18 | if (timeLag % 60 < 10) { 19 | seconds = "0" + timeLag % 60; 20 | } else { 21 | seconds = timeLag % 60; 22 | } 23 | let timeobj = { 24 | seconds: seconds, 25 | minutes: Math.floor(timeLag / 60) % 60, 26 | hours: Math.floor(timeLag / 60 / 60) % 24, 27 | days: Math.floor(timeLag / 60 / 60 / 24), 28 | weeks: Math.floor(timeLag / 60 / 60 / 24 / 7), 29 | months: Math.floor(timeLag / 60 / 60 / 24 / 30), 30 | years: Math.floor(timeLag / 60 / 60 / 24 / 365) 31 | }; 32 | this.setState({ 33 | timeobj 34 | }); 35 | window.timer = setInterval(() => { // 创建倒计时定时器 36 | let time = timeLag--; // time为两个时间戳之间相差的秒数 37 | let _clocker = ""; // 打印出时间对象 38 | let seconds = time; 39 | if (time % 60 < 10) { 40 | seconds = "0" + time % 60; 41 | } else { 42 | seconds = time % 60; 43 | } 44 | let timeobj = { 45 | seconds: seconds, 46 | minutes: Math.floor(time / 60) % 60, 47 | hours: Math.floor(time / 60 / 60) % 24, 48 | days: Math.floor(time / 60 / 60 / 24), 49 | weeks: Math.floor(time / 60 / 60 / 24 / 7), 50 | months: Math.floor(time / 60 / 60 / 24 / 30), 51 | years: Math.floor(time / 60 / 60 / 24 / 365) 52 | }; 53 | _clocker = `${add0(timeobj.days)} : ${add0(timeobj.hours)} : ${add0(timeobj.minutes)} : ${add0(timeobj.seconds)}`; 54 | if (time <= 0) { // 当时间差小于等于0的时候证明倒计时已经过结束 55 | console.log("倒计时结束了"); 56 | _clocker = this.props.msg || "倒计时已结束"; 57 | clearInterval(this.timer); 58 | } 59 | this.setState({ 60 | clocker: _clocker, 61 | timeobj 62 | }); 63 | }, 1000); 64 | }; 65 | 66 | componentWillUnmount() { 67 | clearInterval(window.timer); 68 | this.setState = (state, callback) => { 69 | return; 70 | }; 71 | }; 72 | 73 | render() { 74 | const obj = this.state.timeobj; 75 | let isLoad = this.props.isLoad; 76 | if (Object.keys(obj).length === 0) { 77 | return null; 78 | } 79 | return ( 80 | isLoad === 1 ? 81 |
82 |

倒计时:

83 |
84 | {obj.days}天 85 |
86 |
87 | {obj.hours}时 88 |
89 |
90 | {obj.minutes} 分 91 |
92 |
93 | {obj.seconds} 秒 94 |
95 | 96 |
:  {obj.hours}:{obj.minutes}:{obj.seconds} 97 | 98 | ); 99 | } 100 | } 101 | 102 | export default Countdown; 103 | -------------------------------------------------------------------------------- /src/components/Countdown/style.scss: -------------------------------------------------------------------------------- 1 | .countdown{ 2 | display: flex; 3 | align-items: center; 4 | color: #ffffff; 5 | margin-bottom: .1rem; 6 | div{ 7 | background: #ff476f; 8 | margin-right: 5px; 9 | padding: 2px; 10 | } 11 | span,p{ 12 | color: #000000; 13 | } 14 | } -------------------------------------------------------------------------------- /src/components/CourseList/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import styles from "./style.scss"; 4 | import { List, Badge } from "antd-mobile"; 5 | import StatusBtn from "../../components/StatusBtn"; 6 | 7 | /** 8 | * @constructor 9 | * @description <课程列表模版> 10 | */ 11 | export const CourseList = (item) => { 12 | return ( 13 |
14 |
15 |
16 | 17 |
18 | 19 | 20 |
{item.CourseGroupName}
21 |
22 |
23 | 24 |
25 |
26 | 27 |
{item.LiveTimeDisplay}
28 |
29 |
30 |
31 |

{item.SaleUserCnt}人报名

32 |
33 | {item.TuanPrice !== 0 && } 34 |

  ${item.TuanPrice === 0 ? item.OldPrice : item.TuanPrice}

35 |
36 |
37 |
38 | 39 |
40 |
41 | ); 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /src/components/CourseList/style.scss: -------------------------------------------------------------------------------- 1 | 2 | .course-list{ 3 | :global { 4 | .am-list-item { 5 | padding: 0; 6 | } 7 | .am-list-item img { 8 | width: 2.25rem; 9 | height: 2.25rem; 10 | } 11 | .am-badge-text { 12 | background: none; 13 | } 14 | .am-list-line { 15 | display: block !important; 16 | } 17 | .am-list-content { 18 | line-height: 25px !important; 19 | } 20 | .am-badge { 21 | z-index: 0; 22 | } 23 | .am-list-body { 24 | display: inline; 25 | } 26 | .am-list-item .am-list-thumb:first-child { 27 | margin-left: .2rem; 28 | } 29 | } 30 | padding: 0 10px; 31 | //padding-bottom: 2rem; 32 | .badge { 33 | background: #444444; 34 | border-radius: 2px; 35 | position: absolute; 36 | z-index: 1; 37 | } 38 | .head { 39 | display: flex; 40 | align-items: center; 41 | font-size: 12px; 42 | .head-img { 43 | width: .6rem; 44 | height: .6rem; 45 | img { 46 | width: .6rem; 47 | height: .6rem; 48 | border-radius: 50%; 49 | } 50 | } 51 | .detail-info { 52 | color: #6f9cb9; 53 | margin-left: .1rem; 54 | } 55 | } 56 | .item { 57 | padding: 10px 0 !important; 58 | .tit{ 59 | width: 100%; 60 | white-space: normal; 61 | font-size: 14px; 62 | font-weight: bold; 63 | height: .8rem; 64 | line-height: 1.5; 65 | } 66 | } 67 | .foot { 68 | display: flex; 69 | align-items: center; 70 | justify-content: space-between; 71 | margin-top: .2rem; 72 | .userCnt { 73 | color: #6f9cb9; 74 | font-size: 14px; 75 | } 76 | .foot-item { 77 | display: flex; 78 | align-items: center; 79 | .foot-badge { 80 | margin-left: 12px; 81 | padding: 0 3px; 82 | background: #ffffff; 83 | border-radius: 2px; 84 | border: 1px solid #ff476f; 85 | z-index: 0; 86 | :global { 87 | .am-badge-text { 88 | color: #ff476f; 89 | } 90 | } 91 | } 92 | p{ 93 | font-size: 12px; 94 | color: #ff476f; 95 | } 96 | } 97 | 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /src/components/FooterBar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from "react-redux"; 3 | import { TabBar } from 'antd-mobile'; 4 | import { withRouter } from 'react-router-dom'; 5 | import styles from './style.scss'; 6 | import { action } from '../../store/global'; 7 | 8 | const mapStateToProps = (state) => { 9 | return { 10 | tabs: state.getIn(["global","tabs"]).toJS(), 11 | selectedTab: state.getIn(["global","selectedTab"]) 12 | }; 13 | }; 14 | 15 | @withRouter 16 | @connect( 17 | //state => state.global 18 | mapStateToProps 19 | ) 20 | export default class FootTabs extends React.Component { 21 | componentWillMount() { 22 | this.changeTabs(); 23 | } 24 | changeTabs() { 25 | let path = this.props.location.pathname; 26 | let index = 0; 27 | if (path === '/Home') { 28 | index = 0; 29 | } else if (path === '/works') { 30 | index = 1; 31 | } else if (path === '/My') { 32 | index = 2; 33 | } 34 | this.props.dispatch(action.changeTab(index)); 35 | } 36 | 37 | onPress(item) { 38 | this.props.dispatch(action.changeTab(item.id)); 39 | this.props.history.replace(item.hash); 40 | } 41 | 42 | renderIcons(icon) { 43 | return ( 44 |
49 | ) 50 | } 51 | 52 | renderItems() { 53 | const { tabs, selectedTab } = this.props; 54 | return tabs.map((item) => { 55 | return ( 56 | { 64 | this.onPress(item) 65 | }} 66 | > 67 | 68 | ) 69 | }) 70 | } 71 | 72 | render() { 73 | let path = this.props.location.pathname; 74 | return ( 75 | path === '/home' || path === '/works' || path === '/My' ?
76 | 82 | {this.renderItems()} 83 | 84 |
: null 85 | ); 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/components/FooterBar/style.scss: -------------------------------------------------------------------------------- 1 | .footBar{ 2 | width: 100%; 3 | position: fixed !important; 4 | bottom: 0; 5 | z-index: 9; 6 | } -------------------------------------------------------------------------------- /src/components/Loading/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './style.scss'; 3 | export default function Loading() { 4 | const info = "Loading..."; 5 | return ( 6 |
7 |
8 | 13 | 16 | 24 | 25 |
{info}
26 |
27 |
28 | ) 29 | } -------------------------------------------------------------------------------- /src/components/Loading/style.scss: -------------------------------------------------------------------------------- 1 | .loading { 2 | position: fixed; 3 | width: 100%; 4 | height: 100%; 5 | z-index: 9999; 6 | font-size: 14px; 7 | text-align: center; 8 | display: -ms-flexbox; 9 | display: flex; 10 | -ms-flex-pack: center; 11 | justify-content: center; 12 | -ms-flex-align: center; 13 | align-items: center; 14 | left: 0; 15 | top: 0; 16 | -webkit-transform: translateZ(1px); 17 | transform: translateZ(1px); 18 | 19 | } 20 | .loading-text { 21 | border-radius: 5px; 22 | padding: 15px 15px; 23 | min-width: 60px; 24 | color: #fff; 25 | background-color: rgba(58, 58, 58, 0.9); 26 | line-height: 1.5; 27 | 28 | } 29 | 30 | .loading-icon{ 31 | width: 36px; 32 | height: 36px; 33 | -webkit-animation: cirle-anim 1s linear infinite; 34 | animation: cirle-anim 1s linear infinite; 35 | } 36 | 37 | .loading-info{ 38 | margin-top: 6px; 39 | } 40 | 41 | @-webkit-keyframes cirle-anim { 42 | 100% { 43 | -webkit-transform: rotate(360deg); 44 | transform: rotate(360deg); 45 | } 46 | } 47 | @keyframes cirle-anim { 48 | 100% { 49 | -webkit-transform: rotate(360deg); 50 | transform: rotate(360deg); 51 | } 52 | } -------------------------------------------------------------------------------- /src/components/LoadingM/index.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react' 3 | import Proptypes from 'prop-types' 4 | import loading from './loading.gif' 5 | import styles from './index.scss' 6 | /** 7 | * @constructor 8 | * @description 加载更多的数据 9 | */ 10 | 11 | export default class Loading extends React.Component { 12 | static proptypes = { 13 | title: Proptypes.string, 14 | style: Proptypes.object, 15 | }; 16 | 17 | static defaultProps = { 18 | title: '', 19 | style: {}, 20 | }; 21 | 22 | constructor(props) { 23 | super(props) 24 | this.ratio = window.devicePixelRatio 25 | this.state = { 26 | width: 14 * this.ratio, 27 | height: 14 * this.ratio, 28 | } 29 | }; 30 | 31 | render() { 32 | const { width, height } = this.state 33 | // const {style} = this.props 34 | return ( 35 |
36 | 41 |

正在加载更多的数据...

42 |
43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/LoadingM/index.scss: -------------------------------------------------------------------------------- 1 | 2 | .loading { 3 | width: 100%; 4 | text-align: center; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | .desc { 9 | line-height: 20px; 10 | font-size:14px; 11 | color:black; 12 | padding-left: 8px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/LoadingM/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivan-My/live/10badc021aaf6c269ec89bee9135803f91943ca8/src/components/LoadingM/loading.gif -------------------------------------------------------------------------------- /src/components/StatusBtn/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./style.scss"; 3 | 4 | /** 5 | * @constructor 6 | * @description <直播状态按钮> 7 | */ 8 | 9 | class StatusBtn extends React.Component { 10 | 11 | toBtn() { 12 | let style, info; 13 | switch (this.props.status) { 14 | case "L": 15 | style = styles["status-l"]; 16 | info = "开课中"; 17 | break; 18 | case "W": 19 | style = styles["status-w"]; 20 | info = "预告"; 21 | break; 22 | default: 23 | return null; 24 | } 25 | return
{info}
; 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 | {this.toBtn()} 32 | 33 | 34 | ); 35 | } 36 | } 37 | 38 | export default StatusBtn; 39 | 40 | -------------------------------------------------------------------------------- /src/components/StatusBtn/style.scss: -------------------------------------------------------------------------------- 1 | .status-btn { 2 | padding: 4px 8px; 3 | display: inline-block; 4 | border-radius: 3px; 5 | font-size: 12px; 6 | white-space:nowrap; 7 | } 8 | 9 | .status-w{ 10 | background: #000; 11 | color: #fbff05; 12 | } 13 | .status-l{ 14 | background: #000; 15 | color: #36f0ff; 16 | } -------------------------------------------------------------------------------- /src/components/VideoAlert/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './style.scss'; 3 | 4 | export default class VideoAlert extends React.Component { 5 | render() { 6 | const {src, getvideoUrl} = this.props; 7 | return ( 8 |
{ 10 | getvideoUrl({url: ""}, v) 11 | }} 12 | > 13 |
15 | ) 16 | } 17 | } -------------------------------------------------------------------------------- /src/components/VideoAlert/style.scss: -------------------------------------------------------------------------------- 1 | .video-alert { 2 | width: 10rem; 3 | height: 100%; 4 | position: fixed; 5 | z-index: 99; 6 | top: 0; 7 | left: 0; 8 | background: #000; 9 | video { 10 | width: 100%; 11 | height: 4.2rem; 12 | position: fixed; 13 | top: 30% 14 | } 15 | } -------------------------------------------------------------------------------- /src/containers/CourseDetail/DrawerList/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { Drawer } from "antd-mobile"; 4 | import styles from "./style.scss"; 5 | import GroupList from "../GroupList"; 6 | import { getTuanQueryListData, actions } from "../../../store/courseDetail"; 7 | 8 | const mapDispatchToProps = (dispatch) => ({ 9 | getTuanQueryListData: (params) => dispatch(getTuanQueryListData(params)), 10 | toggleDraw: () => dispatch(actions.toggleDraw()) 11 | }); 12 | 13 | @connect( 14 | state => state.courseDetail, 15 | mapDispatchToProps 16 | ) 17 | class DrawerList extends React.Component { 18 | componentDidMount() { 19 | 20 | this.props.getTuanQueryListData({ 21 | CourseGroupId: this.props.id, 22 | pageIndex: 1, 23 | pageSize: 15 24 | }); 25 | } 26 | 27 | sidebars() { 28 | return ( 29 | 30 |
{ 31 | this.props.toggleDraw(); 32 | }}>关闭 33 |
34 | 35 |
36 | ); 37 | } 38 | 39 | render() { 40 | if (Object.keys(this.props.tuanListData).length === 0) { 41 | return null; 42 | } 43 | return ( 44 | 45 | 46 | 52 | 53 | 54 | ); 55 | } 56 | } 57 | 58 | export default DrawerList; 59 | 60 | -------------------------------------------------------------------------------- /src/containers/CourseDetail/DrawerList/style.scss: -------------------------------------------------------------------------------- 1 | .course-detail-drawer{ 2 | position: fixed; 3 | z-index: 1; 4 | :global { 5 | .am-drawer-sidebar { 6 | width: 7.5rem; 7 | overflow: hidden; 8 | background:#ffffff; 9 | } 10 | } 11 | .close-drawer{ 12 | position:absolute; 13 | right:.4rem; 14 | top:.4rem; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/containers/CourseDetail/FooterBar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./style.scss"; 3 | import { Button, WhiteSpace, WingBlank } from "antd-mobile"; 4 | 5 | /** 6 | * @constructor 7 | * @description <底部按钮栏> 8 | */ 9 | 10 | class FooterBar extends React.Component { 11 | 12 | render() { 13 | return ( 14 |
15 |
16 | 17 |

收藏

18 |
19 |
20 |
21 |

199

22 |

原价购买

23 |
24 |
25 |

199

26 |

原价购买

27 |
28 |
29 |
30 | ); 31 | } 32 | } 33 | 34 | export default FooterBar; 35 | 36 | -------------------------------------------------------------------------------- /src/containers/CourseDetail/FooterBar/style.scss: -------------------------------------------------------------------------------- 1 | .footer-bar { 2 | width: 100%; 3 | height: 1.3rem; 4 | position: fixed; 5 | bottom: 0; 6 | background: gray; 7 | .left { 8 | width: 1.8rem; 9 | height: 100%; 10 | background: #032b50; 11 | line-height: 100%; 12 | color: #6f9cb9; 13 | display: inline; 14 | float: left; 15 | padding: .2rem 0; 16 | text-align: center; 17 | .left-icon { 18 | width: .6rem; 19 | height: .6rem; 20 | } 21 | } 22 | .right { 23 | width: 100%; 24 | height: 100%; 25 | background: red; 26 | .buy-item { 27 | width: 2.8rem; 28 | height: 100%; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/containers/CourseDetail/GroupList/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import Countdown from "../../../components/Countdown"; 4 | import styles from "./style.scss"; 5 | import { actions } from "../../../store/courseDetail"; 6 | 7 | /** 8 | * @constructor 9 | * @description 中间团购列表 10 | */ 11 | 12 | const mapDispatchToProps = (dispatch) => ({ 13 | toggleDraw: () => dispatch(actions.toggleDraw()) 14 | }); 15 | 16 | @connect( 17 | null, 18 | mapDispatchToProps 19 | ) 20 | class GroupList extends React.Component { 21 | render() { 22 | const { data } = this.props; 23 | return ( 24 |
25 |
离拼团活动结束还剩 26 |
27 |
28 | {data.length}人正在开团拼单,可直接参与 29 | {!this.props.isDrawLoad && { 30 | this.props.toggleDraw(); 31 | }}>查看更多 > } 32 |
33 | { 34 | data.map(item => { 35 | let count = item.TuanTotalUserCnt - item.TuanJoinUserCnt; 36 | return ( 37 |
38 |

{item.CreateTuanUserData.NickName}

39 |
40 |
41 | 还差{count}人 42 |
剩余 43 | 49 |
50 |
参与拼团
51 |
52 |
53 | ); 54 | }) 55 | } 56 |
57 | ); 58 | } 59 | } 60 | 61 | export default GroupList; -------------------------------------------------------------------------------- /src/containers/CourseDetail/GroupList/style.scss: -------------------------------------------------------------------------------- 1 | .collage-lists { 2 | background: #ffffff; 3 | color: #000000; 4 | padding: .1rem .2rem 0 .2rem; 5 | .tit { 6 | font-weight: bold; 7 | font-size: 16px; 8 | margin-top:.3rem; 9 | } 10 | .more-tuan { 11 | padding: .1rem; 12 | display: flex; 13 | justify-content: space-between; 14 | i { 15 | color: #009dff; 16 | } 17 | } 18 | 19 | .collage-list-item { 20 | display: flex; 21 | align-items: center; 22 | justify-content: space-between; 23 | padding: .4rem 0; 24 | border-bottom: 1px solid #d6f1ff; 25 | .collage-list-item-name{ 26 | position: relative; 27 | left: 1rem; 28 | font-size: 18px; 29 | } 30 | .right { 31 | display: flex; 32 | align-items: center; 33 | .join-btn{ 34 | width: 1.3rem; 35 | height: .4rem; 36 | background: #009dff; 37 | text-align: center; 38 | line-height: .4rem; 39 | color: #ffffff; 40 | border-radius: 3px; 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/containers/CourseDetail/Header/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import styles from "./style.scss"; 4 | import Countdown from "../../../components/Countdown"; 5 | import BackHome from "../../../components/BackHome"; 6 | 7 | 8 | class Header extends React.Component { 9 | render() { 10 | const { d } = this.props; 11 | return ( 12 |
13 | 14 |
15 | 16 |
17 |
18 | 24 |
{d.CourseGroupName}
25 |
26 | {d.CourseStatus === "L" ?
  开课中
: null} 27 | {d.LiveTimeDisplay}  {d.SaleUserCnt}人报名 ¥{d.NowPrice} 28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 | {d.HostData.HostName} | 共{d.CourseCnt}两节课 36 |
37 |
38 | 主播其他课程推荐 > 39 |
40 |
41 |
42 | ); 43 | } 44 | } 45 | 46 | export default Header; 47 | -------------------------------------------------------------------------------- /src/containers/CourseDetail/Header/style.scss: -------------------------------------------------------------------------------- 1 | 2 | .course-detail { 3 | .back { 4 | width: 1.13rem; 5 | height: .4rem; 6 | background: rgba(3, 43, 80, .8); 7 | border-radius: .1rem; 8 | line-height: .4rem; 9 | text-align: center; 10 | font-size: 12px; 11 | position: fixed; 12 | top: .3rem; 13 | left: .3rem; 14 | a { 15 | color: #ffffff; 16 | } 17 | } 18 | .top-img { 19 | width: 100%; 20 | height: 4.2rem; 21 | img { 22 | width: 100%; 23 | height: 4.2rem; 24 | } 25 | } 26 | .white-bg { 27 | display: flex; 28 | margin: .1rem 0; 29 | .live { 30 | color: red; 31 | margin-right: .1rem; 32 | position: relative; 33 | &::before { 34 | display: block; 35 | content: " "; 36 | width: .10rem; 37 | height: .10rem; 38 | background: #ff476f; 39 | border-radius: 50%; 40 | position: absolute; 41 | left: 0; 42 | top: .1rem 43 | } 44 | } 45 | } 46 | .item { 47 | color: black; 48 | background: #fff; 49 | padding: .2rem .2rem .2rem .2rem; 50 | span { 51 | float: right; 52 | position: absolute; 53 | right: .2rem; 54 | } 55 | } 56 | .host-box { 57 | margin: .2rem 0; 58 | height: 50px; 59 | background: #fff; 60 | padding: .1rem .2rem .1rem .2rem; 61 | display: flex; 62 | align-items: center; 63 | .avatar { 64 | width: .6rem; 65 | height: .6rem; 66 | img { 67 | width: 100%; 68 | height: 100%; 69 | border-radius: 50%; 70 | } 71 | } 72 | .name { 73 | color: black; 74 | margin-left: .1rem; 75 | span { 76 | color: #009dff; 77 | } 78 | } 79 | .hose-go { 80 | position: absolute; 81 | right: .3rem; 82 | } 83 | span { 84 | color: #009dff; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/containers/CourseDetail/Tab/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { Tabs, Badge, List } from "antd-mobile"; 4 | import cls from "classnames"; 5 | import styles from "./style.scss"; 6 | 7 | /** 8 | * @constructor 9 | * @description 中间tabs 10 | */ 11 | 12 | export default class Tab extends React.Component { 13 | tabs = [ 14 | { title: "课程简介", sub: "1" }, 15 | { title: "课程目录", sub: "2" } 16 | ]; 17 | 18 | renderList(data) { 19 | return ( 20 | 21 | { 22 | data.map((item) => { 23 | let s = item.IsFree; 24 | let tit = s === "F" ? "未购买" : "可以试听"; 25 | // let color = s === "L" ? "yellow" : "#36f0ff"; 26 | return ( 27 |
28 | 29 | < List.Item thumb={item.CourseImg}> 30 |
{item.CourseName}
31 |
32 |

{item.EndTime} 33 | {item.VideoStatus === "W" ?  预告 : null} 34 |

35 |
36 |

{item.ViewUserCnt}人学习

37 | 38 |
39 |
40 | 41 | 42 |
43 | ); 44 | }) 45 | } 46 |
47 | ); 48 | } 49 | 50 | render() { 51 | let { data, listData } = this.props; 52 | if (listData === undefined) { 53 | listData = []; 54 | return null; 55 | } 56 | const clsname = cls({ 57 | [styles.show]: listData.length === 0, 58 | [styles["courseDetail-bar"]]: listData.length > 0 59 | }); 60 | return ( 61 |
62 | 68 |
69 | {this.renderList(listData)} 70 | 71 |
72 | ); 73 | } 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/containers/CourseDetail/Tab/style.scss: -------------------------------------------------------------------------------- 1 | .courseDetail-bar { 2 | :global { 3 | .am-list-item img { 4 | width: 1.5rem; 5 | height: 1.5rem; 6 | } 7 | .am-list-line::after { 8 | opacity: 0; 9 | } 10 | .am-list-body::after { 11 | opacity: 0; 12 | } 13 | .am-badge-text { 14 | border-radius: 3px; 15 | } 16 | 17 | } 18 | margin-top: .1rem; 19 | .htmlCon { 20 | width: 100%; 21 | align-items: center; 22 | justify-content: center; 23 | height: auto; 24 | background-color: #fff; 25 | padding: .3rem; 26 | padding-bottom: .6rem; 27 | overflow-x: hidden; 28 | line-height: 1.5; 29 | img { 30 | width: 100%; 31 | } 32 | } 33 | .courseDetail-list { 34 | width: 100%; 35 | padding-bottom: 100px; 36 | .item { 37 | padding: 10px 0; 38 | } 39 | .end-time { 40 | color: #6f9cb9; 41 | font-size: 12px; 42 | .badge-btn { 43 | } 44 | } 45 | } 46 | } 47 | 48 | .foot-num { 49 | display: flex; 50 | align-items: center; 51 | justify-content: space-between; 52 | } 53 | 54 | .show { 55 | :global { 56 | .am-tabs-tab-bar-wrap { 57 | display: none; 58 | } 59 | } 60 | .htmlCon { 61 | width: 100%; 62 | height: auto; 63 | background-color: #fff; 64 | padding: .3rem; 65 | padding-bottom: .6rem; 66 | overflow-x: hidden; 67 | line-height: 1.5; 68 | margin-top: .2rem; 69 | img { 70 | width: 100%; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/containers/CourseDetail/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import DocumentTitle from "react-document-title"; 4 | import Header from "./Header"; 5 | import GroupList from "./GroupList"; 6 | import Tab from "./Tab"; 7 | import DrawerList from "./DrawerList"; 8 | import { getEnterCourseGroupData } from "../../store/courseDetail.js"; 9 | import { clearState } from "../../store/clearState"; 10 | import FooterBar from './FooterBar'; 11 | 12 | /** 13 | * @constructor 14 | * @description 课程详情页 15 | */ 16 | 17 | const mapStateToProps = (state) => { 18 | return { 19 | courseDetailData: state.getIn(["courseDetail", "courseDetailData"]).toJS(), 20 | isDrawLoad: state.getIn(["courseDetail", "isDrawLoad"]) 21 | }; 22 | }; 23 | const mapDispatchToProps = (dispatch) => ({ 24 | clearState: () => dispatch(clearState()), 25 | getEnterCourseGroupData: (params) => dispatch(getEnterCourseGroupData(params)) 26 | }); 27 | 28 | @connect( 29 | mapStateToProps, 30 | mapDispatchToProps 31 | ) 32 | class CourseDetail extends React.Component { 33 | componentDidMount() { 34 | let id = this.props.match.params.id; 35 | this.props.getEnterCourseGroupData({ 36 | CourseGroupId: id 37 | }); 38 | }; 39 | 40 | componentWillUnmount() { 41 | this.props.clearState(); 42 | } 43 | 44 | render() { 45 | const data = this.props.courseDetailData; 46 | if (Object.keys(data).length === 0) { 47 | return null; 48 | } 49 | return ( 50 | 51 | 52 |
53 | {data.IsTuan === 'T' && } 54 | 55 | {this.props.isDrawLoad && } 56 | {/**/} 57 | 58 | ); 59 | } 60 | } 61 | 62 | 63 | export default CourseDetail; -------------------------------------------------------------------------------- /src/containers/Home/CourseTabs/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { TabBar } from "antd-mobile"; 4 | import styles from "./style.scss"; 5 | import { getCourseTabsData, getSumGetChannelCourseGroupData, actions } from "../../../store/home"; 6 | import ContentLoader from "react-content-loader"; 7 | 8 | 9 | const MyLoader = props => ( 10 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | /** 32 | * @constructor 33 | * @description 首页中间分类tabs 34 | */ 35 | 36 | 37 | const mapStateToProps = (state) => { 38 | return { 39 | tabsData: state.getIn(["home", "tabsData"]), 40 | selectedTab: state.getIn(["home", "selectedTab"]), 41 | scrollLoad: state.getIn(["home", "scrollLoad"]) 42 | }; 43 | }; 44 | const mapDispatchToProps = (dispatch) => ({ 45 | getCourseTabsData: () => dispatch(getCourseTabsData()), 46 | toggleTopShow: (show) => dispatch(actions.toggleTopShow(show)), 47 | selectBar: (index) => dispatch(actions.selectBar(index)), 48 | getSumGetChannelCourseGroupData: (params) => dispatch(getSumGetChannelCourseGroupData(params)) 49 | }); 50 | @connect( 51 | mapStateToProps, 52 | mapDispatchToProps 53 | ) 54 | export default class CourseTabs extends React.Component { 55 | constructor(props) { 56 | super(props); 57 | this.handleScroll = this.handleScroll.bind(this); 58 | } 59 | 60 | componentDidMount() { 61 | this.props.getCourseTabsData(); 62 | window.addEventListener("scroll", this.handleScroll); 63 | } 64 | 65 | componentWillUnmount() { 66 | window.removeEventListener("scroll", this.handleScroll); 67 | } 68 | 69 | handleScroll() { 70 | let scrollY = window.scrollY; 71 | let scrollLoad = scrollY > 150 && true; 72 | setTimeout(() => { 73 | this.props.toggleTopShow(scrollLoad); 74 | }, 0); 75 | } 76 | 77 | renderItems(tabsData, selectedTab) { 78 | return tabsData.map((item) => { 79 | return ( 80 | {item.ChannelName}} 88 | icon={ 89 |
97 | } 98 | onPress={() => { 99 | this.props.selectBar(item.CourseChannelId); 100 | this.props.getSumGetChannelCourseGroupData({ CourseChannelId: item.CourseChannelId }); 101 | }} 102 | /> 103 | ); 104 | }); 105 | } 106 | 107 | render() { 108 | const { tabsData, selectedTab, scrollLoad } = this.props; 109 | const newTabsData = tabsData.toJS(); 110 | return ( 111 |
121 | {newTabsData.length > 0 ? 122 | {this.renderItems(newTabsData, selectedTab)} 123 | : } 124 |
125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/containers/Home/CourseTabs/style.scss: -------------------------------------------------------------------------------- 1 | .course-tabs { 2 | .item { 3 | img { 4 | width: 0.75rem; 5 | height: 0.75rem; 6 | } 7 | p { 8 | text-align: center; 9 | } 10 | } 11 | 12 | :global { 13 | .am-tab-bar-bar { 14 | height: 1.5rem; 15 | } 16 | } 17 | } 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/containers/Home/Lists/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { List } from "antd-mobile"; 4 | import styles from "./style.scss"; 5 | import NullCourse from "../NullCourse"; 6 | import { CourseList } from "../../../components/CourseList"; 7 | import { getSumGetChannelCourseGroupData } from "../../../store/home"; 8 | 9 | import ContentLoader from "react-content-loader"; 10 | 11 | const MyFacebookLoader = () => ( 12 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | 25 | /** 26 | * @constructor 27 | * @description 作品list 28 | */ 29 | 30 | const mapStateToProps = (state) => { 31 | return { 32 | hotData: state.getIn(["home", "hotData"]), 33 | newData: state.getIn(["home", "newData"]), 34 | recommendData: state.getIn(["home", "recommendData"]), 35 | selectedTab: state.getIn(["home", "selectedTab"]) 36 | }; 37 | }; 38 | 39 | @connect( 40 | mapStateToProps, 41 | { getSumGetChannelCourseGroupData } 42 | ) 43 | class Lists extends React.Component { 44 | componentDidMount() { 45 | let id = this.props.selectedTab; 46 | this.props.getSumGetChannelCourseGroupData({ CourseChannelId: id }); 47 | } 48 | 49 | renderList(data, info) { 50 | return ( 51 | info}> 52 | {data.map((item) => CourseList(item))} 53 | 54 | ); 55 | } 56 | 57 | renderNullData(hotData, newData, recommendData, index) { 58 | if (hotData.length === 0 && newData.length === 0 && recommendData.length === 0 && index !== 1) { 59 | return ; 60 | } 61 | } 62 | 63 | render() { 64 | const { hotData, newData, recommendData, selectedTab } = this.props; 65 | const newGroup = hotData.toJS(); 66 | const newnewData = newData.toJS(); 67 | const newrecommendData = recommendData.toJS(); 68 | return ( 69 | 70 | {this.renderNullData(newGroup, newnewData, newrecommendData, selectedTab)} 71 | {newnewData.length > 0 ? this.renderList(newnewData, "好课上新") : } 72 | {newrecommendData.length > 0 ? this.renderList(newrecommendData, "人气推荐") : } 73 | {newGroup.length > 0 ? this.renderList(newGroup, "更多严选") : } 74 | 75 | ); 76 | } 77 | } 78 | 79 | export default Lists; -------------------------------------------------------------------------------- /src/containers/Home/Lists/style.scss: -------------------------------------------------------------------------------- 1 | .my-list { 2 | :global { 3 | .am-list-body { 4 | display: inline; 5 | } 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/containers/Home/NullCourse/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./style.scss"; 3 | 4 | /** 5 | * @constructor 6 | * @description <没有数据时显示的内容> 7 | */ 8 | 9 | class NullCourse extends React.Component { 10 | static toImg(index) { 11 | let src = ''; 12 | switch (index) { 13 | case 1: 14 | return src = "http://116.211.88.85:8602/static/roomChannel/draw.png"; 15 | case 2: 16 | return src = "http://116.211.88.85:8602/static/roomChannel/music.png"; 17 | case 3: 18 | return src = "http://116.211.88.85:8602/static/roomChannel/science.png"; 19 | case 4: 20 | return src = "http://116.211.88.85:8602/static/roomChannel/life.png"; 21 | default: 22 | return null; 23 | } 24 | } 25 | render() { 26 | let index = this.props.index; 27 | return ( 28 |
29 |
30 | 31 |
32 |

更多好课正在赶来

33 |

同学们敬请期待

34 |
35 | ); 36 | } 37 | } 38 | 39 | export default NullCourse; 40 | 41 | -------------------------------------------------------------------------------- /src/containers/Home/NullCourse/style.scss: -------------------------------------------------------------------------------- 1 | .no-course{ 2 | text-align: center; 3 | padding-top: 1.6rem; 4 | .no-img{ 5 | width: 2.16rem; 6 | height: 2.16rem; 7 | opacity: 0.5; 8 | display: block; 9 | margin: auto; 10 | img{ 11 | width: 100%; 12 | height: 100%; 13 | } 14 | } 15 | p{ 16 | margin: 10px; 17 | font-size: 16px; 18 | color: #6F9CB9; 19 | } 20 | } -------------------------------------------------------------------------------- /src/containers/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { getQueryListData } from "../../store/home"; 4 | import DocumentTitle from "react-document-title"; 5 | import Banner from "../../components/Banner"; 6 | import CourseTabs from "./CourseTabs/index"; 7 | import List from "./Lists"; 8 | 9 | /** 10 | * @constructor 11 | * @description 作品 12 | */ 13 | 14 | const mapStateToProps = (state) => { 15 | return { 16 | banner: state.getIn(["home", "bannerData"]).toJS() 17 | }; 18 | }; 19 | @connect( 20 | mapStateToProps, 21 | { getQueryListData } 22 | ) 23 | class Home extends React.Component { 24 | 25 | componentDidMount() { 26 | this.props.getQueryListData({ 27 | newsType: "banner", 28 | pageIndex: 1, 29 | pageSize: 10 30 | }); 31 | } 32 | 33 | render() { 34 | const { banner } = this.props; 35 | return ( 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | export default Home; 47 | -------------------------------------------------------------------------------- /src/containers/My/List/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { List } from "antd-mobile"; 3 | import { listInfos } from "./listConfig"; 4 | import { Link } from "react-router-dom"; 5 | import styles from "./style.scss"; 6 | 7 | export default class Tabs extends React.Component { 8 | render() { 9 | return ( 10 |
11 | 12 | { 13 | listInfos.map(item => { 14 | return ( 15 | 18 | {item.tit} 22 | 23 | ); 24 | }) 25 | } 26 | 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/containers/My/List/listConfig.js: -------------------------------------------------------------------------------- 1 | export const listInfos = [ 2 | { 3 | id:0, 4 | tit:'账户信息', 5 | thumb:"http://test.hihiworld.com/web/static/img/myinformation.png", 6 | hash:'/userinfo' 7 | }, 8 | { 9 | id:1, 10 | tit:'我的直播课', 11 | thumb:"http://test.hihiworld.com/web/static/img/mylive.png", 12 | hash:'/mylive' 13 | }, 14 | { 15 | id:2, 16 | tit:'我的作品', 17 | thumb:"http://test.hihiworld.com/web/static/img/mywork.png", 18 | hash:'/myworks' 19 | }, 20 | { 21 | id:3, 22 | tit:'拼团记录', 23 | thumb:"http://test.hihiworld.com/web/static/img/mygroup.png", 24 | hash:'/home' 25 | }, 26 | { 27 | id:4, 28 | tit:'联系我们', 29 | thumb:"http://test.hihiworld.com/web/static/img/mycontactus.png", 30 | hash:'/contactUs' 31 | }, 32 | 33 | ]; 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/containers/My/List/style.scss: -------------------------------------------------------------------------------- 1 | .my-list{ 2 | :global { 3 | .am-list-body{ 4 | background: none; 5 | } 6 | .am-list-item{ 7 | margin-bottom: .2rem; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/containers/My/Top/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from "react-redux" 3 | import CSSModules from 'react-css-modules'; 4 | import styles from "./style.scss" 5 | 6 | 7 | 8 | CSSModules(styles); 9 | @connect( 10 | state => state.userInfos 11 | 12 | ) 13 | 14 | 15 | export default class Tabs extends React.Component { 16 | 17 | render() { 18 | const{ HeadImg,NickName} = this.props; 19 | return ( 20 |
21 |
22 | 23 |

{NickName}

24 |
25 |
26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/containers/My/Top/style.scss: -------------------------------------------------------------------------------- 1 | .top{ 2 | width: 100%; 3 | height: 3.8rem; 4 | background: url("http://test.hihiworld.com/web/static/img/banner.png"); 5 | background-position: center; 6 | background-repeat: no-repeat; 7 | background-size: cover; 8 | .headImg{ 9 | width: 1.2rem; 10 | height: 1.2rem; 11 | position: absolute; 12 | left: 3.1rem; 13 | top: .6rem; 14 | 15 | img{ 16 | width: 100%; 17 | height: 100%; 18 | border-radius: 50%; 19 | } 20 | p{ 21 | text-align: center; 22 | color: #ffffff; 23 | font-size: 22px; 24 | margin-top: .5rem; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/containers/My/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { setTitle } from "../../utils/utils"; 3 | import Top from "./Top" 4 | import List from './List'; 5 | 6 | class My extends React.Component { 7 | componentDidMount() { 8 | setTitle("我的"); 9 | } 10 | render() { 11 | return ( 12 |
13 | 14 | 15 |
16 | ); 17 | } 18 | } 19 | 20 | export default My; 21 | -------------------------------------------------------------------------------- /src/containers/OtherCourse/Header/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import styles from "./style.scss"; 4 | import BackHome from "../../../components/BackHome"; 5 | import { getSumGetChannelCourseGroupData } from "../../../store/home"; 6 | 7 | /** 8 | * @constructor
9 | * @description <头部> 10 | */ 11 | 12 | //return { otherCourseData: state.otherCourse.otherCourseData }; 13 | //queryList: state.worksDetail.getIn(["queryList"]).toJS(), 14 | function mapStateToProps(state) { 15 | return { 16 | otherCourseData: state.getIn(["otherCourse","otherCourseData"]).toJS() 17 | }; 18 | // return { otherCourseData: state.otherCourse.otherCourseData } 19 | } 20 | 21 | @connect( 22 | mapStateToProps, 23 | null 24 | ) 25 | class Header extends React.Component { 26 | render() { 27 | if (this.props.otherCourseData.length === 0) return null; 28 | let { HeadImg, HostName } = this.props.otherCourseData[0].HostData; 29 | return ( 30 |
31 |
32 | 33 |

{HostName}

34 |
35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | export default Header; 44 | 45 | -------------------------------------------------------------------------------- /src/containers/OtherCourse/Header/style.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | height: .9rem; 4 | background: #ffffff; 5 | position: relative; 6 | display: flex; 7 | .head_img{ 8 | display: flex; 9 | align-items: center; 10 | img{ 11 | width: .6rem; 12 | height: .6rem; 13 | border-radius: 50%; 14 | margin:0 .3rem ; 15 | } 16 | } 17 | .right { 18 | div{ 19 | left: auto !important; 20 | right:.3rem 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/containers/OtherCourse/List/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { ListView } from "antd-mobile"; 4 | import { CourseList } from "../../../components/CourseList"; 5 | import { getCourseGroupQueryListData } from "../../../store/otherCourse"; 6 | import { clearState } from "../../../store/clearState"; 7 | 8 | /** 9 | * @constructor 10 | * @description <课程列表> 11 | */ 12 | 13 | 14 | function mapStateToProps(state) { 15 | return { 16 | otherCourseData: state.getIn(["otherCourse", "otherCourseData"]).toJS() 17 | }; 18 | } 19 | 20 | const mapDispatchToProps = (dispatch) => ({ 21 | clearState: () => dispatch(clearState()), 22 | getCourseGroupQueryListData: (params) => dispatch(getCourseGroupQueryListData(params)) 23 | }); 24 | 25 | 26 | @connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | ) 30 | class List extends React.Component { 31 | componentDidMount() { 32 | this.props.getCourseGroupQueryListData({ 33 | HostAccountId: this.props.id, 34 | pageIndex: 1, 35 | pageSize: 8 36 | }); 37 | } 38 | 39 | componentWillUnmount() { 40 | this.props.clearState(); 41 | } 42 | 43 | onEndReached = () => { 44 | }; 45 | 46 | transDataSource() { 47 | const data = new ListView.DataSource({ 48 | rowHasChanged: (row1, row2) => row1 !== row2 49 | }); 50 | const dataSource = data.cloneWithRows(this.props.otherCourseData); 51 | return dataSource; 52 | } 53 | 54 | render() { 55 | const { otherCourseData } = this.props; 56 | return ( 57 | 58 | { 59 | otherCourseData.length > 0 && otherCourseData.map(item => { 60 | return CourseList(item); 61 | }) 62 | } 63 | 64 | ); 65 | } 66 | } 67 | 68 | export default List; 69 | 70 | -------------------------------------------------------------------------------- /src/containers/OtherCourse/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Header from './Header'; 3 | import List from './List'; 4 | 5 | 6 | /** 7 | * @constructor 8 | * @description <其他课程> 9 | */ 10 | 11 | class OtherCourse extends React.Component { 12 | 13 | render() { 14 | return ( 15 | 16 |
17 | 18 | 19 | ); 20 | } 21 | } 22 | 23 | export default OtherCourse; 24 | 25 | -------------------------------------------------------------------------------- /src/containers/Test/Wrapper/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | class Wrapper extends Component { 4 | render() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | } 12 | 13 | export default Wrapper; -------------------------------------------------------------------------------- /src/containers/Test/hook.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const hooks = (WrapComponent) => { 4 | return class extends React.Component { 5 | state = { 6 | name: 'mingyang' 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 | 高级组件已经加载完成 13 | 14 |
15 | ) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/containers/Test/hooks.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useState } from 'react'; 3 | 4 | export default function Example() { 5 | // Declare a new state variable, which we'll call "count" 6 | const [count, setCount] = useState(0); 7 | 8 | return ( 9 |
10 |

You clicked {count} times

11 | 14 |
15 | ); 16 | } -------------------------------------------------------------------------------- /src/containers/Test/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Toast } from "antd-mobile"; 3 | import { 4 | getQueryList, 5 | getSumGetChannelCourseGroup, 6 | getUserInfos 7 | } from "../../api"; 8 | import { 9 | clearState 10 | } from "../../store/clearState"; 11 | import axios from "axios"; 12 | import { 13 | getCourseWork 14 | } from "../../api"; 15 | import Example from './hooks' 16 | 17 | import './style.css' 18 | 19 | 20 | let index = 1; 21 | 22 | class Test extends React.Component { 23 | state = { 24 | data: [], 25 | num:0 26 | 27 | }; 28 | 29 | 30 | 31 | componentWillMount() { 32 | window.addEventListener("scroll", this.onScroll.bind(this)); 33 | this.setState({ 34 | num:0 35 | }) 36 | } 37 | 38 | componentDidMount() { 39 | 40 | getCourseWork({ 41 | pageIndex: index, 42 | pageSize: 10, 43 | OrderType: 2 44 | }).then(res => { 45 | const data = res.Data.Data 46 | this.setState({ 47 | data 48 | }) 49 | }) 50 | } 51 | 52 | componentWillUnmount() { 53 | window.removeEventListener("scroll", this.onScroll.bind(this)); 54 | } 55 | getData() { 56 | getCourseWork({ 57 | pageIndex: 1, 58 | pageSize: 10, 59 | OrderType: 2 60 | }).then(res => { 61 | console.log(res.Data.Data) 62 | }) 63 | } 64 | onScroll(e) { 65 | let clientHeight = document.documentElement.clientHeight; //浏览器高度 66 | let scrollHeight = document.body.scrollHeight; 67 | let scrollTop = document.documentElement.scrollTop; 68 | let proLoadDis = 5; 69 | 70 | 71 | if ((scrollTop + clientHeight) >= (scrollHeight - proLoadDis)) { 72 | console.log("到底了"); 73 | let i = index++ 74 | getCourseWork({ 75 | pageIndex: i, 76 | pageSize: 10, 77 | OrderType: 2 78 | }).then(res => { 79 | const data = res.Data.Data 80 | this.setState({ 81 | data: data.concat(this.state.data) 82 | }) 83 | }) 84 | } 85 | } 86 | render() { 87 | 88 | // const Ele = function () { 89 | // return React.createElement( 90 | // "div", 91 | // { 92 | // "className": "main", "id": "18", "data": '000' 93 | // }, 94 | // React.createElement( 95 | // 'div', 96 | // {"id":"div2"}, 97 | // React.createElement( 98 | // 'span', 99 | // {"className":"span"}, 100 | // 'wo shi span', 101 | // ) 102 | // ) 103 | // ); 104 | // } 105 | 106 | 107 | return ( 108 |
109 | asdf 110 | {this.state.num} 111 | 112 |
113 | ); 114 | } 115 | } 116 | 117 | 118 | function withHeader(WrappedComponent) { 119 | return class HOC extends WrappedComponent { 120 | render() { 121 | const newProps = { 122 | test:'hoc' 123 | } 124 | // 透传props,并且传递新的newProps 125 | return
126 | 127 |
128 | } 129 | } 130 | } 131 | 132 | function Hook(Wrapper) { 133 | return class Inheritance extends Wrapper { 134 | render() { 135 | // console.log(this.state) 136 | if (this.state.data.length === 0) { 137 | Toast.loading("Loading...", 0); 138 | } 139 | return super.render(); 140 | } 141 | } 142 | 143 | } 144 | 145 | export default Test; -------------------------------------------------------------------------------- /src/containers/Test/main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | * 1,创建阶段; 5 | * constructor 6 | * 7 | * 8 | * 2,更新阶段; 9 | * 3,卸载阶段; 10 | * 11 | */ 12 | 13 | class Main extends Component { 14 | /** 15 | * 父组件传入新的 props 时,用来和当前的 state 对比,判断是否需要更新 state。 16 | * 以前一般使用 componentWillReceiveProps 做这个操作。 17 | */ 18 | static getDerivedStateFromProps(){ 19 | console.log("static") 20 | return null; 21 | } 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | name: "mingyang" 26 | } 27 | console.log("1") 28 | this.setName = this.setName.bind(this); 29 | } 30 | 31 | // componentWillMount() { //新版本中会废除; 32 | // console.log("2") 33 | // } 34 | 35 | componentDidMount() { 36 | console.log("5") 37 | } 38 | shouldComponentUpdate() { 39 | console.log("3") 40 | return true 41 | } 42 | componentWillUnmount() { 43 | console.log("6") 44 | } 45 | componentDidCatch(err,info){ 46 | console.log(err); 47 | console.log(info) 48 | } 49 | setName() { 50 | this.setState({ 51 | name:"xiaobao" 52 | }) 53 | } 54 | 55 | render() { 56 | console.log("4") 57 | return ( 58 |
59 | {this.state.name} 60 |
61 | ); 62 | } 63 | 64 | } 65 | 66 | export default Main; -------------------------------------------------------------------------------- /src/containers/Test/style.css: -------------------------------------------------------------------------------- 1 | .main{ 2 | width: 100px; 3 | height: 100px; 4 | background: red; 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | right: 0; 9 | bottom: 0; 10 | margin: auto; 11 | display: none; 12 | } -------------------------------------------------------------------------------- /src/containers/UserInfo/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /** 4 | * @constructor 5 | * @description <账户信息> 6 | */ 7 | 8 | class UserInfo extends React.Component { 9 | 10 | // static getDerivedStateFromProps() { 11 | // console.log("我执行了...."); 12 | // return null 13 | // } 14 | 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | num: 1 19 | }; 20 | console.log("我是初始化...."); 21 | this.setNum = this.setNum.bind(this); 22 | this.doc = this.doc.bind(this); 23 | } 24 | 25 | componentWillMount() { 26 | console.log("组件还未渲染...."); 27 | } 28 | 29 | componentDidMount() { 30 | console.log("组件已经渲染了...."); 31 | } 32 | 33 | shouldComponentUpdate() { 34 | console.log("我控制了渲染...."); 35 | return true; 36 | } 37 | 38 | componentWillUnmount() { 39 | console.log("离开了组件...."); 40 | return true 41 | } 42 | 43 | setNum() { 44 | let num = this.state.num; 45 | num++; 46 | this.setState({ 47 | num: num 48 | }); 49 | } 50 | 51 | doc(e){ 52 | console.log(e) 53 | } 54 | render() { 55 | console.log("组件开始渲染了...."); 56 | return ( 57 |
58 |

{this.state.num}

59 | 60 |
61 | ); 62 | } 63 | 64 | 65 | } 66 | 67 | export default UserInfo; 68 | 69 | -------------------------------------------------------------------------------- /src/containers/WorkDetail/FooterBtn/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import styles from "./style.scss"; 4 | import { getIsLikeData, getRemoveLikeData, getAddLikeData, actions } from "../../../store/workDetail"; 5 | 6 | /** 7 | * @constructor 8 | * @description 详情页底部是点赞 9 | */ 10 | 11 | const mapState = (state) => ({ 12 | isLike: state.getIn(["worksDetail","isLike"]) 13 | }); 14 | const mapDispatchToProps = (dispatch) => { 15 | return ({ 16 | toggleInput: () => dispatch(actions.toggleInput()), 17 | getIsLikeData: (params) => dispatch(getIsLikeData(params)), 18 | getRemoveLikeData: (params) => dispatch(getRemoveLikeData(params)), 19 | getAddLikeData: (params) => dispatch(getAddLikeData(params)) 20 | }); 21 | }; 22 | 23 | @connect( 24 | mapState, 25 | mapDispatchToProps 26 | ) 27 | class FooterBtn extends React.Component { 28 | params = { 29 | TargetType: 3, 30 | TargetId: this.props.id 31 | }; 32 | 33 | constructor(props) { 34 | super(props); 35 | this.toggleLike = this.toggleLike.bind(this); 36 | } 37 | 38 | componentDidMount() { 39 | this.props.getIsLikeData(this.params); 40 | }; 41 | 42 | toggleLike() { 43 | let { isLike, getRemoveLikeData, getAddLikeData } = this.props; 44 | isLike ? getRemoveLikeData(this.params) : getAddLikeData(this.params); 45 | } 46 | 47 | render() { 48 | const { likeNum, isLike } = this.props; 49 | let url = isLike ? "http://test.hihiworld.com/web/static/img/good.png" : "http://test.hihiworld.com/web/static/img/workgood.png"; 50 | return ( 51 |
52 |
53 | 54 |   {likeNum} 55 |
56 |
57 |
this.props.toggleInput()}> 58 | 59 |
60 |
61 | ); 62 | } 63 | } 64 | 65 | export default FooterBtn; -------------------------------------------------------------------------------- /src/containers/WorkDetail/FooterBtn/style.scss: -------------------------------------------------------------------------------- 1 | .add-btn { 2 | width: 100%; 3 | height: 1rem; 4 | background: #0383d3; 5 | position: fixed; 6 | bottom: 0; 7 | display: flex; 8 | justify-content: space-around; 9 | align-items: center; 10 | z-index: 0; 11 | .left { 12 | width: .4rem; 13 | height: .4rem; 14 | display: flex; 15 | align-items: center; 16 | font-size: 18px; 17 | color: #ffffff; 18 | line-height: .12rem; 19 | img { 20 | width: 100%; 21 | height: 100%; 22 | } 23 | } 24 | .center{ 25 | width: 2px; 26 | height: .37rem; 27 | //position: absolute; 28 | //top: .465rem; 29 | //left: 5rem; 30 | background: #009dff; 31 | } 32 | .right{ 33 | width: .4rem; 34 | height: .4rem; 35 | img { 36 | width: 100%; 37 | height: 100%; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/containers/WorkDetail/FooterInput/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import styles from "./style.scss"; 4 | import { getAddComment } from "../../../api"; 5 | import { TextareaItem } from "antd-mobile"; 6 | import { actions } from "../../../store/workDetail"; 7 | 8 | const mapDispatchToProps = (dispatch) => ({ 9 | toggleInput: () => dispatch(actions.toggleInput()) 10 | }); 11 | 12 | @connect( 13 | null, 14 | mapDispatchToProps 15 | ) 16 | class FooterInput extends React.Component { 17 | constructor(props) { 18 | super(); 19 | this.state = { 20 | msg: "" 21 | }; 22 | this.sendMsg = this.sendMsg.bind(this); 23 | } 24 | 25 | onInput(e) { 26 | //console.log(e); 27 | this.setState({ 28 | msg: e 29 | }); 30 | } 31 | 32 | sendMsg() { 33 | console.log(this.state.msg); 34 | } 35 | 36 | componentDidMount() { 37 | 38 | }; 39 | 40 | render() { 41 | const { id, toggleInput } = this.props; 42 | return ( 43 |
44 |
toggleInput()}/> 45 |
46 | this.autoFocusInst = el} 51 | autoHeight 52 | onFocus={(e) => this.onInput(e)} 53 | /> 54 |

发送

55 |
56 |
57 | 58 | ); 59 | } 60 | } 61 | 62 | export default FooterInput; -------------------------------------------------------------------------------- /src/containers/WorkDetail/FooterInput/style.scss: -------------------------------------------------------------------------------- 1 | 2 | .input-pop { 3 | :global { 4 | .am-textarea-control { 5 | padding: 0; 6 | } 7 | .am-list-item { 8 | height: 35px !important; 9 | width: 65%; 10 | border-radius: 10px; 11 | min-height: 35px 12 | } 13 | .am-textarea-control textarea { 14 | font-size: 16px; 15 | line-height: 19px; 16 | } 17 | } 18 | .bottom-input { 19 | width: 100%; 20 | height: 1rem; 21 | background: #d6f1ff; 22 | position: fixed; 23 | bottom: 0; 24 | display: flex; 25 | justify-content: space-around; 26 | align-items: center; 27 | z-index: 99999999; 28 | .input { 29 | background: #ffffff; 30 | } 31 | .send { 32 | font-size: 18px; 33 | } 34 | 35 | } 36 | } 37 | 38 | .hei{ 39 | width: 10rem; 40 | height: 100%; 41 | position: fixed; 42 | top: 0; 43 | left: 0; 44 | background: rgba(0,0,0,.7); 45 | z-index: 1; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/containers/WorkDetail/Header/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import styles from "./style.scss"; 4 | import { strArr,isDataSize } from "../../../utils/utils"; 5 | 6 | /** 7 | * @constructor
8 | * @description 上半部内容 9 | */ 10 | 11 | class Header extends React.Component { 12 | renderImg(data) { 13 | return strArr(data.Image).map((item, index) => { 14 | return ( 15 |
16 | 17 |
18 | ); 19 | }) 20 | } 21 | render() { 22 | const { data } = this.props; 23 | if (isDataSize(data)) return null; 24 | return ( 25 |
26 |
27 | {this.renderImg(data)} 28 |
29 |
30 |

{data.Title}

31 |
来自:{data.UserData.NickName} {data.CreateTime.substr(0, 10)}
32 |
33 | 34 |
35 |

{data.CourseGroupName}

36 |
>
37 |
38 | 39 |
40 | ); 41 | } 42 | } 43 | 44 | export default Header; -------------------------------------------------------------------------------- /src/containers/WorkDetail/Header/style.scss: -------------------------------------------------------------------------------- 1 | .work-detail-top { 2 | background: #ffffff; 3 | margin-top: 1rem; 4 | .work-img-list { 5 | display: flex; 6 | .item { 7 | width: 3.2rem; 8 | height: 3.2rem; 9 | padding: 0 5px; 10 | margin-top: .2rem; 11 | img { 12 | width: 100%; 13 | height: 100%; 14 | } 15 | } 16 | } 17 | .tit { 18 | margin: .4rem .8rem; 19 | p { 20 | font-size: 16px; 21 | margin-bottom: .1rem; 22 | } 23 | div { 24 | font-size: 14px; 25 | color: #6f9cb9; 26 | } 27 | } 28 | .work-name { 29 | display: flex; 30 | justify-content: space-around; 31 | .name { 32 | font-size: 16px; 33 | margin-bottom: .6rem; 34 | } 35 | div { 36 | font-size: 14px; 37 | color: #6f9cb9; 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/containers/WorkDetail/List_tpl/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./style.scss"; 3 | 4 | 5 | export const List_tpl = (item) => { 6 | let createTime = item.CreateTime.substr(0, 10); 7 | return ( 8 |
10 |
11 |
12 | 13 |
14 |
{item.Title}
15 |
{createTime}
16 |
17 |
18 | {item.Detail} 19 |
20 |
21 | ); 22 | }; -------------------------------------------------------------------------------- /src/containers/WorkDetail/List_tpl/style.scss: -------------------------------------------------------------------------------- 1 | @import "../../../assets/style/theme.scss"; 2 | 3 | .comment-list { 4 | padding: 7.5px 15px; 5 | border-bottom: 1px solid #d6f1ff; 6 | .top { 7 | display: flex; 8 | align-items: center; 9 | justify-content: space-around; 10 | .head-img { 11 | width: .8rem; 12 | height: .8rem; 13 | img { 14 | width: 100%; 15 | height: 100%; 16 | border-radius: 50%; 17 | } 18 | } 19 | .title { 20 | margin-right: 1rem; 21 | margin-left: -.8rem; 22 | } 23 | } 24 | .list-content { 25 | font-size: 16px; 26 | margin-left: 1.5rem; 27 | margin-top: .2rem; 28 | margin-bottom: .2rem; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/containers/WorkDetail/TeachList/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "../TopicList/style.scss"; 3 | import { List_tpl } from "../List_tpl"; 4 | 5 | class TeachList extends React.Component { 6 | render() { 7 | const { data } = this.props; 8 | if (data === undefined || data.length === 0) { 9 | return null; 10 | } 11 | return ( 12 |
13 |
老师点评
14 | { 15 | data.map(item => { 16 | return List_tpl(item); 17 | }) 18 | } 19 |
20 | ); 21 | } 22 | } 23 | 24 | export default TeachList; -------------------------------------------------------------------------------- /src/containers/WorkDetail/TopicList/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { ListView } from "antd-mobile"; 4 | import styles from "./style.scss"; 5 | import { getCourseWorkQueryByIdData, getCommentQueryListData } from "../../../store/workDetail"; 6 | import Loading from "../../../components/LoadingM"; 7 | import { List_tpl } from "../List_tpl"; 8 | import { getCommentQueryList } from "../../../api"; 9 | 10 | /** 11 | * @constructor 12 | * @description 评论列表 13 | */ 14 | 15 | @connect( 16 | null, 17 | { getCourseWorkQueryByIdData, getCommentQueryListData } 18 | ) 19 | class TopicList extends React.Component { 20 | state = { 21 | data: [], 22 | isLoding: true, 23 | type: 3, 24 | index: 0, 25 | size: 5, 26 | hasMore: false 27 | }; 28 | 29 | componentDidMount() { 30 | this.getData(); 31 | }; 32 | 33 | onEndReached = () => { 34 | if (!this.state.isLoading && this.state.hasMore) { 35 | return; 36 | } 37 | this.setState({ isLoading: true }); 38 | this.getData(); 39 | 40 | }; 41 | 42 | async getData() { 43 | let id = this.props.id; 44 | let index = this.state.index; 45 | index++; 46 | const res = await getCommentQueryList({ 47 | TargetType: this.state.type, 48 | pageIndex: index, 49 | pageSize: this.state.size, 50 | TargetId: id 51 | }); 52 | const data = this.state.data.concat(res.Data.Data); 53 | res.Data.Data.length < this.state.size ? this.setState({ 54 | hasMore: true 55 | }) : null; 56 | this.setState({ 57 | data, 58 | index, 59 | isLoading: false 60 | }); 61 | } 62 | 63 | formatDataSource(data) { 64 | const _data = new ListView.DataSource({ 65 | rowHasChanged: (row1, row2) => row1 !== row2 66 | }); 67 | return _data.cloneWithRows(data); 68 | } 69 | 70 | render() { 71 | const data = this.state.data; 72 | if (Object.keys(data).length === 0) { 73 | return null; 74 | } 75 | const row = (item) => { 76 | return List_tpl(item); 77 | }; 78 | return ( 79 | this.lv = el} 83 | dataSource={this.formatDataSource(data)} 84 | renderHeader={() =>
评论
} 85 | renderFooter={() => (
86 | {this.state.isLoading ? : "没有数据了"} 87 |
)} 88 | renderRow={row} 89 | useBodyScroll 90 | scrollRenderAheadDistance={1000} 91 | onEndReached={this.onEndReached} 92 | onEndReachedThreshold={1} 93 | /> 94 | ); 95 | } 96 | } 97 | 98 | export default TopicList; 99 | -------------------------------------------------------------------------------- /src/containers/WorkDetail/TopicList/style.scss: -------------------------------------------------------------------------------- 1 | .work-prompt { 2 | :global { 3 | .am-list-header { 4 | padding: 0 5 | } 6 | } 7 | background: #ffffff; 8 | .head { 9 | height: 1rem; 10 | font-size: 18px; 11 | background: #d6f1ff; 12 | line-height: 1rem; 13 | position: relative; 14 | padding-left: .4rem; 15 | &::before { 16 | content: " "; 17 | position: absolute; 18 | width: 2px; 19 | height: .32rem; 20 | left: .2rem; 21 | top: .34rem; 22 | background: #87dbff; 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/containers/WorkDetail/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { setTitle } from "../../utils/utils"; 4 | import { getCourseWorkQueryByIdData } from "../../store/workDetail"; 5 | import BackHome from "../../components/BackHome"; 6 | import Header from "./Header"; 7 | import TeachList from "./TeachList"; 8 | import TopicList from "./TopicList"; 9 | import FooterBtn from "./FooterBtn"; 10 | import FooterInput from "./FooterInput"; 11 | import { clearState } from "../../store/clearState"; 12 | 13 | 14 | const mapState = (state) => ({ 15 | queryList: state.getIn(["worksDetail", "queryList"]).toJS(), 16 | isInput: state.getIn(["worksDetail", "isInput"]) 17 | }); 18 | 19 | const mapDispatchToProps = (dispatch) => ({ 20 | clearState: () => dispatch(clearState()), 21 | getCourseWorkQueryByIdData: (params) => dispatch(getCourseWorkQueryByIdData(params)) 22 | }); 23 | 24 | @connect( 25 | mapState, 26 | mapDispatchToProps 27 | ) 28 | class WorkDetail extends React.Component { 29 | 30 | componentDidMount() { 31 | let workId = this.props.match.params.id; 32 | this.props.getCourseWorkQueryByIdData({ workId }); 33 | }; 34 | 35 | componentWillUnmount() { 36 | this.props.clearState(); 37 | } 38 | 39 | render() { 40 | const { queryList, isInput } = this.props; 41 | let id = this.props.match.params.id; 42 | setTitle(queryList.Title); 43 | return ( 44 |
45 | 46 |
47 | 48 | {queryList.TeacherCommentDataList && } 49 | 50 | {isInput ? : null} 51 |
52 | ); 53 | } 54 | } 55 | 56 | export default WorkDetail; -------------------------------------------------------------------------------- /src/containers/Works/List/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | import { Icon } from "antd-mobile"; 5 | import { getCourseWorkData, getvideoUrl } from "../../../store/works"; 6 | import styles from "./style.scss"; 7 | import Loading from "../../../components/LoadingM"; 8 | import VideoAlert from "../../../components/VideoAlert"; 9 | 10 | const mapStateToProps = (state) => { 11 | return { 12 | data: state.getIn(["works", "data"]).toJS(), 13 | pageSize: state.getIn(["works", "pageSize"]), 14 | OrderType: state.getIn(["works", "OrderType"]), 15 | tabIndex: state.getIn(["works", "tabIndex"]), 16 | pageIndex: state.getIn(["works", "pageIndex"]), 17 | videoUrl: state.getIn(["works", "videoUrl"]) 18 | }; 19 | }; 20 | @connect( 21 | mapStateToProps, 22 | { getCourseWorkData, getvideoUrl } 23 | ) 24 | export default class Lists extends React.Component { 25 | constructor(props) { 26 | super(props) 27 | this.state = { 28 | isLoad: false, 29 | loadtxt:"加载中" 30 | } 31 | this.page = 0; 32 | } 33 | 34 | componentWillMount() { 35 | window.addEventListener("scroll", this.onScroll.bind(this)); 36 | 37 | } 38 | componentDidMount() { 39 | this.getListData(1); 40 | } 41 | componentWillUnmount() { 42 | window.removeEventListener("scroll", this.onScroll.bind(this)); 43 | } 44 | 45 | getListData(index) { 46 | const { getCourseWorkData, pageSize, tabIndex } = this.props; 47 | getCourseWorkData({ 48 | pageSize: pageSize, 49 | OrderType: tabIndex 50 | }, index, tabIndex); 51 | } 52 | onScroll(e) { 53 | let clientHeight = document.documentElement.clientHeight; //浏览器高度 54 | let scrollHeight = document.body.scrollHeight; 55 | let scrollTop = document.documentElement.scrollTop; 56 | let proLoadDis = 5; 57 | if ((scrollTop + clientHeight) >= (scrollHeight - proLoadDis)) { 58 | this.page++; 59 | if(this.page > 2 ){ 60 | this.setState({ 61 | isLoad: true, 62 | loadtxt:"加载完成" 63 | }) 64 | }else{ 65 | const { getCourseWorkData, pageSize, tabIndex, pageIndex } = this.props; 66 | let index = pageIndex; 67 | index++; 68 | getCourseWorkData({ 69 | pageIndex: index, 70 | pageSize: pageSize, 71 | OrderType: tabIndex 72 | }, index, tabIndex); 73 | } 74 | } 75 | } 76 | 77 | renderImgList(d) { 78 | return ( 79 | d.Image !== "" ? d.Image.split(",").map((item, index, array) => {a 80 | return ( 81 |
83 | 84 |
85 | ); 86 | }) :
87 | 88 | this.props.getvideoUrl({ url: d.VideoUrl }, v)} 90 | src="http://test.hihiworld.com/web/static/img/biggestplay@3x.png" alt="" /> 91 |
92 | ); 93 | } 94 | 95 | renderRow(d) { 96 | return ( 97 |
100 |
101 |
102 | 103 |
104 | {d.UserData.NickName} 105 | {d.CreateTime} 106 |
107 |
108 | {this.renderImgList(d)} 109 |
110 |
111 |
{d.Title}
114 | 115 |
116 |
117 | 118 |  {d.BrowseCnt} 119 |
120 |
121 | 122 |  {d.LikeCnt} 123 |
124 |
125 | 126 |   127 |
128 |
129 | 130 |
131 | 132 |
133 |
134 | 135 |
136 |
{d.CourseGroupName}
137 | 138 | 139 |
140 | 141 |
142 | ); 143 | } 144 | 145 | 146 | render() { 147 | const { videoUrl, getvideoUrl } = this.props; 148 | return ( 149 | 150 |
151 | { 152 | this.props.data.map((item) => { 153 | return this.renderRow(item) 154 | }) 155 | } 156 | 157 |
158 | {videoUrl !== "" && } 159 | 160 |
161 | ); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/containers/Works/List/style.scss: -------------------------------------------------------------------------------- 1 | 2 | .my-listView { 3 | overflow: auto; 4 | background: none; 5 | margin-bottom: 50px; 6 | margin-top: 35px; 7 | :global { 8 | .am-list-body { 9 | margin-top: 45px; 10 | } 11 | .am-list-footer { 12 | padding: 0; 13 | } 14 | } 15 | .renderFooter { 16 | padding: 15px; 17 | text-align: center; 18 | position: relative; 19 | // background: #ffffff; 20 | } 21 | .item { 22 | background: #ffffff; 23 | margin: .3rem 0; 24 | padding: .3rem; 25 | padding-top: 0; 26 | margin-bottom: 0; 27 | .top { 28 | height: 1rem; 29 | display: flex; 30 | align-items: center; 31 | .time { 32 | color: #6f9cb9; 33 | margin-left: .2rem; 34 | } 35 | .top-img { 36 | width: .56rem; 37 | height: .56rem; 38 | margin-right: .2rem; 39 | img { 40 | width: .56rem; 41 | height: .56rem; 42 | border-radius: 50%; 43 | } 44 | } 45 | } 46 | .imgList { 47 | display: flex; 48 | .list { 49 | width: 2.2rem; 50 | height: 2.2rem; 51 | margin-right: .2rem; 52 | } 53 | .alist { 54 | width: 3rem; 55 | height: 3rem; 56 | margin-right: .2rem; 57 | position: relative; 58 | } 59 | img { 60 | width: 100%; 61 | height: 100%; 62 | } 63 | .play { 64 | width: .75rem; 65 | height: .75rem; 66 | position: absolute; 67 | top: 1.12rem; 68 | left: 1.12rem; 69 | opacity: 0.3; 70 | } 71 | } 72 | .work-data { 73 | margin-top: .1rem; 74 | display: flex; 75 | border-bottom: 1px solid #87dbff; 76 | height: .7rem; 77 | line-height: .7rem; 78 | } 79 | .work-data-item { 80 | margin-right: 0.2rem; 81 | img { 82 | width: 0.3rem; 83 | height: 0.3rem; 84 | vertical-align: middle; 85 | } 86 | } 87 | .coursename { 88 | margin-top: .1rem; 89 | display: flex; 90 | align-items: center; 91 | .coursenameImg { 92 | width: .6rem; 93 | height: .6rem; 94 | img { 95 | width: 100%; 96 | height: 100%; 97 | border-radius: 50%; 98 | } 99 | } 100 | div { 101 | margin-right: .2rem; 102 | } 103 | .icon-left { 104 | position: absolute; 105 | right: .3rem; 106 | color: #87dbff; 107 | } 108 | } 109 | } 110 | .videoAlert { 111 | width: 10rem; 112 | height: 100%; 113 | position: fixed; 114 | z-index: 99; 115 | top: 0; 116 | left: 0; 117 | background: #000; 118 | } 119 | .video { 120 | width: 100%; 121 | height: 4.2rem; 122 | position: absolute; 123 | top: 43%; 124 | } 125 | } -------------------------------------------------------------------------------- /src/containers/Works/Lists/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | import { Icon, ListView } from "antd-mobile"; 5 | import { getCourseWorkData, getvideoUrl } from "../../../store/works"; 6 | import styles from "./style.scss"; 7 | import Loading from "../../../components/LoadingM"; 8 | import VideoAlert from "../../../components/VideoAlert"; 9 | 10 | const mapStateToProps = (state) => { 11 | return { 12 | data: state.getIn(["works", "data"]).toJS(), 13 | pageSize: state.getIn(["works", "pageSize"]), 14 | OrderType: state.getIn(["works", "OrderType"]), 15 | tabIndex: state.getIn(["works", "tabIndex"]), 16 | pageIndex: state.getIn(["works", "pageIndex"]), 17 | videoUrl: state.getIn(["works", "videoUrl"]) 18 | }; 19 | }; 20 | @connect( 21 | mapStateToProps, 22 | { getCourseWorkData, getvideoUrl } 23 | ) 24 | export default class Lists extends React.Component { 25 | componentDidMount() { 26 | this.getListData(1); 27 | } 28 | 29 | getListData(index) { 30 | const { getCourseWorkData, pageSize, tabIndex } = this.props; 31 | getCourseWorkData({ 32 | pageSize: pageSize, 33 | OrderType: tabIndex 34 | }, index, tabIndex); 35 | } 36 | 37 | onEndReached = () => { 38 | const { getCourseWorkData, pageSize, tabIndex, pageIndex } = this.props; 39 | let index = pageIndex; 40 | index++; 41 | getCourseWorkData({ 42 | pageIndex: index, 43 | pageSize: pageSize, 44 | OrderType: tabIndex 45 | }, index, tabIndex); 46 | }; 47 | 48 | renderImgList(d) { 49 | return ( 50 | d.Image !== "" ? d.Image.split(",").map((item, index, array) => { 51 | return ( 52 |
54 | 55 |
56 | ); 57 | }) :
58 | 59 | this.props.getvideoUrl({ url: d.VideoUrl }, v)} 61 | src="http://test.hihiworld.com/web/static/img/biggestplay@3x.png" alt=""/> 62 |
63 | ); 64 | } 65 | 66 | renderRow(d) { 67 | return ( 68 |
71 |
72 |
73 | 74 |
75 | {d.UserData.NickName} 76 | {d.CreateTime} 77 |
78 |
79 | {this.renderImgList(d)} 80 |
81 |
82 |
{d.Title}
85 | 86 |
87 |
88 | 89 |  {d.BrowseCnt} 90 |
91 |
92 | 93 |  {d.LikeCnt} 94 |
95 |
96 | 97 |  {d.CommentCnt} 98 |
99 |
100 | 101 |
102 | 103 |
104 |
105 | 106 |
107 |
{d.CourseGroupName}
108 | 109 | 110 |
111 | 112 |
113 | ); 114 | } 115 | 116 | formatDataSource() { 117 | const data = new ListView.DataSource({ 118 | rowHasChanged: (row1, row2) => row1 !== row2 119 | }); 120 | return data.cloneWithRows(this.props.data); 121 | } 122 | 123 | render() { 124 | const { videoUrl, getvideoUrl } = this.props; 125 | const row = (d) => { 126 | return this.renderRow(d); 127 | }; 128 | return ( 129 | 130 | this.lv = el} 133 | dataSource={this.formatDataSource()} 134 | renderRow={row} 135 | useBodyScroll 136 | scrollRenderAheadDistance={500} 137 | onEndReached={this.onEndReached} 138 | onEndReachedThreshold={3} 139 | renderFooter={() => ( 140 |
141 | )} 142 | /> 143 | {videoUrl !== "" && } 144 |
145 | ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/containers/Works/Lists/style.scss: -------------------------------------------------------------------------------- 1 | .my-listView { 2 | overflow: auto; 3 | background: none; 4 | margin-bottom: 50px; 5 | :global { 6 | .am-list-body { 7 | margin-top: 45px; 8 | } 9 | .am-list-footer { 10 | padding: 0; 11 | } 12 | } 13 | .renderFooter { 14 | padding: 15px; 15 | text-align: center; 16 | position: relative; 17 | // background: #ffffff; 18 | } 19 | .item { 20 | background: #ffffff; 21 | margin: .3rem 0; 22 | padding: .3rem; 23 | padding-top: 0; 24 | margin-bottom: 0; 25 | .top { 26 | height: 1rem; 27 | display: flex; 28 | align-items: center; 29 | .time { 30 | color: #6f9cb9; 31 | margin-left: .2rem; 32 | } 33 | .top-img { 34 | width: .56rem; 35 | height: .56rem; 36 | margin-right: .2rem; 37 | img { 38 | width: .56rem; 39 | height: .56rem; 40 | border-radius: 50%; 41 | } 42 | } 43 | } 44 | .imgList { 45 | display: flex; 46 | .list { 47 | width: 2.2rem; 48 | height: 2.2rem; 49 | margin-right: .2rem; 50 | } 51 | .alist { 52 | width: 3rem; 53 | height: 3rem; 54 | margin-right: .2rem; 55 | position: relative; 56 | } 57 | img { 58 | width: 100%; 59 | height: 100%; 60 | } 61 | .play { 62 | width: .75rem; 63 | height: .75rem; 64 | position: absolute; 65 | top: 1.12rem; 66 | left: 1.12rem; 67 | opacity: 0.3; 68 | } 69 | } 70 | .work-data { 71 | margin-top: .1rem; 72 | display: flex; 73 | border-bottom: 1px solid #87dbff; 74 | height: .7rem; 75 | line-height: .7rem; 76 | } 77 | .work-data-item { 78 | margin-right: 0.2rem; 79 | img { 80 | width: 0.3rem; 81 | height: 0.3rem; 82 | vertical-align: middle; 83 | } 84 | } 85 | .coursename { 86 | margin-top: .1rem; 87 | display: flex; 88 | align-items: center; 89 | .coursenameImg { 90 | width: .6rem; 91 | height: .6rem; 92 | img { 93 | width: 100%; 94 | height: 100%; 95 | border-radius: 50%; 96 | } 97 | } 98 | div { 99 | margin-right: .2rem; 100 | } 101 | .icon-left { 102 | position: absolute; 103 | right: .3rem; 104 | color: #87dbff; 105 | } 106 | } 107 | } 108 | .videoAlert { 109 | width: 10rem; 110 | height: 100%; 111 | position: fixed; 112 | z-index: 99; 113 | top: 0; 114 | left: 0; 115 | background: #000; 116 | } 117 | .video { 118 | width: 100%; 119 | height: 4.2rem; 120 | position: absolute; 121 | top: 43%; 122 | } 123 | } -------------------------------------------------------------------------------- /src/containers/Works/Topbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { Tabs } from "antd-mobile"; 4 | import { getCourseWorkData, getTabIndex } from "../../../store/works"; 5 | 6 | 7 | /** 8 | * @constructor 9 | * @description 作品顶部tabs 10 | */ 11 | 12 | const tabs = [ 13 | { title: "推荐" }, 14 | { title: "最新" }, 15 | { title: "最热" } 16 | ]; 17 | 18 | const style = { 19 | width: "7.5rem", 20 | position: "fixed", 21 | top: "0", 22 | zIndex: "1" 23 | }; 24 | 25 | const mapStateToProps = (state) => { 26 | return { 27 | tabIndex: state.getIn(["works", "tabIndex"]) 28 | }; 29 | }; 30 | 31 | @connect( 32 | mapStateToProps, 33 | { getCourseWorkData, getTabIndex } 34 | ) 35 | class TopBar extends React.Component { 36 | render() { 37 | const { getTabIndex, pageSize, getCourseWorkData } = this.props; 38 | return ( 39 |
40 | { 45 | getTabIndex(index); 46 | getCourseWorkData({ 47 | pageIndex: 1, 48 | pageSize: pageSize, 49 | OrderType: index 50 | }, 1, index); 51 | }} 52 | /> 53 |
54 | ); 55 | } 56 | } 57 | 58 | export default TopBar; 59 | -------------------------------------------------------------------------------- /src/containers/Works/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import DocumentTitle from "react-document-title"; 3 | import TopBar from "./Topbar"; 4 | import Lists from "./Lists"; 5 | 6 | 7 | class Works extends React.Component { 8 | render() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | } 18 | 19 | export default Works; 20 | 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | 7 | -------------------------------------------------------------------------------- /src/router/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { Route, HashRouter, Redirect } from "react-router-dom"; 3 | import FooterBar from "../components/FooterBar"; 4 | import Loading from "../components/Loading"; 5 | import RouterList from "./routes"; 6 | 7 | const Router = () => ( 8 | 9 | }> 10 | 11 | } /> 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | 19 | export default Router; 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | lazy 3 | } from "react"; 4 | import { 5 | Route 6 | } from "react-router-dom"; 7 | 8 | const RouterLis = [{ 9 | component: lazy(() => 10 | import ("../containers/Home")), 11 | path: "/" 12 | }, { 13 | component: lazy(() => 14 | import ("../containers/Home")), 15 | path: "/home" 16 | }, { 17 | component: lazy(() => 18 | import ("../containers/My")), 19 | path: "/my" 20 | }, { 21 | component: lazy(() => 22 | import ("../containers/UserInfo")), 23 | path: "/my/userInfo" 24 | }, { 25 | component: lazy(() => 26 | import ("../containers/Works")), 27 | path: "/works" 28 | }, { 29 | component: lazy(() => 30 | import ("../containers/CourseDetail")), 31 | path: "/courseDetail/:id" 32 | }, { 33 | component: lazy(() => 34 | import ("../containers/OtherCourse")), 35 | path: "/otherCourse/:id" 36 | }, { 37 | component: lazy(() => 38 | import ("../containers/WorkDetail")), 39 | path: "/workDetail/:id" 40 | }]; 41 | 42 | const RouterList = () => ( 43 | RouterLis.map((item, key) => { 44 | return ; 45 | }) 46 | ); 47 | 48 | export default RouterList; -------------------------------------------------------------------------------- /src/store/clearState.js: -------------------------------------------------------------------------------- 1 | export const CLEAR_STATE = "clear_state"; 2 | export const clearState = () => { 3 | return { 4 | type: CLEAR_STATE 5 | }; 6 | }; -------------------------------------------------------------------------------- /src/store/courseDetail.js: -------------------------------------------------------------------------------- 1 | import { getEnterCourseGroup, getTuanQueryList } from "../api"; 2 | import { CLEAR_STATE } from "./clearState"; 3 | import { fromJS } from "immutable"; 4 | const types = { 5 | INIT_COURSE_DETAIL: "ini_course_detail", 6 | INIT_TUAN_LIST: "init_tuan_list", 7 | TOGGLE_DRAW: "toggle_draw" 8 | }; 9 | 10 | const defaultState = fromJS({ 11 | courseDetailData: {}, 12 | tuanListData: {}, 13 | isDrawLoad: false 14 | }); 15 | 16 | export const courseDetailRedurces = (state = defaultState, action) => { 17 | switch (action.type) { 18 | case types.INIT_COURSE_DETAIL: 19 | return state.merge({ 20 | courseDetailData: action.payload , 21 | }); 22 | // return { ...state, courseDetailData: action.payload }; 23 | case types.INIT_TUAN_LIST: 24 | return state.merge({ 25 | tuanListData: action.payload , 26 | }); 27 | // return { ...state, tuanListData: action.payload }; 28 | case types.TOGGLE_DRAW: 29 | const isLoad = state.isDrawLoad; 30 | return state.merge({ 31 | isDrawLoad: isLoad 32 | }); 33 | // return { ...state, isDrawLoad: !isLoad }; 34 | case CLEAR_STATE: 35 | return defaultState; 36 | default: 37 | return state; 38 | } 39 | }; 40 | 41 | export const actions = { 42 | initData: (params) => { 43 | return { 44 | payload: params, 45 | type: types.INIT_COURSE_DETAIL 46 | }; 47 | }, 48 | initTuanList: (params) => { 49 | return { 50 | type: types.INIT_TUAN_LIST, 51 | payload: params 52 | }; 53 | }, 54 | toggleDraw: () => { 55 | return { 56 | type: types.TOGGLE_DRAW 57 | }; 58 | } 59 | }; 60 | 61 | export const getEnterCourseGroupData = (params) => { 62 | return (dispatch) => { 63 | getEnterCourseGroup(params).then(res => { 64 | dispatch(actions.initData(res.Data)); 65 | }); 66 | }; 67 | }; 68 | 69 | export const getTuanQueryListData = (params) => { 70 | return (dispatch) => { 71 | getTuanQueryList(params).then(res => { 72 | dispatch(actions.initTuanList(res.Data)); 73 | }); 74 | }; 75 | }; -------------------------------------------------------------------------------- /src/store/global.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from "immutable"; 2 | 3 | const types = { 4 | CHANGE_TAB: "change_tab" 5 | }; 6 | const defaultState = fromJS({ 7 | tabs: [ 8 | { 9 | id: 0, 10 | tit: "发现", 11 | icon: "http://test.hihiworld.com/web/static/img/find.png", 12 | selectedIcon: "http://test.hihiworld.com/web/static/img/findon.png", 13 | hash: "/home" 14 | }, 15 | { 16 | id: 1, 17 | tit: "作品", 18 | icon: "http://test.hihiworld.com/web/static/img/work.png", 19 | selectedIcon: "http://test.hihiworld.com/web/static/img/workon.png", 20 | hash: "/works" 21 | }, 22 | { 23 | id: 2, 24 | tit: "我的", 25 | icon: "http://test.hihiworld.com/web/static/img/My.png", 26 | selectedIcon: "http://test.hihiworld.com/web/static/img/myon.png", 27 | hash: "/My" 28 | } 29 | ], 30 | selectedTab: 0 31 | }); 32 | 33 | export const globalRedurces = (state = defaultState, action) => { 34 | switch (action.type) { 35 | case types.CHANGE_TAB: 36 | return state.set("selectedTab", action.payload); 37 | default: 38 | return state; 39 | } 40 | }; 41 | 42 | export const action = { 43 | changeTab: (params) => { 44 | return { 45 | payload: params, 46 | type: types.CHANGE_TAB 47 | }; 48 | } 49 | }; -------------------------------------------------------------------------------- /src/store/home.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from "immutable"; 2 | import { 3 | getQueryList, 4 | getCourseChannelQueryList, 5 | getCourseGroupQueryList, 6 | getSumGetChannelCourseGroup 7 | } from "../api"; 8 | 9 | const types = { 10 | BANNer_DATA: "banner_data", 11 | COURSE_TABS: "course_Tabs", 12 | TOGGLE_SCROLL_TOP: "toggle_scroll_top", 13 | SELECT_BAR: "select_bar", 14 | GROUP: "group" 15 | }; 16 | 17 | const defaultState = fromJS({ 18 | bannerData: [], 19 | tabsData: [], 20 | selectedTab: 1, 21 | scrollLoad: false, 22 | hotData: [], 23 | newData: [], 24 | recommendData: [] 25 | }); 26 | 27 | const addGroupList = (state, action) => { 28 | 29 | return state.merge({ 30 | hotData: action.payload.DataCourseGroup_Hot.Data.Data, 31 | newData: action.payload.DataCourseGroup_New.Data.Data, 32 | recommendData: action.payload.DataCourseGroup_New.Data.Data 33 | }); 34 | }; 35 | export const homeRedurces = (state = defaultState, action) => { 36 | switch (action.type) { 37 | case types.BANNer_DATA: 38 | return state.merge({ 39 | bannerData: action.payload 40 | }); 41 | case types.COURSE_TABS: 42 | return state.merge({ 43 | tabsData: action.payload 44 | }); 45 | case types.TOGGLE_SCROLL_TOP: 46 | return state.merge({ 47 | scrollLoad: action.show 48 | }); 49 | case types.SELECT_BAR: 50 | return state.merge({ 51 | selectedTab: action.index 52 | }); 53 | case types.GROUP: 54 | return addGroupList(state, action); 55 | 56 | default: 57 | return state; 58 | } 59 | }; 60 | 61 | export const actions = { 62 | banner: (params) => { 63 | return { 64 | payload: params, 65 | type: types.BANNer_DATA 66 | }; 67 | }, 68 | courseTabs: (params) => { 69 | return { 70 | payload: params, 71 | type: types.COURSE_TABS 72 | }; 73 | }, 74 | 75 | toggleTopShow: (show) => ({ 76 | type: types.TOGGLE_SCROLL_TOP, 77 | show 78 | }), 79 | selectBar: (index) => ({ 80 | type: types.SELECT_BAR, 81 | index 82 | }), 83 | group: (params) => { 84 | return { 85 | payload: params, 86 | type: types.GROUP 87 | }; 88 | } 89 | }; 90 | 91 | export const getSumGetChannelCourseGroupData = (params) => { 92 | return (dispatch) => { 93 | getSumGetChannelCourseGroup(params).then(res => { 94 | dispatch(actions.group(res.Data)); 95 | }); 96 | }; 97 | }; 98 | 99 | export const getQueryListData = (params) => { 100 | return (dispatch) => { 101 | getQueryList(params).then(res => { 102 | dispatch(actions.banner(res.Data.Data)); 103 | }); 104 | 105 | }; 106 | }; 107 | 108 | export const getCourseTabsData = (params) => { 109 | return (dispatch) => { 110 | getCourseChannelQueryList(params).then(res => { 111 | dispatch(actions.courseTabs(res.Data)); 112 | }); 113 | }; 114 | }; 115 | 116 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from "redux-immutable" 2 | import { createStore,compose, applyMiddleware } from "redux"; 3 | import thunk from "redux-thunk"; 4 | import { homeRedurces } from "./home"; 5 | import { workerRedurces } from "./works"; 6 | import { userInfoRedurces } from "./userInfos"; 7 | import { workDetailReducers } from "./workDetail"; 8 | import { globalRedurces } from "./global"; 9 | import {courseDetailRedurces} from './courseDetail.js'; 10 | import {otherCourseRedurces} from "./otherCourse"; 11 | 12 | 13 | const store = createStore( 14 | combineReducers({ 15 | global: globalRedurces, 16 | home: homeRedurces, 17 | works: workerRedurces, 18 | userInfos: userInfoRedurces, 19 | worksDetail: workDetailReducers, 20 | courseDetail: courseDetailRedurces, 21 | otherCourse:otherCourseRedurces 22 | }), compose( 23 | applyMiddleware(thunk), 24 | window.devToolsExtension ? window.devToolsExtension() : f => f 25 | )); 26 | 27 | export default store; -------------------------------------------------------------------------------- /src/store/otherCourse.js: -------------------------------------------------------------------------------- 1 | import { getCourseGroupQueryList } from "../api"; 2 | import { fromJS } from "immutable"; 3 | import { CLEAR_STATE } from "./clearState"; 4 | 5 | const types = { 6 | INIT_OTHER_COURSE: "init_other_course" 7 | }; 8 | 9 | const defaultState = fromJS({ 10 | otherCourseData: [] 11 | }); 12 | 13 | export const otherCourseRedurces = (state = defaultState, action) => { 14 | switch (action.type) { 15 | case types.INIT_OTHER_COURSE: 16 | //return { ...state, otherCourseData: action.payload }; 17 | return state.merge({ 18 | otherCourseData:fromJS(action.payload) 19 | }); 20 | case CLEAR_STATE: 21 | return defaultState; 22 | default: 23 | return state; 24 | } 25 | }; 26 | 27 | export const actions = { 28 | initData: (params) => { 29 | return { 30 | payload: params, 31 | type: types.INIT_OTHER_COURSE 32 | }; 33 | } 34 | }; 35 | 36 | export const getCourseGroupQueryListData = (params) => { 37 | return (dispatch) => { 38 | getCourseGroupQueryList(params).then(res => { 39 | dispatch(actions.initData(res.Data.Data)); 40 | }); 41 | }; 42 | }; -------------------------------------------------------------------------------- /src/store/userInfos.js: -------------------------------------------------------------------------------- 1 | const defaultState = { 2 | Address: null, 3 | Area: null, 4 | Birthday: "", 5 | City: null, 6 | Country: null, 7 | Email: null, 8 | Exp: null, 9 | HeadImg: "http://116.211.88.85:8602/avatar/20180923/e151b0d9-c057-42f8-9329-b5213470ee5c.jpg", 10 | IdNumber: null, 11 | IdPictureBack: null, 12 | IdPictureFront: null, 13 | Mobile: null, 14 | MySign: null, 15 | NickName: "名扬", 16 | Province: null, 17 | Sex: null, 18 | SourceId: 0, 19 | UserAccountId: 100071, 20 | Vip: null 21 | }; 22 | 23 | export const userInfoRedurces = (state = defaultState, action) => { 24 | return state; 25 | }; 26 | -------------------------------------------------------------------------------- /src/store/workDetail.js: -------------------------------------------------------------------------------- 1 | import { getCourseWorkQueryById, getCommentQueryList, getIsLike, getRemoveLike, getAddLike } from "../api"; 2 | import { fromJS } from "immutable"; 3 | import { Toast } from "antd-mobile"; 4 | import { CLEAR_STATE } from "./clearState"; 5 | 6 | const types = { 7 | INIT_COURSEWORK: "init_courseWork", 8 | ININ_COMMENTLIST: "init_commentList", 9 | INIT_ISLIKE: "init_isLike", 10 | TOGGLE_INPUT: "toggle_input", 11 | ADD_LIKE: "add_like", 12 | REMOVE_LIKE: "remove_like", 13 | TOGGLE_ISLIKE: "toggle_isLike" 14 | }; 15 | 16 | const defaultState = fromJS({ 17 | queryList: {}, 18 | commentlist: [], 19 | isInput: false, 20 | isLike: false 21 | }); 22 | 23 | //点赞自+ ,自- 24 | function toggleIsLike(state, action) { 25 | const queryList = state.get("queryList").toJS(); 26 | let likeCnt = state.get("queryList").toJS().LikeCnt; 27 | state.get("isLike") ? likeCnt-- : likeCnt++; 28 | queryList.LikeCnt = likeCnt; 29 | return state.merge({ 30 | isLike: fromJS(!state.get("isLike")), 31 | queryList: fromJS(queryList) 32 | }); 33 | } 34 | 35 | export const workDetailReducers = (state = defaultState, action) => { 36 | switch (action.type) { 37 | case types.INIT_COURSEWORK: 38 | return state.merge({ 39 | queryList: fromJS(action.payload) 40 | }); 41 | case types.ININ_COMMENTLIST: 42 | return state.merge({ 43 | commentlist: fromJS(state.get("commentlist").concat(action.payload)) 44 | }); 45 | case types.TOGGLE_INPUT: 46 | return state.merge({ 47 | isInput: fromJS(!state.get("isInput")) 48 | }); 49 | case types.INIT_ISLIKE: 50 | return state.merge({ 51 | isLike: fromJS(action.payload) 52 | }); 53 | case types.TOGGLE_ISLIKE: 54 | return toggleIsLike(state, action); 55 | case CLEAR_STATE: 56 | return defaultState; 57 | default: 58 | return state; 59 | } 60 | }; 61 | 62 | export const actions = { 63 | queryList: (params) => { 64 | return { 65 | payload: params, 66 | type: types.INIT_COURSEWORK 67 | }; 68 | }, 69 | commentlist: (params) => { 70 | return { 71 | payload: params, 72 | type: types.ININ_COMMENTLIST 73 | }; 74 | }, 75 | toggleInput: () => { 76 | return { 77 | type: types.TOGGLE_INPUT 78 | }; 79 | }, 80 | isLike: (params) => { 81 | return { 82 | payload: params, 83 | type: types.INIT_ISLIKE 84 | }; 85 | }, 86 | toggleIsLike: () => { 87 | return { 88 | type: types.TOGGLE_ISLIKE 89 | }; 90 | } 91 | 92 | }; 93 | 94 | 95 | export const getCourseWorkQueryByIdData = (params) => { 96 | return (dispatch) => { 97 | getCourseWorkQueryById(params).then(res => { 98 | dispatch(actions.queryList(res.Data)); 99 | }); 100 | }; 101 | }; 102 | 103 | //评论 104 | export const getCommentQueryListData = (params) => { 105 | return (dispatch) => { 106 | getCommentQueryList(params).then(res => { 107 | dispatch(actions.commentlist(res.Data.Data)); 108 | }); 109 | }; 110 | }; 111 | //点赞状态 112 | export const getIsLikeData = (params) => { 113 | return (dispatch) => { 114 | getIsLike(params).then(res => { 115 | dispatch(actions.isLike(res.Data)); 116 | }); 117 | }; 118 | }; 119 | 120 | //取消点赞 121 | export const getRemoveLikeData = (params) => { 122 | return (dispatch) => { 123 | getRemoveLike(params).then(res => { 124 | 125 | Toast.info("取消成功", 1); 126 | dispatch(actions.toggleIsLike()); 127 | }); 128 | }; 129 | }; 130 | //点赞成功 131 | export const getAddLikeData = (params) => { 132 | return (dispatch) => { 133 | getAddLike(params).then(res => { 134 | Toast.info("点赞成功", 1); 135 | dispatch(actions.toggleIsLike()); 136 | }); 137 | }; 138 | }; -------------------------------------------------------------------------------- /src/store/works.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from "immutable"; 2 | import { getCourseWork } from "../api"; 3 | 4 | export const types = { 5 | COURSE_WORK: "course_work", 6 | TAB_Index: "tab_index", 7 | ADD_VIDEOURL: "add_videoUrl" 8 | }; 9 | 10 | const defaultState = fromJS({ 11 | data: [], 12 | tabIndex: 2, 13 | OrderType: 0, 14 | pageIndex: 1, 15 | pageSize: 5, 16 | isLoading: true, 17 | videoUrl: "" 18 | }); 19 | 20 | export const workerRedurces = (state = defaultState, action) => { 21 | switch (action.type) { 22 | case types.COURSE_WORK: 23 | const datas = state.getIn(["data"]).toJS(); 24 | const data = datas.concat(action.payload); 25 | return state.merge({ 26 | data: data, 27 | pageIndex: action.pageIndex, 28 | tabIndex: action.tabIndex 29 | }); 30 | case types.TAB_Index: 31 | return state.merge({ 32 | data: [], 33 | pageIndex: 1, 34 | tabIndex: action.tabIndex 35 | }); 36 | case types.ADD_VIDEOURL: 37 | return state.set('selectedTab', action.payload.url); 38 | default: 39 | return state; 40 | } 41 | }; 42 | 43 | export const actions = { 44 | courseWorks: (params, pageIndex, tabIndex) => { 45 | return { 46 | payload: params, 47 | pageIndex: pageIndex, 48 | tabIndex: tabIndex, 49 | type: types.COURSE_WORK 50 | }; 51 | }, 52 | tabsIndex: (params) => { 53 | return { 54 | payload: params, 55 | type: types.TAB_Index 56 | }; 57 | }, 58 | videoAlert: (params) => { 59 | return { 60 | payload: params, 61 | type: types.ADD_VIDEOURL 62 | }; 63 | } 64 | }; 65 | 66 | export const getCourseWorkData = (params, pageIndex, tabIndex) => { 67 | return (dispatch) => { 68 | getCourseWork(params).then(res => { 69 | dispatch(actions.courseWorks(res.Data.Data, pageIndex, tabIndex)); 70 | }); 71 | 72 | }; 73 | }; 74 | 75 | export const getTabIndex = (params) => { 76 | return (dispatch) => { 77 | dispatch(actions.tabsIndex(params)); 78 | }; 79 | }; 80 | 81 | export const getvideoUrl = (params) => { 82 | return (dispatch) => { 83 | dispatch(actions.videoAlert(params)); 84 | }; 85 | }; -------------------------------------------------------------------------------- /src/utils/rem.js: -------------------------------------------------------------------------------- 1 | (function(a, d) { 2 | var b = a.documentElement, c = function() { 3 | var a = b.clientWidth; 4 | a && (b.style.fontSize = a / 750 * 100 + "px"); 5 | }; 6 | a.addEventListener && (d.addEventListener("orientationchange" in window ? "orientationchange" : "resize", c, !1), a.addEventListener("DOMContentLoaded", c, !1)); 7 | })(document, window); 8 | 9 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | export const browser = getBrowser(); 2 | 3 | function getBrowser() { 4 | let ua = window.navigator.userAgent; 5 | /** 6 | * 不要随意直接在getOs中添加任何新属性,如果添加,一定要添加形如ua.match(/android [\d\.]+/i)格式的正则 7 | */ 8 | let getOs = { 9 | android: ua.match(/android [\d]+/i), 10 | ios: ua.match(/(iphone|ipad|ipod|itouch);[\w\s]+[\d_]+/i), 11 | mac: ua.match(/mac[\w\s]+[\d_]+/i), 12 | windows: ua.match(/windows[\w\s]+[\d]+/i), 13 | ie: ua.match(/(Edge|ie|rv)[\s:][\d.]+/i), 14 | weixin: ua.match(/micromessenger\/[\d]+/i), 15 | mqqbrowser: ua.match(/mqqbrowser\/[\d]+/i), 16 | weibo: ua.match(/weibo[\d_]+/i), 17 | qq: ua.match(/qq\/[\d_]+/i), 18 | chrome: ua.match(/chrome[\s]+[\d_]+/i) 19 | }; 20 | let tmpVersion = {}; 21 | for (let i in getOs) { 22 | if (getOs.hasOwnProperty(i)) { 23 | if (getOs[i]) { 24 | let result = getOs[i][0]; 25 | let version = result.match(/[\d_]+/)[0]; 26 | version = version.replace(/^_+|_+$/g, ""); 27 | version = version.replace(/_+/g, "."); 28 | tmpVersion[i + "V"] = version; 29 | getOs[i] = true; 30 | } 31 | } 32 | } 33 | getOs = Object.assign({}, getOs, tmpVersion); 34 | getOs.mobile = /mobile/i.test(ua) ? true : null; 35 | getOs.nettype = /nettype/i.test(ua) ? ua.match(/nettype\/\w+/i)[0].split("/")[1].toLowerCase() : null; 36 | getOs.tbs = /tbs/i.test(ua) ? ua.match(/tbs\/\d+/i)[0].split("/")[1] : null; 37 | if (getOs.chrome && getOs.chromeV.match(/\d+/ > 100)) { 38 | getOs.chrome = null; 39 | getOs.chromeV = null; 40 | } 41 | return getOs; 42 | } 43 | 44 | 45 | export const setTitle = function(title) { 46 | document.title = title === undefined ? document.title : title; 47 | }; 48 | 49 | export const strArr = function(str) { 50 | return str.split(","); 51 | }; 52 | 53 | export const isDataSize = function(data) { 54 | if (Object.keys(data).length === 0) return true; 55 | return false; 56 | }; 57 | --------------------------------------------------------------------------------