Commit 4fd7b88d by guomingpang
parents 806f97d6 b7abc31c
'use strict' const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const fs = require('fs') const postcssNormalize = require('postcss-normalize');
const path = require('path')
const webpack = require('webpack')
const resolve = require('resolve')
const PnpWebpackPlugin = require('pnp-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const safePostCssParser = require('postcss-safe-parser')
const ManifestPlugin = require('webpack-manifest-plugin')
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
const paths = require('./paths')
const modules = require('./modules')
const vConsolePlugin = require('vconsole-webpack-plugin')
const getClientEnvironment = require('./env')
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin')
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin')
const typescriptFormatter = require('react-dev-utils/typescriptFormatter')
const postcssNormalize = require('postcss-normalize') const appPackageJson = require(paths.appPackageJson);
const appPackageJson = require(paths.appPackageJson)
// Source maps are resource heavy and can cause out of memory issue for large source files. // Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false' const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Some apps do not need the benefits of saving a web request, so not inlining the chunk // Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process. // makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false' const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true' const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true';
const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || '10000') const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || '10000');
// Check if TypeScript is setup // Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig) const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes // style files regexes
const cssRegex = /\.css$/ const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/ const cssModuleRegex = /\.module\.css$/;
const lessRegex = /\.less$/ const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/ const lessModuleRegex = /\.module\.less$/;
// This is the production and development configuration. // This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle. // It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function (webpackEnv) { module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development' const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production' const isEnvProduction = webpackEnv === 'production';
// Variable used for enabling profiling in Production // Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command // passed into alias object. Uses a flag if passed into the build command
const isEnvProductionProfile = isEnvProduction && process.argv.includes('--profile') const isEnvProductionProfile = isEnvProduction && process.argv.includes('--profile');
// We will provide `paths.publicUrlOrPath` to our app // We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
// Get environment variables to inject into our app. // Get environment variables to inject into our app.
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)) const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
var PREFIX = 'b-'; //static 文件夹前缀
// common function to get style loaders // common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => { const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [ const loaders = [
...@@ -73,11 +70,11 @@ module.exports = function (webpackEnv) { ...@@ -73,11 +70,11 @@ module.exports = function (webpackEnv) {
loader: MiniCssExtractPlugin.loader, loader: MiniCssExtractPlugin.loader,
// css is located in `static/css`, use '../../' to locate index.html folder // css is located in `static/css`, use '../../' to locate index.html folder
// in production `paths.publicUrlOrPath` can be a relative path // in production `paths.publicUrlOrPath` can be a relative path
options: paths.publicUrlOrPath.startsWith('.') ? { publicPath: '../../' } : {} options: paths.publicUrlOrPath.startsWith('.') ? { publicPath: '../../' } : {},
}, },
{ {
loader: require.resolve('css-loader'), loader: require.resolve('css-loader'),
options: cssOptions options: cssOptions,
}, },
{ {
// Options for PostCSS as we reference these options twice // Options for PostCSS as we reference these options twice
...@@ -92,32 +89,32 @@ module.exports = function (webpackEnv) { ...@@ -92,32 +89,32 @@ module.exports = function (webpackEnv) {
require('postcss-flexbugs-fixes'), require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({ require('postcss-preset-env')({
autoprefixer: { autoprefixer: {
flexbox: 'no-2009' flexbox: 'no-2009',
}, },
stage: 3 stage: 3,
}), }),
// Adds PostCSS Normalize as the reset css with default options, // Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json // so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs. // which in turn let's users customize the target behavior as per their needs.
postcssNormalize() postcssNormalize(),
], ],
sourceMap: isEnvProduction && shouldUseSourceMap sourceMap: isEnvProduction && shouldUseSourceMap,
} },
} },
].filter(Boolean) ].filter(Boolean);
if (preProcessor) { if (preProcessor) {
loaders.push( loaders.push(
{ {
loader: require.resolve('resolve-url-loader'), loader: require.resolve('resolve-url-loader'),
options: { options: {
sourceMap: isEnvProduction && shouldUseSourceMap sourceMap: isEnvProduction && shouldUseSourceMap,
} },
}, },
{ {
loader: require.resolve(preProcessor), loader: require.resolve(preProcessor),
options: { options: {
sourceMap: true sourceMap: true,
} },
}, },
{ {
loader: require.resolve('less-loader'), loader: require.resolve('less-loader'),
...@@ -127,14 +124,14 @@ module.exports = function (webpackEnv) { ...@@ -127,14 +124,14 @@ module.exports = function (webpackEnv) {
'primary-color': '#2966FF', 'primary-color': '#2966FF',
'border-radius-base': '2px', 'border-radius-base': '2px',
}, },
javascriptEnabled: true javascriptEnabled: true,
} },
} },
}
)
} }
return loaders );
} }
return loaders;
};
return { return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
...@@ -156,7 +153,7 @@ module.exports = function (webpackEnv) { ...@@ -156,7 +153,7 @@ module.exports = function (webpackEnv) {
isEnvDevelopment && require.resolve('webpack/hot/dev-server'), isEnvDevelopment && require.resolve('webpack/hot/dev-server'),
isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'), isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code: // Finally, this is your app's code:
paths.appIndexJs paths.appIndexJs,
// We include the app code last so that if there is a runtime error during // We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and // initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh. // changing JS code would still trigger a refresh.
...@@ -168,11 +165,11 @@ module.exports = function (webpackEnv) { ...@@ -168,11 +165,11 @@ module.exports = function (webpackEnv) {
pathinfo: isEnvDevelopment, pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk. // There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files. // In development, it does not produce real files.
filename: isEnvProduction ? 'static/js/[name].[contenthash:8].js' : isEnvDevelopment && 'static/js/bundle.js', filename: isEnvProduction ? PREFIX + 'static/js/[name].[contenthash:8].js' : isEnvDevelopment && PREFIX + 'static/js/bundle.js',
// TODO: remove this when upgrading to webpack 5 // TODO: remove this when upgrading to webpack 5
futureEmitAssets: true, futureEmitAssets: true,
// There are also additional JS chunk files if you use code splitting. // There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction ? 'static/js/[name].[contenthash:8].chunk.js' : isEnvDevelopment && 'static/js/[name].chunk.js', chunkFilename: isEnvProduction ? PREFIX + 'static/js/[name].[contenthash:8].chunk.js' : isEnvDevelopment && PREFIX + 'static/js/[name].chunk.js',
// webpack uses `publicPath` to determine where the app is being served from. // webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path. // It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage. // We inferred the "public path" (such as / or /my-project) from homepage.
...@@ -189,7 +186,7 @@ module.exports = function (webpackEnv) { ...@@ -189,7 +186,7 @@ module.exports = function (webpackEnv) {
globalObject: 'this', globalObject: 'this',
library: appPackageJson.name, library: appPackageJson.name,
libraryTarget: 'umd', libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${appPackageJson.name}` jsonpFunction: `webpackJsonp_${appPackageJson.name}`,
}, },
optimization: { optimization: {
minimize: isEnvProduction, minimize: isEnvProduction,
...@@ -203,7 +200,7 @@ module.exports = function (webpackEnv) { ...@@ -203,7 +200,7 @@ module.exports = function (webpackEnv) {
// into invalid ecma 5 code. This is why the 'compress' and 'output' // into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe // sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234 // https://github.com/facebook/create-react-app/pull/4234
ecma: 8 ecma: 8,
}, },
compress: { compress: {
ecma: 5, ecma: 5,
...@@ -217,10 +214,10 @@ module.exports = function (webpackEnv) { ...@@ -217,10 +214,10 @@ module.exports = function (webpackEnv) {
// https://github.com/facebook/create-react-app/issues/5250 // https://github.com/facebook/create-react-app/issues/5250
// Pending further investigation: // Pending further investigation:
// https://github.com/terser-js/terser/issues/120 // https://github.com/terser-js/terser/issues/120
inline: 2 inline: 2,
}, },
mangle: { mangle: {
safari10: true safari10: true,
}, },
// Added for profiling in devtools // Added for profiling in devtools
keep_classnames: isEnvProductionProfile, keep_classnames: isEnvProductionProfile,
...@@ -230,10 +227,10 @@ module.exports = function (webpackEnv) { ...@@ -230,10 +227,10 @@ module.exports = function (webpackEnv) {
comments: false, comments: false,
// Turned on because emoji and regex is not minified properly using default // Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488 // https://github.com/facebook/create-react-app/issues/2488
ascii_only: true ascii_only: true,
}
}, },
sourceMap: shouldUseSourceMap },
sourceMap: shouldUseSourceMap,
}), }),
// This is only used in production mode // This is only used in production mode
new OptimizeCSSAssetsPlugin({ new OptimizeCSSAssetsPlugin({
...@@ -246,28 +243,28 @@ module.exports = function (webpackEnv) { ...@@ -246,28 +243,28 @@ module.exports = function (webpackEnv) {
inline: false, inline: false,
// `annotation: true` appends the sourceMappingURL to the end of // `annotation: true` appends the sourceMappingURL to the end of
// the css file, helping the browser find the sourcemap // the css file, helping the browser find the sourcemap
annotation: true annotation: true,
} }
: false : false,
}, },
cssProcessorPluginOptions: { cssProcessorPluginOptions: {
preset: ['default', { minifyFontValues: { removeQuotes: false } }] preset: ['default', { minifyFontValues: { removeQuotes: false } }],
} },
}) }),
], ],
// Automatically split vendor and commons // Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474 // https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: { splitChunks: {
chunks: 'all', chunks: 'all',
name: false name: false,
}, },
// Keep the runtime chunk separated to enable long term caching // Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985 // https://twitter.com/wSokra/status/969679223278505985
// https://github.com/facebook/create-react-app/issues/5358 // https://github.com/facebook/create-react-app/issues/5358
runtimeChunk: { runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}` name: (entrypoint) => `runtime-${entrypoint.name}`,
} },
}, },
resolve: { resolve: {
// This allows you to set a fallback for where webpack should look for modules. // This allows you to set a fallback for where webpack should look for modules.
...@@ -290,9 +287,9 @@ module.exports = function (webpackEnv) { ...@@ -290,9 +287,9 @@ module.exports = function (webpackEnv) {
// Allows for better profiling with ReactDevTools // Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && { ...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling', 'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling' 'scheduler/tracing': 'scheduler/tracing-profiling',
}), }),
...(modules.webpackAliases || {}) ...(modules.webpackAliases || {}),
}, },
plugins: [ plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding // Adds support for installing with Plug'n'Play, leading to faster installs and adding
...@@ -303,15 +300,15 @@ module.exports = function (webpackEnv) { ...@@ -303,15 +300,15 @@ module.exports = function (webpackEnv) {
// To fix this, we prevent you from importing files out of src/ -- if you'd like to, // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in. // please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way. // Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]) new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
] ],
}, },
resolveLoader: { resolveLoader: {
plugins: [ plugins: [
// Also related to Plug'n'Play, but this time it tells webpack to load its loaders // Also related to Plug'n'Play, but this time it tells webpack to load its loaders
// from the current package. // from the current package.
PnpWebpackPlugin.moduleLoader(module) PnpWebpackPlugin.moduleLoader(module),
] ],
}, },
module: { module: {
strictExportPresence: true, strictExportPresence: true,
...@@ -330,12 +327,12 @@ module.exports = function (webpackEnv) { ...@@ -330,12 +327,12 @@ module.exports = function (webpackEnv) {
cache: true, cache: true,
formatter: require.resolve('react-dev-utils/eslintFormatter'), formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'), eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: __dirname resolvePluginsRelativeTo: __dirname,
},
loader: require.resolve('eslint-loader'),
}, },
loader: require.resolve('eslint-loader')
}
], ],
include: paths.appSrc include: paths.appSrc,
}, },
{ {
// "oneOf" will traverse all following loaders until one will // "oneOf" will traverse all following loaders until one will
...@@ -350,8 +347,8 @@ module.exports = function (webpackEnv) { ...@@ -350,8 +347,8 @@ module.exports = function (webpackEnv) {
loader: require.resolve('url-loader'), loader: require.resolve('url-loader'),
options: { options: {
limit: imageInlineSizeLimit, limit: imageInlineSizeLimit,
name: 'static/media/[name].[hash:8].[ext]' name: PREFIX + 'static/media/[name].[hash:8].[ext]',
} },
}, },
// Process application JS with Babel. // Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features. // The preset includes JSX, Flow, TypeScript, and some ESnext features.
...@@ -368,11 +365,11 @@ module.exports = function (webpackEnv) { ...@@ -368,11 +365,11 @@ module.exports = function (webpackEnv) {
{ {
loaderMap: { loaderMap: {
svg: { svg: {
ReactComponent: '@svgr/webpack?-svgo,+titleProp,+ref![path]' ReactComponent: '@svgr/webpack?-svgo,+titleProp,+ref![path]',
} },
} },
} },
] ],
], ],
// This is a feature of `babel-loader` for webpack (not Babel itself). // This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/ // It enables caching results in ./node_modules/.cache/babel-loader/
...@@ -380,8 +377,8 @@ module.exports = function (webpackEnv) { ...@@ -380,8 +377,8 @@ module.exports = function (webpackEnv) {
cacheDirectory: true, cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled // See #6846 for context on why cacheCompression is disabled
cacheCompression: false, cacheCompression: false,
compact: isEnvProduction compact: isEnvProduction,
} },
}, },
// Process any JS outside of the app with Babel. // Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features. // Unlike the application JS, we only compile the standard ES features.
...@@ -402,8 +399,8 @@ module.exports = function (webpackEnv) { ...@@ -402,8 +399,8 @@ module.exports = function (webpackEnv) {
// code. Without the options below, debuggers like VSCode // code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines. // show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap, sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap inputSourceMap: shouldUseSourceMap,
} },
}, },
// "postcss" loader applies autoprefixer to our CSS. // "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies. // "css" loader resolves paths in CSS and adds assets as dependencies.
...@@ -417,13 +414,13 @@ module.exports = function (webpackEnv) { ...@@ -417,13 +414,13 @@ module.exports = function (webpackEnv) {
exclude: cssModuleRegex, exclude: cssModuleRegex,
use: getStyleLoaders({ use: getStyleLoaders({
importLoaders: 1, importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap sourceMap: isEnvProduction && shouldUseSourceMap,
}), }),
// Don't consider CSS imports dead code even if the // Don't consider CSS imports dead code even if the
// containing package claims to have no side effects. // containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this. // Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571 // See https://github.com/webpack/webpack/issues/6571
sideEffects: true sideEffects: true,
}, },
// Adds support for CSS Modules (https://github.com/css-modules/css-modules) // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css // using the extension .module.css
...@@ -433,9 +430,9 @@ module.exports = function (webpackEnv) { ...@@ -433,9 +430,9 @@ module.exports = function (webpackEnv) {
importLoaders: 1, importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
modules: { modules: {
getLocalIdent: getCSSModuleLocalIdent getLocalIdent: getCSSModuleLocalIdent,
} },
}) }),
}, },
// Opt-in support for SASS (using .scss or .sass extensions). // Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the // By default we support SASS Modules with the
...@@ -446,11 +443,11 @@ module.exports = function (webpackEnv) { ...@@ -446,11 +443,11 @@ module.exports = function (webpackEnv) {
use: getStyleLoaders( use: getStyleLoaders(
{ {
importLoaders: 2, importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap sourceMap: isEnvProduction && shouldUseSourceMap,
}, },
'less-loader' 'less-loader'
), ),
sideEffects: true sideEffects: true,
}, },
{ {
test: lessModuleRegex, test: lessModuleRegex,
...@@ -459,10 +456,10 @@ module.exports = function (webpackEnv) { ...@@ -459,10 +456,10 @@ module.exports = function (webpackEnv) {
importLoaders: 2, importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap, sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true, modules: true,
getLocalIdent: getCSSModuleLocalIdent getLocalIdent: getCSSModuleLocalIdent,
}, },
'less-loader' 'less-loader'
) ),
}, },
// "file" loader makes sure those assets get served by WebpackDevServer. // "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename. // When you `import` an asset, you get its (virtual) filename.
...@@ -477,14 +474,14 @@ module.exports = function (webpackEnv) { ...@@ -477,14 +474,14 @@ module.exports = function (webpackEnv) {
// by webpacks internal loaders. // by webpacks internal loaders.
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: { options: {
name: 'static/media/[name].[hash:8].[ext]' name: PREFIX + 'static/media/[name].[hash:8].[ext]',
} },
} },
// ** STOP ** Are you adding a new loader? // ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader. // Make sure to add the new loader(s) before the "file" loader.
] ],
} },
] ],
}, },
plugins: [ plugins: [
// Generates an `index.html` file with the <script> injected. // Generates an `index.html` file with the <script> injected.
...@@ -493,7 +490,7 @@ module.exports = function (webpackEnv) { ...@@ -493,7 +490,7 @@ module.exports = function (webpackEnv) {
{}, {},
{ {
inject: true, inject: true,
template: paths.appHtml template: paths.appHtml,
}, },
isEnvProduction isEnvProduction
? { ? {
...@@ -507,8 +504,8 @@ module.exports = function (webpackEnv) { ...@@ -507,8 +504,8 @@ module.exports = function (webpackEnv) {
keepClosingSlash: true, keepClosingSlash: true,
minifyJS: true, minifyJS: true,
minifyCSS: true, minifyCSS: true,
minifyURLs: true minifyURLs: true,
} },
} }
: undefined : undefined
) )
...@@ -547,8 +544,8 @@ module.exports = function (webpackEnv) { ...@@ -547,8 +544,8 @@ module.exports = function (webpackEnv) {
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output // Options similar to the same options in webpackOptions.output
// both options are optional // both options are optional
filename: 'static/css/[name].[contenthash:8].css', filename: PREFIX + 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css' chunkFilename: PREFIX + 'static/css/[name].[contenthash:8].chunk.css',
}), }),
// Generate an asset manifest file with the following content: // Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding // - "files" key: Mapping of all asset filenames to their corresponding
...@@ -561,16 +558,16 @@ module.exports = function (webpackEnv) { ...@@ -561,16 +558,16 @@ module.exports = function (webpackEnv) {
publicPath: paths.publicUrlOrPath, publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => { generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => { const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path manifest[file.name] = file.path;
return manifest return manifest;
}, seed) }, seed);
const entrypointFiles = entrypoints.main.filter((fileName) => !fileName.endsWith('.map')) const entrypointFiles = entrypoints.main.filter((fileName) => !fileName.endsWith('.map'));
return { return {
files: manifestFiles, files: manifestFiles,
entrypoints: entrypointFiles entrypoints: entrypointFiles,
} };
} },
}), }),
// Moment.js is an extremely popular library that bundles large locale files // Moment.js is an extremely popular library that bundles large locale files
...@@ -594,14 +591,14 @@ module.exports = function (webpackEnv) { ...@@ -594,14 +591,14 @@ module.exports = function (webpackEnv) {
// as they're likely a resource and not a SPA route. // as they're likely a resource and not a SPA route.
// URLs containing a "?" character won't be blacklisted as they're likely // URLs containing a "?" character won't be blacklisted as they're likely
// a route with query params (e.g. auth callbacks). // a route with query params (e.g. auth callbacks).
new RegExp('/[^/?]+\\.[^/]+$') new RegExp('/[^/?]+\\.[^/]+$'),
] ],
}), }),
// TypeScript type checking // TypeScript type checking
useTypeScript && useTypeScript &&
new ForkTsCheckerWebpackPlugin({ new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', { typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules basedir: paths.appNodeModules,
}), }),
async: isEnvDevelopment, async: isEnvDevelopment,
useTypescriptIncrementalApi: true, useTypescriptIncrementalApi: true,
...@@ -612,11 +609,8 @@ module.exports = function (webpackEnv) { ...@@ -612,11 +609,8 @@ module.exports = function (webpackEnv) {
reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*', '!**/src/setupProxy.*', '!**/src/setupTests.*'], reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*', '!**/src/setupProxy.*', '!**/src/setupTests.*'],
silent: true, silent: true,
// The formatter is invoked directly in WebpackDevServerUtils during development // The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined formatter: isEnvProduction ? typescriptFormatter : undefined,
}) }),
// new vConsolePlugin({
// enable: (process.env.DEPLOY_ENV === 'prod' || process.env.DEPLOY_ENV === 'beta') ? false : true
// })
].filter(Boolean), ].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser. // Some libraries import Node modules but don't use them in the browser.
// Tell webpack to provide empty mocks for them so importing them works. // Tell webpack to provide empty mocks for them so importing them works.
...@@ -628,10 +622,10 @@ module.exports = function (webpackEnv) { ...@@ -628,10 +622,10 @@ module.exports = function (webpackEnv) {
http2: 'empty', http2: 'empty',
net: 'empty', net: 'empty',
tls: 'empty', tls: 'empty',
child_process: 'empty' child_process: 'empty',
}, },
// Turn off performance processing because we utilize // Turn off performance processing because we utilize
// our own hints via the FileSizeReporter // our own hints via the FileSizeReporter
performance: false performance: false,
} };
} };
...@@ -110,6 +110,7 @@ ...@@ -110,6 +110,7 @@
"start:dev1": "cross-env DEPLOY_ENV=dev node scripts/start.js", "start:dev1": "cross-env DEPLOY_ENV=dev node scripts/start.js",
"start:rc": "cross-env DEPLOY_ENV=rc node scripts/start.js", "start:rc": "cross-env DEPLOY_ENV=rc node scripts/start.js",
"start:gray": "cross-env DEPLOY_ENV=gray node scripts/start.js", "start:gray": "cross-env DEPLOY_ENV=gray node scripts/start.js",
"start:prod": "cross-env DEPLOY_ENV=prod node scripts/start.js",
"build:dev": "cross-env DEPLOY_ENV=dev node scripts/build.js", "build:dev": "cross-env DEPLOY_ENV=dev node scripts/build.js",
"build:dev1": "cross-env DEPLOY_ENV=dev node scripts/build.js", "build:dev1": "cross-env DEPLOY_ENV=dev node scripts/build.js",
"build:rc": "cross-env DEPLOY_ENV=rc node scripts/build.js", "build:rc": "cross-env DEPLOY_ENV=rc node scripts/build.js",
...@@ -143,7 +144,6 @@ ...@@ -143,7 +144,6 @@
"@types/ali-oss": "^6.0.5", "@types/ali-oss": "^6.0.5",
"@types/jquery": "^3.5.4", "@types/jquery": "^3.5.4",
"ali-oss": "^6.12.0", "ali-oss": "^6.12.0",
"react-sortable-hoc": "^1.11.0", "react-sortable-hoc": "^1.11.0"
"vconsole-webpack-plugin": "^1.5.2"
} }
} }
...@@ -13,8 +13,9 @@ ...@@ -13,8 +13,9 @@
} }
.container { .container {
overflow: scroll; overflow-y: auto;
height: 100%;; overflow-x: hidden;
height: 100%;
.course-cover, .course-url { .course-cover, .course-url {
width: 100%; width: 100%;
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
} }
} }
.operate{ .operate{
display:inline-block;
margin-top:24px; margin-top:24px;
.btn { .btn {
padding:5px 12px; padding:5px 12px;
......
...@@ -44,6 +44,10 @@ const FileVerifyMap = { ...@@ -44,6 +44,10 @@ const FileVerifyMap = {
type: "word", type: "word",
maxSize: 100 maxSize: 100
}, },
"application/wps-writer": {
type: "word",
maxSize: 100,
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": { "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
type: "word", type: "word",
maxSize: 100 maxSize: 100
...@@ -133,12 +137,13 @@ const FileTypeIcon = { ...@@ -133,12 +137,13 @@ const FileTypeIcon = {
PPTX: "https://image.xiaomaiketang.com/xm/847pFAdYGW.png", PPTX: "https://image.xiaomaiketang.com/xm/847pFAdYGW.png",
PDF: "https://image.xiaomaiketang.com/xm/rrEJMNkhTG.png", PDF: "https://image.xiaomaiketang.com/xm/rrEJMNkhTG.png",
MP3: "https://image.xiaomaiketang.com/xm/ykjnSWDyQ6.png", MP3: "https://image.xiaomaiketang.com/xm/ykjnSWDyQ6.png",
MP4: "https://image.xiaomaiketang.com/xm/whSYMTdR57.png", MP4: "https://image.xiaomaiketang.com/xm/yK3ASiS8ch.png",
JPG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png", JPG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
JPEG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png", JPEG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
PNG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png", PNG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
GIF: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png", GIF: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
BMP: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png", BMP: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
VIDEO: 'https://image.xiaomaiketang.com/xm/yK3ASiS8ch.png'
}; };
const UploadIcon = "https://image.xiaomaiketang.com/xm/4DXNrZWWsd.png"; const UploadIcon = "https://image.xiaomaiketang.com/xm/4DXNrZWWsd.png";
......
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-31 09:34:31 * @Date: 2020-08-31 09:34:31
* @LastEditors: Please set LastEditors * @LastEditors: yuananting
* @LastEditTime: 2021-06-24 19:34:21 * @LastEditTime: 2021-07-12 17:40:26
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -115,6 +115,10 @@ class Axios { ...@@ -115,6 +115,10 @@ class Axios {
window.RCHistory.replace('/login'); window.RCHistory.replace('/login');
return Promise.reject(); return Promise.reject();
break; break;
case 504:
message.error('网络状况不稳定,如果出现数据异常,请刷新页面');
Promise.reject();
break;
default: default:
message.error(error.message); message.error(error.message);
break; break;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-31 09:34:25 * @Date: 2020-08-31 09:34:25
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-23 16:08:50 * @LastEditTime: 2021-07-20 17:21:16
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -48,6 +48,10 @@ class User { ...@@ -48,6 +48,10 @@ class User {
return window.currentStoreUserInfo.userId || Storage.get(`${PREFIX}_userId`); return window.currentStoreUserInfo.userId || Storage.get(`${PREFIX}_userId`);
} }
getAvatar(){
return Storage.get(`${PREFIX}_avatar`);
}
getUserRole() { getUserRole() {
return Storage.get(`${PREFIX}_userRole`); return Storage.get(`${PREFIX}_userRole`);
} }
...@@ -96,6 +100,10 @@ class User { ...@@ -96,6 +100,10 @@ class User {
return Storage.set(`${PREFIX}_userId`, value); return Storage.set(`${PREFIX}_userId`, value);
} }
setAvatar(value: any) {
return Storage.set(`${PREFIX}_avatar`, value);
}
setUserRole(value: any) { setUserRole(value: any) {
return Storage.set(`${PREFIX}_userRole`, value); return Storage.set(`${PREFIX}_userRole`, value);
} }
......
.ant-popover .ant-popover-content .ant-popover-inner { .contact-widget-popover .ant-popover-content .ant-popover-inner {
box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.06); box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.06);
.ant-popover-inner-content { .ant-popover-inner-content {
padding: 0; padding: 0;
......
...@@ -27,6 +27,7 @@ function Content() { ...@@ -27,6 +27,7 @@ function Content() {
export default function ContactWidget(props:ContactWidgetProps) { export default function ContactWidget(props:ContactWidgetProps) {
return <Popover return <Popover
className="contact-widget-popover"
placement={props.placement} placement={props.placement}
arrowPointAtCenter arrowPointAtCenter
content={Content} content={Content}
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
*/ */
@import './variables.less'; @import './variables.less';
@active-color: #2966FF; @active-color: #2966ff;
// 消息提示框 // 消息提示框
.ant-message { .ant-message {
...@@ -41,6 +41,9 @@ ...@@ -41,6 +41,9 @@
.ant-switch-small:after { .ant-switch-small:after {
top: 0.3px; top: 0.3px;
} }
tbody .ant-switch-small {
min-width: 32px;
}
.ant-pagination { .ant-pagination {
display: inline !important; display: inline !important;
...@@ -234,7 +237,7 @@ ...@@ -234,7 +237,7 @@
} }
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
font-weight: 500!important; font-weight: 500 !important;
} }
.ant-tabs-nav .ant-tabs-tab-active { .ant-tabs-nav .ant-tabs-tab-active {
...@@ -264,7 +267,7 @@ ...@@ -264,7 +267,7 @@
position: absolute; position: absolute;
width: 30px; width: 30px;
height: 4px; height: 4px;
background: #2966FF !important; background: #2966ff !important;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
} }
...@@ -339,14 +342,14 @@ mr0 { ...@@ -339,14 +342,14 @@ mr0 {
background: #f3f6fa; background: #f3f6fa;
.ant-select-selected-icon { .ant-select-selected-icon {
color:#2966FF !important; color: #2966ff !important;
} }
} }
.ant-select-dropdown-menu-item-selected { .ant-select-dropdown-menu-item-selected {
background: none; background: none;
font-weight: 400 !important; font-weight: 400 !important;
color:#2966FF; color: #2966ff;
} }
.ant-select-open .ant-select-selection { .ant-select-open .ant-select-selection {
...@@ -444,7 +447,7 @@ mr0 { ...@@ -444,7 +447,7 @@ mr0 {
.ant-modal { .ant-modal {
.ant-input { .ant-input {
&:focus { &:focus {
border: 1px solid #2966FF !important; border: 1px solid #2966ff !important;
} }
} }
} }
...@@ -457,8 +460,8 @@ mr0 { ...@@ -457,8 +460,8 @@ mr0 {
.ant-calendar-footer-extra { .ant-calendar-footer-extra {
.ant-tag-blue { .ant-tag-blue {
background: #fff; background: #fff;
color: #2966FF; color: #2966ff;
border-color: #2966FF; border-color: #2966ff;
} }
} }
} }
...@@ -474,7 +477,7 @@ mr0 { ...@@ -474,7 +477,7 @@ mr0 {
} }
.ant-modal-confirm-info .ant-modal-confirm-body > .anticon { .ant-modal-confirm-info .ant-modal-confirm-body > .anticon {
color: #2966FF!important; color: #2966ff !important;
} }
.ant-breadcrumb > span:last-child .ant-breadcrumb-separator { .ant-breadcrumb > span:last-child .ant-breadcrumb-separator {
...@@ -502,7 +505,7 @@ mr0 { ...@@ -502,7 +505,7 @@ mr0 {
font-size: 22px !important; font-size: 22px !important;
line-height: 22px !important; line-height: 22px !important;
float: left !important; float: left !important;
color:#2966FF !important; color: #2966ff !important;
margin-right: 16px !important; margin-right: 16px !important;
&.blue { &.blue {
color: #5cbaff !important; color: #5cbaff !important;
...@@ -527,8 +530,8 @@ mr0 { ...@@ -527,8 +530,8 @@ mr0 {
float: left; float: left;
margin-right: 16px; margin-right: 16px;
} }
>.confirm-icon{ > .confirm-icon {
color: #2966FF; color: #2966ff;
} }
.ant-modal-confirm-content { .ant-modal-confirm-content {
margin-left: 38px; margin-left: 38px;
...@@ -594,9 +597,9 @@ mr0 { ...@@ -594,9 +597,9 @@ mr0 {
// background: #FAFAFA !important; // background: #FAFAFA !important;
// } // }
.ant-table-thead > tr > th{ .ant-table-thead > tr > th {
font-weight:bold !important; font-weight: bold !important;
color:#333 !important; color: #333 !important;
} }
td.ant-table-column-sort { td.ant-table-column-sort {
background: none; background: none;
...@@ -657,11 +660,11 @@ td.ant-table-column-sort { ...@@ -657,11 +660,11 @@ td.ant-table-column-sort {
border-color: @xm-color-text-select-primary !important; border-color: @xm-color-text-select-primary !important;
border: none !important; border: none !important;
&:hover { &:hover {
background-color: #2966FF !important; background-color: #2966ff !important;
opacity: 0.8 !important; opacity: 0.8 !important;
} }
&:active{ &:active {
background-color:#5C8AFF !important; background-color: #5c8aff !important;
} }
} }
...@@ -720,9 +723,9 @@ td.ant-table-column-sort { ...@@ -720,9 +723,9 @@ td.ant-table-column-sort {
border-right: 0px !important; border-right: 0px !important;
} }
.ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button span{ .ant-input-search > .ant-input-group > .ant-input-group-addon:last-child .ant-input-search-button span {
color:#bfbfbf !important; color: #bfbfbf !important;
font-size:12px; font-size: 12px;
} }
.ant-modal-footer { .ant-modal-footer {
...@@ -774,4 +777,3 @@ td.ant-table-column-sort { ...@@ -774,4 +777,3 @@ td.ant-table-column-sort {
.ant-table-column-sorter { .ant-table-column-sorter {
margin-left: 8px !important; margin-left: 8px !important;
} }
...@@ -1156,3 +1156,15 @@ window.XMShowClassName = (date, itemName) => { ...@@ -1156,3 +1156,15 @@ window.XMShowClassName = (date, itemName) => {
} }
return 'new-icon' return 'new-icon'
} }
// 格式化时间段为时分
window.formatDuration = function (time) {
const diff = Math.floor(time % 3600);
let hours = Math.floor(time / 3600);
let mins = Math.floor(diff / 60);
let seconds = Math.floor(time % 60);
hours = hours < 10 ? ("0" + hours) : hours;
mins = mins < 10 ? ("0" + mins) : mins;
seconds = seconds < 10 ? ("0" + seconds) : seconds;
return hours + ":" + mins + ":" + seconds;
};
\ No newline at end of file
/* /*
* @Author: yuananting * @Author: yuananting
* @Date: 2021-03-03 15:13:12 * @Date: 2021-03-03 15:13:12
* @LastEditors: Please set LastEditors * @LastEditors: yuananting
* @LastEditTime: 2021-06-22 14:31:46 * @LastEditTime: 2021-07-09 16:58:34
* @Description: 助学工具接口 * @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -88,3 +88,7 @@ export function batchQueryQuestionDetails(params: object) { ...@@ -88,3 +88,7 @@ export function batchQueryQuestionDetails(params: object) {
export function queryQuestionPageListWithContent(params: object) { export function queryQuestionPageListWithContent(params: object) {
return Service.Hades('public/hades/queryQuestionPageListWithContent', params); return Service.Hades('public/hades/queryQuestionPageListWithContent', params);
} }
export function queryCategoryTreeByPackage(params: object) {
return Service.Hades('public/externalHades/queryPackageCategory', params);
}
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: wufan * @Author: wufan
* @Date: 2020-12-01 17:21:21 * @Date: 2020-12-01 17:21:21
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-22 14:56:34 * @LastEditTime: 2021-07-09 15:33:33
* @Description: Description * @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -62,6 +62,9 @@ export function saveYoZoFileVersionId(params: object) { ...@@ -62,6 +62,9 @@ export function saveYoZoFileVersionId(params: object) {
export function yoZoUpload(ossUrl:String,appId:String,uploadSign:String){ export function yoZoUpload(ossUrl:String,appId:String,uploadSign:String){
return axios.post(`https://dmc.yozocloud.cn/api/file/http?fileUrl=${ossUrl}&appId=${appId}&sign=${uploadSign}`) return axios.post(`https://dmc.yozocloud.cn/api/file/http?fileUrl=${ossUrl}&appId=${appId}&sign=${uploadSign}`)
} }
export function saveFileVersionIdByCourseChapter(params: object) {
return Service.Hades('public/hades/saveFileVersionIdByCourseChapter', params);
}
export const getOssClient = ( export const getOssClient = (
data: object, data: object,
instId: string, instId: string,
......
/* /*
* @Author: wufan * @Author: wufan
* @Date: 2020-12-12 11:57:10 * @Date: 2020-12-12 11:57:10
* @LastEditors: zangsuyun * @LastEditors: wufan
* @LastEditTime: 2021-03-22 13:54:20 * @LastEditTime: 2021-07-05 15:07:13
* @Description: Description * @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -57,7 +57,7 @@ export function getLiveCloudCourseBasePage(params: object) { ...@@ -57,7 +57,7 @@ export function getLiveCloudCourseBasePage(params: object) {
return Service.Hades("public/courseCloud/getLiveCloudCourseBasePage", params); return Service.Hades("public/courseCloud/getLiveCloudCourseBasePage", params);
} }
//视频课相关接口 //线上课相关接口
export function changeVideoShelfState(params: object) { export function changeVideoShelfState(params: object) {
return Service.Hades("public/hades/changeVideoShelfState", params); return Service.Hades("public/hades/changeVideoShelfState", params);
} }
...@@ -73,6 +73,9 @@ export function editVideoSchedule(params: object) { ...@@ -73,6 +73,9 @@ export function editVideoSchedule(params: object) {
export function userWatchInfo(params: object) { export function userWatchInfo(params: object) {
return Service.Hades("public/hades/userWatchInfo", params); return Service.Hades("public/hades/userWatchInfo", params);
} }
export function lineDetailWatchInfo(params: object) {
return Service.Hades("public/hades/lineDetailWatchInfo", params);
}
export function videoScheduleDetail(params: object) { export function videoScheduleDetail(params: object) {
return Service.Hades("public/hades/videoScheduleDetail", params); return Service.Hades("public/hades/videoScheduleDetail", params);
} }
......
...@@ -81,7 +81,7 @@ class KnowledgeAPI { ...@@ -81,7 +81,7 @@ class KnowledgeAPI {
exportPicLearnSync = (params: object) => { exportPicLearnSync = (params: object) => {
return Service.Hades("public/knowledge/exportPicLearnSync", params); return Service.Hades("public/knowledge/exportPicLearnSync", params);
} }
// 视频课观看记录导出 // 线上课观看记录导出
exportVideoLearnSync = (params: object) => { exportVideoLearnSync = (params: object) => {
return Service.Hades("public/knowledge/exportVideoLearnSync", params); return Service.Hades("public/knowledge/exportVideoLearnSync", params);
} }
......
/* /*
* @Author: zhangleyuan * @Author: zhangleyuan
* @Date: 2021-02-21 16:08:38 * @Date: 2021-02-21 16:08:38
* @LastEditors: zhangleyuan * @LastEditors: yuananting
* @LastEditTime: 2021-03-09 12:54:31 * @LastEditTime: 2021-07-06 11:26:27
* @Description: 描述一下 * @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import Service from "@/common/js/service"; import Service from '@/common/js/service';
export function getTrainingPlanPage(params: object) { export function getTrainingPlanPage(params: object) {
return Service.Hades("public/hades/getTrainingPlanPage", params); return Service.Hades('public/hades/getTrainingPlanPage', params);
} }
export function createTrainingPlan(params: object) { export function createTrainingPlan(params: object) {
return Service.Hades("public/hades/createTrainingPlan", params); return Service.Hades('public/hades/createTrainingPlan', params);
} }
export function updateStateTrainingPlan(params: object) { export function updateStateTrainingPlan(params: object) {
return Service.Hades("public/hades/updateStateTrainingPlan", params); return Service.Hades('public/hades/updateStateTrainingPlan', params);
} }
export function getTrainingPlanDetail(params: object) { export function getTrainingPlanDetail(params: object) {
return Service.Hades("public/hades/getTrainingPlanDetail", params); return Service.Hades('public/hades/getTrainingPlanDetail', params);
} }
export function updateTrainingPlan(params: object) { export function updateTrainingPlan(params: object) {
return Service.Hades("public/hades/updateTrainingPlan", params); return Service.Hades('public/hades/updateTrainingPlan', params);
} }
export function deleteTrainingPlan(params: object) { export function deleteTrainingPlan(params: object) {
return Service.Hades("public/hades/deleteTrainingPlan", params); return Service.Hades('public/hades/deleteTrainingPlan', params);
} }
export function getPlanUserRecordPage(params: object) { export function getPlanUserRecordPage(params: object) {
return Service.Hades("public/hades/getPlanUserRecordPage", params); return Service.Hades('public/hades/getPlanUserRecordPage', params);
} }
export function getPlanCustomerRecordPage(params: object) { export function getPlanCustomerRecordPage(params: object) {
return Service.Hades("public/hades/getPlanCustomerRecordPage", params); return Service.Hades('public/hades/getPlanCustomerRecordPage', params);
} }
export function getPlanCustomerDetail(params: object) { export function getPlanCustomerDetail(params: object) {
return Service.Hades("public/hades/getPlanCustomerDetail", params); return Service.Hades('public/customerHades/getPlanCustomerDetail', params);
} }
export function getPlanCustomerAboutUser(params: object) { export function getPlanCustomerAboutUser(params: object) {
return Service.Hades("public/hades/getPlanCustomerAboutUser", params); return Service.Hades('public/hades/getPlanCustomerAboutUser', params);
} }
export function removePlanCustomer(params: object) { export function removePlanCustomer(params: object) {
return Service.Hades("public/hades/removePlanCustomer", params); return Service.Hades('public/hades/removePlanCustomer', params);
} }
export function getStorePlanAll(params: object) { export function getStorePlanAll(params: object) {
return Service.Hades("public/hades/getStorePlanAll", params); return Service.Hades('public/hades/getStorePlanAll', params);
} }
export function getTrainingCourseAutoCancel(params: object) { export function getTrainingCourseAutoCancel(params: object) {
return Service.Hades("public/hades/getTrainingCourseAutoCancel", params); return Service.Hades('public/hades/getTrainingCourseAutoCancel', params);
} }
/* /*
* @Author: yuananting * @Author: yuananting
* @Date: 2021-03-11 11:34:37 * @Date: 2021-03-11 11:34:37
* @LastEditors: fusanqiasng * @LastEditors: yuananting
* @LastEditTime: 2021-06-16 09:56:46 * @LastEditTime: 2021-07-09 16:54:54
* @Description: 助学工具接口 * @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import { import {
queryExternalCategoryTree, queryExternalCategoryTree,
queryCategoryTreeByPackage,
queryCategoryTree, queryCategoryTree,
addCategory, addCategory,
delCategory, delCategory,
...@@ -38,6 +39,11 @@ export default class AidToolService { ...@@ -38,6 +39,11 @@ export default class AidToolService {
return queryExternalCategoryTree(parmas); return queryExternalCategoryTree(parmas);
} }
// 课程分类树(按课程包)
static queryCategoryTreeByPackage(params: any) {
return queryCategoryTreeByPackage(params);
}
// 获取题目分类树 // 获取题目分类树
static queryCategoryTree(params: any) { static queryCategoryTree(params: any) {
return queryCategoryTree(params); return queryCategoryTree(params);
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
* @Author: wufan * @Author: wufan
* @Date: 2020-12-01 17:20:49 * @Date: 2020-12-01 17:20:49
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-22 14:57:01 * @LastEditTime: 2021-07-09 15:33:59
* @Description: Description * @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import { getUserStore, getUserPermission ,logout,getStoreUser,sendBizAuthCode,editUserPhone,checkBizAuthCode,sendNewPhoneAuthCode,sendLoginAuthCode,login,getLastedVersion, getEnterpriseUser,getWXWorkLoginNoCheck,getLesseeVersionMsg,getYoZoSign,saveYoZoFileVersionId,yoZoUpload} from '@/data-source/base/request-apis'; import { getUserStore, getUserPermission ,logout,getStoreUser,sendBizAuthCode,editUserPhone,checkBizAuthCode,sendNewPhoneAuthCode,sendLoginAuthCode,login,getLastedVersion, getEnterpriseUser,getWXWorkLoginNoCheck,getLesseeVersionMsg,getYoZoSign,saveYoZoFileVersionId,yoZoUpload,saveFileVersionIdByCourseChapter} from '@/data-source/base/request-apis';
export default class StoreService { export default class StoreService {
// 获取员工列表 // 获取员工列表
...@@ -73,4 +73,7 @@ export default class StoreService { ...@@ -73,4 +73,7 @@ export default class StoreService {
static yoZoUpload(ossUrl:String,appId:String,uploadSign:String){ static yoZoUpload(ossUrl:String,appId:String,uploadSign:String){
return yoZoUpload(ossUrl,appId,uploadSign); return yoZoUpload(ossUrl,appId,uploadSign);
} }
static saveFileVersionIdByCourseChapter(params: any){
return saveFileVersionIdByCourseChapter(params);
}
} }
\ No newline at end of file
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
import { import {
fetchLecturerData, getCategoryTree, knowledgeMediaCoursePage, fetchUserData, exportStudentCourseData, exportPlayBackCourseData, fetchPlaybackList, createLiveCloudCourse, getLiveCloudCoursePage, fetchLecturerData, getCategoryTree, knowledgeMediaCoursePage, fetchUserData, exportStudentCourseData, exportPlayBackCourseData, fetchPlaybackList, createLiveCloudCourse, getLiveCloudCoursePage,
getLiveCloudCourseDetail, updateLiveCloudCourse, turnOnOrOffLiveCloudCourse, delLiveCloudCourse, changeVideoShelfState, createVideoSchedule, delVideoSchedule, getLiveCloudCourseDetail, updateLiveCloudCourse, turnOnOrOffLiveCloudCourse, delLiveCloudCourse, changeVideoShelfState, createVideoSchedule, delVideoSchedule,
editVideoSchedule, userWatchInfo, videoSchedulePage, videoScheduleDetail, videoWatchInfo, getQrcode, getLiveCloudCourseBasePage, videoScheduleBasePage, relatedCourseToPlan editVideoSchedule, userWatchInfo, videoSchedulePage, videoScheduleDetail, videoWatchInfo, getQrcode, getLiveCloudCourseBasePage, videoScheduleBasePage, relatedCourseToPlan,
lineDetailWatchInfo
} from '@/data-source/course/request-api'; } from '@/data-source/course/request-api';
export default class courseService { export default class courseService {
...@@ -85,6 +86,9 @@ export default class courseService { ...@@ -85,6 +86,9 @@ export default class courseService {
static videoWatchInfo(params: any) { static videoWatchInfo(params: any) {
return videoWatchInfo(params); return videoWatchInfo(params);
} }
static lineDetailWatchInfo(params: any) {
return lineDetailWatchInfo(params);
}
static getLiveCloudCourseBasePage(params: any) { static getLiveCloudCourseBasePage(params: any) {
return getLiveCloudCourseBasePage(params); return getLiveCloudCourseBasePage(params);
} }
......
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-20 09:21:40 * @Date: 2020-08-20 09:21:40
* @LastEditors: zhangleyuan * @LastEditors: yuananting
* @LastEditTime: 2021-03-04 17:16:30 * @LastEditTime: 2021-07-15 11:48:58
* @Description: * @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -77,6 +77,10 @@ const FILR_VERIFY_MAP = { ...@@ -77,6 +77,10 @@ const FILR_VERIFY_MAP = {
type: "word", type: "word",
maxSize: 100 maxSize: 100
}, },
"application/wps-writer": {
type: "word",
maxSize: 100,
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": { "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
type: "word", type: "word",
maxSize: 100 maxSize: 100
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
/> />
<meta <meta
name="keywords" name="keywords"
content="小麦企学院,企业培训,员工培训,企业大学,企业内训,企业外训,培训计划,培训素材,企培,企训,资料云盘,培训课程,培训任务,直播课,视频课,图文课,线下课,知识库,作业,考试,排行榜,培训类别管理,定制培训计划,管理数据,学习数据,企学院,资料共享,培训数字化,数字化培训,培训工具,在线培训,线上培训,培训saas,培训管理,企业微信培训,对客培训,客户培训,直播培训,互联网培训,新员工培训,管理培训,管理者培训,工人培训,制造业培训,餐饮培训,服务业培训,零售培训,门店培训,工厂培训,车间培训,培训补贴,人事培训,财务培训,职场培训,企业学院平台,教育企业学院,教育企业平台,教育平台学院,企业学习,酷学院,小鹅通,企业学院,云学堂,时代光华,云课堂,魔学院,云大学,米知云,授课学堂" content="小麦企学院,企业培训,员工培训,企业大学,企业内训,企业外训,培训计划,培训素材,企培,企训,资料云盘,培训课程,培训任务,直播课,线上课,图文课,线下课,知识库,作业,考试,排行榜,培训类别管理,定制培训计划,管理数据,学习数据,企学院,资料共享,培训数字化,数字化培训,培训工具,在线培训,线上培训,培训saas,培训管理,企业微信培训,对客培训,客户培训,直播培训,互联网培训,新员工培训,管理培训,管理者培训,工人培训,制造业培训,餐饮培训,服务业培训,零售培训,门店培训,工厂培训,车间培训,培训补贴,人事培训,财务培训,职场培训,企业学院平台,教育企业学院,教育企业平台,教育平台学院,企业学习,酷学院,小鹅通,企业学院,云学堂,时代光华,云课堂,魔学院,云大学,米知云,授课学堂"
/> />
<!-- <link rel="apple-touch-icon" href="../src/common/images/logo.png" /> --> <!-- <link rel="apple-touch-icon" href="../src/common/images/logo.png" /> -->
<link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/c4KiP2epBP.png" /> <link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/c4KiP2epBP.png" />
......
...@@ -111,9 +111,8 @@ class CollegeInfoPage extends React.Component { ...@@ -111,9 +111,8 @@ class CollegeInfoPage extends React.Component {
} = this.state; } = this.state;
return ( return (
<div className="page college-info-page"> <div className="page college-info-page">
<div className="content-header">学院基本信息</div> <div className="content-header">学院信息</div>
<div className="box"> <div className="box">
<div className="college-info-header">学院基本信息</div>
<div className="college-info-page-form"> <div className="college-info-page-form">
<Form ref={this.formRef}> <Form ref={this.formRef}>
......
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { DatePicker } from 'antd'; import { ConfigProvider, DatePicker } from 'antd';
import moment from 'moment'; import moment from 'moment';
import 'moment/locale/zh-cn';
import locale from 'antd/lib/locale/zh_CN';
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
class DateRangePicker extends React.Component { class DateRangePicker extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -15,6 +19,7 @@ class DateRangePicker extends React.Component { ...@@ -15,6 +19,7 @@ class DateRangePicker extends React.Component {
render() { render() {
const showTime = { showTime: false } const showTime = { showTime: false }
return ( return (
<ConfigProvider locale={locale}>
<RangePicker <RangePicker
format={this.props.format || 'YYYY-MM-DD'} format={this.props.format || 'YYYY-MM-DD'}
allowClear={this.props.allowClear} allowClear={this.props.allowClear}
...@@ -29,6 +34,8 @@ class DateRangePicker extends React.Component { ...@@ -29,6 +34,8 @@ class DateRangePicker extends React.Component {
suffixIcon={<span className="icon iconfont">&#xe838;</span>} suffixIcon={<span className="icon iconfont">&#xe838;</span>}
{...this.props} {...this.props}
/> />
</ConfigProvider>
) )
} }
......
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
*/ */
import React from 'react'; import React from 'react';
import { withRouter } from "react-router-dom"; import { withRouter } from 'react-router-dom';
import { Button, message, Modal } from 'antd'; import { Button, message, Modal } from 'antd';
import ShowTips from "@/components/ShowTips"; import ShowTips from '@/components/ShowTips';
import Breadcrumbs from "@/components/Breadcrumbs"; import Breadcrumbs from '@/components/Breadcrumbs';
import Bus from '../../core/bus' import Bus from '../../core/bus';
import AddLiveBasic from './components/AddLiveBasic'; import AddLiveBasic from './components/AddLiveBasic';
import AddLiveClass from './components/AddLiveClass'; import AddLiveClass from './components/AddLiveClass';
...@@ -20,10 +20,10 @@ import AddLiveIntro from './components/AddLiveIntro'; ...@@ -20,10 +20,10 @@ import AddLiveIntro from './components/AddLiveIntro';
import { randomString } from '@/domains/basic-domain/utils'; import { randomString } from '@/domains/basic-domain/utils';
import Upload from '@/core/upload'; import Upload from '@/core/upload';
import PreviewCourseModal from './modal/PreviewCourseModal'; import PreviewCourseModal from './modal/PreviewCourseModal';
import CourseService from "@/domains/course-domain/CourseService"; import CourseService from '@/domains/course-domain/CourseService';
import moment from 'moment'; import moment from 'moment';
import User from '@/common/js/user'; import User from '@/common/js/user';
import _ from "underscore"; import _ from 'underscore';
import $ from 'jquery'; import $ from 'jquery';
import './AddLive.less'; import './AddLive.less';
...@@ -31,14 +31,14 @@ const defaultCover = 'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'; ...@@ -31,14 +31,14 @@ const defaultCover = 'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png';
const defaultBasicInfo = { const defaultBasicInfo = {
courseName: null, // 课程名称 courseName: null, // 课程名称
coverUrl: defaultCover, coverUrl: defaultCover,
coverId:null, coverId: null,
categoryId:null, categoryId: null,
categoryName:null, categoryName: null,
}; };
const defaultClassInfo = { const defaultClassInfo = {
teacherId: null, //讲师的Id teacherId: null, //讲师的Id
assistant:[], //助教 assistant: [], //助教
teacherName: null, teacherName: null,
liveDate: null, liveDate: null,
timeHorizonStart: null, timeHorizonStart: null,
...@@ -50,61 +50,63 @@ const defaultClassInfo = { ...@@ -50,61 +50,63 @@ const defaultClassInfo = {
const defaultIntroInfo = { const defaultIntroInfo = {
needRecord: 'YES', needRecord: 'YES',
whetherVisitorsJoin:'NO', whetherVisitorsJoin: 'NO',
liveCourseWarmMedia: {}, liveCourseWarmMedia: {},
// 讲师简介 // 讲师简介
liveCourseMediaRequests: [{ liveCourseMediaRequests: [
contentType:"INTRO", {
contentType: 'INTRO',
mediaType: 'TEXT', mediaType: 'TEXT',
mediaContent: '', mediaContent: '',
key: Math.random() key: Math.random(),
}], },
} ],
};
class AddLive extends React.Component { class AddLive extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const id = getParameterByName("id"); const id = getParameterByName('id');
const type = getParameterByName("type"); const type = getParameterByName('type');
this.state = { this.state = {
id, id,
type, type,
loading: false, loading: false,
isEdit: true, isEdit: true,
courseState:'UN_START', courseState: 'UN_START',
// 直播课基本信息 // 直播课基本信息
addLiveBasicInfo: { addLiveBasicInfo: {
courseName: null, // 课程名称 courseName: null, // 课程名称
coverUrl: defaultCover, coverUrl: defaultCover,
coverId:null, coverId: null,
categoryId:null, categoryId: null,
categoryName:null, categoryName: null,
}, },
// 直播课上课信息 // 直播课上课信息
addLiveClassInfo: { addLiveClassInfo: {
teacherId: null, teacherId: null,
teacherName: null, teacherName: null,
assistant:[], assistant: [],
assistantStoreUserId:[], assistantStoreUserId: [],
assistantNames:[], assistantNames: [],
liveDate: null, liveDate: null,
timeHorizonStart: null, timeHorizonStart: null,
timeHorizonEnd: null, timeHorizonEnd: null,
calendarTime: [], // 批量排课 calendarTime: [], // 批量排课
startTime: new Date().getTime() + 300000, // 批量开始时分 startTime: new Date().getTime() + 300000, // 批量开始时分
endTime: new Date().getTime() + 300000 // 批量结束时分 endTime: new Date().getTime() + 300000, // 批量结束时分
}, },
// 直播课简介 // 直播课简介
addLiveIntroInfo: { addLiveIntroInfo: {
needRecord: 'YES', needRecord: 'YES',
whetherVisitorsJoin:'NO', whetherVisitorsJoin: 'NO',
liveCourseWarmMedia: {}, liveCourseWarmMedia: {},
introduce: '', introduce: '',
liveCourseMediaRequests: [], liveCourseMediaRequests: [],
}, },
} };
} }
componentDidMount() { componentDidMount() {
...@@ -112,13 +114,18 @@ class AddLive extends React.Component { ...@@ -112,13 +114,18 @@ class AddLive extends React.Component {
if (type === 'edit') { if (type === 'edit') {
this.getCourseDetail(); this.getCourseDetail();
} }
Bus.bind('editorLimit', (editorTextLength) => {
this.setState({
editorTextLength,
});
});
} }
getCourseDetail = () => { getCourseDetail = () => {
let { isEdit } = this.state; let { isEdit } = this.state;
this.setState({ loading: true }); this.setState({ loading: true });
CourseService.getLiveCloudCourseDetail({ CourseService.getLiveCloudCourseDetail({
liveCourseId: this.state.id liveCourseId: this.state.id,
}).then((res) => { }).then((res) => {
const { const {
teacherId, teacherId,
...@@ -134,7 +141,7 @@ class AddLive extends React.Component { ...@@ -134,7 +141,7 @@ class AddLive extends React.Component {
categoryId, categoryId,
categoryName, categoryName,
admins, admins,
courseState courseState,
} = res.result; } = res.result;
let coverId; let coverId;
let coverUrl; let coverUrl;
...@@ -142,15 +149,15 @@ class AddLive extends React.Component { ...@@ -142,15 +149,15 @@ class AddLive extends React.Component {
let liveCourseWarmMedia; let liveCourseWarmMedia;
let hasIntro = false; let hasIntro = false;
courseMediaVOS.map((item) => { courseMediaVOS.map((item) => {
switch (item.contentType){ switch (item.contentType) {
case "COVER": case 'COVER':
coverId = item.mediaContent; coverId = item.mediaContent;
coverUrl = item.mediaUrl; coverUrl = item.mediaUrl;
break; break;
case "WARMUP": case 'WARMUP':
liveCourseWarmMedia = item; liveCourseWarmMedia = item;
break; break;
case "INTRO": case 'INTRO':
hasIntro = true; hasIntro = true;
this.getTextDetail('introduce', item.mediaUrl); this.getTextDetail('introduce', item.mediaUrl);
break; break;
...@@ -159,22 +166,22 @@ class AddLive extends React.Component { ...@@ -159,22 +166,22 @@ class AddLive extends React.Component {
} }
return item; return item;
}) });
const addLiveBasicInfo = { const addLiveBasicInfo = {
courseName, courseName,
coverUrl: coverUrl || defaultCover, coverUrl: coverUrl || defaultCover,
coverId, coverId,
categoryId, categoryId,
categoryName categoryName,
}; };
const liveDate = startTime; const liveDate = startTime;
const timeHorizonStart = startTime; const timeHorizonStart = startTime;
const timeHorizonEnd = endTime; const timeHorizonEnd = endTime;
const assistant = _.pluck(admins, "adminId"); const assistant = _.pluck(admins, 'adminId');
const assistantStoreUserId = _.pluck(admins, "adminStoreUserId"); //编辑时的选中的助教的查询用storeUserId查询 const assistantStoreUserId = _.pluck(admins, 'adminStoreUserId'); //编辑时的选中的助教的查询用storeUserId查询
const assistantNames = _.pluck(admins, "adminName"); const assistantNames = _.pluck(admins, 'adminName');
const addLiveClassInfo = { const addLiveClassInfo = {
assistant, assistant,
liveDate, liveDate,
...@@ -186,20 +193,19 @@ class AddLive extends React.Component { ...@@ -186,20 +193,19 @@ class AddLive extends React.Component {
startTime, startTime,
endTime, endTime,
assistantNames, assistantNames,
assistantStoreUserId assistantStoreUserId,
} };
const addLiveIntroInfo = { const addLiveIntroInfo = {
liveCourseWarmMedia, liveCourseWarmMedia,
needRecord, needRecord,
whetherVisitorsJoin, whetherVisitorsJoin,
liveCourseMediaRequests, liveCourseMediaRequests,
} };
// 晚于开课前30分钟 // 晚于开课前30分钟
if(new Date().getTime() > startTime - 1800000) { if (new Date().getTime() > startTime - 1800000) {
isEdit = false isEdit = false;
} }
this.setState({ this.setState({
loadintroduce: !hasIntro, loadintroduce: !hasIntro,
...@@ -210,219 +216,210 @@ class AddLive extends React.Component { ...@@ -210,219 +216,210 @@ class AddLive extends React.Component {
addLiveClassInfo, addLiveClassInfo,
addLiveBasicInfo, addLiveBasicInfo,
}); });
}) });
} };
getTextDetail = (key, url) => { getTextDetail = (key, url) => {
$.ajax({ $.ajax({
data: {}, data: {},
type: 'GET', type: 'GET',
url, url,
contentType:'application/x-www-form-urlencoded; charset=UTF-8', contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
success: (res) => { success: (res) => {
this.setState({ addLiveIntroInfo: { ...this.state.addLiveIntroInfo, [key]: res }, [`load${key}`]: true }); this.setState({ addLiveIntroInfo: { ...this.state.addLiveIntroInfo, [key]: res }, [`load${key}`]: true });
}, },
error: () => { error: () => {
message.warning('获取简介失败') message.warning('获取简介失败');
} },
}) });
} };
// 修改基本信息 // 修改基本信息
// 修改基本信息 // 修改基本信息
handleChangeBasicInfo = (field, value) => { handleChangeBasicInfo = (field, value) => {
const { coverUrl } = this.state.addLiveBasicInfo; const { coverUrl } = this.state.addLiveBasicInfo;
this.setState({ this.setState({
addLiveBasicInfo: { addLiveBasicInfo: {
...this.state.addLiveBasicInfo, ...this.state.addLiveBasicInfo,
[field]: value, [field]: value,
} },
}) });
} };
// 修改上课信息 // 修改上课信息
handleChangeClassInfo = (field, value ,type, optionValue) => { handleChangeClassInfo = (field, value, type, optionValue) => {
const _value = value ? value.valueOf() : null; const _value = value ? value.valueOf() : null;
const { teacherName } = this.state.addLiveClassInfo; const { teacherName } = this.state.addLiveClassInfo;
const { assistantNames } = this.state.addLiveClassInfo; const { assistantNames } = this.state.addLiveClassInfo;
const { assistantStoreUserId } = this.state.addLiveClassInfo const { assistantStoreUserId } = this.state.addLiveClassInfo;
this.setState({ this.setState({
addLiveClassInfo: { addLiveClassInfo: {
...this.state.addLiveClassInfo, ...this.state.addLiveClassInfo,
[field]: _value, [field]: _value,
teacherName:type==='teacherType'?optionValue:teacherName, teacherName: type === 'teacherType' ? optionValue : teacherName,
assistantNames:type==='assistantType'?_.pluck(optionValue, "children"):assistantNames, assistantNames: type === 'assistantType' ? _.pluck(optionValue, 'children') : assistantNames,
assistantStoreUserId:type==='assistantType'?_.pluck(optionValue, "key"):assistantStoreUserId, assistantStoreUserId: type === 'assistantType' ? _.pluck(optionValue, 'key') : assistantStoreUserId,
} },
}); });
} };
// 修改简介 // 修改简介
handleChangeIntroInfo = (field, value) => { handleChangeIntroInfo = (field, value) => {
this.setState({ this.setState({
addLiveIntroInfo: { addLiveIntroInfo: {
...this.state.addLiveIntroInfo, ...this.state.addLiveIntroInfo,
[field]: value [field]: value,
} },
}) });
} };
// 完成创建/编辑 // 完成创建/编辑
handleSubmit = () => { handleSubmit = () => {
//过期判断 //过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) { if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({ Modal.warning({
title:"服务已到期", title: '服务已到期',
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买", content: '当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买',
okText: "我知道了" okText: '我知道了',
}) });
return return;
} }
const { addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, id, isEdit, type } = this.state; const { addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, id, isEdit, type, editorTextLength } = this.state;
const {liveDate, timeHorizonStart} = addLiveClassInfo; const { liveDate, timeHorizonStart } = addLiveClassInfo;
const _liveDate = moment(liveDate).format("YYYY-MM-DD"); const _liveDate = moment(liveDate).format('YYYY-MM-DD');
const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm'); const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm');
const startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x'); const startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x');
if(type === 'edit' && isEdit && new Date().getTime() > startTime - 1800000) { if (type === 'edit' && isEdit && new Date().getTime() > startTime - 1800000) {
Modal.info({ Modal.info({
title: "提示", title: '提示',
icon: ( icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
<span className="icon iconfont default-confirm-icon"> content: '晚于开课前30分钟,部分信息不可修改',
&#xe6f4;
</span>
),
content: "晚于开课前30分钟,部分信息不可修改",
okText: '我知道了', okText: '我知道了',
onOk: () => { onOk: () => {
this.getCourseDetail(); this.getCourseDetail();
} },
}); });
return return;
} }
this.handleValidate(addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, isEdit).then((res) => { this.handleValidate(addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, isEdit, editorTextLength).then((res) => {
if (!res) return; if (!res) return;
Upload.uploadTextToOSS(addLiveIntroInfo.introduce, `${randomString()}.txt`, (introduceId) => { Upload.uploadTextToOSS(
addLiveIntroInfo.introduce,
`${randomString()}.txt`,
(introduceId) => {
this.submitRemote({ introduceId, addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, id }); this.submitRemote({ introduceId, addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, id });
}, () => message.warning('上传课程简介失败')); },
}) () => message.warning('上传课程简介失败')
} );
});
};
submitRemote = ({ introduceId, addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, id }) => { submitRemote = ({ introduceId, addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, id }) => {
const { type } = this.state; const { type } = this.state;
const { courseName,coverUrl,coverId,categoryId} = addLiveBasicInfo; const { courseName, coverUrl, coverId, categoryId } = addLiveBasicInfo;
const { const { liveDate, teacherId, assistant, timeHorizonEnd, timeHorizonStart, calendarTime } = addLiveClassInfo;
liveDate,
teacherId,
assistant,
timeHorizonEnd,
timeHorizonStart,
calendarTime,
} = addLiveClassInfo;
let { startTime, endTime } = addLiveClassInfo; let { startTime, endTime } = addLiveClassInfo;
const { needRecord,whetherVisitorsJoin,liveCourseWarmMedia} = addLiveIntroInfo; const { needRecord, whetherVisitorsJoin, liveCourseWarmMedia } = addLiveIntroInfo;
if(type === 'add') { if (type === 'add') {
startTime = startTime; startTime = startTime;
endTime = endTime; endTime = endTime;
} else { } else {
const _liveDate = moment(liveDate).format("YYYY-MM-DD"); const _liveDate = moment(liveDate).format('YYYY-MM-DD');
const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm'); const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm');
const _timeHorizonEnd = moment(timeHorizonEnd).format('HH:mm'); const _timeHorizonEnd = moment(timeHorizonEnd).format('HH:mm');
startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x'); startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x');
endTime = moment(_liveDate + ' ' + _timeHorizonEnd).format('x'); endTime = moment(_liveDate + ' ' + _timeHorizonEnd).format('x');
} }
let coverObj ={ let coverObj = {
contentType:'COVER', contentType: 'COVER',
mediaContent:coverId, mediaContent: coverId,
mediaType:'PICTURE', mediaType: 'PICTURE',
mediaUrl: coverUrl, mediaUrl: coverUrl,
} };
let scheduleMediaRequests = []; let scheduleMediaRequests = [];
if(coverId){ if (coverId) {
scheduleMediaRequests = [coverObj,...scheduleMediaRequests] scheduleMediaRequests = [coverObj, ...scheduleMediaRequests];
} }
if(liveCourseWarmMedia && liveCourseWarmMedia.mediaUrl){ if (liveCourseWarmMedia && liveCourseWarmMedia.mediaUrl) {
scheduleMediaRequests = [liveCourseWarmMedia,...scheduleMediaRequests] scheduleMediaRequests = [liveCourseWarmMedia, ...scheduleMediaRequests];
} }
const commonParams = { const commonParams = {
adminIds:assistant, adminIds: assistant,
calendarTime, calendarTime,
categoryId, categoryId,
endTime, endTime,
needRecord, needRecord,
startTime, startTime,
courseName: courseName.trim(), courseName: courseName.trim(),
storeId:User.getStoreId(), storeId: User.getStoreId(),
teacherId:teacherId, teacherId: teacherId,
whetherVisitorsJoin, whetherVisitorsJoin,
scheduleMediaRequests scheduleMediaRequests,
} };
if (type === 'add') { if (type === 'add') {
const params = { const params = {
...commonParams, ...commonParams,
operatorId: User.getUserId(), operatorId: User.getUserId(),
introduceId, introduceId,
} };
CourseService.createLiveCloudCourse(params).then((res) => { CourseService.createLiveCloudCourse(params).then((res) => {
if (res.success){ if (res.success) {
message.success("新建成功"); message.success('新建成功');
window.RCHistory.push({ window.RCHistory.push({
pathname: `/live-course`, pathname: `/live-course`,
}); });
} }
}); });
} else { } else {
const params = { const params = {
...commonParams, ...commonParams,
updateUserId:User.getUserId(), updateUserId: User.getUserId(),
liveCourseId: id, liveCourseId: id,
introduceId, introduceId,
} };
CourseService.updateLiveCloudCourse(params).then((res) => { CourseService.updateLiveCloudCourse(params).then((res) => {
if (res.success){ if (res.success) {
message.success("更新成功"); message.success('更新成功');
window.RCHistory.push({ window.RCHistory.push({
pathname: `/live-course`, pathname: `/live-course`,
}); });
} }
}); });
} }
} };
handleValidate = (addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, isEdit) => { handleValidate = (addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, isEdit, editorTextLength) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const { type } = this.state; const { type } = this.state;
const { courseName,categoryId} = addLiveBasicInfo; const { courseName, categoryId } = addLiveBasicInfo;
const { const { liveDate, timeHorizonStart, timeHorizonEnd, teacherId, calendarTime } = addLiveClassInfo;
liveDate, timeHorizonStart, timeHorizonEnd, teacherId, calendarTime
} = addLiveClassInfo;
const { liveCourseMediaRequests } = addLiveIntroInfo; const { liveCourseMediaRequests } = addLiveIntroInfo;
const currentTime = +new Date(); const currentTime = +new Date();
if(!courseName) { if (!courseName) {
message.warning('请输入课程名称'); message.warning('请输入课程名称');
resolve(false); resolve(false);
return; return;
} }
if(!categoryId){ if (!categoryId) {
message.warning('请选择课程分类'); message.warning('请选择课程分类');
resolve(false); resolve(false);
return; return;
} }
if(type === 'add') { if (type === 'add') {
const { startTime, endTime } = addLiveClassInfo; const { startTime, endTime } = addLiveClassInfo;
if(calendarTime.length === 0) { if (calendarTime.length === 0) {
message.warning('请选择上课日期'); message.warning('请选择上课日期');
resolve(false); resolve(false);
return; return;
} else if(startTime === endTime || startTime > endTime) { } else if (startTime === endTime || startTime > endTime) {
message.warning('结束时间必须晚于开始时间'); message.warning('结束时间必须晚于开始时间');
resolve(false); resolve(false);
return; return;
...@@ -432,12 +429,12 @@ handleChangeBasicInfo = (field, value) => { ...@@ -432,12 +429,12 @@ handleChangeBasicInfo = (field, value) => {
const itemToday = _.find(calendarTime, (item) => { const itemToday = _.find(calendarTime, (item) => {
const itemDay = moment(item).format('YYYY-MM-DD'); const itemDay = moment(item).format('YYYY-MM-DD');
return itemDay === currentDay; return itemDay === currentDay;
}) });
if(itemToday) { if (itemToday) {
const itemDay = moment(itemToday).format('YYYY-MM-DD'); const itemDay = moment(itemToday).format('YYYY-MM-DD');
const itemHour = moment(startTime).format('HH:mm'); const itemHour = moment(startTime).format('HH:mm');
if(itemDay === currentDay) { if (itemDay === currentDay) {
if(moment(itemDay + ' ' + itemHour).format('x') < currentTime) { if (moment(itemDay + ' ' + itemHour).format('x') < currentTime) {
message.warning('开始时间不能早于现在'); message.warning('开始时间不能早于现在');
resolve(false); resolve(false);
return; return;
...@@ -445,13 +442,13 @@ handleChangeBasicInfo = (field, value) => { ...@@ -445,13 +442,13 @@ handleChangeBasicInfo = (field, value) => {
} }
} }
} else { } else {
const _liveDate = moment(liveDate).format("YYYY-MM-DD"); const _liveDate = moment(liveDate).format('YYYY-MM-DD');
const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm'); const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm');
const _timeHorizonEnd = moment(timeHorizonEnd).format('HH:mm'); const _timeHorizonEnd = moment(timeHorizonEnd).format('HH:mm');
const startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x'); const startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x');
const endTime = moment(_liveDate + ' ' + _timeHorizonEnd).format('x'); const endTime = moment(_liveDate + ' ' + _timeHorizonEnd).format('x');
if(!startTime || !endTime) { if (!startTime || !endTime) {
message.warning('日期不能为空'); message.warning('日期不能为空');
resolve(false); resolve(false);
return; return;
...@@ -472,23 +469,29 @@ handleChangeBasicInfo = (field, value) => { ...@@ -472,23 +469,29 @@ handleChangeBasicInfo = (field, value) => {
resolve(false); resolve(false);
return; return;
} else if (isEdit && endTime <= startTime) { } else if (isEdit && endTime <= startTime) {
message.warning("结束时间不能早于开始时间"); message.warning('结束时间不能早于开始时间');
resolve(false); resolve(false);
return; return;
} }
} }
if(!teacherId){ if (!teacherId) {
message.warning('请选择讲师'); message.warning('请选择讲师');
resolve(false); resolve(false);
return; return;
} }
resolve(true)
}); if (editorTextLength > 1000) {
message.warning('课程简介超过字数限定');
resolve(false);
return;
} }
resolve(true);
});
};
// 显示预览课程弹窗 // 显示预览课程弹窗
handleShowPreviewModal = () => { handleShowPreviewModal = () => {
const { addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo,type,courseState} = this.state; const { addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, type, courseState } = this.state;
const previewLiveCourseModal = ( const previewLiveCourseModal = (
<PreviewCourseModal <PreviewCourseModal
courseBasicInfo={addLiveBasicInfo} courseBasicInfo={addLiveBasicInfo}
...@@ -498,104 +501,77 @@ handleChangeBasicInfo = (field, value) => { ...@@ -498,104 +501,77 @@ handleChangeBasicInfo = (field, value) => {
courseState={courseState} courseState={courseState}
close={() => { close={() => {
this.setState({ this.setState({
previewLiveCourseModal: null previewLiveCourseModal: null,
}) });
}} }}
/> />
); );
this.setState({ previewLiveCourseModal }); this.setState({ previewLiveCourseModal });
} };
// 取消编辑并返回上一级路由 // 取消编辑并返回上一级路由
handleGoBack = () => { handleGoBack = () => {
// 比较state的addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo和默认数据是否相等 // 比较state的addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo和默认数据是否相等
const { addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo } = this.state; const { addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo } = this.state;
if (!_.isEqual(addLiveBasicInfo, defaultBasicInfo) || if (!_.isEqual(addLiveBasicInfo, defaultBasicInfo) || !_.isEqual(addLiveClassInfo, defaultClassInfo) || !_.isEqual(addLiveIntroInfo, defaultIntroInfo)) {
!_.isEqual(addLiveClassInfo, defaultClassInfo) ||
!_.isEqual(addLiveIntroInfo, defaultIntroInfo)
) {
Modal.confirm({ Modal.confirm({
title: '确定要返回吗?', title: '确定要返回吗?',
content: '返回后,本次编辑的内容将不被保存', content: '返回后,本次编辑的内容将不被保存',
okText: '确认返回', okText: '确认返回',
cancelText: '留在本页', cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>, icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => { onOk: () => {
window.RCHistory.push({ window.RCHistory.push({
pathname: `/live-course`, pathname: `/live-course`,
}); });
} },
}) });
} else { } else {
window.RCHistory.push({ window.RCHistory.push({
pathname: `/live-course`, pathname: `/live-course`,
}); });
} }
} };
render() { render() {
const { id, type, addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, isEdit, loadintroduce } = this.state;
const {
id,
type,
addLiveBasicInfo,
addLiveClassInfo,
addLiveIntroInfo,
isEdit,
loadintroduce,
} = this.state;
return ( return (
<div className="page add-live-page"> <div className='page add-live-page'>
<Breadcrumbs <Breadcrumbs navList={type == 'add' ? '新建直播课' : '编辑直播课'} goBack={this.handleGoBack} />
navList={type == "add" ? "新建直播课" : "编辑直播课"} <div className='box'>
goBack={this.handleGoBack} <div className='show-tips'>
/> <ShowTips message='请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企学院保有依据国家规定及平台规则进行处理的权利' />
<div className="box">
<div className="show-tips">
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企学院保有依据国家规定及平台规则进行处理的权利" />
</div> </div>
<div className="add-live-page__form"> <div className='add-live-page__form'>
<div className="basic-info__wrap"> <div className='basic-info__wrap'>
<div className="title">基本信息</div> <div className='title'>基本信息</div>
<AddLiveBasic <AddLiveBasic isEdit={isEdit} pageType={type} data={addLiveBasicInfo} onChange={this.handleChangeBasicInfo} />
isEdit={isEdit}
pageType={type}
data={addLiveBasicInfo}
onChange={this.handleChangeBasicInfo}
/>
</div> </div>
<div className="class-info__wrap"> <div className='class-info__wrap'>
<div className="title">上课信息</div> <div className='title'>上课信息</div>
<AddLiveClass <AddLiveClass isEdit={isEdit} pageType={type} data={{ ...addLiveClassInfo, id }} onChange={this.handleChangeClassInfo} />
isEdit={isEdit}
pageType={type}
data={{...addLiveClassInfo, id} }
onChange={this.handleChangeClassInfo}
/>
</div> </div>
<div className="intro-info__wrap"> <div className='intro-info__wrap'>
<div className="title">更多信息</div> <div className='title'>更多信息</div>
<AddLiveIntro <AddLiveIntro isEdit={isEdit} data={{ ...addLiveIntroInfo, loadintroduce, id }} onChange={this.handleChangeIntroInfo} />
isEdit={isEdit}
data={{ ...addLiveIntroInfo, loadintroduce, id }}
onChange={this.handleChangeIntroInfo}
/>
</div> </div>
</div> </div>
</div> </div>
<div className="footer shrink-footer"> <div className='footer shrink-footer'>
<Button onClick={this.handleGoBack}>取消</Button> <Button onClick={this.handleGoBack}>取消</Button>
<Button onClick={this.handleShowPreviewModal}>预览</Button> <Button onClick={this.handleShowPreviewModal}>预览</Button>
<Button type="primary" onClick={_.debounce(() => this.handleSubmit(), 3000, true)}>保存</Button> <Button type='primary' onClick={_.debounce(() => this.handleSubmit(), 3000, true)}>
保存
</Button>
</div> </div>
{ this.state.previewLiveCourseModal } {this.state.previewLiveCourseModal}
{ this.state.lackConsumeStudentModal } {this.state.lackConsumeStudentModal}
</div> </div>
) );
} }
} }
......
...@@ -7,10 +7,10 @@ ...@@ -7,10 +7,10 @@
*/ */
import React from 'react'; import React from 'react';
import { Input, Button, message ,Cascader,Modal} from 'antd'; import { Input, Button, message, Cascader, Modal } from 'antd';
import UploadOss from "@/core/upload"; import UploadOss from '@/core/upload';
import { ImgCutModalNew } from '@/components'; import { ImgCutModalNew } from '@/components';
import StoreService from "@/domains/store-domain/storeService"; import StoreService from '@/domains/store-domain/storeService';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal'; import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import Upload from '@/core/upload'; import Upload from '@/core/upload';
import Cropper from 'react-cropper'; import Cropper from 'react-cropper';
...@@ -21,63 +21,62 @@ import './AddLiveBasic.less'; ...@@ -21,63 +21,62 @@ import './AddLiveBasic.less';
const defaultCover = 'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'; const defaultCover = 'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png';
const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' }; const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' };
let cutFlag = false; let cutFlag = false;
let timer = null let timer = null;
class AddLiveBasic extends React.Component { class AddLiveBasic extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
imageFile: null, imageFile: null,
showCutModal: false, showCutModal: false,
courseCatalogList:[], courseCatalogList: [],
showSelectFileModal: false, showSelectFileModal: false,
cutImageBlob: null, cutImageBlob: null,
hasImgReady: false, // 图片是否上传成功 hasImgReady: false, // 图片是否上传成功
cropperInstace:null cropperInstace:null
} }
} }
componentWillUnmount() { componentWillUnmount() {}
}
componentDidMount(){ componentDidMount() {
this.getCourseCatalogList(); this.getCourseCatalogList();
} }
getCourseCatalogList = ()=>{ getCourseCatalogList = () => {
StoreService.getCourseCatalogList({current:1,size:1000}).then((res) => { StoreService.getCourseCatalogList({ current: 1, size: 1000 }).then((res) => {
this.setState({ this.setState({
courseCatalogList:res.result.records courseCatalogList: res.result.records,
}) });
}); });
} }
// 使用默认封面图 // 使用默认封面图
handleResetCoverUrl = () => { handleResetCoverUrl = () => {
const { data: { coverUrl } } = this.props; const {
data: { coverUrl },
} = this.props;
const isDefaultCover = coverUrl === defaultCover; const isDefaultCover = coverUrl === defaultCover;
// 如果已经是默认图的话,不做任何任何处理 // 如果已经是默认图的话,不做任何任何处理
if (isDefaultCover) return; if (isDefaultCover) return;
message.success('已替换为默认图'); message.success('已替换为默认图');
this.props.onChange('coverUrl',defaultCover); this.props.onChange('coverUrl', defaultCover);
setTimeout(()=>{ setTimeout(() => {
this.props.onChange('coverId', null); this.props.onChange('coverId', null);
},1000) }, 1000);
} };
catalogChange= (value) => { catalogChange = (value) => {
const changeValueLength = value.length; const changeValueLength = value.length;
switch (changeValueLength){ switch (changeValueLength) {
case 1: case 1:
this.props.onChange('categoryId',value[0]); this.props.onChange('categoryId', value[0]);
break; break;
case 2: case 2:
this.props.onChange('categoryId',value[1]); this.props.onChange('categoryId', value[1]);
break; break;
default: default:
this.props.onChange('categoryId',null); this.props.onChange('categoryId', null);
break; break;
} }
} };
handleSelectCover = (file) => { handleSelectCover = (file) => {
this.setState({ this.setState({
visible: true, visible: true,
...@@ -90,91 +89,122 @@ class AddLiveBasic extends React.Component { ...@@ -90,91 +89,122 @@ class AddLiveBasic extends React.Component {
//获取resourceId //获取resourceId
getSignature = (blob, fileName) => { getSignature = (blob, fileName) => {
const { choosedBannerId } = this.state; const { choosedBannerId } = this.state;
Upload.uploadBlobToOSS(blob, 'cover' + (new Date()).valueOf(),null,'signInfo').then((signInfo) => { Upload.uploadBlobToOSS(blob, 'cover' + new Date().valueOf(), null, 'signInfo').then((signInfo) => {
this.setState({ this.setState(
coverClicpPath:signInfo.fileUrl, {
coverId:signInfo.resourceId, coverClicpPath: signInfo.fileUrl,
visible: false coverId: signInfo.resourceId,
},()=>this.updateCover()) visible: false,
},
() => this.updateCover()
);
}); });
}; };
updateCover = () =>{ updateCover = () =>{
const {coverClicpPath,coverId} = this.state const {coverClicpPath,coverId} = this.state
this.setState({ this.setState({
showSelectFileModal: false showSelectFileModal: false,
}) });
this.props.onChange('coverUrl', coverClicpPath); this.props.onChange('coverUrl', coverClicpPath);
setTimeout(()=>{ setTimeout(() => {
this.props.onChange('coverId', coverId); this.props.onChange('coverId', coverId);
},1000) }, 1000);
};
}
render() { render() {
const { showCutModal, imageFile,courseCatalogList,showSelectFileModal,visible,cutImageBlob,hasImgReady const { showCutModal, imageFile, courseCatalogList, showSelectFileModal, visible, cutImageBlob, hasImgReady } = this.state;
} = this.state; const { data, pageType, isEdit } = this.props;
const { data,pageType,isEdit} = this.props; const { courseName, categoryName, coverUrl } = data;
const { courseName,categoryName,coverUrl} = data;
const fileName = ''; const fileName = '';
// 当前是否使用的是默认图片 // 当前是否使用的是默认图片
const isDefaultCover = coverUrl === defaultCover; const isDefaultCover = coverUrl === defaultCover;
return ( return (
<div className="add-live__basic-info"> <div className='add-live__basic-info'>
<div className="course-name"> <div className='course-name'>
<span className="label"><span className="require">*</span>课程名称:</span> <span className='label'>
<span className='require'>*</span>课程名称:
</span>
<Input <Input
value={courseName} value={courseName}
placeholder="请输入直播名称(40字以内)" placeholder='请输入直播名称(40字以内)'
maxLength={40} maxLength={40}
style={{ width: 240 }} style={{ width: 240 }}
onChange={(e) => { this.props.onChange('courseName', e.target.value)}} onChange={(e) => {
this.props.onChange('courseName', e.target.value);
}}
/> />
</div> </div>
<div className="course-cover"> <div className='course-cover'>
<span className="label">封面图:</span> <span className='label'>封面图:</span>
<div className="course-cover__wrap"> <div className='course-cover__wrap'>
<div className='opt-btns'>
<div className="img-content"> <Button
{ onClick={() => {
isDefaultCover && <span className="tag">默认图</span>
}
<img src={coverUrl} />
</div>
<div className="opt-btns">
<Button onClick={() => {
this.setState({ this.setState({
showSelectFileModal: true showSelectFileModal: true,
}) });
}}>上传图片</Button> }}>
<span 上传图片
className={`default-btn ${isDefaultCover ? 'disabled' : ''}`} </Button>
onClick={this.handleResetCoverUrl} <span className={`default-btn ${isDefaultCover ? 'disabled' : ''}`} onClick={this.handleResetCoverUrl}>
>使用默认图</span> 使用默认图
<div className="tips">建议尺寸1280*720px,图片支持jpg、jpeg、png格式。</div> </span>
<div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div>
<div className='img-content'>
{isDefaultCover && <span className='tag'>默认图</span>}
<img src={coverUrl} />
</div> </div>
</div> </div>
</div> </div>
<div className="course-catalog"> <div className='course-catalog'>
<span className="label"><span className="require">*</span>课程分类:</span> <span className='label'>
{ pageType === 'add' && <span className='require'>*</span>课程分类:
<Cascader options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}/> </span>
{pageType === 'add' && (
<Cascader
options={courseCatalogList}
displayRender={(label) => label.join('-')}
fieldNames={fieldNames}
onChange={this.catalogChange}
style={{ width: 240 }}
placeholder='请选择课程分类'
suffixIcon={
<span className='icon iconfont' style={{ fontSize: '12px', color: '#BFBFBF' }}>
&#xe835;
</span>
} }
{ (pageType === 'edit' && categoryName) && />
<Cascader disabled={!isEdit ? true: false} defaultValue={[categoryName]} options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>} /> )}
{pageType === 'edit' && categoryName && (
<Cascader
disabled={!isEdit ? true : false}
defaultValue={[categoryName]}
options={courseCatalogList}
displayRender={(label) => label.join('-')}
fieldNames={fieldNames}
onChange={this.catalogChange}
style={{ width: 240 }}
placeholder='请选择课程分类'
suffixIcon={
<span className='icon iconfont' style={{ fontSize: '12px', color: '#BFBFBF' }}>
&#xe835;
</span>
} }
/>
)}
</div> </div>
{showSelectFileModal && {showSelectFileModal &&
<SelectPrepareFileModal <SelectPrepareFileModal
key="basic" key='basic'
operateType="select" operateType='select'
multiple={false} multiple={false}
accept="image/jpeg,image/png,image/jpg" accept='image/jpeg,image/png,image/jpg'
selectTypeList={['JPG', 'JPEG', 'PNG']} selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png' tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectFileModal} isOpen={showSelectFileModal}
onClose={() => { onClose={() => {
this.setState({ showSelectFileModal: false }) this.setState({ showSelectFileModal: false });
}} }}
onSelect={this.handleSelectCover} onSelect={this.handleSelectCover}
/> />
...@@ -183,7 +213,7 @@ class AddLiveBasic extends React.Component { ...@@ -183,7 +213,7 @@ class AddLiveBasic extends React.Component {
<ImgClipModal visible={visible} imgUrl={imageFile.ossUrl} onConfirm={this.getSignature} onClose={()=>{this.setState({ visible: false });}}/> <ImgClipModal visible={visible} imgUrl={imageFile.ossUrl} onConfirm={this.getSignature} onClose={()=>{this.setState({ visible: false });}}/>
} }
</div> </div>
) );
} }
} }
......
...@@ -2,41 +2,25 @@ ...@@ -2,41 +2,25 @@
.label { .label {
width: 100px; width: 100px;
text-align: right; text-align: right;
display:inline-block; display: inline-block;
.require { .require {
color: #EC4B35; color: #ec4b35;
} }
} }
.course-cover { .course-cover {
margin-left: 14px; margin-left: 14px;
display: flex; display: flex;
margin-top: 16px; margin-top: 24px;
&__wrap { &__wrap {
position: relative; position: relative;
.tag {
border-radius: 2px;
background: #D6D6D6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content { .img-content {
margin-top: 8px;
margin-right: 20px; margin-right: 20px;
width: 299px; width: 299px;
height: 169px; height: 169px;
position: relative;
img { img {
width: 100%; width: 100%;
...@@ -44,17 +28,28 @@ ...@@ -44,17 +28,28 @@
object-fit: contain; object-fit: contain;
border-radius: 4px; border-radius: 4px;
} }
.tag {
border-radius: 2px;
background: #d6d6d6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #fff;
position: absolute;
top: 8px;
left: 8px;
}
} }
.opt-btns { .opt-btns {
.default-btn { .default-btn {
margin-left: 16px; margin-left: 14px;
color: #2966FF; color: #2966ff;
cursor: pointer; cursor: pointer;
&.disabled { &.disabled {
color: #CCC; color: #ccc;
cursor: not-allowed; cursor: not-allowed;
} }
} }
...@@ -62,21 +57,21 @@ ...@@ -62,21 +57,21 @@
.ant-upload-list { .ant-upload-list {
display: none; display: none;
} }
}
.tips { .tips {
margin-top: 8px; margin-top: 8px;
color: #999; color: #999;
} }
} }
.course-catalog{
margin:20px 0 0 14px;
} }
}
.course-catalog {
margin: 20px 0 0 14px;
}
} }
.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled){ .ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled) {
font-weight:normal !important; font-weight: normal !important;
color:#2966FF !important; color: #2966ff !important;
} }
#imgCutModalNew { #imgCutModalNew {
width: 500px; width: 500px;
......
...@@ -17,19 +17,17 @@ import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepare ...@@ -17,19 +17,17 @@ import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepare
import { DISK_MAP } from '@/common/constants/academic/lessonEnum'; import { DISK_MAP } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components'; import { ImgCutModalNew } from '@/components';
const { TextArea } = Input; const { TextArea } = Input;
const defaultCover = 'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599635741526.png'; const defaultCover = 'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599635741526.png';
class AddLiveIntro extends React.Component { class AddLiveIntro extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
warmUrl: defaultCover, warmUrl: defaultCover,
showSelectFileModal: false, showSelectFileModal: false,
diskList: [], diskList: [],
selectType:null selectType: null,
} };
} }
// 上传封面图 // 上传封面图
...@@ -40,34 +38,34 @@ class AddLiveIntro extends React.Component { ...@@ -40,34 +38,34 @@ class AddLiveIntro extends React.Component {
imageFile, imageFile,
showCutModal: true, showCutModal: true,
}); });
} };
// 选择暖场资源 // 选择暖场资源
handleSelectVideo = (file) => { handleSelectVideo = (file) => {
const { selectType } = this.state; const { selectType } = this.state;
this.setState({ this.setState({
showSelectFileModal: false showSelectFileModal: false,
}) });
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file; const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
if(selectType === 'WARMUP'){ if (selectType === 'WARMUP') {
const liveCourseWarmMedia = { const liveCourseWarmMedia = {
contentType: 'WARMUP', contentType: 'WARMUP',
mediaType: folderFormat === 'MP4' || folderFormat === 'video/mp4' ? 'VIDEO' : 'PICTURE', mediaType: folderFormat === 'MP4' || folderFormat === 'video/mp4' ? 'VIDEO' : 'PICTURE',
mediaContent: resourceId, mediaContent: resourceId,
mediaUrl: ossUrl, mediaUrl: ossUrl,
mediaName: folderName, mediaName: folderName,
size: folderSize size: folderSize,
} };
this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia); this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia);
}else{ } else {
// 最多添加九图片 // 最多添加九图片
const { liveCourseMediaRequests } = this.props.data; const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => { const list = _.filter(liveCourseMediaRequests, (item) => {
return item.mediaType == "PICTURE"; return item.mediaType == 'PICTURE';
}); });
if (list.length > 8) { if (list.length > 8) {
message.warning("最多添加9张图片"); message.warning('最多添加9张图片');
return; return;
} }
liveCourseMediaRequests.push({ liveCourseMediaRequests.push({
...@@ -80,146 +78,161 @@ class AddLiveIntro extends React.Component { ...@@ -80,146 +78,161 @@ class AddLiveIntro extends React.Component {
}); });
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests); this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
} }
};
}
changeIntro = (value) => { changeIntro = (value) => {
this.props.onChange('introduce', value); this.props.onChange('introduce', value);
} };
whetherVisitorsJoinChange = ()=>{ whetherVisitorsJoinChange = () => {
if(this.props.data.whetherVisitorsJoin==="NO"){ if (this.props.data.whetherVisitorsJoin === 'NO') {
this.props.onChange('whetherVisitorsJoin','YES') this.props.onChange('whetherVisitorsJoin', 'YES');
}else{ } else {
this.props.onChange('whetherVisitorsJoin','NO') this.props.onChange('whetherVisitorsJoin', 'NO');
}
} }
};
render() { render() {
const {liveType, isXiaomai, isEdit, data: { id, introduce, needRecord,whetherVisitorsJoin, loadintroduce, liveCourseWarmMedia = {} } } = this.props; const {
const { showCutModal, warmUrl, showSelectFileModal, diskList, imageFile,selectType} = this.state liveType,
isXiaomai,
isEdit,
data: { id, introduce, needRecord, whetherVisitorsJoin, loadintroduce, liveCourseWarmMedia = {} },
} = this.props;
const { showCutModal, warmUrl, showSelectFileModal, diskList, imageFile, selectType } = this.state;
return ( return (
<div className="add-live__intro-info"> <div className='add-live__intro-info'>
<div className="playback" > <div className='playback'>
<span className='label'>
<span className="label"><span className="require">*</span>直播回放:</span> <span className='require'>*</span>直播回放:
<div className="content"> </span>
<Radio.Group value={needRecord} onChange={(e) => { this.props.onChange('needRecord', e.target.value) }} disabled={!isEdit ? true: false}> <div className='content'>
<Radio.Group
value={needRecord}
onChange={(e) => {
this.props.onChange('needRecord', e.target.value);
}}
disabled={!isEdit ? true : false}>
<Row style={{ marginBottom: '5px' }}> <Row style={{ marginBottom: '5px' }}>
<Col span={24}> <Col span={24}>
<Radio value="YES"> <Radio value='YES'>
自动录制 自动录制
<span className="playback__text">系统自助进行全程直播录制</span> <span className='playback__text'>系统自助进行全程直播录制</span>
</Radio> </Radio>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col span={24}> <Col span={24}>
<Radio value="NO"> <Radio value='NO'>
手动录制 手动录制
<span className="playback__text">讲师手动选择何时开始录制</span> <span className='playback__text'>讲师手动选择何时开始录制</span>
</Radio> </Radio>
</Col> </Col>
</Row> </Row>
</Radio.Group> </Radio.Group>
</div> </div>
</div> </div>
<div className="allow-tourist-join"> <div className='allow-tourist-join'>
<span className="label">观看设置:</span> <span className='label'>观看设置:</span>
<div className="content"> <div className='content'>
<div> <Switch checked={whetherVisitorsJoin === 'NO' ? true : false} onChange={this.whetherVisitorsJoinChange} />
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/> <div class='instro-text'>{whetherVisitorsJoin === 'NO' ? '已开启,学员需绑定手机号才可观看' : '已关闭,学员无需绑定手机号即可观看'}</div>
</div> </div>
<div> </div>
<div class="instro-text"> <div className='warmup'>
<div>开启:允许未绑定手机号的学员进入直播间观看直播</div> <span className='label'>直播暖场图:</span>
<div>关闭:仅限绑定了手机号的学员可以进入直播间观看直播</div> <div className='course-cover__wrap'>
</div> <div className='img-content' style={liveCourseWarmMedia.mediaUrl ? { background: '#000' } : {}}>
</div> <img
</div> src={
</div> liveCourseWarmMedia.mediaType === 'VIDEO'
<div className="warmup"> ? `${liveCourseWarmMedia.mediaUrl}?x-oss-process=video/snapshot,t_0,m_fast`
<span className="label">直播暖场图:</span> : liveCourseWarmMedia.mediaUrl
<div className="course-cover__wrap"> ? liveCourseWarmMedia.mediaUrl
<div className="img-content" style={ liveCourseWarmMedia.mediaUrl ? {background: '#000'} : {} }> : defaultCover
<img src={liveCourseWarmMedia.mediaType === 'VIDEO' ? `${liveCourseWarmMedia.mediaUrl}?x-oss-process=video/snapshot,t_0,m_fast` : (liveCourseWarmMedia.mediaUrl ? liveCourseWarmMedia.mediaUrl : defaultCover )} /> }
{ />
liveCourseWarmMedia.mediaUrl && {liveCourseWarmMedia.mediaUrl && (
<div className="img-delete-wrap"> <div className='img-delete-wrap'>
<img src="https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1600073872956.png" onClick={() => { <img
src='https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1600073872956.png'
onClick={() => {
this.props.onChange('liveCourseWarmMedia', {}); this.props.onChange('liveCourseWarmMedia', {});
}}/> }}
/>
</div> </div>
} )}
</div> </div>
<div className="opt-btns"> <div className='opt-btns'>
<Button <Button
disabled={!isEdit} disabled={!isEdit}
onClick={() => { onClick={() => {
this.setState({ this.setState({
showSelectFileModal: true, showSelectFileModal: true,
selectType:'WARMUP' selectType: 'WARMUP',
}) });
}}>上传图片/视频</Button> }}>
上传图片/视频
</Button>
<div className="tips"> <div className='tips'>
<div>建议尺寸1280*720px或16:9。图片最大5M,支持jpg、jpeg和png;视频最大2G,</div> <div>建议尺寸1280*720px或16:9。图片最大5M,支持jpg、jpeg和png;视频最大2G,</div>
<div>支持mp4。</div> <div>支持mp4。</div>
</div> </div>
<Popover content={ <Popover
<div className="example-wrap"> content={
<p className="title">直播间暖场图示例</p> <div className='example-wrap'>
<p className="text">直播开始前,展示在直播间视频区域</p> <p className='title'>直播间暖场图示例</p>
<p className='text'>直播开始前,展示在直播间视频区域</p>
<img src='https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599644482652.png'></img> <img src='https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599644482652.png'></img>
</div> </div>
}> }>
<div className="checkExample">查看示例</div> <div className='checkExample'>查看示例</div>
</Popover> </Popover>
</div> </div>
</div> </div>
</div> </div>
<div className='introduce'>
<div className="introduce"> <span className='label'>课程简介:</span>
<span className="label">直播课简介:</span> <div className='content'>
<div className="content"> <div className='intro-list'>
<div className="intro-list"> <div className='intro-list__item introduce-editor'>
<div className="intro-list__item introduce-editor"> {(!id || loadintroduce) && (
{(!id || loadintroduce) &&
<GraphicsEditor <GraphicsEditor
id="intro" id='intro'
isIntro={true} isIntro={true}
maxLimit={1000}
detail={{ detail={{
content: introduce content: introduce,
}}
onChange={(val) => {
this.changeIntro(val);
}} }}
onChange={(val) => { this.changeIntro(val) }}
/> />
} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* 选择暖场图文件弹窗 */} {/* 选择暖场图文件弹窗 */}
{ showSelectFileModal && {showSelectFileModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
key="instro" key='instro'
operateType="select" operateType='select'
accept={selectType==="INTRO"?"image/jpeg,image/png,image/jpg":"video/mp4,image/jpeg,image/png,image/jpg"} accept={selectType === 'INTRO' ? 'image/jpeg,image/png,image/jpg' : 'video/mp4,image/jpeg,image/png,image/jpg'}
selectTypeList={ selectType==="INTRO" ? ['JPG', 'JPEG', 'PNG']: ['MP4', 'JPG', 'JPEG', 'PNG'] } selectTypeList={selectType === 'INTRO' ? ['JPG', 'JPEG', 'PNG'] : ['MP4', 'JPG', 'JPEG', 'PNG']}
tooltip={ selectType==="INTRO"?'支持文件类型:jpg、jpeg、png':'支持文件类型:jpg、jpeg、png、mp4'} tooltip={selectType === 'INTRO' ? '支持文件类型:jpg、jpeg、png' : '支持文件类型:jpg、jpeg、png、mp4'}
isOpen={showSelectFileModal} isOpen={showSelectFileModal}
onClose={() => { onClose={() => {
this.setState({ showSelectFileModal: false }) this.setState({ showSelectFileModal: false });
}} }}
onSelect={this.handleSelectVideo} onSelect={this.handleSelectVideo}
/> />
} )}
</div> </div>
) );
} }
} }
......
/*
* @Author: yuananting
* @Date: 2021-07-05 10:47:19
* @LastEditors: yuananting
* @LastEditTime: 2021-07-12 17:13:38
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { message } from 'antd'; import { message } from 'antd';
import React from 'react'; import React from 'react';
import E from 'wangeditor'; import E from 'wangeditor';
...@@ -9,25 +18,23 @@ const { BtnMenu } = E; ...@@ -9,25 +18,23 @@ const { BtnMenu } = E;
class GraphicsEditor extends React.Component { class GraphicsEditor extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
editorId: window.random_string(16), editorId: window.random_string(16),
textLength: 0, textLength: 0,
showSelectImageModal: false, showSelectImageModal: false,
showSelectVideoModal: false, showSelectVideoModal: false,
diskList: [], diskList: [],
} };
this.editorInt = null; this.editorInt = null;
} }
componentDidMount() { componentDidMount() {
this.renderEditor() this.renderEditor();
this.resetIndex(true); this.resetIndex(true);
this.initBus(); this.initBus();
} }
componentWillUnmount() { componentWillUnmount() {
this.resetIndex(); this.resetIndex();
this.removeBus(); this.removeBus();
...@@ -38,11 +45,11 @@ class GraphicsEditor extends React.Component { ...@@ -38,11 +45,11 @@ class GraphicsEditor extends React.Component {
const leftDom = document.querySelector('.left-container'); const leftDom = document.querySelector('.left-container');
// topDom.style.zIndex = bool ? 'auto' : 112; // topDom.style.zIndex = bool ? 'auto' : 112;
// leftDom.style.zIndex = bool ? 'auto' : 2; // leftDom.style.zIndex = bool ? 'auto' : 2;
} };
renderEditor() { renderEditor() {
const { editorId } = this.state; const { editorId } = this.state;
const { detail, onChange, isIntro, maxLimit } = this.props; const { detail, onChange, isIntro, maxLimit, editorType } = this.props;
class ImageMenu extends BtnMenu { class ImageMenu extends BtnMenu {
constructor(editor) { constructor(editor) {
// data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述 // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
...@@ -50,17 +57,15 @@ class GraphicsEditor extends React.Component { ...@@ -50,17 +57,15 @@ class GraphicsEditor extends React.Component {
`<div class="w-e-menu" data-title="图片"> `<div class="w-e-menu" data-title="图片">
<i class="w-e-icon-image"></i> <i class="w-e-icon-image"></i>
</div>` </div>`
) );
super($elem, editor) super($elem, editor);
} }
// 菜单点击事件 // 菜单点击事件
clickHandler() { clickHandler() {
Bus.trigger(`graphicsEditorImage${isIntro ? '' : 'Content'}`) Bus.trigger(`graphicsEditorImage${isIntro ? '' : 'Content'}`);
} }
tryChangeActive() { tryChangeActive() {}
}
} }
class VideoMenu extends BtnMenu { class VideoMenu extends BtnMenu {
...@@ -70,40 +75,24 @@ class GraphicsEditor extends React.Component { ...@@ -70,40 +75,24 @@ class GraphicsEditor extends React.Component {
`<div class="w-e-menu" data-title="视频"> `<div class="w-e-menu" data-title="视频">
<i class="w-e-icon-play"></i> <i class="w-e-icon-play"></i>
</div>` </div>`
) );
super($elem, editor) super($elem, editor);
} }
// 菜单点击事件 // 菜单点击事件
clickHandler() { clickHandler() {
Bus.trigger('graphicsEditorVideo') Bus.trigger('graphicsEditorVideo');
} }
tryChangeActive() { tryChangeActive() {}
}
} }
this.editorInt = new E(`#editor${editorId}`); this.editorInt = new E(`#editor${editorId}`);
this.editorInt.config.focus = false; this.editorInt.config.focus = false;
this.editorInt.config.showFullScreen = !isIntro this.editorInt.config.showFullScreen = !isIntro;
this.editorInt.menus.extend('xmimage', ImageMenu); this.editorInt.menus.extend('xmimage', ImageMenu);
!isIntro && this.editorInt.menus.extend('xmvideo', VideoMenu); !isIntro && this.editorInt.menus.extend('xmvideo', VideoMenu);
this.editorInt.config.menus = isIntro ? this.editorInt.config.menus = isIntro
[ ? ['head', 'bold', 'fontSize', 'fontName', 'italic', 'underline', 'strikeThrough', 'foreColor', 'backColor', 'list', 'justify', 'emoticon', 'xmimage']
'head',
'bold',
'fontSize',
'fontName',
'italic',
'underline',
'strikeThrough',
'foreColor',
'backColor',
'list',
'justify',
'emoticon',
'xmimage',
]
: [ : [
'head', 'head',
'bold', 'bold',
...@@ -134,33 +123,41 @@ class GraphicsEditor extends React.Component { ...@@ -134,33 +123,41 @@ class GraphicsEditor extends React.Component {
{ {
title: 'emoji', title: 'emoji',
type: 'emoji', type: 'emoji',
content: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '😊', '🙂', '🙃', '😉', '😓', '😅', '😪', '🤔', '😬', '🤐'] content: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '😊', '🙂', '🙃', '😉', '😓', '😅', '😪', '🤔', '😬', '🤐'],
} },
] ];
this.editorInt.config.zIndex = 1; this.editorInt.config.zIndex = 1;
this.editorInt.config.pasteFilterStyle = false; this.editorInt.config.pasteFilterStyle = false;
this.editorInt.config.pasteIgnoreImg = true; this.editorInt.config.pasteIgnoreImg = true;
// 自定义处理粘贴的文本内容 // 自定义处理粘贴的文本内容
this.editorInt.config.pasteTextHandle = function (content) { this.editorInt.config.pasteTextHandle = (content) => {
if (content == '' && !content) return '' if (content == '' && !content) return '';
var str = content var str = content; // 保留空格和换行的其他字符
str = str.replace(/<xml>[\s\S]*?<\/xml>/ig, '') str = str.replace(/<xml>[\s\S]*?<\/xml>/gi, '');
str = str.replace(/<style>[\s\S]*?<\/style>/ig, '') str = str.replace(/<style>[\s\S]*?<\/style>/gi, '');
str = str.replace(/[ | ]*\n/g, '\n') str = str.replace(/<\/?a.*?>/g, '');
str = str.replace(/\&nbsp\;/ig, ' ') return str;
return str };
}
this.editorInt.config.onchange = (html) => { this.editorInt.config.onchange = (html) => {
var str = this.editorInt.txt.text(); // 去除所有特殊字符
str = str.replace(/<xml>[\s\S]*?<\/xml>/gi, '');
str = str.replace(/<style>[\s\S]*?<\/style>/gi, '');
str = str.replace(/<\/?[^>]*>/g, '');
str = str.replace(/[ | ]*\n/g, '\n');
str = str.replace(/\&nbsp\;/gi, ' ');
str = str.replace(/[\r\n]/g, '');
str = str.replace(/<\/?a.*?>/g, '');
const videoCount = ((html || '').match(/<iframe/g) || []).length; const videoCount = ((html || '').match(/<iframe/g) || []).length;
const imageCount = ((html || '').match(/<img/g) || []).length; const imageCount = ((html || '').match(/<img/g) || []).length;
const textLength = this.editorInt.txt.text().replace(/\&nbsp\;/ig, ' ').length + videoCount + imageCount; const textLength = str.length + videoCount + imageCount;
this.setState({ textLength }, () => { this.setState({ textLength }, () => {
if (textLength > maxLimit) { if (textLength > maxLimit) {
// message.warning('超过字数限定'); message.warning(`内容过长,不能超过${maxLimit}字`);
} }
Bus.trigger('editorLimit', textLength, editorType);
onChange(html, this.state.textLength); onChange(html, this.state.textLength);
}) });
} };
this.editorInt.create(); this.editorInt.create();
this.editorInt.txt.html(detail.content); this.editorInt.txt.html(detail.content);
} }
...@@ -170,59 +167,64 @@ class GraphicsEditor extends React.Component { ...@@ -170,59 +167,64 @@ class GraphicsEditor extends React.Component {
const { ossUrl } = file || {}; const { ossUrl } = file || {};
if (!ossUrl) return null; if (!ossUrl) return null;
this.setState({ this.setState({
showSelectVideoModal: false showSelectVideoModal: false,
}) });
const { detail } = this.props; const { detail } = this.props;
this.editorInt && this.editorInt.txt.html(`${detail.content}<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="${ossUrl}"></iframe><br/></p><p><br/></p>`) this.editorInt &&
} this.editorInt.txt.html(
`${detail.content}<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="${ossUrl}"></iframe><br/></p><p><br/></p>`
);
};
handleSelectImage = (file) => { handleSelectImage = (file) => {
const { ossUrl } = file || {}; const { ossUrl } = file || {};
if (!ossUrl) return null; if (!ossUrl) return null;
this.setState({ this.setState({
showSelectImageModal: false showSelectImageModal: false,
}) });
const { detail } = this.props; const { detail } = this.props;
this.editorInt && this.editorInt.txt.html(`${detail.content}<p><img style="max-width: 100%;" src="${ossUrl}" /><br/><p>`) this.editorInt && this.editorInt.txt.html(`${detail.content}<p><img style="max-width: 100%;" src="${ossUrl}" /><br/><p>`);
} };
initBus = () => { initBus = () => {
const { isIntro } = this.props; const { isIntro } = this.props;
Bus.bind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage) Bus.bind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage);
!isIntro && Bus.bind('graphicsEditorVideo', this.uploadVideo) !isIntro && Bus.bind('graphicsEditorVideo', this.uploadVideo);
} };
removeBus = () => { removeBus = () => {
const { isIntro } = this.props; const { isIntro } = this.props;
Bus.unbind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage) Bus.unbind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage);
!isIntro && Bus.unbind('graphicsEditorVideo', this.uploadVideo) !isIntro && Bus.unbind('graphicsEditorVideo', this.uploadVideo);
} };
uploadImage = () => { uploadImage = () => {
this.setState({ showSelectImageModal: true }) this.setState({ showSelectImageModal: true });
} };
uploadVideo = () => { uploadVideo = () => {
this.setState({ showSelectVideoModal: true }) this.setState({ showSelectVideoModal: true });
} };
render() { render() {
const { const { editorId, textLength, showSelectImageModal, showSelectVideoModal, diskList } = this.state;
editorId, const { isIntro, maxLimit } = this.props;
textLength, return (
showSelectImageModal, <div className={`graphics-editor-container${isIntro ? ' introduce' : ''} ${textLength > maxLimit && 'warning'}`}>
showSelectVideoModal, <div className='editor-box' id={`editor${editorId}`}></div>
diskList, <div className='editor-tips'>
} = this.state; ({textLength > maxLimit ? <span style={{ color: 'red' }}>{textLength}</span> : textLength}/{maxLimit || 1000})
const { limitLength = 1000, isIntro, maxLimit } = this.props; </div>
return <div className={`graphics-editor-container${isIntro ? ' introduce' : ''} ${(textLength > maxLimit)&& 'warning'}`}> {textLength > maxLimit && (
<div className="editor-box" id={`editor${editorId}`} ></div> <div style={{ top: isIntro ? '245px' : '512px' }} className='editor-warning'>
<div className="editor-tips">( {(textLength > maxLimit) ? <span style={{ color: 'red' }} >{textLength}</span> : textLength}/{maxLimit || 100000})</div> 最多只能输入{maxLimit || 1000}字
{showSelectVideoModal && </div>
)}
{showSelectVideoModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
operateType="select" operateType='select'
selectTypeList={['MP4']} selectTypeList={['MP4']}
accept="video/mp4" accept='video/mp4'
confirm={{ confirm={{
title: '文件过大,无法上传', title: '文件过大,无法上传',
content: '为保障学员的观看体验,上传的图文大小不能超过2G', content: '为保障学员的观看体验,上传的图文大小不能超过2G',
...@@ -232,27 +234,28 @@ class GraphicsEditor extends React.Component { ...@@ -232,27 +234,28 @@ class GraphicsEditor extends React.Component {
diskList={diskList} diskList={diskList}
addVideo={true} addVideo={true}
onClose={() => { onClose={() => {
this.setState({ showSelectVideoModal: false }) this.setState({ showSelectVideoModal: false });
}} }}
onSelect={this.handleSelectVideo} onSelect={this.handleSelectVideo}
/> />
} )}
{showSelectImageModal && {showSelectImageModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
key="basic" key='basic'
operateType="select" operateType='select'
multiple={false} multiple={false}
accept="image/jpeg,image/png,image/jpg" accept='image/jpeg,image/png,image/jpg'
selectTypeList={['JPG', 'JPEG', 'PNG']} selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png' tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectImageModal} isOpen={showSelectImageModal}
onClose={() => { onClose={() => {
this.setState({ showSelectImageModal: false }) this.setState({ showSelectImageModal: false });
}} }}
onSelect={this.handleSelectImage} onSelect={this.handleSelectImage}
/> />
} )}
</div> </div>
);
} }
} }
......
.graphics-editor-container { .graphics-editor-container {
border: 1px solid #E8E8E8; border: 1px solid #e8e8e8;
border-radius: 4px; border-radius: 4px;
width: 702px; width: 702px;
height: 510px; height: 510px;
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.w-e-toolbar { .w-e-toolbar {
background-color: #fff !important; background-color: #fff !important;
border: none !important; border: none !important;
border-bottom: 1px solid #E8E8E8 !important; border-bottom: 1px solid #e8e8e8 !important;
} }
.w-e-text-container { .w-e-text-container {
...@@ -38,19 +38,26 @@ ...@@ -38,19 +38,26 @@
color: #666; color: #666;
z-index: 1; z-index: 1;
} }
.editor-warning {
position: absolute;
right: 0;
color: red;
}
.w-e-full-screen-editor { .w-e-full-screen-editor {
.w-e-text-container { .w-e-text-container {
height: ~'calc(100vh - 109px)' !important; height: ~'calc(100vh - 109px)' !important;
} }
} }
&.introduce { &.introduce {
height: 200px; height: 240px;
.w-e-text-container { .w-e-text-container {
height: ~'calc(100% - 69px)' !important; height: ~'calc(100% - 69px)' !important;
} }
} }
&.warning{ &.warning {
border-color: red; border-color: red;
} }
} }
...@@ -125,7 +125,7 @@ class LiveCourseList extends React.Component { ...@@ -125,7 +125,7 @@ class LiveCourseList extends React.Component {
columns = [ columns = [
{ {
title: '直播课', title: '直播课',
width: '23%',
key: 'course', key: 'course',
fixed: 'left', fixed: 'left',
dataIndex: 'courseName', dataIndex: 'courseName',
...@@ -284,6 +284,7 @@ class LiveCourseList extends React.Component { ...@@ -284,6 +284,7 @@ class LiveCourseList extends React.Component {
render: (val, item, index) => { render: (val, item, index) => {
return ( return (
<Switch <Switch
size='small'
checked={item.shelfState === 'YES'} checked={item.shelfState === 'YES'}
defaultChecked={item.shelfState === 'YES' ? true : false} defaultChecked={item.shelfState === 'YES' ? true : false}
onChange={(checked) => this.changeShelfState(index, item, checked)} onChange={(checked) => this.changeShelfState(index, item, checked)}
...@@ -311,7 +312,7 @@ class LiveCourseList extends React.Component { ...@@ -311,7 +312,7 @@ class LiveCourseList extends React.Component {
return ( return (
<div className='related-task'> <div className='related-task'>
<Choose> <Choose>
<When condition={record.relatedPlanLis}> <When condition={record.relatedPlanList}>
<Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter> <Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter>
{record.relatedPlanList.map((item, index) => { {record.relatedPlanList.map((item, index) => {
return ( return (
......
...@@ -27,6 +27,7 @@ import _ from 'underscore'; ...@@ -27,6 +27,7 @@ import _ from 'underscore';
import Upload from '@/core/upload'; import Upload from '@/core/upload';
import ImgClipModal from '@/components/ImgClipModal' import ImgClipModal from '@/components/ImgClipModal'
import './AddGraphicsCourse.less'; import './AddGraphicsCourse.less';
import Bus from '@/core/bus';
const EDIT_BOX_KEY = Math.random(); const EDIT_BOX_KEY = Math.random();
const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' }; const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' };
...@@ -34,7 +35,7 @@ const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryL ...@@ -34,7 +35,7 @@ const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryL
//添加课程时课程默认的一些值 //添加课程时课程默认的一些值
const defaultShelfState = 'YES'; const defaultShelfState = 'YES';
const whetherVisitorsJoin = 'NO'; const whetherVisitorsJoin = 'NO';
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png'; const defaultCover = 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png';
let cutFlag = false; let cutFlag = false;
class AddGraphicsCourse extends React.Component { class AddGraphicsCourse extends React.Component {
...@@ -53,7 +54,7 @@ class AddGraphicsCourse extends React.Component { ...@@ -53,7 +54,7 @@ class AddGraphicsCourse extends React.Component {
introduce: '', introduce: '',
courseMediaId: null, // 图文课链接 courseMediaId: null, // 图文课链接
coverId: null, // 图文封面的recourceId coverId: null, // 图文封面的recourceId
coverUrl: defaultCoverUrl, // 图文课封面 coverUrl: defaultCover, // 图文课封面
studentList: [], // 上课学员列表 studentList: [], // 上课学员列表
shelfState: 'YES', //是否开启学院展示 shelfState: 'YES', //是否开启学院展示
selectedFileList: [], // 已经从资料云盘中勾选的文件 selectedFileList: [], // 已经从资料云盘中勾选的文件
...@@ -73,6 +74,11 @@ class AddGraphicsCourse extends React.Component { ...@@ -73,6 +74,11 @@ class AddGraphicsCourse extends React.Component {
if (pageType === 'edit') { if (pageType === 'edit') {
this.handleFetchScheudleDetail(id); this.handleFetchScheudleDetail(id);
} }
Bus.bind('editorLimit', (editorTextLength, editorType) => {
this.setState({
[editorType]: editorTextLength,
});
});
} }
initBus = () => { initBus = () => {
...@@ -314,11 +320,11 @@ class AddGraphicsCourse extends React.Component { ...@@ -314,11 +320,11 @@ class AddGraphicsCourse extends React.Component {
//过期判断 //过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) { if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({ Modal.warning({
title:"服务已到期", title: '服务已到期',
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买", content: '当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买',
okText: "我知道了" okText: '我知道了',
}) });
return return;
} }
const { id, coverId, pageType, courseName, courseMedia, introduce, categoryId, shelfState, whetherVisitorsJoin } = this.state; const { id, coverId, pageType, courseName, courseMedia, introduce, categoryId, shelfState, whetherVisitorsJoin } = this.state;
...@@ -387,6 +393,7 @@ class AddGraphicsCourse extends React.Component { ...@@ -387,6 +393,7 @@ class AddGraphicsCourse extends React.Component {
}; };
handleValidate = (courseName, courseMedia, categoryId) => { handleValidate = (courseName, courseMedia, categoryId) => {
const { graphicsCourseIntor, graphicsCourseContent } = this.state;
return new Promise((resolve) => { return new Promise((resolve) => {
if (!courseName) { if (!courseName) {
message.warning('请输入课程名称'); message.warning('请输入课程名称');
...@@ -403,6 +410,18 @@ class AddGraphicsCourse extends React.Component { ...@@ -403,6 +410,18 @@ class AddGraphicsCourse extends React.Component {
resolve(false); resolve(false);
return false; return false;
} }
if (graphicsCourseContent > 1000) {
message.warning('课程内容超过字数限定');
resolve(false);
return;
}
if (graphicsCourseIntor > 1000) {
message.warning('课程简介超过字数限定');
resolve(false);
return;
}
// const textMedia = scheduleMedia.filter((item) => item.mediaType === 'TEXT'); // const textMedia = scheduleMedia.filter((item) => item.mediaType === 'TEXT');
// for (let i = 0, len = textMedia.length; i < len; i++) { // for (let i = 0, len = textMedia.length; i < len; i++) {
// if (textMedia[i].mediaContentLength && textMedia[i].mediaContentLength.length > 1000) { // if (textMedia[i].mediaContentLength && textMedia[i].mediaContentLength.length > 1000) {
...@@ -415,6 +434,17 @@ class AddGraphicsCourse extends React.Component { ...@@ -415,6 +434,17 @@ class AddGraphicsCourse extends React.Component {
}); });
}; };
// 使用默认封面图
handleResetCoverUrl = () => {
const { coverUrl } = this.state;
const isDefaultCover = coverUrl === defaultCover;
// 如果已经是默认图的话,不做任何任何处理
if (isDefaultCover) return;
this.setState({ coverUrl: defaultCover, coverId: null }, () => {
message.success('已替换为默认图');
});
};
render() { render() {
const { const {
id, id,
...@@ -442,6 +472,8 @@ class AddGraphicsCourse extends React.Component { ...@@ -442,6 +472,8 @@ class AddGraphicsCourse extends React.Component {
const hasSelectedStu = studentList.length; const hasSelectedStu = studentList.length;
const courseWareIcon = FileVerifyMap[videoType] ? FileTypeIcon[FileVerifyMap[videoType].type] : FileTypeIcon[videoType]; const courseWareIcon = FileVerifyMap[videoType] ? FileTypeIcon[FileVerifyMap[videoType].type] : FileTypeIcon[videoType];
// 当前是否使用的是默认图片
const isDefaultCover = coverUrl === defaultCover;
return ( return (
<div className='page add-graphics-course-page'> <div className='page add-graphics-course-page'>
...@@ -468,19 +500,39 @@ class AddGraphicsCourse extends React.Component { ...@@ -468,19 +500,39 @@ class AddGraphicsCourse extends React.Component {
<div className='cover-url flex mt16'> <div className='cover-url flex mt16'>
<div className='label'>封面图:</div> <div className='label'>封面图:</div>
<div className='cover-url__wrap'> {/* <div className='cover-url__wrap'>
<div className='opt-btns'>
<Button
onClick={() => {
this.setState({
showSelectCoverModal: true,
});
}}>{`${pageType === 'add' && !coverUrl ? '上传' : '修改'}封面`}</Button>
<div className='tips'></div>
</div>
<div className='img-content'> <div className='img-content'>
<img src={coverUrl} /> <img src={coverUrl} />
</div> </div>
</div> */}
<div className='course-cover__wrap'>
<div className='opt-btns'> <div className='opt-btns'>
<Button <Button
onClick={() => { onClick={() => {
this.setState({ this.setState({
showSelectCoverModal: true, showSelectCoverModal: true,
}); });
}}>{`${pageType === 'add' && !coverUrl ? '上传' : '修改'}封面`}</Button> }}>
上传图片
</Button>
<span className={`default-btn ${isDefaultCover ? 'disabled' : ''}`} onClick={this.handleResetCoverUrl}>
使用默认图
</span>
<div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div> <div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div> </div>
<div className='img-content'>
{isDefaultCover && <span className='tag'>默认图</span>}
<img src={coverUrl} />
</div>
</div> </div>
</div> </div>
<div className='course-catalog required'> <div className='course-catalog required'>
......
.add-graphics-course-page { .add-graphics-course-page {
position:relative !important; position: relative !important;
.box{ .box {
margin-bottom:52px !important; margin-bottom: 52px !important;
} }
.ant-radio-group { .ant-radio-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.radio-item { .radio-item {
margin-bottom: 12px; margin-bottom: 12px;
...@@ -28,10 +27,10 @@ ...@@ -28,10 +27,10 @@
.form { .form {
margin-top: 16px; margin-top: 16px;
padding: 0 16px; padding: 0 16px;
.label{ .label {
display:inline-block; display: inline-block;
text-align:right; text-align: right;
width:85px; width: 85px;
} }
.required { .required {
position: relative; position: relative;
...@@ -48,9 +47,9 @@ ...@@ -48,9 +47,9 @@
top: 0; top: 0;
} }
} }
.course-catalog{ .course-catalog {
margin-bottom:16px; margin-bottom: 16px;
margin-top:16px; margin-top: 16px;
} }
.course-ware { .course-ware {
display: flex; display: flex;
...@@ -71,10 +70,20 @@ ...@@ -71,10 +70,20 @@
display: flex; display: flex;
} }
.cover-url__wrap { .course-cover {
margin-left: 14px;
display: flex;
margin-top: 24px;
&__wrap {
position: relative;
.img-content { .img-content {
width: 298px; margin-top: 8px;
height: 172px; margin-right: 20px;
width: 299px;
height: 169px;
position: relative;
img { img {
width: 100%; width: 100%;
...@@ -82,29 +91,78 @@ ...@@ -82,29 +91,78 @@
object-fit: contain; object-fit: contain;
border-radius: 4px; border-radius: 4px;
} }
} .tag {
.empty-img { border-radius: 2px;
width: 298px; background: #d6d6d6;
height: 172px; font-size: 12px;
border: 1px dashed #EBEBEB; height: 18px;
border-radius: 4px; width: 52px;
padding: 12px;
color: #999;
padding: 52px 24px;
text-align: center; text-align: center;
color: #fff;
position: absolute;
top: 8px;
left: 8px;
}
} }
.opt-btns { .opt-btns {
margin-top: 8px; .default-btn {
display: flex; margin-left: 14px;
align-items: center; color: #2966ff;
cursor: pointer;
&.disabled {
color: #ccc;
cursor: not-allowed;
}
}
.ant-upload-list {
display: none;
}
.tips { .tips {
margin-left: 12px; margin-top: 8px;
color: #999; color: #999;
} }
} }
} }
}
// .cover-url__wrap {
// .img-content {
// width: 298px;
// height: 172px;
// img {
// width: 100%;
// height: 100%;
// object-fit: contain;
// border-radius: 4px;
// }
// }
// .empty-img {
// width: 298px;
// height: 172px;
// border: 1px dashed #EBEBEB;
// border-radius: 4px;
// padding: 12px;
// color: #999;
// padding: 52px 24px;
// text-align: center;
// }
// .opt-btns {
// margin-top: 8px;
// display: flex;
// align-items: center;
// .tips {
// margin-left: 12px;
// color: #999;
// }
// }
// }
.select-student { .select-student {
align-items: center; align-items: center;
...@@ -139,7 +197,7 @@ ...@@ -139,7 +197,7 @@
justify-content: flex-end; justify-content: flex-end;
padding-right: 72px; padding-right: 72px;
background: #fff; background: #fff;
border-top: 1px solid #E8E8E8; border-top: 1px solid #e8e8e8;
z-index: 999; z-index: 999;
.ant-btn { .ant-btn {
margin-left: 10px; margin-left: 10px;
......
...@@ -17,17 +17,15 @@ import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepare ...@@ -17,17 +17,15 @@ import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepare
import { DISK_MAP } from '@/common/constants/academic/lessonEnum'; import { DISK_MAP } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components'; import { ImgCutModalNew } from '@/components';
const { TextArea } = Input; const { TextArea } = Input;
class AddGraphicsIntro extends React.Component { class AddGraphicsIntro extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
showSelectFileModal: false, showSelectFileModal: false,
diskList: [], diskList: [],
selectType: null, selectType: null,
} };
} }
// 上传封面图 // 上传封面图
...@@ -38,34 +36,34 @@ class AddGraphicsIntro extends React.Component { ...@@ -38,34 +36,34 @@ class AddGraphicsIntro extends React.Component {
imageFile, imageFile,
showCutModal: true, showCutModal: true,
}); });
} };
// 选择暖场资源 // 选择暖场资源
handleSelectVideo = (file) => { handleSelectVideo = (file) => {
const { selectType } = this.state; const { selectType } = this.state;
this.setState({ this.setState({
showSelectFileModal: false showSelectFileModal: false,
}) });
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file; const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
if(selectType === 'WARMUP'){ if (selectType === 'WARMUP') {
const liveCourseWarmMedia = { const liveCourseWarmMedia = {
contentType: 'WARMUP', contentType: 'WARMUP',
mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE', mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE',
mediaContent: resourceId, mediaContent: resourceId,
mediaUrl: ossUrl, mediaUrl: ossUrl,
mediaName: folderName, mediaName: folderName,
size: folderSize size: folderSize,
} };
this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia); this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia);
}else{ } else {
// 最多添加九图片 // 最多添加九图片
const { liveCourseMediaRequests } = this.props.data; const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => { const list = _.filter(liveCourseMediaRequests, (item) => {
return item.mediaType == "PICTURE"; return item.mediaType == 'PICTURE';
}); });
if (list.length > 8) { if (list.length > 8) {
message.warning("最多添加9张图片"); message.warning('最多添加9张图片');
return; return;
} }
liveCourseMediaRequests.push({ liveCourseMediaRequests.push({
...@@ -78,121 +76,116 @@ class AddGraphicsIntro extends React.Component { ...@@ -78,121 +76,116 @@ class AddGraphicsIntro extends React.Component {
}); });
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests); this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
} }
};
}
changeDetail = (value) => { changeDetail = (value) => {
this.props.onChange('courseMedia', value); this.props.onChange('courseMedia', value);
} };
changeIntro = (value) => { changeIntro = (value) => {
this.props.onChange('introduce', value); this.props.onChange('introduce', value);
} };
whetherVisitorsJoinChange = ()=>{ whetherVisitorsJoinChange = () => {
if(this.props.data.whetherVisitorsJoin==="NO"){ if (this.props.data.whetherVisitorsJoin === 'NO') {
this.props.onChange('whetherVisitorsJoin','YES') this.props.onChange('whetherVisitorsJoin', 'YES');
}else{ } else {
this.props.onChange('whetherVisitorsJoin','NO') this.props.onChange('whetherVisitorsJoin', 'NO');
}
} }
};
shelfStateChange = ()=>{ shelfStateChange = () => {
if(this.props.data.shelfState==="NO"){ if (this.props.data.shelfState === 'NO') {
this.props.onChange('shelfState','YES') this.props.onChange('shelfState', 'YES');
}else{ } else {
this.props.onChange('shelfState','NO') this.props.onChange('shelfState', 'NO');
}
} }
};
render() { render() {
const {data: { id, whetherVisitorsJoin, courseMedia, introduce, shelfState, loadcourseMedia, loadintroduce } } = this.props; const {
data: { id, whetherVisitorsJoin, courseMedia, introduce, shelfState, loadcourseMedia, loadintroduce },
} = this.props;
const { showSelectFileModal, selectType } = this.state; const { showSelectFileModal, selectType } = this.state;
return ( return (
<div className="add-video__intro-info"> <div className='add-graphic__intro-info'>
<div className="allow-tourist-join"> <div className='allow-tourist-join'>
<span className="label">观看设置:</span> <span className='label'>观看设置:</span>
<div className="content"> <div className='content'>
<div> <Switch checked={whetherVisitorsJoin === 'NO' ? true : false} onChange={this.whetherVisitorsJoinChange} />
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/> <div className='desc'>{whetherVisitorsJoin === 'NO' ? '已开启,学员需绑定手机号才可观看' : '已关闭,学员无需绑定手机号即可观看'}</div>
</div> </div>
<div> </div>
<div className="desc"> <div className='store-show'>
<div>开启:允许未绑定手机号的学员观看</div> <span className='label'>学院展示:</span>
<div>关闭:仅限绑定了手机号的学员可以进入观看图文课</div> <div className='content'>
</div> <Switch checked={shelfState === 'YES' ? true : false} onChange={this.shelfStateChange} />
</div> <div className='desc'>{shelfState === 'YES' ? '已开启,课程将在该学院的学员课程列表中显示' : '已关闭,课程将在该学院的学员课程列表中隐藏'}</div>
</div> </div>
</div> </div>
<div className="store-show"> <div className='introduce required'>
<span className="label">学院展示:</span> <span className='label' style={{ marginTop: 5 }}>
<div className="content"> 课程内容:
<Row> </span>
<Col span={3}> <div className='content'>
<Switch checked={shelfState==="YES"? true:false} onChange={this.shelfStateChange}/> <div className='intro-list'>
</Col> <div className='intro-list__item content-editor'>
<Col span={21}> {(!id || loadcourseMedia) && (
<div className="desc">
<div>开启:图文课将在学员学院图文课列表中展示</div>
<div>关闭:图文课将在学员学院图文课列表中隐藏</div>
</div>
</Col>
</Row>
</div>
</div>
<div className="introduce required">
<span className="label" style={{ marginTop: 5 }}>课程内容:</span>
<div className="content">
<div className="intro-list">
<div className="intro-list__item content-editor">
{(!id || loadcourseMedia) &&
<GraphicsEditor <GraphicsEditor
id="content" id='content'
maxLimit={1000}
editorType={'graphicsCourseContent'}
detail={{ detail={{
content: courseMedia content: courseMedia,
}}
onChange={(val) => {
this.changeDetail(val);
}} }}
onChange={(val) => { this.changeDetail(val) }}
/> />
} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="introduce"> <div className='introduce'>
<span className="label">课程简介:</span> <span className='label'>课程简介:</span>
<div className="content"> <div className='content'>
<div className="intro-list"> <div className='intro-list'>
<div className="intro-list__item introduce-editor"> <div className='intro-list__item introduce-editor'>
{(!id || loadintroduce) && {(!id || loadintroduce) && (
<GraphicsEditor <GraphicsEditor
id="intro" id='intro'
isIntro={true} isIntro={true}
maxLimit={1000}
editorType={'graphicsCourseIntor'}
detail={{ detail={{
content: introduce content: introduce,
}}
onChange={(val) => {
this.changeIntro(val);
}} }}
onChange={(val) => { this.changeIntro(val) }}
/> />
} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* 选择暖场图文件弹窗 */} {/* 选择暖场图文件弹窗 */}
{ showSelectFileModal && {showSelectFileModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
operateType="select" operateType='select'
accept={selectType==="INTRO"?"image/jpeg,image/png,image/jpg":"video/mp4,image/jpeg,image/png,image/jpg"} accept={selectType === 'INTRO' ? 'image/jpeg,image/png,image/jpg' : 'video/mp4,image/jpeg,image/png,image/jpg'}
selectTypeList={ selectType==="INTRO" ? ['JPG', 'JPEG', 'PNG']: ['MP4', 'JPG', 'JPEG', 'PNG'] } selectTypeList={selectType === 'INTRO' ? ['JPG', 'JPEG', 'PNG'] : ['MP4', 'JPG', 'JPEG', 'PNG']}
tooltip={ selectType==="INTRO"?'支持文件类型:jpg、jpeg、png':'支持文件类型:jpg、jpeg、png、mp4'} tooltip={selectType === 'INTRO' ? '支持文件类型:jpg、jpeg、png' : '支持文件类型:jpg、jpeg、png、mp4'}
isOpen={showSelectFileModal} isOpen={showSelectFileModal}
onClose={() => { onClose={() => {
this.setState({ showSelectFileModal: false }) this.setState({ showSelectFileModal: false });
}} }}
onSelect={this.handleSelectVideo} onSelect={this.handleSelectVideo}
/> />
} )}
</div> </div>
) );
} }
} }
......
.add-video__intro-info { .add-graphic__intro-info {
.w-e-full-screen-editor { .w-e-full-screen-editor {
background: #fff !important; background: #fff !important;
} }
.playback { .playback {
margin-bottom: 10px; margin-bottom: 10px;
.require { .require {
color: #EC4B35; color: #ec4b35;
} }
&__text { &__text {
color: #999; color: #999;
} }
} }
.label{ .label {
display:inline-block; display: inline-block;
text-align:right; text-align: right;
width:85px; width: 85px;
} }
.playback, .playback,
.introduce { .introduce {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.allow-tourist-join{ .allow-tourist-join {
display:flex; display: flex;
.content{ .content {
display:flex; display: flex;
} }
.desc{ .desc {
margin-left:16px; margin-left: 16px;
font-size:14px; font-size: 14px;
color:#999; color: #999;
display:inline-block; display: inline-block;
} }
} }
.store-show{ .store-show {
display:flex; display: flex;
margin-top:16px; margin-top: 16px;
margin-bottom:16px; margin-bottom: 16px;
.desc{ .content {
margin-left:16px; display: flex;
font-size:14px; }
color:#999; .desc {
display:inline-block; margin-left: 16px;
font-size: 14px;
color: #999;
display: inline-block;
} }
} }
.radio { .radio {
...@@ -59,13 +62,13 @@ ...@@ -59,13 +62,13 @@
.intro-list__item { .intro-list__item {
display: flex; display: flex;
margin-bottom: 16px; margin-bottom: 32px;
position: relative; position: relative;
&.picture { &.picture {
width: 550px; width: 550px;
padding: 16px; padding: 16px;
border: 1px solid #EEE; border: 1px solid #eee;
border-radius: 4px; border-radius: 4px;
.img__wrap { .img__wrap {
...@@ -114,7 +117,7 @@ ...@@ -114,7 +117,7 @@
line-height: 80px; line-height: 80px;
padding: 16px; padding: 16px;
margin-top: 16px; margin-top: 16px;
border: 1px dashed #EBEBEB; border: 1px dashed #ebebeb;
border-radius: 4px; border-radius: 4px;
.ant-upload { .ant-upload {
...@@ -133,7 +136,7 @@ ...@@ -133,7 +136,7 @@
.iconfont { .iconfont {
font-size: 22px; font-size: 22px;
line-height: 22px; line-height: 22px;
color: #BFBFBF; color: #bfbfbf;
text-align: center; text-align: center;
} }
...@@ -152,7 +155,7 @@ ...@@ -152,7 +155,7 @@
} }
.checkExample { .checkExample {
width: 60px; width: 60px;
color: #FF7519; color: #ff7519;
cursor: pointer; cursor: pointer;
} }
.warmup { .warmup {
...@@ -197,21 +200,19 @@ ...@@ -197,21 +200,19 @@
} }
} }
.opt-btns { .opt-btns {
.default-btn { .default-btn {
margin-left: 16px; margin-left: 16px;
color: #FF7519; color: #ff7519;
cursor: pointer; cursor: pointer;
&.disabled { &.disabled {
color: #CCC; color: #ccc;
cursor: not-allowed; cursor: not-allowed;
} }
} }
} }
}
} .example-wrap {
.example-wrap {
font-family: PingFangSC-Regular, PingFang SC; font-family: PingFangSC-Regular, PingFang SC;
text-align: center; text-align: center;
.title { .title {
...@@ -227,14 +228,14 @@ ...@@ -227,14 +228,14 @@
img { img {
width: 180px; width: 180px;
} }
} }
.check-record-rule { .check-record-rule {
width: 120px; width: 120px;
color: #FF7519; color: #ff7519;
cursor: pointer; cursor: pointer;
z-index: 2; z-index: 2;
} }
.record-rule-wrap { .record-rule-wrap {
text-align: left; text-align: left;
ul { ul {
margin-top: 10px; margin-top: 10px;
...@@ -247,9 +248,9 @@ ...@@ -247,9 +248,9 @@
.text { .text {
color: #999; color: #999;
} }
} }
.auto-send-class-report { .auto-send-class-report {
.label { .label {
width: 57px; width: 57px;
height: 12px; height: 12px;
...@@ -258,7 +259,8 @@ ...@@ -258,7 +259,8 @@
color: #666666; color: #666666;
line-height: 12px; line-height: 12px;
} }
.open-text, .close-text { .open-text,
.close-text {
width: 572px; width: 572px;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
...@@ -273,4 +275,4 @@ ...@@ -273,4 +275,4 @@
.close-text { .close-text {
margin-bottom: 16px; margin-bottom: 16px;
} }
} }
\ No newline at end of file
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-05 10:12:45 * @Date: 2020-08-05 10:12:45
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @LastEditTime: 2021-06-15 20:01:05 * @LastEditTime: 2021-07-05 10:23:10
* @Description: 视频课-列表模块 * @Description: 线上课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
import User from '@/common/js/user'; import User from '@/common/js/user';
import college from '@/common/lottie/college'; import college from '@/common/lottie/college';
import { PageControl, XMTable } from '@/components'; import { PageControl, XMTable } from '@/components';
import { appId, LIVE_SHARE } from '@/domains/course-domain/constants'; import { LIVE_SHARE } from '@/domains/course-domain/constants';
import CourseService from '@/domains/course-domain/CourseService'; import CourseService from '@/domains/course-domain/CourseService';
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal'; import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal';
import { Dropdown, message, Modal, Switch, Tooltip } from 'antd'; import { Dropdown, message, Modal, Switch, Tooltip } from 'antd';
...@@ -57,7 +57,7 @@ class GraphicsCourseList extends React.Component { ...@@ -57,7 +57,7 @@ class GraphicsCourseList extends React.Component {
handlePlanName = (planArray) => { handlePlanName = (planArray) => {
let planStr = ''; let planStr = '';
planArray.map((item, index) => { planArray.forEach((item, index) => {
if (index < planArray.length - 1) { if (index < planArray.length - 1) {
planStr = planStr + item.planName + '、'; planStr = planStr + item.planName + '、';
} else { } else {
...@@ -76,18 +76,21 @@ class GraphicsCourseList extends React.Component { ...@@ -76,18 +76,21 @@ class GraphicsCourseList extends React.Component {
width: 321, width: 321,
fixed: 'left', fixed: 'left',
render: (val, record) => { render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record; const { coverUrl } = record;
return ( return (
<div className='record__item'> <div className='record__item'>
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */} {/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img className='course-cover' src={coverUrl || defaultCoverUrl} /> <img className='course-cover' src={coverUrl || defaultCoverUrl} alt='' />
{record.courseName.length > 25 ? ( <Choose>
<When condition={record.courseName.length > 25}>
<Tooltip title={record.courseName}> <Tooltip title={record.courseName}>
<div className='course-name'>{record.courseName}</div> <div className='course-name'>{record.courseName}</div>
</Tooltip> </Tooltip>
) : ( </When>
<Otherwise>
<div className='course-name'>{record.courseName}</div> <div className='course-name'>{record.courseName}</div>
)} </Otherwise>
</Choose>
</div> </div>
); );
}, },
...@@ -146,10 +149,12 @@ class GraphicsCourseList extends React.Component { ...@@ -146,10 +149,12 @@ class GraphicsCourseList extends React.Component {
render: (val, item, index) => { render: (val, item, index) => {
return ( return (
<Switch <Switch
checked={item.shelfState === "YES"} size='small'
defaultChecked={item.shelfState==="YES"?true:false} checked={item.shelfState === 'YES'}
onChange={(checked)=>this.changeShelfState(index,item,checked)}/> defaultChecked={item.shelfState === 'YES' ? true : false}
) onChange={(checked) => this.changeShelfState(index, item, checked)}
/>
);
}, },
}, },
{ {
...@@ -168,7 +173,7 @@ class GraphicsCourseList extends React.Component { ...@@ -168,7 +173,7 @@ class GraphicsCourseList extends React.Component {
dataIndex: 'created', dataIndex: 'created',
sorter: true, sorter: true,
render: (val) => { render: (val) => {
return formatDate('YYYY-MM-DD H:i', val); return window.formatDate('YYYY-MM-DD H:i', val);
}, },
}, },
{ {
...@@ -178,7 +183,7 @@ class GraphicsCourseList extends React.Component { ...@@ -178,7 +183,7 @@ class GraphicsCourseList extends React.Component {
dataIndex: 'updated', dataIndex: 'updated',
sorter: true, sorter: true,
render: (val) => { render: (val) => {
return formatDate('YYYY-MM-DD H:i', val); return window.formatDate('YYYY-MM-DD H:i', val);
}, },
}, },
{ {
...@@ -189,7 +194,8 @@ class GraphicsCourseList extends React.Component { ...@@ -189,7 +194,8 @@ class GraphicsCourseList extends React.Component {
render: (val, record) => { render: (val, record) => {
return ( return (
<div className='related-task'> <div className='related-task'>
{record.relatedPlanList ? ( <Choose>
<When condition={record.relatedPlanList}>
<Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter> <Tooltip title={this.handlePlanName(record.relatedPlanList)} placement='top' arrowPointAtCenter>
{record.relatedPlanList.map((item, index) => { {record.relatedPlanList.map((item, index) => {
return ( return (
...@@ -199,9 +205,11 @@ class GraphicsCourseList extends React.Component { ...@@ -199,9 +205,11 @@ class GraphicsCourseList extends React.Component {
); );
})} })}
</Tooltip> </Tooltip>
) : ( </When>
<Otherwise>
<span></span> <span></span>
)} </Otherwise>
</Choose>
</div> </div>
); );
}, },
...@@ -297,7 +305,7 @@ class GraphicsCourseList extends React.Component { ...@@ -297,7 +305,7 @@ class GraphicsCourseList extends React.Component {
className='operate__item' className='operate__item'
key='edit' key='edit'
onClick={() => { onClick={() => {
RCHistory.push(`/create-graphics-course?type=edit&id=${item.id}`); window.RCHistory.push(`/create-graphics-course?type=edit&id=${item.id}`);
}}> }}>
编辑 编辑
</div> </div>
...@@ -305,17 +313,17 @@ class GraphicsCourseList extends React.Component { ...@@ -305,17 +313,17 @@ class GraphicsCourseList extends React.Component {
删除 删除
</div> </div>
</div> </div>
) );
} };
//改变上架状态 //改变上架状态
changeShelfState = (index,item,checked) =>{ changeShelfState = (index, item, checked) => {
let _shelfState = checked ? "YES" : "NO" let _shelfState = checked ? 'YES' : 'NO';
// if(_shelfState==='NO'){ // if(_shelfState==='NO'){
// _shelfState = "YES"; // _shelfState = "YES";
// }else{ // }else{
// _shelfState = "NO" // _shelfState = "NO"
// } // }
const params={ const params = {
courseId: item.id, courseId: item.id,
shelfState: _shelfState, shelfState: _shelfState,
}; };
...@@ -326,14 +334,14 @@ class GraphicsCourseList extends React.Component { ...@@ -326,14 +334,14 @@ class GraphicsCourseList extends React.Component {
} else { } else {
message.success('已取消展示'); message.success('已取消展示');
} }
this.props.changeShelfState(index,_shelfState) this.props.changeShelfState(index, _shelfState);
} }
}); });
}; };
// 删除视频课 // 删除视频课
handleDeleteGraphicsCourse = (scheduleId) => { handleDeleteGraphicsCourse = (scheduleId) => {
Modal.confirm({ Modal.confirm({
title: '你确定要删除此视频课吗?', title: '你确定要删除此线上课吗?',
content: '删除后,学员将不能进行观看。', content: '删除后,学员将不能进行观看。',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>, icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
okText: '确定', okText: '确定',
...@@ -355,8 +363,6 @@ class GraphicsCourseList extends React.Component { ...@@ -355,8 +363,6 @@ class GraphicsCourseList extends React.Component {
// 显示分享弹窗 // 显示分享弹窗
handleShowShareModal = (record, needStr = false) => { handleShowShareModal = (record, needStr = false) => {
const { id, scheduleVideoUrl } = record; const { id, scheduleVideoUrl } = record;
const _appId = appId;
const htmlUrl = `${LIVE_SHARE}graphics_detail/${id}?id=${User.getStoreId()}`; const htmlUrl = `${LIVE_SHARE}graphics_detail/${id}?id=${User.getStoreId()}`;
const longUrl = htmlUrl; const longUrl = htmlUrl;
const { coverUrl, courseName } = record; const { coverUrl, courseName } = record;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:15 * @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan * @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27 * @LastEditTime: 2020-12-26 16:07:27
* @Description: 视频课-操作模块 * @Description: 线上课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
......
...@@ -17,13 +17,13 @@ class GraphicsCourse extends React.Component { ...@@ -17,13 +17,13 @@ class GraphicsCourse extends React.Component {
courseType: 'PICTURE', courseType: 'PICTURE',
storeId:User.getStoreId() storeId:User.getStoreId()
}, },
dataSource: [], // 视频课列表 dataSource: [], // 线上课列表
totalCount: 0, // 视频课数据总条数 totalCount: 0, // 线上课数据总条数
} }
} }
componentWillMount() { componentWillMount() {
// 获取视频课列表 // 获取线上课列表
this.handleFetchScheduleList(); this.handleFetchScheduleList();
} }
...@@ -76,7 +76,7 @@ class GraphicsCourse extends React.Component { ...@@ -76,7 +76,7 @@ class GraphicsCourse extends React.Component {
{/* 操作模块 */} {/* 操作模块 */}
<GraphicsCourseOpt /> <GraphicsCourseOpt />
{/* 视频课列表模块 */} {/* 线上课列表模块 */}
<GraphicsCourseList <GraphicsCourseList
query={query} query={query}
dataSource={dataSource} dataSource={dataSource}
......
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-07-23 14:54:16 * @Date: 2020-07-23 14:54:16
* @LastEditors: fusanqiasng * @LastEditors: yuananting
* @LastEditTime: 2021-05-24 21:02:21 * @LastEditTime: 2021-07-15 14:02:37
* @Description: 大班直播课预览弹窗 * @Description: 大班直播课预览弹窗
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
import React from 'react' import React from "react";
import { Modal } from 'antd' import { Modal, Tabs } from "antd";
import moment from 'moment' import moment from "moment";
import './PreviewCourseModal.less' import ChapterList from "../video-course/components/ChapterList";
import "./PreviewCourseModal.less";
const { TabPane } = Tabs;
const courseStateShow = { const courseStateShow = {
UN_START: { UN_START: {
title: '待开课' title: "待开课",
}, },
STARTING: { STARTING: {
title: '上课中' title: "上课中",
}, },
FINISH: { FINISH: {
title: '已完成' title: "已完成",
}, },
EXPIRED: { EXPIRED: {
code: 4, code: 4,
title: '未成功开课', title: "未成功开课",
color: '#CCCCCC' color: "#CCCCCC",
} },
} };
class PreviewCourseModal extends React.Component { class PreviewCourseModal extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = {} this.state = {
activeTab: "courseChapter",
};
} }
dealTimeDuration = (time) => { dealTimeDuration = (time) => {
const diff = Math.floor(time % 3600) const diff = Math.floor(time % 3600);
let hours = Math.floor(time / 3600) let hours = Math.floor(time / 3600);
let mins = Math.floor(diff / 60) let mins = Math.floor(diff / 60);
let seconds = Math.floor(time % 60) let seconds = Math.floor(time % 60);
hours = hours < 10 ? '0' + hours : hours hours = hours < 10 ? "0" + hours : hours;
mins = mins < 10 ? '0' + mins : mins mins = mins < 10 ? "0" + mins : mins;
seconds = seconds < 10 ? '0' + seconds : seconds seconds = seconds < 10 ? "0" + seconds : seconds;
return hours + ':' + mins + ':' + seconds return hours + ":" + mins + ":" + seconds;
} };
dealWithTime = (startTime, endTime) => { dealWithTime = (startTime, endTime) => {
const startDate = new Date(Number(startTime)) const startDate = new Date(Number(startTime));
const endDate = new Date(Number(endTime)) const endDate = new Date(Number(endTime));
const year = startDate.getFullYear() const year = startDate.getFullYear();
const month = startDate.getMonth() + 1 < 10 ? `0${startDate.getMonth() + 1}` : startDate.getMonth() + 1 const month =
const day = startDate.getDate() < 10 ? `0${startDate.getDate()}` : startDate.getDate() startDate.getMonth() + 1 < 10
? `0${startDate.getMonth() + 1}`
: startDate.getMonth() + 1;
const day =
startDate.getDate() < 10
? `0${startDate.getDate()}`
: startDate.getDate();
const startHour = startDate.getHours() < 10 ? `0${startDate.getHours()}` : startDate.getHours() const startHour =
const startMinute = startDate.getMinutes() < 10 ? `0${startDate.getMinutes()}` : startDate.getMinutes() startDate.getHours() < 10
? `0${startDate.getHours()}`
: startDate.getHours();
const startMinute =
startDate.getMinutes() < 10
? `0${startDate.getMinutes()}`
: startDate.getMinutes();
const endHour = endDate.getHours() < 10 ? `0${endDate.getHours()}` : endDate.getHours() const endHour =
const endMinute = endDate.getMinutes() < 10 ? `0${endDate.getMinutes()}` : endDate.getMinutes() endDate.getHours() < 10 ? `0${endDate.getHours()}` : endDate.getHours();
const endMinute =
endDate.getMinutes() < 10
? `0${endDate.getMinutes()}`
: endDate.getMinutes();
const liveDateStr = `${year}-${month}-${day}` const liveDateStr = `${year}-${month}-${day}`;
const startTimeStr = `${startHour}:${startMinute}` const startTimeStr = `${startHour}:${startMinute}`;
const endTimeStr = `${endHour}:${endMinute}` const endTimeStr = `${endHour}:${endMinute}`;
return { return {
liveDateStr, liveDateStr,
startTimeStr, startTimeStr,
endTimeStr endTimeStr,
} };
} };
render() { render() {
const { courseBasicInfo, courseClassInfo = {}, courseIntroInfo, type, courseState, origin } = this.props const {
const { coverUrl, courseName, scheduleVideoUrl, videoDuration } = courseBasicInfo courseBasicInfo,
const { liveDate, calendarTime, startTime, endTime, timeHorizonStart, timeHorizonEnd, teacherName } = courseClassInfo courseClassInfo = {},
const { introduce } = courseIntroInfo courseIntroInfo,
type,
let liveDateStr, startTimeStr, endTimeStr courseState,
courseChapterList = [],
if (type === 'add') { } = this.props;
const _liveDate = moment(calendarTime[0]).format('YYYY-MM-DD') const { coverUrl, courseName, scheduleVideoUrl, videoDuration } =
console.log('_liveDate', _liveDate) courseBasicInfo;
const _timeHorizonStart = moment(startTime).format('HH:mm') const {
const _timeHorizonEnd = moment(endTime).format('HH:mm') liveDate,
const _startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x') calendarTime,
const _endTime = moment(_liveDate + ' ' + _timeHorizonEnd).format('x') startTime,
const { liveDateStr: _liveDateStr, startTimeStr: _startTimeStr, endTimeStr: _endTimeStr } = this.dealWithTime(_startTime, _endTime) endTime,
timeHorizonStart,
liveDateStr = _liveDateStr timeHorizonEnd,
startTimeStr = _startTimeStr teacherName,
endTimeStr = _endTimeStr } = courseClassInfo;
const { introduce, categoryName } = courseIntroInfo;
let { activeTab } = this.state;
let liveDateStr, startTimeStr, endTimeStr;
if (type === "add") {
const _liveDate = moment(calendarTime[0]).format("YYYY-MM-DD");
console.log("_liveDate", _liveDate);
const _timeHorizonStart = moment(startTime).format("HH:mm");
const _timeHorizonEnd = moment(endTime).format("HH:mm");
const _startTime = moment(_liveDate + " " + _timeHorizonStart).format(
"x"
);
const _endTime = moment(_liveDate + " " + _timeHorizonEnd).format("x");
const {
liveDateStr: _liveDateStr,
startTimeStr: _startTimeStr,
endTimeStr: _endTimeStr,
} = this.dealWithTime(_startTime, _endTime);
liveDateStr = _liveDateStr;
startTimeStr = _startTimeStr;
endTimeStr = _endTimeStr;
} else { } else {
const _liveDate = moment(liveDate).format('YYYY-MM-DD') const _liveDate = moment(liveDate).format("YYYY-MM-DD");
const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm') const _timeHorizonStart = moment(timeHorizonStart).format("HH:mm");
const _timeHorizonEnd = moment(timeHorizonEnd).format('HH:mm') const _timeHorizonEnd = moment(timeHorizonEnd).format("HH:mm");
const startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x') const startTime = moment(_liveDate + " " + _timeHorizonStart).format("x");
const endTime = moment(_liveDate + ' ' + _timeHorizonEnd).format('x') const endTime = moment(_liveDate + " " + _timeHorizonEnd).format("x");
const { liveDateStr: _liveDateStr, startTimeStr: _startTimeStr, endTimeStr: _endTimeStr } = this.dealWithTime(startTime, endTime) const {
liveDateStr: _liveDateStr,
liveDateStr = _liveDateStr startTimeStr: _startTimeStr,
startTimeStr = _startTimeStr endTimeStr: _endTimeStr,
endTimeStr = _endTimeStr } = this.dealWithTime(startTime, endTime);
liveDateStr = _liveDateStr;
startTimeStr = _startTimeStr;
endTimeStr = _endTimeStr;
} }
return ( return (
<Modal <Modal
title='预览' title="预览"
visible={true} visible={true}
width={680} width={680}
onCancel={this.props.close} onCancel={this.props.close}
footer={null} footer={null}
maskClosable={false} maskClosable={false}
closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>} closeIcon={
className='preview-live-course-modal'> <span className="icon iconfont modal-close-icon">&#xe6ef;</span>
<div className='container__wrap'> }
<div className='container'> className="preview-live-course-modal"
>
<div className="container__wrap">
<div className="container">
<div className='container__header'> <div className='container__header'>
{type === 'videoCourse' ? ( <Choose>
<video <When condition={type === 'videoCourse'}>
controls <img src={coverUrl ? coverUrl : `https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png`} className='course-cover' alt='' />
src={scheduleVideoUrl} </When>
poster={coverUrl ? coverUrl : `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`} <Otherwise>
className='course-url' <img src={coverUrl} className='course-cover' alt='' />
/> </Otherwise>
) : ( </Choose>
<img src={coverUrl} className='course-cover' />
)}
</div> </div>
{type === 'videoCourse' ? ( <Choose>
<When condition={type === 'videoCourse'}>
<div className='container__body'> <div className='container__body'>
<div className='title__name'>{courseName}</div> <div className='title__name'>{courseName}</div>
{videoDuration && <div>视频时长:{this.dealTimeDuration(videoDuration)}</div>} <div className='title__category'>课程分类:{categoryName}</div>
<div className='title__chapter'>共{courseChapterList.length}小节</div>
</div> </div>
) : ( </When>
<Otherwise>
<div className='container__body'> <div className='container__body'>
<div className='container__body__title'> <div className='container__body__title'>
<div className='title__name'>{courseName}</div> <div className='title__name'>{courseName}</div>
...@@ -152,28 +203,71 @@ class PreviewCourseModal extends React.Component { ...@@ -152,28 +203,71 @@ class PreviewCourseModal extends React.Component {
<span className='teacher__value'>{teacherName}</span> <span className='teacher__value'>{teacherName}</span>
</div> </div>
</div> </div>
)} </Otherwise>
</Choose>
<div className="container__introduction">
<Choose>
<When condition={type === "videoCourse"}>
<Tabs
activeKey={activeTab}
onChange={(key) => {
this.setState({
activeTab: key,
});
}}
>
<TabPane tab="课程目录" key="courseChapter"></TabPane>
<TabPane tab="课程简介" key="courseIntro"></TabPane>
</Tabs>
</When>
<Otherwise>
<div className="container__introduction__title">
直播课简介
</div>
</Otherwise>
</Choose>
<div className='container__introduction'> <Choose>
{type === 'videoCourse' ? ( <When condition={type === "videoCourse"}>
<div className='container__introduction__title'>视频课简介</div> {activeTab === "courseChapter" && (
) : ( <div className="container__chapter">
<div className='container__introduction__title'>直播课简介</div> {
<ChapterList
chapterType="VIDEO"
courseChapterList={courseChapterList}
/>
}
</div>
)}
{activeTab === "courseIntro" && (
<div className="container__introduction__list editor-box">
<div
className="intro-item text"
dangerouslySetInnerHTML={{
__html: introduce,
}}
/>
</div>
)} )}
<div className='container__introduction__list editor-box'> </When>
<Otherwise>
<div className="container__introduction__list editor-box">
<div <div
className='intro-item text' className="intro-item text"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: introduce __html: introduce,
}} }}
/> />
</div> </div>
</Otherwise>
</Choose>
</div> </div>
</div> </div>
</div> </div>
</Modal> </Modal>
) );
} }
} }
export default PreviewCourseModal export default PreviewCourseModal;
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
} }
.container { .container {
overflow: scroll; overflow-y: auto;
overflow-x: hidden;
height: 100%;; height: 100%;;
.course-cover, .course-url { .course-cover, .course-url {
...@@ -30,6 +31,16 @@ ...@@ -30,6 +31,16 @@
color: #000; color: #000;
} }
.title__category {
color: #999999;
margin-top: 4px;
margin-bottom: 4px;
}
.title__chapter {
color: #999999;
margin-bottom: 4px;
}
.title__inst-name { .title__inst-name {
color: #666; color: #666;
font-size: 12px; font-size: 12px;
......
...@@ -116,11 +116,7 @@ class ShareLiveModal extends React.Component { ...@@ -116,11 +116,7 @@ class ShareLiveModal extends React.Component {
} }
break; break;
case 'videoClass': // 视频课 case 'videoClass': // 视频课
coverImgSrc = coverImgSrc = coverUrl || 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png';
coverUrl ||
(courseDivision === 'internal'
? `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring`
: 'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png');
break; break;
case 'graphicsClass': // 图文课 case 'graphicsClass': // 图文课
coverImgSrc = coverUrl || 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png'; coverImgSrc = coverUrl || 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png';
......
...@@ -2,60 +2,46 @@ ...@@ -2,60 +2,46 @@
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-05 10:07:47 * @Date: 2020-08-05 10:07:47
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-07-06 14:47:23 * @LastEditTime: 2021-07-18 10:13:42
* @Description: 线下课新增/编辑页 * @Description: 线下课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
import React from 'react'; import React from 'react';
import { import { Button, Input, Radio, message, Modal, TreeSelect, Select, Switch, TimePicker, InputNumber, Tooltip } from 'antd';
Button,
Input,
Radio,
message,
Modal,
TreeSelect,
Select,
Switch,
TimePicker,
InputNumber,
Tooltip,
} from 'antd';
import $ from 'jquery'; import $ from 'jquery';
import RangePicker from "@/modules/common/DateRangePicker"; import RangePicker from '@/modules/common/DateRangePicker';
import ShowTips from "@/components/ShowTips"; import ShowTips from '@/components/ShowTips';
import Breadcrumbs from "@/components/Breadcrumbs"; import Breadcrumbs from '@/components/Breadcrumbs';
import SelectStudent from '../modal/select-student';
import SelectPrepareFileModal from '../../prepare-lesson/modal/SelectPrepareFileModal'; import SelectPrepareFileModal from '../../prepare-lesson/modal/SelectPrepareFileModal';
import PreviewOfflineModal from './modal/PreviewOfflineModal'; import PreviewOfflineModal from './modal/PreviewOfflineModal';
import StoreService from "@/domains/store-domain/storeService"; import StoreService from '@/domains/store-domain/storeService';
import Service from '@/common/js/service'; import Service from '@/common/js/service';
import { randomString } from '@/domains/basic-domain/utils'; import { randomString } from '@/domains/basic-domain/utils';
import User from '@/common/js/user'; import User from '@/common/js/user';
import _ from "underscore"; import _ from 'underscore';
import moment from 'moment'; import moment from 'moment';
import Upload from '@/core/upload'; import Upload from '@/core/upload';
import GraphicsEditor from '../components/GraphicsEditor'; import GraphicsEditor from '../components/GraphicsEditor';
import MultipleDatePicker from '@/components/MultipleDatePicker'; import MultipleDatePicker from '@/components/MultipleDatePicker';
import ImgClipModal from '@/components/ImgClipModal' import ImgClipModal from '@/components/ImgClipModal';
import './AddOfflineCourse.less'; import './AddOfflineCourse.less';
import Bus from '@/core/bus';
const { Option } = Select; const { Option } = Select;
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/pxbWKsYA87.png'; const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/pxbWKsYA87.png';
let cutFlag = false;
const unitList = [ const unitList = [
{ key: 'HOUR', value: '小时' }, { key: 'HOUR', value: '小时' },
{ key: 'MINUTE', value: '分钟' }, { key: 'MINUTE', value: '分钟' },
] ];
class AddOfflineCourse extends React.Component { class AddOfflineCourse extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const courseId = getParameterByName("id"); const courseId = window.getParameterByName('id');
const pageType = getParameterByName("type"); const pageType = window.getParameterByName('type');
this.state = { this.state = {
courseId, // 线下课ID,编辑的时候从URL上带过来 courseId, // 线下课ID,编辑的时候从URL上带过来
...@@ -71,17 +57,17 @@ class AddOfflineCourse extends React.Component { ...@@ -71,17 +57,17 @@ class AddOfflineCourse extends React.Component {
selectedFileList: [], // 已经从资料云盘中勾选的文件 selectedFileList: [], // 已经从资料云盘中勾选的文件
showCutModal: false, // 是否显示截图弹窗 showCutModal: false, // 是否显示截图弹窗
studentModal: false, studentModal: false,
categoryName:null, //分类名称 categoryName: null, //分类名称
categoryList: [], categoryList: [],
courseCatalogList:[], //分类列表 courseCatalogList: [], //分类列表
categoryId:null, //分类的Id值 categoryId: null, //分类的Id值
whetherVisitorsJoin: 'NO', // 是否允许游客加入 whetherVisitorsJoin: 'NO', // 是否允许游客加入
isContent: true, isContent: true,
teacherList: [], teacherList: [],
teacherQuery: { teacherQuery: {
size: 15, size: 15,
current: 1, current: 1,
nickName:null nickName: null,
}, },
calendarTime: [], calendarTime: [],
offlineCourseType: 'ALL_DAY_OFFLINE', offlineCourseType: 'ALL_DAY_OFFLINE',
...@@ -101,7 +87,7 @@ class AddOfflineCourse extends React.Component { ...@@ -101,7 +87,7 @@ class AddOfflineCourse extends React.Component {
isEditDisablie: false, isEditDisablie: false,
startTime: new Date().getTime() + 300000, // 批量开始时分 startTime: new Date().getTime() + 300000, // 批量开始时分
endTime: new Date().getTime() + 300000, // 批量结束时分 endTime: new Date().getTime() + 300000, // 批量结束时分
} };
} }
componentWillMount() { componentWillMount() {
...@@ -111,30 +97,35 @@ class AddOfflineCourse extends React.Component { ...@@ -111,30 +97,35 @@ class AddOfflineCourse extends React.Component {
if (pageType === 'edit') { if (pageType === 'edit') {
this.handleFetchScheudleDetail(courseId); this.handleFetchScheudleDetail(courseId);
} }
Bus.bind('editorLimit', (editorTextLength) => {
this.setState({
editorTextLength,
});
});
} }
initBus = () => { initBus = () => {
Bus.bind('offlineEditorImage', this.uploadImage) Bus.bind('offlineEditorImage', this.uploadImage);
} };
removeBus = () => { removeBus = () => {
Bus.unbind('offlineEditorImage', this.uploadImage) Bus.unbind('offlineEditorImage', this.uploadImage);
} };
uploadImage = () => { uploadImage = () => {
this.setState({ showSelectImageModal: true }) this.setState({ showSelectImageModal: true });
} };
//获取分类列表 //获取分类列表
getCourseCatalogList = ()=>{ getCourseCatalogList = () => {
Service.Hades('public/hades/queryCategoryTree', { source: 0, tenantId: User.getStoreId(), count: false, userId: User.getUserId() }).then((res) => { Service.Hades('public/hades/queryCategoryTree', { source: 0, tenantId: User.getStoreId(), count: false, userId: User.getUserId() }).then((res) => {
const { categoryList = [] } = res.result; const { categoryList = [] } = res.result;
this.setState({ this.setState({
categoryList, categoryList,
courseCatalogList: this.renderTreeNodes(categoryList), courseCatalogList: this.renderTreeNodes(categoryList),
})
}); });
} });
};
renderTreeNodes = (data) => { renderTreeNodes = (data) => {
let newTreeData = data.map((item) => { let newTreeData = data.map((item) => {
...@@ -151,22 +142,21 @@ class AddOfflineCourse extends React.Component { ...@@ -151,22 +142,21 @@ class AddOfflineCourse extends React.Component {
checkDetail = (courseId) => { checkDetail = (courseId) => {
return Service.Hades('public/hades/getOfflineCourseDetail', { return Service.Hades('public/hades/getOfflineCourseDetail', {
courseId courseId,
}).then((res) => { }).then((res) => {
const { courseState } = res.result; const { courseState } = res.result;
return courseState === 'UN_START'; return courseState === 'UN_START';
}); });
} };
// 获取线下课详情 // 获取线下课详情
handleFetchScheudleDetail = (courseId) => { handleFetchScheudleDetail = (courseId) => {
return Service.Hades('public/hades/getOfflineCourseDetail',{ return Service.Hades('public/hades/getOfflineCourseDetail', {
courseId courseId,
}).then((res) => { }).then((res) => {
const { result = {} } = res || {}; const { result = {} } = res || {};
const { const {
courseName, courseName,
courseState,
categoryId, categoryId,
offlinePlace, offlinePlace,
whetherVisitorsJoin, whetherVisitorsJoin,
...@@ -199,15 +189,15 @@ class AddOfflineCourse extends React.Component { ...@@ -199,15 +189,15 @@ class AddOfflineCourse extends React.Component {
let hasIntro = false; let hasIntro = false;
courseMediaVOS.map((item) => { courseMediaVOS.map((item) => {
switch (item.contentType){ switch (item.contentType) {
case "COVER": case 'COVER':
coverId = item.mediaContent; coverId = item.mediaContent;
coverUrl = item.mediaUrl; coverUrl = item.mediaUrl;
break; break;
case "SCHEDULE": case 'SCHEDULE':
this.getTextDetail('courseMedia', item.mediaUrl); this.getTextDetail('courseMedia', item.mediaUrl);
break; break;
case "INTRO": case 'INTRO':
hasIntro = true; hasIntro = true;
this.getTextDetail('introduce', item.mediaUrl); this.getTextDetail('introduce', item.mediaUrl);
break; break;
...@@ -215,7 +205,7 @@ class AddOfflineCourse extends React.Component { ...@@ -215,7 +205,7 @@ class AddOfflineCourse extends React.Component {
break; break;
} }
return item; return item;
}) });
this.setState({ this.setState({
loadintroduce: !hasIntro, loadintroduce: !hasIntro,
coverId, coverId,
...@@ -248,49 +238,42 @@ class AddOfflineCourse extends React.Component { ...@@ -248,49 +238,42 @@ class AddOfflineCourse extends React.Component {
signOutType, signOutType,
isEditDisablie: whetherHaveApply === 'YES', isEditDisablie: whetherHaveApply === 'YES',
}); });
}) });
} };
getTextDetail = (key, url) => { getTextDetail = (key, url) => {
$.ajax({ $.ajax({
data: {}, data: {},
type: 'GET', type: 'GET',
url, url,
contentType:'application/x-www-form-urlencoded; charset=UTF-8', contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
success: (res) => { success: (res) => {
this.setState({ [key]: res, [`load${key}`]: true }); this.setState({ [key]: res, [`load${key}`]: true });
} },
}) });
} };
handleGoBack = () => { handleGoBack = () => {
const { const { coverId, videoName, videoDuration, courseName, categoryId, whetherVisitorsJoin } = this.state;
coverId, if (videoName || videoDuration || categoryId || courseName || coverId || whetherVisitorsJoin !== whetherVisitorsJoin) {
videoName,
videoDuration,
courseName,
categoryId,
whetherVisitorsJoin
} = this.state;
if(videoName || videoDuration || categoryId || courseName || coverId || whetherVisitorsJoin !== whetherVisitorsJoin ){
Modal.confirm({ Modal.confirm({
title: '确认要返回吗?', title: '确认要返回吗?',
content: '返回后,本次编辑的内容将不被保存。', content: '返回后,本次编辑的内容将不被保存。',
okText: '确认返回', okText: '确认返回',
cancelText: '留在本页', cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>, icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => { onOk: () => {
window.RCHistory.push({ window.RCHistory.push({
pathname: `/offline-course`, pathname: `/offline-course`,
}); });
} },
}); });
}else{ } else {
window.RCHistory.push({ window.RCHistory.push({
pathname: `/offline-course`, pathname: `/offline-course`,
}); });
} }
} };
// 显示预览弹窗 // 显示预览弹窗
handleShowPreviewModal = () => { handleShowPreviewModal = () => {
...@@ -340,67 +323,68 @@ class AddOfflineCourse extends React.Component { ...@@ -340,67 +323,68 @@ class AddOfflineCourse extends React.Component {
signOutStartTimeUnit, signOutStartTimeUnit,
signOutEndTimeNum, signOutEndTimeNum,
signOutEndTimeUnit, signOutEndTimeUnit,
} };
const previewOfflineModal = ( const previewOfflineModal = (
<PreviewOfflineModal <PreviewOfflineModal
data={data} data={data}
close={() => { close={() => {
this.setState({ this.setState({
previewOfflineModal: null previewOfflineModal: null,
}) });
}} }}
/> />
); );
this.setState({ previewOfflineModal }); this.setState({ previewOfflineModal });
} };
handleSelectCover = (file)=> { handleSelectCover = (file) => {
this.setState({ this.setState({
visible: true, visible: true,
imageFile:file imageFile: file
}); });
} };
//获取resourceId //获取resourceId
getSignature = (blob, fileName) => { getSignature = (blob, fileName) => {
Upload.uploadBlobToOSS(blob, 'cover' + (new Date()).valueOf(),null,'signInfo').then((signInfo) => { Upload.uploadBlobToOSS(blob, 'cover' + new Date().valueOf(), null, 'signInfo').then((signInfo) => {
this.setState({ this.setState(
coverClicpPath:signInfo.fileUrl, {
coverId:signInfo.resourceId, coverClicpPath: signInfo.fileUrl,
visible: false coverId: signInfo.resourceId,
},()=>this.updateCover()) visible: false,
},
() => this.updateCover()
);
}); });
}; };
updateCover = () =>{ updateCover = () => {
const {coverClicpPath,coverId} = this.state const { coverClicpPath, coverId } = this.state;
this.setState({ this.setState({
showSelectCoverModal: false, showSelectCoverModal: false,
coverUrl:coverClicpPath, coverUrl: coverClicpPath,
coverId:coverId coverId: coverId,
}) });
} };
preSubmit = () => { preSubmit = () => {
//过期判断 //过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) { if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({ Modal.warning({
title:"服务已到期", title: '服务已到期',
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买", content: '当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买',
okText: "我知道了" okText: '我知道了',
}) });
return return;
} }
const { courseId } = this.state; const { courseId } = this.state;
if (courseId) { if (courseId) {
this.checkDetail(courseId).then(bool => bool ? this.handleSubmit() : message.warning('课程已开始,无法继续编辑')) this.checkDetail(courseId).then((bool) => (bool ? this.handleSubmit() : message.warning('课程已开始,无法继续编辑')));
} else { } else {
this.handleSubmit(); this.handleSubmit();
} }
} };
// 保存 // 保存
handleSubmit = () => { handleSubmit = () => {
...@@ -433,23 +417,24 @@ class AddOfflineCourse extends React.Component { ...@@ -433,23 +417,24 @@ class AddOfflineCourse extends React.Component {
signOutStartTimeUnit, signOutStartTimeUnit,
signOutEndTimeNum, signOutEndTimeNum,
signOutEndTimeUnit, signOutEndTimeUnit,
isMore, // isMore,
editorTextLength,
} = this.state; } = this.state;
let coverObj ={ let coverObj = {
contentType:'COVER', contentType: 'COVER',
mediaContent: coverId, mediaContent: coverId,
mediaType:'PICTURE', mediaType: 'PICTURE',
mediaUrl: coverUrl, mediaUrl: coverUrl,
} };
let scheduleMediaRequests = []; let scheduleMediaRequests = [];
if(coverId){ if (coverId) {
scheduleMediaRequests = [coverObj] scheduleMediaRequests = [coverObj];
} }
// 编辑且使用默认图时不传 // 编辑且使用默认图时不传
if (pageType === 'edit' && coverUrl === defaultCoverUrl) { if (pageType === 'edit' && coverUrl === defaultCoverUrl) {
scheduleMediaRequests = [] scheduleMediaRequests = [];
} }
const commonParams = { const commonParams = {
categoryId, categoryId,
...@@ -466,7 +451,8 @@ class AddOfflineCourse extends React.Component { ...@@ -466,7 +451,8 @@ class AddOfflineCourse extends React.Component {
startTime, startTime,
endTime, endTime,
calendarTime, calendarTime,
isMore, editorTextLength,
// isMore,
}; };
if (whetherSetApply === 'YES') { if (whetherSetApply === 'YES') {
...@@ -494,16 +480,21 @@ class AddOfflineCourse extends React.Component { ...@@ -494,16 +480,21 @@ class AddOfflineCourse extends React.Component {
// 校验必填字段:课程名称, 课程线下 // 校验必填字段:课程名称, 课程线下
this.handleValidate(commonParams).then((res) => { this.handleValidate(commonParams).then((res) => {
if (!res) return; if (!res) return;
Upload.uploadTextToOSS(introduce, `${randomString()}.txt`, (introduceId) => { Upload.uploadTextToOSS(
introduce,
`${randomString()}.txt`,
(introduceId) => {
this.submitRemote({ this.submitRemote({
courseId, courseId,
pageType, pageType,
commonParams, commonParams,
introduceId, introduceId,
}); });
}, () => message.warning('上传课程简介失败')); },
() => message.warning('上传课程简介失败')
);
}); });
} };
submitRemote = (data) => { submitRemote = (data) => {
const { courseId, pageType, commonParams, introduceId } = data; const { courseId, pageType, commonParams, introduceId } = data;
...@@ -511,75 +502,78 @@ class AddOfflineCourse extends React.Component { ...@@ -511,75 +502,78 @@ class AddOfflineCourse extends React.Component {
if (pageType === 'add') { if (pageType === 'add') {
Service.Hades('public/hades/createOfflineCourse', commonParams).then((res) => { Service.Hades('public/hades/createOfflineCourse', commonParams).then((res) => {
if (!res) return; if (!res) return;
message.success("新建成功"); message.success('新建成功');
window.RCHistory.push({ window.RCHistory.push({
pathname: `/offline-course`, pathname: `/offline-course`,
}); });
}) });
} else { } else {
const editParams = { const editParams = {
courseId:courseId, courseId: courseId,
...commonParams, ...commonParams,
} };
Service.Hades('public/hades/updateOfflineCourse', editParams).then((res) => { Service.Hades('public/hades/updateOfflineCourse', editParams).then((res) => {
if (!res) return; if (!res) return;
message.success("保存成功"); message.success('保存成功');
window.RCHistory.push({ window.RCHistory.push({
pathname: `/offline-course`, pathname: `/offline-course`,
}); });
}); });
} }
} };
handleValidate = (data) => { handleValidate = (data) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!data.courseName) { if (!data.courseName) {
message.warning('请输入课程名称'); message.warning('请输入课程名称');
resolve(false); resolve(false);
} else if(!data.categoryId){ } else if (!data.categoryId) {
message.warning('请选择课程分类'); message.warning('请选择课程分类');
resolve(false); resolve(false);
} else if(!data.offlinePlace){ } else if (!data.offlinePlace) {
message.warning('请输入上课地点'); message.warning('请输入上课地点');
resolve(false); resolve(false);
} else if(!data.teacherId ){ } else if (!data.teacherId) {
message.warning('请选择讲师'); message.warning('请选择讲师');
resolve(false); resolve(false);
} else if(_.isEmpty(data.calendarTime)){ } else if (_.isEmpty(data.calendarTime)) {
message.warning('请选择上课日期'); message.warning('请选择上课日期');
resolve(false); resolve(false);
} else if(!data.startTime || !data.endTime){ } else if (!data.startTime || !data.endTime) {
message.warning('请选择上课时间'); message.warning('请选择上课时间');
resolve(false); resolve(false);
} else if(moment(moment(data.calendarTime[0]).format('YYYY-MM-DD') + moment(data.startTime).format(' HH:mm')).valueOf() < Date.now()){ } else if (moment(moment(data.calendarTime[0]).format('YYYY-MM-DD') + moment(data.startTime).format(' HH:mm')).valueOf() < Date.now()) {
message.warning('上课时间不能早于现在'); message.warning('上课时间不能早于现在');
resolve(false); resolve(false);
} else if(data.startTime >= data.endTime){ } else if (data.startTime >= data.endTime) {
message.warning('上课结束时间不能早于上课开始时间'); message.warning('上课结束时间不能早于上课开始时间');
resolve(false); resolve(false);
} else if(data.whetherSetApply === 'YES' && !data.startTimeApply){ } else if (data.whetherSetApply === 'YES' && !data.startTimeApply) {
message.warning('请选择报名时间'); message.warning('请选择报名时间');
resolve(false); resolve(false);
} else if(data.whetherSetApply === 'YES' && data.startTimeApply >= data.endTimeApply){ } else if (data.whetherSetApply === 'YES' && data.startTimeApply >= data.endTimeApply) {
message.warning('报名结束时间需大于报名开始时间'); message.warning('报名结束时间需大于报名开始时间');
resolve(false); resolve(false);
} else if(data.whetherSetApply === 'YES' && data.endTimeApply > moment(moment(data.calendarTime[0]).format('YYYY-MM-DD') + moment(data.endTime).format(' HH:mm:ss')).valueOf()){ } else if (
data.whetherSetApply === 'YES' &&
data.endTimeApply > moment(moment(data.calendarTime[0]).format('YYYY-MM-DD') + moment(data.endTime).format(' HH:mm:ss')).valueOf()
) {
message.warning('报名结束时间需小于上课开始时间'); message.warning('报名结束时间需小于上课开始时间');
resolve(false); resolve(false);
} else if(data.whetherSetSignIn === 'YES' && !data.signInTimeNum){ } else if (data.whetherSetSignIn === 'YES' && !data.signInTimeNum) {
message.warning('请输入签到时间'); message.warning('请输入签到时间');
resolve(false); resolve(false);
} else if(data.whetherSetSignOut === 'YES' && ((data.signOutType === 'START_LATER' && !data.signOutStartTimeNum) || !data.signOutEndTimeNum)){ } else if (data.whetherSetSignOut === 'YES' && ((data.signOutType === 'START_LATER' && !data.signOutStartTimeNum) || !data.signOutEndTimeNum)) {
message.warning('请输入签退时间'); message.warning('请输入签退时间');
resolve(false); resolve(false);
} else if (data.isMore) { } else if (data.editorTextLength > 1000) {
message.warning('简介超过字数限定'); message.warning('课程简介超过字数限定');
resolve(false); resolve(false);
} else { } else {
resolve(true); resolve(true);
} }
}); });
} };
// 使用默认封面图 // 使用默认封面图
handleResetCoverUrl = () => { handleResetCoverUrl = () => {
...@@ -589,7 +583,7 @@ class AddOfflineCourse extends React.Component { ...@@ -589,7 +583,7 @@ class AddOfflineCourse extends React.Component {
if (isDefaultCover) return; if (isDefaultCover) return;
message.success('已替换为默认图'); message.success('已替换为默认图');
this.setState({ coverUrl: defaultCoverUrl }); this.setState({ coverUrl: defaultCoverUrl });
} };
// 滑动加载更多讲师列表 // 滑动加载更多讲师列表
handleScrollTeacherList = (e) => { handleScrollTeacherList = (e) => {
...@@ -601,46 +595,47 @@ class AddOfflineCourse extends React.Component { ...@@ -601,46 +595,47 @@ class AddOfflineCourse extends React.Component {
if (scrollToBottom && hasNext) { if (scrollToBottom && hasNext) {
const { teacherQuery } = this.state; const { teacherQuery } = this.state;
let _teacherQuery = teacherQuery; let _teacherQuery = teacherQuery;
_teacherQuery.current = _teacherQuery.current + 1 _teacherQuery.current = _teacherQuery.current + 1;
this.setState({ this.setState(
teacherQuery:{..._teacherQuery} {
},()=>{this.getTeacherList(_teacherQuery.current)}) teacherQuery: { ..._teacherQuery },
},
() => {
this.getTeacherList(_teacherQuery.current);
} }
);
} }
};
getTeacherList(current = 1, selectList){ getTeacherList(current = 1, selectList) {
const { teacherQuery,teacherList} = this.state; const { teacherQuery, teacherList } = this.state;
const _query = { const _query = {
...teacherQuery, ...teacherQuery,
current, current,
size:15 size: 15,
}; };
StoreService.getStoreUserBasicPage( _query).then((res) => { StoreService.getStoreUserBasicPage(_query).then((res) => {
const { result = {} } = res; const { result = {} } = res;
const { records = [], total = 0, hasNext } = result; const { records = [], hasNext } = result;
const list = current > 1 ? teacherList.concat(records) : records; const list = current > 1 ? teacherList.concat(records) : records;
this.setState({ this.setState({
hasNext, hasNext,
teacherList: list, teacherList: list,
teacherQuery:{..._query} teacherQuery: { ..._query },
}) });
}); });
} }
changeIntro = (value, textLength) => { changeIntro = (value) => {
const isMore = textLength > 1000; this.setState({ introduce: value });
if (isMore) { };
message.warning('内容过长,不能超过1000字');
}
this.setState({ introduce: value, isMore });
}
selectMultiDate = (calendarTime) => { selectMultiDate = (calendarTime) => {
const dateList = _.sortBy(calendarTime); const dateList = _.sortBy(calendarTime);
this.setState({ this.setState({
calendarTime: dateList, calendarTime: dateList,
}) });
} };
handleChangeDates = (dates) => { handleChangeDates = (dates) => {
const data = {}; const data = {};
...@@ -652,15 +647,20 @@ class AddOfflineCourse extends React.Component { ...@@ -652,15 +647,20 @@ class AddOfflineCourse extends React.Component {
data.endTimeApply = dates[1].startOf('minute').valueOf() + 59000; data.endTimeApply = dates[1].startOf('minute').valueOf() + 59000;
} }
this.setState(data); this.setState(data);
} };
whetherVisitorsJoinChange = ()=>{ whetherVisitorsJoinChange = () => {
if(this.state.whetherVisitorsJoin === "NO"){ const {whetherSetApply,whetherVisitorsJoin} =this.state;
if(whetherSetApply =='NO'){
message.warning('关闭报名无法获取手机号!')
return
}
if (this.state.whetherVisitorsJoin === 'NO') {
this.setState({ whetherVisitorsJoin: 'YES' }); this.setState({ whetherVisitorsJoin: 'YES' });
}else{ } else {
this.setState({ whetherVisitorsJoin: 'NO' }); this.setState({ whetherVisitorsJoin: 'NO' });
} }
} };
handleChangeCatalogList = (value, label) => { handleChangeCatalogList = (value, label) => {
this.setState({ categoryId: value, categoryName: label[0] }); this.setState({ categoryId: value, categoryName: label[0] });
...@@ -674,14 +674,11 @@ class AddOfflineCourse extends React.Component { ...@@ -674,14 +674,11 @@ class AddOfflineCourse extends React.Component {
coverUrl, coverUrl,
introduce, introduce,
categoryId, categoryId,
categoryList,
courseCatalogList, courseCatalogList,
whetherVisitorsJoin, whetherVisitorsJoin,
loadintroduce, loadintroduce,
showSelectCoverModal, showSelectCoverModal,
visible, visible,
hasImgReady,
cutImageBlob,
teacherId, teacherId,
teacherList, teacherList,
calendarTime, calendarTime,
...@@ -709,64 +706,66 @@ class AddOfflineCourse extends React.Component { ...@@ -709,64 +706,66 @@ class AddOfflineCourse extends React.Component {
} = this.state; } = this.state;
const isDefaultCover = coverUrl === defaultCoverUrl; const isDefaultCover = coverUrl === defaultCoverUrl;
return ( return (
<div className="page add-offline-course-page"> <div className='page add-offline-course-page'>
<Breadcrumbs <Breadcrumbs navList={pageType === 'add' ? '新建线下课' : '编辑线下课'} goBack={this.handleGoBack} />
navList={pageType === "add" ? "新建线下课" : "编辑线下课"}
goBack={this.handleGoBack}
/>
<div className="box"> <div className='box'>
<div className="show-tips"> <div className='show-tips'>
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企学院保有依据国家规定及平台规则进行处理的权利" /> <ShowTips message='请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企学院保有依据国家规定及平台规则进行处理的权利' />
</div> </div>
<div className="form"> <div className='form'>
<div className="basic-info__wrap"> <div className='basic-info__wrap'>
<div className="title">基本信息</div> <div className='title'>基本信息</div>
<div className="course-name"> <div className='course-name'>
<span className="label"><span className="require">*</span>课程名称:</span> <span className='label'>
<span className='require'>*</span>课程名称:
</span>
<Input <Input
value={courseName} value={courseName}
placeholder="请输入线下课的名称(40字以内)" placeholder='请输入线下课的名称(40字以内)'
maxLength={40} maxLength={40}
style={{ width: 240 }} style={{ width: 240 }}
onChange={(e) => { this.setState({ courseName: e.target.value }) }} onChange={(e) => {
this.setState({ courseName: e.target.value });
}}
/> />
</div> </div>
<div className="course-cover"> <div className='course-cover'>
<span className="label">封面图:</span> <span className='label'>封面图:</span>
<div className="course-cover__wrap"> <div className='course-cover__wrap'>
<div className='opt-btns'>
<div className="img-content"> <Button
{ onClick={() => {
isDefaultCover && <span className="tag">默认图</span>
}
<img src={coverUrl} />
</div>
<div className="opt-btns">
<Button onClick={() => {
this.setState({ this.setState({
showSelectCoverModal: true showSelectCoverModal: true,
}) });
}}>上传图片</Button> }}>
<span 上传图片
className={`default-btn ${isDefaultCover ? 'disabled' : ''}`} </Button>
onClick={this.handleResetCoverUrl} <span className={`default-btn ${isDefaultCover ? 'disabled' : ''}`} onClick={this.handleResetCoverUrl}>
>使用默认图</span> 使用默认图
<div className="tips">建议尺寸1280*720px,图片支持jpg、jpeg、png格式。</div> </span>
<div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div> </div>
<div className='img-content'>
{isDefaultCover && <span className='tag'>默认图</span>}
<img src={coverUrl} />
</div> </div>
</div> </div>
<div className="course-catalog"> </div>
<span className="label special"><span className="require">*</span>课程分类:</span> <div className='course-catalog'>
<span className='label special'>
<span className='require'>*</span>课程分类:
</span>
<TreeSelect <TreeSelect
showSearch showSearch
treeNodeFilterProp="title" treeNodeFilterProp='title'
style={{ width: 240 }} style={{ width: 240 }}
dropdownStyle={{ maxHeight: 300, overflow: "auto" }} dropdownStyle={{ maxHeight: 300, overflow: 'auto' }}
treeData={courseCatalogList} treeData={courseCatalogList}
placeholder="请选择课程类型" placeholder='请选择课程类型'
allowClear allowClear
value={categoryId} value={categoryId}
treeDefaultExpandAll treeDefaultExpandAll
...@@ -775,184 +774,175 @@ class AddOfflineCourse extends React.Component { ...@@ -775,184 +774,175 @@ class AddOfflineCourse extends React.Component {
}} }}
/> />
</div> </div>
<div className="course-catalog"> <div className='course-catalog'>
<span className="label special"><span className="require">*</span>上课地点:</span> <span className='label special'>
<span className='require'>*</span>上课地点:
</span>
<Input <Input
value={offlinePlace} value={offlinePlace}
maxLength={40} maxLength={40}
style={{ width: 240 }} style={{ width: 240 }}
placeholder="请输入上课地点(40字以内)" placeholder='请输入上课地点(40字以内)'
onChange={(e) => { onChange={(e) => {
this.setState({ offlinePlace: e.target.value }) this.setState({ offlinePlace: e.target.value });
}} }}
/> />
</div> </div>
<div className="course-catalog" id="teacher"> <div className='course-catalog' id='teacher'>
<span className="label special"><span className="require">* </span>讲师:</span> <span className='label special'>
<span className='require'>* </span>讲师:
</span>
<Select <Select
placeholder="请选择讲师" placeholder='请选择讲师'
value={teacherId} value={teacherId}
style={{ width: 240 }} style={{ width: 240 }}
showSearch showSearch
allowClear allowClear
filterOption={(input, option) => option} filterOption={(input, option) => option}
dropdownClassName="offline-dropdown-box" dropdownClassName='offline-dropdown-box'
onPopupScroll={this.handleScrollTeacherList} onPopupScroll={this.handleScrollTeacherList}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>} suffixIcon={
<span className='icon iconfont' style={{ fontSize: '12px', color: '#BFBFBF' }}>
&#xe835;
</span>
}
onChange={(value, option) => { onChange={(value, option) => {
if (option) { if (option) {
this.setState({ teacherId: value, teacherName: option.children }); this.setState({ teacherId: value, teacherName: option.children });
}else{ } else {
this.setState({ teacherId: value, teacherName: "" }); this.setState({ teacherId: value, teacherName: '' });
} }
}} }}
onSearch={(value) => { onSearch={(value) => {
let _teacherQuery = {...this.state.teacherQuery}; let _teacherQuery = { ...this.state.teacherQuery };
_teacherQuery.nickName = value _teacherQuery.nickName = value;
this.setState({ this.setState(
teacherQuery: _teacherQuery {
}, () => { teacherQuery: _teacherQuery,
this.getTeacherList() },
}) () => {
this.getTeacherList();
}
);
}} }}
onClear ={(value)=>{ onClear={(value) => {
this.setState({ this.setState(
teacherQuery:{ {
teacherQuery: {
size: 15, size: 15,
current: 1, current: 1,
nickName:null nickName: null,
} },
}, () => { },
this.getTeacherList() () => {
}) this.getTeacherList();
}
}
getPopupContainer={() =>
document.getElementById("teacher")
} }
> );
{_.map(teacherList, (item, index) => { }}
getPopupContainer={() => document.getElementById('teacher')}>
{_.map(teacherList, (item) => {
return ( return (
<Option value={item.id} key={item.id}>{item.nickName}</Option> <Option value={item.id} key={item.id}>
{item.nickName}
</Option>
); );
})} })}
</Select> </Select>
</div> </div>
<div className="allow-tourist-join"> <div className='introduce'>
<span className="label">观看设置:</span> <span className='label'>课程简介:</span>
<div className="content"> <div className='content'>
<div> <div className='intro-list'>
<Switch <div className='intro-list__item introduce-editor'>
checked={whetherVisitorsJoin === "YES" ? true : false} {(!courseId || loadintroduce) && (
onChange={this.whetherVisitorsJoinChange}
/>
</div>
<div>
<div className="desc">
<div>开启:允许未绑定手机号的学员观看</div>
<div>关闭:仅限绑定了手机号的学员可以进入观看线下课</div>
</div>
</div>
</div>
</div>
<div className="introduce">
<span className="label">课程简介:</span>
<div className="content">
<div className="intro-list">
<div className="intro-list__item introduce-editor">
{(!courseId || loadintroduce) &&
<GraphicsEditor <GraphicsEditor
id="intro" id='intro'
isIntro={true} isIntro={true}
maxLimit={1000} maxLimit={1000}
detail={{ detail={{
content: introduce content: introduce,
}} }}
onChange={(val, textLength) => { onChange={(val) => {
this.changeIntro(val, textLength) this.changeIntro(val);
}} }}
/> />
} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="title" style={{ marginTop: 24 }}>课程设置</div> <div className='title' style={{ marginTop: 24 }}>
<div className="day"> 课程设置
<span className="label"> </div>
<span className="require">*</span> <div className='day'>
<span className='label'>
<span className='require'>*</span>
上课日期: 上课日期:
</span> </span>
<div> <div>
<div className='select-day'> <div className='select-day'>
已选 <span className="mark-day">{isLongArr(calendarTime) ? calendarTime.length : 0}</span> 天 已选 <span className='mark-day'>{window.isLongArr(calendarTime) ? calendarTime.length : 0}</span> 天
</div> </div>
<MultipleDatePicker <MultipleDatePicker disabled={isEditDisablie} selectDateList={calendarTime} onSelect={this.selectMultiDate} canSelectTodayBefore={false} />
disabled={isEditDisablie}
selectDateList={calendarTime}
onSelect={this.selectMultiDate}
canSelectTodayBefore={false}
/>
</div> </div>
</div> </div>
<div className="hour" id="hour"> <div className='hour' id='hour'>
<span className="label"><span className="require">*</span>上课时间:</span> <span className='label'>
<span className='require'>*</span>上课时间:
</span>
<TimePicker <TimePicker
disabled={isEditDisablie} disabled={isEditDisablie}
className="time-picker" className='time-picker'
format="HH:mm" format='HH:mm'
value={startTime ? moment(startTime) : null} value={startTime ? moment(startTime) : null}
placeholder="开始时间" placeholder='开始时间'
showNow={false} showNow={false}
style={{ width: 100, minWidth: 100}} style={{ width: 100, minWidth: 100 }}
onSelect={(time) => { onSelect={(time) => {
this.setState({ startTime: time }); this.setState({ startTime: time });
}} }}
getPopupContainer={() => getPopupContainer={() => document.getElementById('hour')}
document.getElementById("hour") />
} &nbsp;&nbsp;~&nbsp;&nbsp;
/>&nbsp;&nbsp;~&nbsp;&nbsp;
<TimePicker <TimePicker
disabled={isEditDisablie} disabled={isEditDisablie}
className="time-picker" className='time-picker'
format="HH:mm" format='HH:mm'
value={endTime ? moment(endTime) : null} value={endTime ? moment(endTime) : null}
placeholder="结束时间" placeholder='结束时间'
showNow={false} showNow={false}
style={{ width: 100, minWidth: 100 }} style={{ width: 100, minWidth: 100 }}
onSelect={(time) => { onSelect={(time) => {
this.setState({ endTime: time }); this.setState({ endTime: time });
}} }}
getPopupContainer={() => getPopupContainer={() => document.getElementById('hour')}
document.getElementById("hour")
}
/> />
</div> </div>
<div className="course-catalog"> <div className='course-catalog'>
<span className="label"><span className="require">* </span>学员上课方式:</span> <span className='label'>
<span className='require'>* </span>学员上课方式:
</span>
<Radio.Group <Radio.Group
style={{ display: 'inline-block' }} style={{ display: 'inline-block' }}
value={offlineCourseType} value={offlineCourseType}
onChange={(e) => { onChange={(e) => {
this.setState({ offlineCourseType: e.target.value }); this.setState({ offlineCourseType: e.target.value });
}} }}
className="mt5" className='mt5'
disabled={isEditDisablie} disabled={isEditDisablie}>
> <Radio value='ALL_DAY_OFFLINE' className='mr-16'>
<Radio value="ALL_DAY_OFFLINE" className="mr-16"> <span style={{ color: '#333' }}>所选日期都要上课</span>
<span style={{ color: "#333" }}>所选日期都要上课</span>
</Radio> </Radio>
<Radio value="ANY_DAY_POFFLINE" className="mr-16"> <Radio value='ANY_DAY_POFFLINE' className='mr-16'>
<span style={{ color: "#333" }}>选择任意1天上课</span> <span style={{ color: '#333' }}>选择任意1天上课</span>
</Radio> </Radio>
</Radio.Group> </Radio.Group>
</div> </div>
<div className="course-catalog"> <div className='course-catalog'>
<span className="label">课程报名:</span> <span className='label'>课程报名:</span>
<div className="switch-box"> <div className='switch-box'>
<div className="switch-item" key="1"> <div className='switch-item' key='1'>
<Switch <Switch
disabled={isEditDisablie} disabled={isEditDisablie}
checked={whetherSetApply === 'YES'} checked={whetherSetApply === 'YES'}
...@@ -962,59 +952,98 @@ class AddOfflineCourse extends React.Component { ...@@ -962,59 +952,98 @@ class AddOfflineCourse extends React.Component {
startTimeApply: undefined, startTimeApply: undefined,
endTimeApply: undefined, endTimeApply: undefined,
quota: null, quota: null,
whetherVisitorsJoin: whetherSetApply !== 'YES' ? whetherVisitorsJoin : false
}); });
}} }}
/> />
<span className="switch-tip">开启后可设置课程报名时间,获取报名数据</span> <span className='switch-tip'>开启后可设置课程报名时间,获取报名数据</span>
</div> </div>
{whetherSetApply === 'YES' && <div className="switch-item" key="2"> {whetherSetApply === 'YES' && (
<span className="switch-label">报名日期:</span> <div className='switch-item' key='2'>
<span className='switch-label'>报名日期:</span>
<RangePicker <RangePicker
id="course_date_picker" id='course_date_picker'
showTime={{ showTime: 'HH:mm' }} showTime={{ showTime: 'HH:mm' }}
allowClear={false} allowClear={false}
value={startTimeApply ? [moment(startTimeApply), moment(endTimeApply)] : null } value={startTimeApply ? [moment(startTimeApply), moment(endTimeApply)] : null}
format={"YYYY-MM-DD HH:mm"} format={'YYYY-MM-DD HH:mm'}
onChange={(dates) => { this.handleChangeDates(dates) }} onChange={(dates) => {
renderExtraFooter={() => calendarTime[0] ? <div style={{ position: 'absolute', bottom: 8, cursor: 'pointer' }}> this.handleChangeDates(dates);
}}
renderExtraFooter={() => (
<If condition={calendarTime[0]}>
<div style={{ position: 'absolute', bottom: 8, cursor: 'pointer' }}>
<span <span
onClick={() => this.setState({ startTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).subtract(1, 'days').valueOf(), endTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).valueOf() - 1000 })} onClick={() =>
this.setState({
startTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`)
.subtract(1, 'days')
.valueOf(),
endTimeApply:
moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).valueOf() - 1000,
})
}
style={{ style={{
color: '#2966FF', color: '#2966FF',
border: '1px solid #2966FF', border: '1px solid #2966FF',
padding: '2px 8px', padding: '2px 8px',
borderRadius: '2px', borderRadius: '2px',
marginRight: 8, marginRight: 8,
}} }}>
>上课前1天</span> 上课前1天
</span>
<span <span
onClick={() => this.setState({ startTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).subtract(2, 'days').valueOf(), endTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).valueOf() - 1000 })} onClick={() =>
this.setState({
startTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`)
.subtract(2, 'days')
.valueOf(),
endTimeApply:
moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).valueOf() - 1000,
})
}
style={{ style={{
color: '#2966FF', color: '#2966FF',
border: '1px solid #2966FF', border: '1px solid #2966FF',
padding: '2px 8px', padding: '2px 8px',
borderRadius: '2px', borderRadius: '2px',
marginRight: 8, marginRight: 8,
}} }}>
>上课前2天</span> 上课前2天
</span>
<span <span
onClick={() => this.setState({ startTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).subtract(3, 'days').valueOf(), endTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).valueOf() - 1000 })} onClick={() =>
this.setState({
startTimeApply: moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`)
.subtract(3, 'days')
.valueOf(),
endTimeApply:
moment(`${moment(calendarTime[0]).format('YYYY-MM-DD')} ${moment(startTime).format('HH:mm')}`).valueOf() - 1000,
})
}
style={{ style={{
color: '#2966FF', color: '#2966FF',
border: '1px solid #2966FF', border: '1px solid #2966FF',
padding: '2px 8px', padding: '2px 8px',
borderRadius: '2px', borderRadius: '2px',
marginRight: 8, marginRight: 8,
}} }}>
>上课前3天</span> 上课前3天
</div> : null} </span>
</div>
</If>
)}
/> />
</div>} </div>
{whetherSetApply === 'YES' && <div className="switch-item" key="3"> )}
<span className="switch-label"> {whetherSetApply === 'YES' && (
<div className='switch-item' key='3'>
<span className='switch-label'>
报名人数 报名人数
<Tooltip title="报名一旦开始,报名人数不支持减少"> <Tooltip title='报名一旦开始,报名人数不支持减少'>
<span style={{ margin: '0 4px', color: '#999' }} className="icon iconfont">&#xe7c4;</span> <span style={{ margin: '0 4px', color: '#999' }} className='icon iconfont'>
&#xe7c4;
</span>
</Tooltip> </Tooltip>
:最多 :最多
</span> </span>
...@@ -1026,18 +1055,26 @@ class AddOfflineCourse extends React.Component { ...@@ -1026,18 +1055,26 @@ class AddOfflineCourse extends React.Component {
style={{ margin: '0 4px', width: 90 }} style={{ margin: '0 4px', width: 90 }}
disabled={oldQuta < 0} disabled={oldQuta < 0}
onChange={(value) => { onChange={(value) => {
this.setState({ quota: value }) this.setState({ quota: value });
}} }}
/> />
<span className="switch-label">人</span> <span className='switch-label'>人</span>
<span className="switch-tip">未填写时默认为不限制</span> <span className='switch-tip'>未填写时默认为不限制</span>
</div>} </div>
)}
</div>
</div>
<div className='allow-tourist-join'>
<span className='label'>观看设置:</span>
<div className='content'>
<Switch checked={whetherVisitorsJoin === 'NO' ? true : false} onChange={this.whetherVisitorsJoinChange} />
<div className='desc'>{whetherVisitorsJoin === 'NO' ? '已开启,仅限绑定了手机号的学员报名线下课' : '已关闭,允许未绑定手机号的学员报名线下课'}</div>
</div> </div>
</div> </div>
<div className="course-catalog"> <div className='course-catalog'>
<span className="label">考勤签到:</span> <span className='label'>考勤签到:</span>
<div className="switch-box"> <div className='switch-box'>
<div className="switch-item" key="1"> <div className='switch-item' key='1'>
<Switch <Switch
checked={whetherSetSignIn === 'YES'} checked={whetherSetSignIn === 'YES'}
onChange={(value) => { onChange={(value) => {
...@@ -1046,31 +1083,33 @@ class AddOfflineCourse extends React.Component { ...@@ -1046,31 +1083,33 @@ class AddOfflineCourse extends React.Component {
signInType: 'START_AGO', signInType: 'START_AGO',
signInTimeNum: null, signInTimeNum: null,
signInTimeUnit: 'MINUTE', signInTimeUnit: 'MINUTE',
}) });
}} }}
/> />
<span className="switch-tip">开启后可设置获取签到考勤数据</span> <span className='switch-tip'>开启后可设置获取签到考勤数据</span>
</div> </div>
{whetherSetSignIn === 'YES' && <div className="switch-item" key="2"> {whetherSetSignIn === 'YES' && (
<span className="switch-label">签到时间:</span> <div className='switch-item' key='2'>
<span className='switch-label'>签到时间:</span>
<Radio.Group <Radio.Group
style={{ display: 'inline-block' }} style={{ display: 'inline-block' }}
value={signInType} value={signInType}
onChange={(e) => { onChange={(e) => {
this.setState({ signInType: e.target.value }); this.setState({ signInType: e.target.value });
}} }}
className="mt5" className='mt5'>
> <Radio value='START_AGO' className='mr-16'>
<Radio value="START_AGO" className="mr-16"> <span style={{ color: '#333' }}>课程开始前</span>
<span style={{ color: "#333" }}>课程开始前</span>
</Radio> </Radio>
<Radio value="END_AGO" className="mr-16"> <Radio value='END_AGO' className='mr-16'>
<span style={{ color: "#333" }}>课程结束前</span> <span style={{ color: '#333' }}>课程结束前</span>
</Radio> </Radio>
</Radio.Group> </Radio.Group>
</div>} </div>
{whetherSetSignIn === 'YES' && <div className="switch-item" key="3"> )}
<span className="switch-label">课程{signInType === 'START_AGO' ? '开始' : '结束'}前</span> {whetherSetSignIn === 'YES' && (
<div className='switch-item' key='3'>
<span className='switch-label'>课程{signInType === 'START_AGO' ? '开始' : '结束'}前</span>
<InputNumber <InputNumber
value={signInTimeNum} value={signInTimeNum}
min={1} min={1}
...@@ -1078,32 +1117,34 @@ class AddOfflineCourse extends React.Component { ...@@ -1078,32 +1117,34 @@ class AddOfflineCourse extends React.Component {
precision={0} precision={0}
style={{ margin: '0 4px', width: 90 }} style={{ margin: '0 4px', width: 90 }}
onChange={(value) => { onChange={(value) => {
this.setState({ signInTimeNum: value }) this.setState({ signInTimeNum: value });
}} }}
/> />
<Select <Select
style={{ width: 72, marginRight: 4 }} style={{ width: 72, marginRight: 4 }}
value={signInTimeUnit} value={signInTimeUnit}
onChange={(value) => { onChange={(value) => {
const data = { signInTimeUnit: value } const data = { signInTimeUnit: value };
if (value === 'HOUR' && signInTimeNum > 24) { if (value === 'HOUR' && signInTimeNum > 24) {
data.signInTimeNum = 24; data.signInTimeNum = 24;
} }
this.setState(data); this.setState(data);
}} }}>
> {unitList.map((item) => (
{unitList.map(item => ( <Option value={item.key} key={item.key}>
<Option value={item.key} key={item.key}>{item.value}</Option> {item.value}
</Option>
))} ))}
</Select> </Select>
<span className="switch-label">内可签到</span> <span className='switch-label'>内可签到</span>
</div>} </div>
)}
</div> </div>
</div> </div>
<div className="course-catalog"> <div className='course-catalog'>
<span className="label">考勤签退:</span> <span className='label'>考勤签退:</span>
<div className="switch-box"> <div className='switch-box'>
<div className="switch-item" key="1"> <div className='switch-item' key='1'>
<Switch <Switch
checked={whetherSetSignOut === 'YES'} checked={whetherSetSignOut === 'YES'}
onChange={(value) => { onChange={(value) => {
...@@ -1114,57 +1155,64 @@ class AddOfflineCourse extends React.Component { ...@@ -1114,57 +1155,64 @@ class AddOfflineCourse extends React.Component {
signOutStartTimeUnit: 'MINUTE', signOutStartTimeUnit: 'MINUTE',
signOutEndTimeNum: null, signOutEndTimeNum: null,
signOutEndTimeUnit: 'MINUTE', signOutEndTimeUnit: 'MINUTE',
}) });
}} }}
/> />
<span className="switch-tip">开启后可设置获取签退考勤数据</span> <span className='switch-tip'>开启后可设置获取签退考勤数据</span>
</div> </div>
{whetherSetSignOut === 'YES' && <div className="switch-item" key="2"> {whetherSetSignOut === 'YES' && (
<span className="switch-label">签退时间:</span> <div className='switch-item' key='2'>
<span className='switch-label'>签退时间:</span>
<Radio.Group <Radio.Group
style={{ display: 'inline-block' }} style={{ display: 'inline-block' }}
value={signOutType} value={signOutType}
onChange={(e) => { onChange={(e) => {
this.setState({ signOutType: e.target.value }); this.setState({ signOutType: e.target.value });
}} }}
className="mt5" className='mt5'>
> <Radio value='START_LATER' className='mr-16'>
<Radio value="START_LATER" className="mr-16"> <span style={{ color: '#333' }}>课程开始后</span>
<span style={{ color: "#333" }}>课程开始后</span>
</Radio> </Radio>
<Radio value="END_LATER" className="mr-16"> <Radio value='END_LATER' className='mr-16'>
<span style={{ color: "#333" }}>课程结束后</span> <span style={{ color: '#333' }}>课程结束后</span>
</Radio> </Radio>
</Radio.Group> </Radio.Group>
</div>} </div>
{whetherSetSignOut === 'YES' && <div className="switch-item" key="3"> )}
<span className="switch-label">课程{signOutType === 'START_LATER' ? '开始' : '结束'}后</span> {whetherSetSignOut === 'YES' && (
{signOutType === 'START_LATER' && <InputNumber <div className='switch-item' key='3'>
<span className='switch-label'>课程{signOutType === 'START_LATER' ? '开始' : '结束'}后</span>
{signOutType === 'START_LATER' && (
<InputNumber
value={signOutStartTimeNum} value={signOutStartTimeNum}
min={1} min={1}
max={signOutStartTimeUnit === 'MINUTE' ? 1440 : 24} max={signOutStartTimeUnit === 'MINUTE' ? 1440 : 24}
precision={0} precision={0}
style={{ margin: '0 4px', width: 90 }} style={{ margin: '0 4px', width: 90 }}
onChange={(value) => { onChange={(value) => {
this.setState({ signOutStartTimeNum: value }) this.setState({ signOutStartTimeNum: value });
}} }}
/>} />
{signOutType === 'START_LATER' && <Select )}
{signOutType === 'START_LATER' && (
<Select
style={{ width: 72, marginRight: 4 }} style={{ width: 72, marginRight: 4 }}
value={signOutStartTimeUnit} value={signOutStartTimeUnit}
onChange={(value) => { onChange={(value) => {
const data = { signOutStartTimeUnit: value } const data = { signOutStartTimeUnit: value };
if (value === 'HOUR' && signOutStartTimeNum > 24) { if (value === 'HOUR' && signOutStartTimeNum > 24) {
data.signOutStartTimeNum = 24; data.signOutStartTimeNum = 24;
} }
this.setState(data); this.setState(data);
}} }}>
> {unitList.map((item) => (
{unitList.map(item => ( <Option value={item.key} key={item.key}>
<Option value={item.key} key={item.key}>{item.value}</Option> {item.value}
</Option>
))} ))}
</Select>} </Select>
{signOutType === 'START_LATER' && <span className="switch-label">就可签退,截止签退时间为下课后</span>} )}
{signOutType === 'START_LATER' && <span className='switch-label'>就可签退,截止签退时间为下课后</span>}
<InputNumber <InputNumber
value={signOutEndTimeNum} value={signOutEndTimeNum}
min={1} min={1}
...@@ -1172,61 +1220,70 @@ class AddOfflineCourse extends React.Component { ...@@ -1172,61 +1220,70 @@ class AddOfflineCourse extends React.Component {
precision={0} precision={0}
style={{ margin: '0 4px', width: 90 }} style={{ margin: '0 4px', width: 90 }}
onChange={(value) => { onChange={(value) => {
this.setState({ signOutEndTimeNum: value }) this.setState({ signOutEndTimeNum: value });
}} }}
/> />
<Select <Select
style={{ width: 72, marginRight: 4 }} style={{ width: 72, marginRight: 4 }}
value={signOutEndTimeUnit} value={signOutEndTimeUnit}
onChange={(value) => { onChange={(value) => {
const data = { signOutEndTimeUnit: value } const data = { signOutEndTimeUnit: value };
if (value === 'HOUR' && signOutEndTimeNum > 24) { if (value === 'HOUR' && signOutEndTimeNum > 24) {
data.signOutEndTimeNum = 24; data.signOutEndTimeNum = 24;
} }
this.setState(data); this.setState(data);
}} }}>
> {unitList.map((item) => (
{unitList.map(item => ( <Option value={item.key} key={item.key}>
<Option value={item.key} key={item.key}>{item.value}</Option> {item.value}
</Option>
))} ))}
</Select> </Select>
{signOutType !== 'START_LATER' && {signOutType !== 'START_LATER' && <span className='switch-label'>内可签退</span>}
<span className="switch-label">内可签退</span> </div>
} )}
</div>}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="footer"> <div className='footer'>
<Button onClick={this.handleGoBack}>取消</Button> <Button onClick={this.handleGoBack}>取消</Button>
<Button onClick={this.handleShowPreviewModal}>预览</Button> <Button onClick={this.handleShowPreviewModal}>预览</Button>
<Button type="primary" onClick={_.debounce(() => this.preSubmit(), 3000, true)}>保存</Button> <Button type='primary' onClick={_.debounce(() => this.preSubmit(), 3000, true)}>
保存
</Button>
</div> </div>
{showSelectCoverModal && {showSelectCoverModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
key="basic" key='basic'
operateType="select" operateType='select'
multiple={false} multiple={false}
accept="image/jpeg,image/png,image/jpg" accept='image/jpeg,image/png,image/jpg'
selectTypeList={['JPG', 'JPEG', 'PNG']} selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png' tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectCoverModal} isOpen={showSelectCoverModal}
onClose={() => { onClose={() => {
this.setState({ showSelectCoverModal: false }) this.setState({ showSelectCoverModal: false });
}} }}
onSelect={this.handleSelectCover} onSelect={this.handleSelectCover}
/> />
} )}
{ visible && {visible && (
<ImgClipModal visible={visible} imgUrl={imageFile.ossUrl} onConfirm={this.getSignature} onClose={()=>{this.setState({ visible: false });}}/> <ImgClipModal
} visible={visible}
{ this.state.previewOfflineModal } imgUrl={imageFile.ossUrl}
onConfirm={this.getSignature}
onClose={() => {
this.setState({ visible: false });
}}
/>
)}
{this.state.previewOfflineModal}
</div> </div>
) );
} }
} }
......
.add-offline-course-page { .add-offline-course-page {
position:relative !important; position: relative !important;
.box{ .box {
margin-bottom:52px !important; margin-bottom: 52px !important;
} }
.ant-radio-group { .ant-radio-group {
display: flex; display: flex;
...@@ -39,18 +39,18 @@ ...@@ -39,18 +39,18 @@
position: relative; position: relative;
padding-left: 14px; padding-left: 14px;
&::before { &::before {
content: ""; content: '';
position: absolute; position: absolute;
left: 0px; left: 0px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
width: 4px; width: 4px;
height: 10px; height: 10px;
background: #2966FF; background: #2966ff;
} }
} }
} }
.label{ .label {
display: inline-block; display: inline-block;
text-align: right; text-align: right;
width: 120px; width: 120px;
...@@ -75,8 +75,8 @@ ...@@ -75,8 +75,8 @@
} }
} }
.course-catalog { .course-catalog {
margin-bottom:16px; margin-bottom: 16px;
margin-top:16px; margin-top: 16px;
display: flex; display: flex;
.switch-box { .switch-box {
.switch-item { .switch-item {
...@@ -110,19 +110,19 @@ ...@@ -110,19 +110,19 @@
.select-day { .select-day {
margin-bottom: 4px; margin-bottom: 4px;
.mark-day { .mark-day {
color: #2966FF; color: #2966ff;
} }
} }
} }
.allow-tourist-join{ .allow-tourist-join {
display:flex; display: flex;
margin-bottom:16px; margin-bottom: 16px;
.desc { .desc {
color:#999; color: #999;
margin-left:12px; margin-left: 12px;
} }
.content{ .content {
display:flex; display: flex;
} }
} }
.course-ware { .course-ware {
...@@ -150,45 +150,41 @@ ...@@ -150,45 +150,41 @@
&__wrap { &__wrap {
position: relative; position: relative;
.tag {
border-radius: 2px;
background: #D6D6D6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content { .img-content {
margin-top: 8px;
margin-right: 20px; margin-right: 20px;
width: 299px; width: 299px;
height: 169px; height: 169px;
position: relative;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
border-radius: 4px;
}
.tag {
border-radius: 2px;
background: #d6d6d6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #fff;
position: absolute;
top: 8px;
left: 8px;
} }
} }
.opt-btns { .opt-btns {
.default-btn { .default-btn {
margin-left: 16px; margin-left: 14px;
color: #2966FF; color: #2966ff;
cursor: pointer; cursor: pointer;
&.disabled { &.disabled {
color: #CCC; color: #ccc;
cursor: not-allowed; cursor: not-allowed;
} }
} }
...@@ -196,13 +192,14 @@ ...@@ -196,13 +192,14 @@
.ant-upload-list { .ant-upload-list {
display: none; display: none;
} }
}
.tips { .tips {
margin-top: 8px; margin-top: 8px;
color: #999; color: #999;
} }
} }
}
}
.select-student { .select-student {
align-items: center; align-items: center;
...@@ -236,7 +233,7 @@ ...@@ -236,7 +233,7 @@
justify-content: flex-end; justify-content: flex-end;
padding-right: 72px; padding-right: 72px;
background: #fff; background: #fff;
border-top: 1px solid #E8E8E8; border-top: 1px solid #e8e8e8;
z-index: 999; z-index: 999;
.ant-btn { .ant-btn {
margin-left: 10px; margin-left: 10px;
......
...@@ -6,28 +6,20 @@ ...@@ -6,28 +6,20 @@
* @Description: 添加直播-简介 * @Description: 添加直播-简介
*/ */
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import { Col, message, Row, Switch } from 'antd';
import React from 'react'; import React from 'react';
import { Input, message, Upload, Radio, Row, Col, Button, Popover, Switch } from 'antd';
import Service from '@/common/js/service';
import GraphicsEditor from '../../components/GraphicsEditor'; import GraphicsEditor from '../../components/GraphicsEditor';
import User from '@/common/js/user';
import UploadOss from '@/core/upload';
import './AddGraphicsIntro.less'; import './AddGraphicsIntro.less';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import { DISK_MAP } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components';
const { TextArea } = Input;
class AddGraphicsIntro extends React.Component { class AddGraphicsIntro extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
showSelectFileModal: false, showSelectFileModal: false,
diskList: [], diskList: [],
selectType: null, selectType: null,
} };
} }
// 上传封面图 // 上传封面图
...@@ -38,34 +30,34 @@ class AddGraphicsIntro extends React.Component { ...@@ -38,34 +30,34 @@ class AddGraphicsIntro extends React.Component {
imageFile, imageFile,
showCutModal: true, showCutModal: true,
}); });
} };
// 选择暖场资源 // 选择暖场资源
handleSelectVideo = (file) => { handleSelectVideo = (file) => {
const { selectType } = this.state; const { selectType } = this.state;
this.setState({ this.setState({
showSelectFileModal: false showSelectFileModal: false,
}) });
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file; const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
if(selectType === 'WARMUP'){ if (selectType === 'WARMUP') {
const liveCourseWarmMedia = { const liveCourseWarmMedia = {
contentType: 'WARMUP', contentType: 'WARMUP',
mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE', mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE',
mediaContent: resourceId, mediaContent: resourceId,
mediaUrl: ossUrl, mediaUrl: ossUrl,
mediaName: folderName, mediaName: folderName,
size: folderSize size: folderSize,
} };
this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia); this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia);
}else{ } else {
// 最多添加九图片 // 最多添加九图片
const { liveCourseMediaRequests } = this.props.data; const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => { const list = _.filter(liveCourseMediaRequests, (item) => {
return item.mediaType == "PICTURE"; return item.mediaType == 'PICTURE';
}); });
if (list.length > 8) { if (list.length > 8) {
message.warning("最多添加9张图片"); message.warning('最多添加9张图片');
return; return;
} }
liveCourseMediaRequests.push({ liveCourseMediaRequests.push({
...@@ -78,61 +70,62 @@ class AddGraphicsIntro extends React.Component { ...@@ -78,61 +70,62 @@ class AddGraphicsIntro extends React.Component {
}); });
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests); this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
} }
};
}
changeDetail = (value) => { changeDetail = (value) => {
this.props.onChange('courseMedia', value); this.props.onChange('courseMedia', value);
} };
changeIntro = (value) => { changeIntro = (value) => {
this.props.onChange('introduce', value); this.props.onChange('introduce', value);
} };
whetherVisitorsJoinChange = ()=>{ whetherVisitorsJoinChange = () => {
if(this.props.data.whetherVisitorsJoin==="NO"){ if (this.props.data.whetherVisitorsJoin === 'NO') {
this.props.onChange('whetherVisitorsJoin','YES') this.props.onChange('whetherVisitorsJoin', 'YES');
}else{ } else {
this.props.onChange('whetherVisitorsJoin','NO') this.props.onChange('whetherVisitorsJoin', 'NO');
}
} }
};
shelfStateChange = ()=>{ shelfStateChange = () => {
if(this.props.data.shelfState==="NO"){ if (this.props.data.shelfState === 'NO') {
this.props.onChange('shelfState','YES') this.props.onChange('shelfState', 'YES');
}else{ } else {
this.props.onChange('shelfState','NO') this.props.onChange('shelfState', 'NO');
}
} }
};
render() { render() {
const {data: { id, whetherVisitorsJoin, courseMedia, introduce, shelfState, loadcourseMedia, loadintroduce } } = this.props; const {
data: { id, whetherVisitorsJoin, courseMedia, introduce, shelfState, loadcourseMedia, loadintroduce },
} = this.props;
const { showSelectFileModal, selectType } = this.state; const { showSelectFileModal, selectType } = this.state;
return ( return (
<div className="add-video__intro-info"> <div className='add-video__intro-info'>
<div className="allow-tourist-join"> <div className='allow-tourist-join'>
<span className="label">观看设置:</span> <span className='label'>观看设置:</span>
<div className="content"> <div className='content'>
<div> <div>
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/> <Switch checked={whetherVisitorsJoin === 'YES' ? true : false} onChange={this.whetherVisitorsJoinChange} />
</div> </div>
<div> <div>
<div className="desc"> <div className='desc'>
<div>开启:允许未绑定手机号的学员观看</div> <div>开启:允许未绑定手机号的学员观看</div>
<div>关闭:仅限绑定了手机号的学员可以进入观看图文课</div> <div>关闭:仅限绑定了手机号的学员可以进入观看图文课</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="store-show"> <div className='store-show'>
<span className="label">学院展示:</span> <span className='label'>学院展示:</span>
<div className="content"> <div className='content'>
<Row> <Row>
<Col span={3}> <Col span={3}>
<Switch checked={shelfState==="YES"? true:false} onChange={this.shelfStateChange}/> <Switch checked={shelfState === 'YES' ? true : false} onChange={this.shelfStateChange} />
</Col> </Col>
<Col span={21}> <Col span={21}>
<div className="desc"> <div className='desc'>
<div>开启:图文课将在学员学院图文课列表中展示</div> <div>开启:图文课将在学员学院图文课列表中展示</div>
<div>关闭:图文课将在学员学院图文课列表中隐藏</div> <div>关闭:图文课将在学员学院图文课列表中隐藏</div>
</div> </div>
...@@ -140,59 +133,65 @@ class AddGraphicsIntro extends React.Component { ...@@ -140,59 +133,65 @@ class AddGraphicsIntro extends React.Component {
</Row> </Row>
</div> </div>
</div> </div>
<div className="introduce required"> <div className='introduce required'>
<span className="label" style={{ marginTop: 5 }}>课程内容:</span> <span className='label' style={{ marginTop: 5 }}>
<div className="content"> 课程内容:
<div className="intro-list"> </span>
<div className="intro-list__item content-editor"> <div className='content'>
{(!id || loadcourseMedia) && <div className='intro-list'>
<div className='intro-list__item content-editor'>
{(!id || loadcourseMedia) && (
<GraphicsEditor <GraphicsEditor
id="content" id='content'
detail={{ detail={{
content: courseMedia content: courseMedia,
}}
onChange={(val) => {
this.changeDetail(val);
}} }}
onChange={(val) => { this.changeDetail(val) }}
/> />
} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="introduce"> <div className='introduce'>
<span className="label">课程简介:</span> <span className='label'>课程简介:</span>
<div className="content"> <div className='content'>
<div className="intro-list"> <div className='intro-list'>
<div className="intro-list__item introduce-editor"> <div className='intro-list__item introduce-editor'>
{(!id || loadintroduce) && {(!id || loadintroduce) && (
<GraphicsEditor <GraphicsEditor
id="intro" id='intro'
isIntro={true} isIntro={true}
detail={{ detail={{
content: introduce content: introduce,
}}
onChange={(val) => {
this.changeIntro(val);
}} }}
onChange={(val) => { this.changeIntro(val) }}
/> />
} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* 选择暖场图文件弹窗 */} {/* 选择暖场图文件弹窗 */}
{ showSelectFileModal && {showSelectFileModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
operateType="select" operateType='select'
accept={selectType==="INTRO"?"image/jpeg,image/png,image/jpg":"video/mp4,image/jpeg,image/png,image/jpg"} accept={selectType === 'INTRO' ? 'image/jpeg,image/png,image/jpg' : 'video/mp4,image/jpeg,image/png,image/jpg'}
selectTypeList={ selectType==="INTRO" ? ['JPG', 'JPEG', 'PNG']: ['MP4', 'JPG', 'JPEG', 'PNG'] } selectTypeList={selectType === 'INTRO' ? ['JPG', 'JPEG', 'PNG'] : ['MP4', 'JPG', 'JPEG', 'PNG']}
tooltip={ selectType==="INTRO"?'支持文件类型:jpg、jpeg、png':'支持文件类型:jpg、jpeg、png、mp4'} tooltip={selectType === 'INTRO' ? '支持文件类型:jpg、jpeg、png' : '支持文件类型:jpg、jpeg、png、mp4'}
isOpen={showSelectFileModal} isOpen={showSelectFileModal}
onClose={() => { onClose={() => {
this.setState({ showSelectFileModal: false }) this.setState({ showSelectFileModal: false });
}} }}
onSelect={this.handleSelectVideo} onSelect={this.handleSelectVideo}
/> />
} )}
</div> </div>
) );
} }
} }
......
.add-video__intro-info { .add-offline__intro-info {
.w-e-full-screen-editor { .w-e-full-screen-editor {
background: #fff !important; background: #fff !important;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:45 * @Date: 2020-08-05 10:12:45
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-11 16:44:42 * @LastEditTime: 2021-06-11 16:44:42
* @Description: 视频课-列表模块 * @Description: 线上课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
import Service from '@/common/js/service'; import Service from '@/common/js/service';
...@@ -17,6 +17,7 @@ import OfflineCourseData from '@/modules/course-manage/offline-course/OfflineCou ...@@ -17,6 +17,7 @@ import OfflineCourseData from '@/modules/course-manage/offline-course/OfflineCou
import { Dropdown, message, Modal, Switch, Tooltip } from 'antd'; import { Dropdown, message, Modal, Switch, Tooltip } from 'antd';
import moment from 'moment'; import moment from 'moment';
import React from 'react'; import React from 'react';
import { find, reduce, last } from 'underscore';
import { Route, withRouter } from 'react-router-dom'; import { Route, withRouter } from 'react-router-dom';
import ENUM from '../../../knowledge-base/ENUM.js'; import ENUM from '../../../knowledge-base/ENUM.js';
import PreviewOfflineModal from '../modal/PreviewOfflineModal'; import PreviewOfflineModal from '../modal/PreviewOfflineModal';
...@@ -29,7 +30,7 @@ class OfflineCourseList extends React.Component { ...@@ -29,7 +30,7 @@ class OfflineCourseList extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
id: '', // 视频课ID id: '', // 线上课ID
studentIds: [], studentIds: [],
}; };
} }
...@@ -44,7 +45,7 @@ class OfflineCourseList extends React.Component { ...@@ -44,7 +45,7 @@ class OfflineCourseList extends React.Component {
handlePlanName = (planArray) => { handlePlanName = (planArray) => {
let planStr = ''; let planStr = '';
planArray.map((item, index) => { planArray.forEach((item, index) => {
if (index < planArray.length - 1) { if (index < planArray.length - 1) {
planStr = planStr + item.planName + '、'; planStr = planStr + item.planName + '、';
} else { } else {
...@@ -64,13 +65,13 @@ class OfflineCourseList extends React.Component { ...@@ -64,13 +65,13 @@ class OfflineCourseList extends React.Component {
fixed: 'left', fixed: 'left',
render: (val, record) => { render: (val, record) => {
const { courseMediaVOS, courseName, offlinePlace, calendarTime, startTime, endTime } = record; const { courseMediaVOS, courseName, offlinePlace, calendarTime, startTime, endTime } = record;
const coverUrl = (_.find(courseMediaVOS, (data) => data.contentType === 'COVER') || {}).mediaUrl; const coverUrl = (find(courseMediaVOS, (data) => data.contentType === 'COVER') || {}).mediaUrl;
let isContinue = calendarTime.length > 1; let isContinue = calendarTime.length > 1;
_.reduce(calendarTime, (a, b) => { reduce(calendarTime, (a, b) => {
isContinue = isContinue && b - a === 86400000; isContinue = isContinue && b - a === 86400000;
return b; return b;
}); });
const lastTime = _.last(calendarTime); const lastTime = last(calendarTime);
const time = `${ const time = `${
!isContinue !isContinue
? calendarTime.map((item) => moment(item).format('MM-DD')).join('、') ? calendarTime.map((item) => moment(item).format('MM-DD')).join('、')
...@@ -138,7 +139,9 @@ class OfflineCourseList extends React.Component { ...@@ -138,7 +139,9 @@ class OfflineCourseList extends React.Component {
width: 120, width: 120,
dataIndex: 'courseware', dataIndex: 'courseware',
render: (val, item, index) => { render: (val, item, index) => {
return <Switch disabled={item.courseState === 'EXPIRED'} checked={item.shelfState === 'YES'} onChange={() => this.changeShelfState(item)} />; return (
<Switch size='small' disabled={item.courseState === 'EXPIRED'} checked={item.shelfState === 'YES'} onChange={() => this.changeShelfState(item)} />
);
}, },
}, },
{ {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:15 * @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan * @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27 * @LastEditTime: 2020-12-26 16:07:27
* @Description: 视频课-操作模块 * @Description: 线上课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
......
...@@ -14,7 +14,7 @@ const hasExportPermission = (type) => { ...@@ -14,7 +14,7 @@ const hasExportPermission = (type) => {
return Permission.hasInteractiveExport(); return Permission.hasInteractiveExport();
} }
// 视频课导出权限 // 线上课导出权限
if (type === 'videoClass') { if (type === 'videoClass') {
return Permission.hasVideoClassExport(); return Permission.hasVideoClassExport();
} }
......
...@@ -2,15 +2,14 @@ ...@@ -2,15 +2,14 @@
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-05 10:07:47 * @Date: 2020-08-05 10:07:47
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-07-08 19:34:10 * @LastEditTime: 2021-07-18 11:54:53
* @Description: 视频课新增/编辑页 * @Description: 线上课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
import React from 'react' import React from 'react'
import { Button, Input, Radio, message, Modal, Cascader } from 'antd' import { Button, Input, message, Modal, Cascader, Tooltip, Form, Popconfirm,Menu,Dropdown} from 'antd'
import { FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum'
import { DISK_MAP, FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum'
import ShowTips from '@/components/ShowTips' import ShowTips from '@/components/ShowTips'
import Breadcrumbs from '@/components/Breadcrumbs' import Breadcrumbs from '@/components/Breadcrumbs'
import moment from 'moment' import moment from 'moment'
...@@ -28,7 +27,9 @@ import { randomString } from '@/domains/basic-domain/utils' ...@@ -28,7 +27,9 @@ import { randomString } from '@/domains/basic-domain/utils'
import ImgClipModal from '@/components/ImgClipModal' import ImgClipModal from '@/components/ImgClipModal'
import $ from 'jquery' import $ from 'jquery'
import './AddVideoCourse.less' import './AddVideoCourse.less'
import Bus from '@/core/bus'
const { TextArea } = Input;
const EDIT_BOX_KEY = Math.random() const EDIT_BOX_KEY = Math.random()
const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' } const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' }
...@@ -44,7 +45,14 @@ const defaultScheduleMedia = [ ...@@ -44,7 +45,14 @@ const defaultScheduleMedia = [
] ]
const whetherVisitorsJoin = 'NO' const whetherVisitorsJoin = 'NO'
let cutFlag = false let cutFlag = false;
const SUPPORT_WORD_PDF = [
"application/msword",
"application/wps-writer",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/pdf",
"application/wps-office.pdf"
];
class AddVideoCourse extends React.Component { class AddVideoCourse extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
...@@ -53,18 +61,17 @@ class AddVideoCourse extends React.Component { ...@@ -53,18 +61,17 @@ class AddVideoCourse extends React.Component {
const pageType = getParameterByName('type') const pageType = getParameterByName('type')
this.state = { this.state = {
id, // 视频课ID,编辑的时候从URL上带过来 id, // 线上课ID,编辑的时候从URL上带过来
pageType, // 页面类型: add->新建 edit->编辑 pageType, // 页面类型: add->新建 edit->编辑
imageFile: null, // 需要被截取的图片 imageFile: null, // 需要被截取的图片
courseName: null, // 视频课名称 courseName: null, // 线上课名称
scheduleVideoId: null, // 视频课链接
coverId: null, // 视频封面的recourceId coverId: null, // 视频封面的recourceId
coverUrl: null, // 视频课封面 coverUrl: null, // 线上课封面
studentList: [], // 上课学员列表 studentList: [], // 上课学员列表
shelfState: 'YES', //是否开启学院展示 shelfState: 'YES', //是否开启学院展示
scheduleMedia: [ scheduleMedia: [
{ {
// 视频课媒体资源 // 线上课媒体资源
contentType: 'INTRO', contentType: 'INTRO',
mediaType: 'TEXT', mediaType: 'TEXT',
mediaContent: '', mediaContent: '',
...@@ -82,7 +89,13 @@ class AddVideoCourse extends React.Component { ...@@ -82,7 +89,13 @@ class AddVideoCourse extends React.Component {
whetherVisitorsJoin: 'NO', // 是否允许游客加入 whetherVisitorsJoin: 'NO', // 是否允许游客加入
showSelectCoverModal: false, showSelectCoverModal: false,
cutImageBlob: null, cutImageBlob: null,
introduce: '' introduce: '',
courseChapterList:[
], // 课节列表
// videoType: "MP4",
mediaNameAlias: '', // 任一视频重命名的名称(气泡框)
selectTypeList:['MP4'],
accept:'video/mp4'
} }
} }
...@@ -92,6 +105,11 @@ class AddVideoCourse extends React.Component { ...@@ -92,6 +105,11 @@ class AddVideoCourse extends React.Component {
if (pageType === 'edit') { if (pageType === 'edit') {
this.handleFetchScheudleDetail(id) this.handleFetchScheudleDetail(id)
} }
Bus.bind('editorLimit', (editorTextLength) => {
this.setState({
editorTextLength,
});
});
} }
//获取分类列表 //获取分类列表
...@@ -103,34 +121,33 @@ class AddVideoCourse extends React.Component { ...@@ -103,34 +121,33 @@ class AddVideoCourse extends React.Component {
}) })
} }
catalogChange = (value) => { catalogChange = (value, _categoryName) => {
const categoryName =_.pluck(_categoryName,'categoryName').join('-')
const changeValueLength = value.length const changeValueLength = value.length
switch (changeValueLength) { switch (changeValueLength) {
case 1: case 1:
this.setState({ categoryId: value[0] }) this.setState({ categoryId: value[0], categoryName })
break break
case 2: case 2:
this.setState({ categoryId: value[1] }) this.setState({ categoryId: value[1], categoryName })
break break
default: default:
this.setState({ categoryId: null }) this.setState({ categoryId: null })
break break
} }
} }
// 获取视频课详情 // 获取线上课详情
handleFetchScheudleDetail = (courseId) => { handleFetchScheudleDetail = (courseId) => {
CourseService.videoScheduleDetail({ CourseService.videoScheduleDetail({
courseId courseId
}).then((res) => { }).then((res) => {
const { result = {} } = res || {} const { result = {} } = res || {}
const { courseName, shelfState, whetherVisitorsJoin, courseMediaVOS, categoryOneName, categoryTwoName, categoryId } = result const { courseName, shelfState, whetherVisitorsJoin, courseMediaVOS, categoryOneName, categoryTwoName, categoryId, courseChapterVOList =[] } = result
let coverId let coverId
let coverUrl let coverUrl
let videoType // let videoDuration
let videoDuration // let videoName
let videoName
let scheduleMedia = [] let scheduleMedia = []
let scheduleVideoId
let scheduleVideoUrl let scheduleVideoUrl
let hasIntro let hasIntro
...@@ -140,13 +157,6 @@ class AddVideoCourse extends React.Component { ...@@ -140,13 +157,6 @@ class AddVideoCourse extends React.Component {
coverId = item.mediaContent coverId = item.mediaContent
coverUrl = item.mediaUrl coverUrl = item.mediaUrl
break break
case 'SCHEDULE':
videoDuration = item.videoDuration
videoName = item.mediaName
scheduleVideoId = item.mediaContent
scheduleVideoUrl = item.mediaUrl
videoType = item.mediaType
break
case 'INTRO': case 'INTRO':
hasIntro = true hasIntro = true
this.getTextDetail('introduce', item.mediaUrl) this.getTextDetail('introduce', item.mediaUrl)
...@@ -163,21 +173,27 @@ class AddVideoCourse extends React.Component { ...@@ -163,21 +173,27 @@ class AddVideoCourse extends React.Component {
} else { } else {
categoryName = `${categoryOneName}` categoryName = `${categoryOneName}`
} }
const _courseChapterVOList = courseChapterVOList.map(item => {
item.mediaName = item.name;
item.resourceId = item.id;
return item
})
this.setState({ this.setState({
loadintroduce: !hasIntro, loadintroduce: !hasIntro,
coverId, coverId,
coverUrl, coverUrl,
videoType, // videoName,
videoName, // videoDuration,
videoDuration,
scheduleMedia, scheduleMedia,
courseName, courseName,
scheduleVideoId,
scheduleVideoUrl, scheduleVideoUrl,
shelfState, shelfState,
whetherVisitorsJoin, whetherVisitorsJoin,
categoryName, categoryName,
categoryId categoryId,
courseChapterList: _courseChapterVOList
}) })
}) })
} }
...@@ -195,11 +211,9 @@ class AddVideoCourse extends React.Component { ...@@ -195,11 +211,9 @@ class AddVideoCourse extends React.Component {
} }
handleGoBack = () => { handleGoBack = () => {
const { coverId, videoName, videoDuration, courseName, scheduleMedia, scheduleVideoId, categoryId, shelfState, whetherVisitorsJoin } = this.state const { coverId,courseName, scheduleMedia, courseChapterList, categoryId, shelfState, whetherVisitorsJoin } = this.state
if ( if (
videoName || !courseChapterList.length ||
videoDuration ||
scheduleVideoId ||
!_.isEqual(scheduleMedia, defaultScheduleMedia) || !_.isEqual(scheduleMedia, defaultScheduleMedia) ||
categoryId || categoryId ||
courseName || courseName ||
...@@ -267,17 +281,16 @@ class AddVideoCourse extends React.Component { ...@@ -267,17 +281,16 @@ class AddVideoCourse extends React.Component {
// 显示预览弹窗 // 显示预览弹窗
handleShowPreviewModal = () => { handleShowPreviewModal = () => {
const { coverUrl, scheduleVideoUrl, courseName, scheduleMedia, videoDuration, introduce } = this.state const { coverUrl, scheduleVideoUrl, courseName, scheduleMedia,introduce, courseChapterList, categoryName } = this.state
const courseBasinInfo = { const courseBasinInfo = {
coverUrl, coverUrl,
scheduleVideoUrl, scheduleVideoUrl,
courseName, courseName,
videoDuration
} }
const courseIntroInfo = { const courseIntroInfo = {
liveCourseMediaRequests: scheduleMedia, liveCourseMediaRequests: scheduleMedia,
introduce introduce,
categoryName
} }
const previewCourseModal = ( const previewCourseModal = (
...@@ -290,6 +303,7 @@ class AddVideoCourse extends React.Component { ...@@ -290,6 +303,7 @@ class AddVideoCourse extends React.Component {
previewCourseModal: null previewCourseModal: null
}) })
}} }}
courseChapterList={courseChapterList}
/> />
) )
...@@ -297,24 +311,92 @@ class AddVideoCourse extends React.Component { ...@@ -297,24 +311,92 @@ class AddVideoCourse extends React.Component {
} }
// 选择视频 // 选择视频
handleSelectVideo = (file) => { handleSelectVideo = (addFolderIds,selectedFileList) => {
this.setState({ this.setState({
showSelectFileModal: false showSelectFileModal: false
}) })
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file
let { courseChapterList } = this.state;
let _courseChapterList = [...courseChapterList];
if(selectedFileList.length + courseChapterList.length > 20){
message.warning(`最多只能上传20个文件`);
return;
}
selectedFileList.map((file,index) => {
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
const _mediaName =folderName.replace(`.${_.last(folderName.split('.')).toLowerCase()}`,'')
console.log('folderFormat',folderFormat);
if(folderFormat === 'MP4' || folderFormat === 'video/mp4'){
const videoDom = document.createElement('video') const videoDom = document.createElement('video')
videoDom.src = ossUrl videoDom.src = ossUrl
videoDom.onloadedmetadata = () => { videoDom.onloadedmetadata = () => {
_courseChapterList.push({
mediaContent: resourceId,
contentType: 'SCHEDULE',
mediaType: "VIDEO",
mediaName: _mediaName,
videoDuration: videoDom.duration,
resourceId,
mediaUrl: ossUrl,
sort: _courseChapterList.length
})
this.setState({
courseChapterList: _courseChapterList
})
}
}else if( folderFormat==="WORD" || folderFormat==="PDF" || SUPPORT_WORD_PDF.indexOf(folderFormat)>-1){
const suffix = _.last(folderName.split('.')).toUpperCase();
_courseChapterList.push({
mediaContent: resourceId,
contentType: 'SCHEDULE',
mediaType: suffix,
mediaName: _mediaName,
resourceId,
mediaUrl: ossUrl,
sort: _courseChapterList.length
})
this.setState({
courseChapterList: _courseChapterList
})
}
})
}
// 校验课节名称
handleValidateChapterName = (chapterName)=> {
let hasError = false;
return new Promise((resolve) => {
if(!chapterName) {
this.setState({
chapterNameValidateStatus: "error",
chapterNameHelpMsg: '请输入课节名称'
})
hasError = true;
resolve(false)
return false
}
if(chapterName.length > 40) {
this.setState({
chapterNameValidateStatus: "error",
chapterNameHelpMsg: '不要超过40字'
})
hasError = true;
resolve(false)
return false
}
if(!hasError){
resolve(true)
this.setState({ this.setState({
size: folderSize, chapterNameValidateStatus: "",
videoName: folderName, chapterNameHelpMsg: ""
videoType: folderFormat,
scheduleVideoUrl: ossUrl,
scheduleVideoId: resourceId,
videoDuration: videoDom.duration
}) })
} }
})
} }
// 保存 // 保存
...@@ -328,32 +410,26 @@ class AddVideoCourse extends React.Component { ...@@ -328,32 +410,26 @@ class AddVideoCourse extends React.Component {
}) })
return return
} }
const { instId, adminId } = window.currentUserInstInfo
const { const {
id, id,
size,
coverId, coverId,
coverUrl,
pageType, pageType,
joinType, // videoName,
videoName, // videoDuration,
videoDuration,
studentList,
courseName, courseName,
scheduleMedia, scheduleMedia,
scheduleVideoId,
scheduleVideoUrl,
categoryId, categoryId,
shelfState, shelfState,
whetherVisitorsJoin, whetherVisitorsJoin,
introduce introduce,
courseChapterList,
editorTextLength,
} = this.state } = this.state
const commonParams = { const commonParams = {
videoName, // videoName,
videoDuration, videoDuration:0, //后端的必要参数,不能传空
courseMediaId: scheduleVideoId,
scheduleMedia: scheduleMedia.filter((item) => !!item.mediaContent), scheduleMedia: scheduleMedia.filter((item) => !!item.mediaContent),
categoryId, categoryId,
courseName, courseName,
...@@ -362,10 +438,11 @@ class AddVideoCourse extends React.Component { ...@@ -362,10 +438,11 @@ class AddVideoCourse extends React.Component {
storeId: User.getStoreId(), storeId: User.getStoreId(),
shelfState, shelfState,
whetherVisitorsJoin, whetherVisitorsJoin,
courseType: 'VOICE' courseType: 'VOICE',
courseChapterList
} }
// 校验必填字段:课程名称, 课程视频 // 校验必填字段:课程名称, 课程视频
this.handleValidate(courseName, scheduleVideoId, categoryId, scheduleMedia).then((res) => { this.handleValidate(courseName, courseChapterList, categoryId, scheduleMedia, editorTextLength).then((res) => {
if (!res) return if (!res) return
Upload.uploadTextToOSS(introduce, `${randomString()}.txt`, (introduceId) => { Upload.uploadTextToOSS(introduce, `${randomString()}.txt`, (introduceId) => {
this.submitRemote({ id, pageType, commonParams: { ...commonParams, introduceId } }) this.submitRemote({ id, pageType, commonParams: { ...commonParams, introduceId } })
...@@ -375,7 +452,7 @@ class AddVideoCourse extends React.Component { ...@@ -375,7 +452,7 @@ class AddVideoCourse extends React.Component {
submitRemote = ({ id, pageType, commonParams }) => { submitRemote = ({ id, pageType, commonParams }) => {
if (pageType === 'add') { if (pageType === 'add') {
Service.Hades('public/hades/createMediaCourse', commonParams).then((res) => { Service.Hades('public/hades/createVideoSchedule', commonParams).then((res) => {
if (!res) return if (!res) return
message.success('新建成功') message.success('新建成功')
window.RCHistory.push({ window.RCHistory.push({
...@@ -387,7 +464,7 @@ class AddVideoCourse extends React.Component { ...@@ -387,7 +464,7 @@ class AddVideoCourse extends React.Component {
courseId: id, courseId: id,
...commonParams ...commonParams
} }
Service.Hades('public/hades/editMediaCourse', editParams).then((res) => { Service.Hades('public/hades/editVideoSchedule', editParams).then((res) => {
if (!res) return if (!res) return
message.success('保存成功') message.success('保存成功')
window.RCHistory.push({ window.RCHistory.push({
...@@ -397,15 +474,15 @@ class AddVideoCourse extends React.Component { ...@@ -397,15 +474,15 @@ class AddVideoCourse extends React.Component {
} }
} }
handleValidate = (courseName, scheduleVideoId, categoryId, scheduleMedia) => { handleValidate = (courseName, courseChapterList, categoryId, scheduleMedia, editorTextLength) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!courseName) { if (!courseName) {
message.warning('请输入课程名称') message.warning('请输入课程名称')
resolve(false) resolve(false)
return false return false
} }
if (!scheduleVideoId) { if (!courseChapterList.length) {
message.warning('请上传视频') message.warning('请上传课节')
resolve(false) resolve(false)
return false return false
} }
...@@ -414,18 +491,19 @@ class AddVideoCourse extends React.Component { ...@@ -414,18 +491,19 @@ class AddVideoCourse extends React.Component {
resolve(false) resolve(false)
return false return false
} }
const textMedia = scheduleMedia.filter((item) => item.mediaType === 'TEXT') if (editorTextLength > 1000) {
for (let i = 0, len = textMedia.length; i < len; i++) { message.warning('课程简介超过字数限定');
if (textMedia[i].mediaContentLength && textMedia[i].mediaContentLength.length > 1000) { resolve(false);
message.warning(`第${i + 1}个文字简介的字数超过了1000个字`) return;
resolve(false)
return false
}
} }
resolve(true) resolve(true)
}) })
} }
handleSelectCover = (file) => { handleSelectCover = (file) => {
if(!file){
message.info("请选择文件!");
return;
}
this.setState({ this.setState({
visible: true, visible: true,
imageFile:file imageFile:file
...@@ -452,23 +530,174 @@ class AddVideoCourse extends React.Component { ...@@ -452,23 +530,174 @@ class AddVideoCourse extends React.Component {
coverId: coverId coverId: coverId
}) })
} }
handleRenameCourseChapter = (chapterId, chapterIndex) => {
const { mediaNameAlias } = this.state;
this.handleValidateChapterName(mediaNameAlias).then(res => {
// 校验不通过不能点确定保存修改课节名称
if (!res) {
return message.warning('重命名失败');
}
let { courseChapterList } = this.state;
let _courseChapterList = [];
_courseChapterList = courseChapterList.map((item,index)=>{
if(item.resourceId === chapterId && chapterIndex === index){
item.mediaName = mediaNameAlias;
item.visible = false;
}
return item
})
this.setState({
courseChapterList: _courseChapterList,
chapterNameValidateStatus: '',
        chapterNameHelpMsg: '',
mediaNameAlias: '',
})
});
}
handleChangePopConfirmVisible = (chapterId, chapterNameIndex, visible)=> {
let { courseChapterList } = this.state;
let _courseChapterList = [];
_courseChapterList = courseChapterList.map((item,index)=>{
if(item.resourceId === chapterId && chapterNameIndex === index){
item.visible = visible
} else {
item.visible = false
}
return item
})
this.setState({
courseChapterList: _courseChapterList,
})
}
handleDeleteCourseChapter = (chapterId, chapterIndex) => {
console.log('chapterId---',chapterId, chapterIndex);
let { courseChapterList } = this.state;
let _courseChapterList = courseChapterList.filter((item,index) => {
return item.resourceId !== chapterId || item.resourceId === chapterId && chapterIndex !== index
})
_courseChapterList.map((item, index) => {
item.sort = index
})
this.setState({
courseChapterList :_courseChapterList
})
}
renderChapterTitle = (item) => {
const { chapterNameValidateStatus, chapterNameHelpMsg} = this.state;
return <div className="course-chapter-title-popover">
<div className="tag-title">课节名称</div>
<Form>
<Form.Item
validateStatus={chapterNameValidateStatus}
help={chapterNameHelpMsg}
>
<TextArea
defaultValue={item.mediaName}
placeholder="请输入课节名称"
maxLength={40}
autoSize
style={{ width: '318px'}}
onChange={(e) => {
this.setState({
mediaNameAlias: e.target.value.trim()
}, () => {
this.handleValidateChapterName(this.state.mediaNameAlias)
})
}}
/>
</Form.Item>
</Form>
</div>
}
// 上下移动
handleChangeIndex = (isUp, sortIndex) => {
const { courseChapterList} = this.state;
// 第一个上移和最后一个下移不能使用
if((isUp && sortIndex === 0) || (!isUp && sortIndex === (courseChapterList.length -1))){
return;
}
let _courseChapterList = [...courseChapterList];
const temp = courseChapterList[sortIndex];
// 若上移
if(isUp){
_courseChapterList[sortIndex -1] = temp;
_courseChapterList[sortIndex -1].sort = sortIndex -1;
_courseChapterList[sortIndex] = courseChapterList[sortIndex - 1];
_courseChapterList[sortIndex].sort = sortIndex;
} else { // 若下移
_courseChapterList[sortIndex + 1] = temp;
_courseChapterList[sortIndex + 1].sort = sortIndex + 1;
_courseChapterList[sortIndex] = courseChapterList[sortIndex + 1];
_courseChapterList[sortIndex].sort = sortIndex;
}
this.setState({
courseChapterList: _courseChapterList
})
}
renderTypemenu =()=>{
return <Menu>
<Menu.Item>
<span onClick={()=>{this.selectFileType("VIDEO")}}>
视频文件
</span>
</Menu.Item>
<Menu.Item>
<span onClick={()=>{this.selectFileType("WORD_PDF")}}>
资料文件
</span>
</Menu.Item>
</Menu>
}
selectFileType = (type) =>{
const { courseChapterList } = this.state;
if(courseChapterList.length >= 20) {
message.warning(`最多只能上传20个文件`);
return;
}
if(type==="VIDEO"){
this.setState({
showSelectFileModal: true,
selectTypeList:['MP4'],
accept:'video/mp4'
})
}else{
this.setState({
showSelectFileModal: true,
selectTypeList:['DOC','DOCX','PDF'],
accept:'.doc,.docx,.pdf'
})
}
}
renderToolTipTitle = ()=> {
return (<div>
<p>视频支持mp4格式,大小不超过2G;</p>
<p>文件支持PDF、docx、doc格式,大小不超过100M</p>
</div>)
}
render() { render() {
const { const {
pageType, pageType,
courseName, courseName,
scheduleVideoId,
coverId,
coverUrl, coverUrl,
scheduleVideoUrl,
studentList,
scheduleMedia, scheduleMedia,
showCutModal,
showSelectFileModal, showSelectFileModal,
diskList, diskList,
imageFile, // videoType,
joinType,
videoName,
videoType,
shelfState, shelfState,
categoryName, categoryName,
courseCatalogList, courseCatalogList,
...@@ -479,17 +708,18 @@ class AddVideoCourse extends React.Component { ...@@ -479,17 +708,18 @@ class AddVideoCourse extends React.Component {
cutImageBlob, cutImageBlob,
introduce, introduce,
loadintroduce, loadintroduce,
id id,
courseChapterList,
imageFile,
selectTypeList,
accept
} = this.state } = this.state
const defaultCover = 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png';
// 已选择的上课学员数量 const isDefaultCover = coverUrl === defaultCover || coverUrl == null;
const hasSelectedStu = studentList.length
const courseWareIcon = FileVerifyMap[videoType] ? FileTypeIcon[FileVerifyMap[videoType].type] : FileTypeIcon[videoType]
return ( return (
<div className='page add-video-course-page'> <div className='page add-video-course-page'>
<Breadcrumbs navList={pageType === 'add' ? '新建视频课' : '编辑视频课'} goBack={this.handleGoBack} /> <Breadcrumbs navList={pageType === 'add' ? '新建线上课' : '编辑线上课'} goBack={this.handleGoBack} />
<div className='box'> <div className='box'>
<div className='show-tips'> <div className='show-tips'>
...@@ -501,7 +731,7 @@ class AddVideoCourse extends React.Component { ...@@ -501,7 +731,7 @@ class AddVideoCourse extends React.Component {
<span className='label'>课程名称:</span> <span className='label'>课程名称:</span>
<Input <Input
value={courseName} value={courseName}
placeholder='请输入视频课的名称(40字以内)' placeholder='请输入线上课的名称(40字以内)'
maxLength={40} maxLength={40}
style={{ width: 240 }} style={{ width: 240 }}
onChange={(e) => { onChange={(e) => {
...@@ -512,59 +742,125 @@ class AddVideoCourse extends React.Component { ...@@ -512,59 +742,125 @@ class AddVideoCourse extends React.Component {
<div className='upload-video mt16'> <div className='upload-video mt16'>
<div className='content flex'> <div className='content flex'>
<span className='label required'>视频上传:</span> <span className='label required upload-chapter'>上传课节:</span>
<div className='value'>
{scheduleVideoId ? (
<div className='course-ware'>
<img className='course-ware__img' src={courseWareIcon} />
<span className='course-ware__name'>{videoName}</span>
</div>
) : (
<div className='course-ware--empty'>从资料云盘中选择视频</div>
)}
</div>
</div> </div>
<div className='sub-content'> <div className='sub-content'>
<Button <div className="btn-wrap">
{/* <Button
onClick={() => { onClick={() => {
if(courseChapterList.length >= 20) {
message.warning(`最多只能上传20个文件`);
return;
}
this.setState({ this.setState({
showSelectFileModal: true showSelectFileModal: true
}) })
}}>{`${pageType === 'add' && !scheduleVideoId ? '选择' : '更换'}视频`}</Button> }}>添加视频</Button> */}
<div className='select-file'>
<Dropdown overlay={this.renderTypemenu()}>
<Button>选择文件</Button>
</Dropdown>
</div>
<div className='course-ware--empty'>从资料云盘中选择视频</div>
</div>
<div className='tips'>
课节数量限制20个,文件规格说明
<Tooltip title={this.renderToolTipTitle()} overlayClassName="my-chapter-tooltip">
<i className='icon iconfont' style={{ cursor: 'pointer', color: '#bfbfbf', fontSize: '14px'}}> &#xe61d;</i>
</Tooltip>
</div>
</div>
</div>
<span className='tips'>视频数量限制1个,大小不超过2G</span> <If condition={courseChapterList.length > 0}>
<div className="course-chapter-list-wrap">
<div className="course-chapter-total">{`共${courseChapterList.length}个课节`}</div>
<div className='course-chapter-list' id="course-chapter-list">
{
_.map(courseChapterList,(item,index) => {
return <div className='course-ware' key={index}>
<div className="course-ware__index">{index < 9 ? `0${index + 1 } ` : `${index + 1 } `}</div>
<img className='course-ware__img' src={FileTypeIcon[item.mediaType]} alt='' />
<div className='course-ware__name'>{item.mediaName && item.mediaName.length > 24 ? <Tooltip title={item.mediaName}>{item.mediaName}</Tooltip>:item.mediaName}</div>
<div className="course-chapter__opt" id={item.resourceId}>
<div className={`up ${Number(index) === 0 ? 'disabled':''}`} onClick={()=> this.handleChangeIndex(true,item.sort,item.resourceId)}>上移</div>
<div className="line">|</div>
<div className={`down ${Number(index) === (courseChapterList.length - 1) ? 'disabled':''}`} onClick={()=> this.handleChangeIndex(false,item.sort,item.resourceId)}>下移</div>
<div className="line">|</div>
<Popconfirm
placement="topLeft"
className="course-chapter-tooltip"
title={this.renderChapterTitle(item)}
color='#fff' trigger="click"
overlayClassName="chapter-popover"
getPopupContainer={() =>
document.getElementById('course-chapter-list')
}
destroyTooltipOnHide={true}
visible={item.visible}
onConfirm={() => this.handleRenameCourseChapter(item.resourceId, index)}
icon={null}
onVisibleChange={(visible)=>{
!visible && this.setState({
chapterNameValidateStatus: '',
        chapterNameHelpMsg: '',
mediaNameAlias: '',
})
}}
onCancel={()=> this.handleChangePopConfirmVisible(item.resourceId, index, false)}
>
<div className="rename" onClick={() => {this.setState({mediaNameAlias: item.mediaName}, ()=>{
this.handleChangePopConfirmVisible(item.resourceId, index, true)})
}}>重命名</div>
</Popconfirm>
<div className="line">|</div>
<div className="delete" onClick={()=>this.handleDeleteCourseChapter(item.resourceId, index)}>移除</div>
</div> </div>
</div> </div>
})
}
<div className='cover-url flex mt16'>
<div className='label'>视频封面:</div>
<div className='cover-url__wrap'>
<div className='img-content'>
{/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */}
{scheduleVideoUrl || coverUrl ? (
<img src={coverUrl || `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`} />
) : (
<div className='empty-img'>
若不上传
<br />
系统默认将视频首帧作为封面图
</div> </div>
)}
</div> </div>
</If>
<div className='cover-url flex mt16'>
<div className='label cover'>封面图:</div>
<div className='cover-url__wrap'>
<div className='opt-btns'> <div className='opt-btns'>
<div>
<Button <Button
onClick={() => { onClick={() => {
this.setState({ showSelectCoverModal: true }) this.setState({ showSelectCoverModal: true })
}}>{`${pageType === 'add' && !scheduleVideoId && !coverUrl ? '上传' : '修改'}封面`}</Button> }}>{`${pageType === 'add' && !coverUrl ? '上传' : '修改'}封面`}</Button>
<span
className={`span ${coverUrl ? 'defalut-span' : ''}`}
onClick={() => {
this.setState({
coverUrl: '',
coverId: ''
})
}}>
使用默认图
</span>
</div>
<div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div> <div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div> </div>
<div className='img-content'>
{
isDefaultCover && <span className="tag">默认图</span>
}
{/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */}
<img src={coverUrl || `https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png`} alt='' />
</div>
</div> </div>
</div> </div>
<div className='course-catalog required'> <div className='course-catalog required'>
<span className='label'>课程分类:</span> <span className='label'>课程分类:</span>
{pageType === 'add' && ( {pageType === 'add' && (
<Cascader <Cascader
defaultValue={[categoryName]} defaultValue={[]}
options={courseCatalogList} options={courseCatalogList}
displayRender={(label) => label.join('-')} displayRender={(label) => label.join('-')}
fieldNames={fieldNames} fieldNames={fieldNames}
...@@ -622,14 +918,16 @@ class AddVideoCourse extends React.Component { ...@@ -622,14 +918,16 @@ class AddVideoCourse extends React.Component {
{/* 选择备课文件弹窗 */} {/* 选择备课文件弹窗 */}
{showSelectFileModal && ( {showSelectFileModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
multiple={true}
operateType='select' operateType='select'
selectTypeList={['MP4']} selectTypeList={selectTypeList}
accept='video/mp4' accept={accept}
queryTypeEnum={'ONLINE'}
confirm={{ confirm={{
title: '文件过大,无法上传', title: '文件过大,无法上传',
content: '为保障学员的观看体验,上传的视频大小不能超过2G' content: '上传的视频大小不能超过2G,文件大小不能超过100M'
}} }}
tooltip={'格式支持mp4,大小不超过2G'} tooltip={''}
isOpen={showSelectFileModal} isOpen={showSelectFileModal}
diskList={diskList} diskList={diskList}
addVideo={true} addVideo={true}
......
...@@ -32,6 +32,12 @@ ...@@ -32,6 +32,12 @@
display:inline-block; display:inline-block;
text-align:right; text-align:right;
width:85px; width:85px;
&.upload-chapter {
margin-top: 6px;
}
&.cover {
margin-top: 2px;
}
} }
.required { .required {
position: relative; position: relative;
...@@ -49,14 +55,16 @@ ...@@ -49,14 +55,16 @@
} }
} }
.course-catalog{ .course-catalog{
margin-bottom:16px; margin-bottom:24px;
margin-top:16px; margin-top:24px;
} }
.course-ware { .course-ware {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 4px; margin-bottom: 4px;
&__index {
width: 18px;
}
&__img { &__img {
width: 24px; width: 24px;
margin-right: 4px; margin-right: 4px;
...@@ -75,7 +83,19 @@ ...@@ -75,7 +83,19 @@
.img-content { .img-content {
width: 298px; width: 298px;
height: 172px; height: 172px;
position: relative;
.tag {
border-radius: 2px;
background: rgba(51, 51, 51, .2);
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -85,7 +105,7 @@ ...@@ -85,7 +105,7 @@
.empty-img { .empty-img {
width: 298px; width: 298px;
height: 172px; height: 172px;
border: 1px dashed #EBEBEB; border: 1px dashed #ebebeb;
border-radius: 4px; border-radius: 4px;
padding: 12px; padding: 12px;
color: #999; color: #999;
...@@ -94,12 +114,17 @@ ...@@ -94,12 +114,17 @@
} }
.opt-btns { .opt-btns {
margin-top: 8px; margin-bottom: 8px;
display: flex; .span {
align-items: center; color: #ccc;
margin-left: 8px;
}
.defalut-span {
cursor: pointer;
color: #2966ff;
}
.tips { .tips {
margin-left: 12px; margin-top: 8px;
color: #999; color: #999;
} }
} }
...@@ -115,18 +140,96 @@ ...@@ -115,18 +140,96 @@
color: #333; color: #333;
} }
} }
.upload-video {
display: flex;
margin-bottom: 24px;
.sub-content { .sub-content {
margin-left: 85px;
margin-top: 4px; margin-top: 4px;
.btn-wrap {
display: flex;
.course-ware--empty {
margin-left: 12px;
line-height: 28px;
}
}
.tips { .tips {
margin-left: 4px; margin-top: 8px;
color: #999; color: #999;
} }
} }
} }
.course-chapter-list-wrap {
position: relative;
margin-left: 85px;
border: 1px solid #E8E8E8;
width: 630px;
.course-chapter-total {
height: 40px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
z-index: 10;
background: #fff;
width: 628px;
text-indent: 16px;
line-height: 40px;
}
.course-chapter-list {
max-height: 245px;
overflow-y: auto ;
border-radius: 4px;
padding: 12px 16px 16px 16px;
.course-ware {
display: flex;
align-items: center;
margin-bottom: 22px;
color: #666;
&:last-child {
margin-bottom: 0px;
}
&__img {
width: 24px;
margin-right: 8px;
margin-left: 16px;
}
&__name {
color: #333;
width: 353px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-right: 24px;
}
.course-chapter__opt {
display: flex;
color: #2966ff;
width: 168px;
.line {
color: #bfbfbf;
margin-left: 4px;
margin-right: 4px;
}
.delete,
.rename,
.up,
.down {
cursor: pointer;
}
.up,
.down {
&.disabled {
color: rgba(204, 204, 204, 1);
}
}
}
}
}
}
}
.footer { .footer {
position: fixed; position: fixed;
left: 196px; left: 196px;
...@@ -138,10 +241,48 @@ ...@@ -138,10 +241,48 @@
justify-content: flex-end; justify-content: flex-end;
padding-right: 72px; padding-right: 72px;
background: #fff; background: #fff;
border-top: 1px solid #E8E8E8; border-top: 1px solid #ccc2c2;
z-index: 999; z-index: 999;
.ant-btn { .ant-btn {
margin-left: 10px; margin-left: 10px;
} }
} }
} }
.chapter-popover {
.ant-popover-message-title {
padding-left: 0;
}
.ant-form-item {
margin-bottom: 0!important;
}
.ant-form-item-explain.ant-form-item-explain-error {
height: 24px;
}
.ant-form-item-explain {
min-height: 0;
}
}
.course-chapter-title-popover {
.tag-title {
color: #333333;
margin-bottom: 8px;
width: 318px;
}
.chapter-btns {
margin-top: 16px;
display: flex;
justify-content: flex-end;
.confirm {
margin-left: 8px;
}
}
.ant-form-item {
margin-bottom: 0 !important;
}
}
.my-chapter-tooltip {
max-width: 600px!important;
}
\ No newline at end of file
.video-course-detail {
padding-bottom: 40px;
.box {
.course-detail {
display: flex;
border-bottom: 2px solid #E8E8E8;
padding-bottom: 24px;
&__img {
margin-right: 24px;
width: 300px;
height: 170px;
background: #FFFFFF;
border-radius: 4px;
img {
width: 300px;
height: 170px;
border-radius: 4px;
}
}
&__info {
display: flex;
flex-direction: column;
.info__title {
font-size: 20px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 28px;
margin-bottom: 16px;
}
.info__category, .info__chapterNum {
width: 100%;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
margin-bottom: 10px;
}
}
}
.course-chapter {
padding-top: 24px;
&__total {
width: 100%;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 20px;
margin-bottom: 24px;
}
&__list {
.course-ware {
display: flex;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 28px;
cursor: pointer;
&:hover {
color: #2966FF;
}
&__index {
width: 20px;
}
&__img {
width: 20px;
height: 20px;
margin-left: 16px;
margin-right: 8px;
}
&__name {
width: 700px;
}
}
}
}
}
}
\ No newline at end of file
/*
* @Author: wufan
* @Date: 2020-04-28 18:05:30
* @LastEditors: yuananting
* @LastEditTime: 2021-07-15 14:04:21
* @Description: 线上课课程课节详情
*/
import React,{useEffect, useState} from 'react';
import Breadcrumbs from "@/components/Breadcrumbs";
import { withRouter } from "react-router-dom";
import CourseService from '@/domains/course-domain/CourseService'
import underscore from 'underscore';
import ScanFileModal from '@/modules/resource-disk/modal/ScanFileModal.jsx';
import { FileTypeIcon } from '@/common/constants/academic/lessonEnum'
import BaseService from "@/domains/basic-domain/baseService";
import {YZ_APPId,YZ_PREVIEW_URL} from '@/domains/basic-domain/constants';
import PreviewFileModal from '@/bu-components/PreviewFileModal';
import './VideoCourseDetail.less';
declare var getParameterByName: any;
declare var window: any;
const FileTypeIconMap:any = FileTypeIcon
function VideoCourseDetail(){
const courseId = window.getParameterByName("courseId"); // 课程ID
const [coverUrl,setCoverUrl] = useState("");
const [videoName,setVideoName] = useState("");
const [scheduleVideoUrl,setScheduleVideoUrl] = useState("");
const [courseChapterList,setCourseChapterList] = useState<Array<Object>>([]);
const [courseName,setCourseName] = useState("");
const [categoryName,setCategoryName] = useState("");
const [scanFileModal,setScanFileModal] = useState<any>(null);
const [showPreviewModal,setShowPreviewModal] = useState(false); //是否显示loading
// const [previewing,setPreviewing] = useState(false); //是否正在预览
const [previewStatus,setPreviewStatus] = useState('UPLOAD'); //预览文件的生成状态
const [url,setUrl] = useState('');
let previewing = false;
useEffect(()=>{
handleFetchScheudleDetail(courseId);
},[courseId])
// 获取线上课详情
function handleFetchScheudleDetail(courseId: string){
CourseService.videoScheduleDetail({
courseId
}).then((res) => {
const { result = {} } = res || {}
const { courseName, courseMediaVOS, categoryName, courseChapterVOList =[] } = result
let coverId:string = "";
let coverUrl: string = "";
let videoType: string = "";
let videoDuration: number = 0;
let videoName: string = "";
let scheduleVideoUrl: string = "";
courseMediaVOS.map((item:any) => {
switch (item.contentType) {
case 'COVER':
coverId = item.mediaContent
coverUrl = item.mediaUrl
break
case 'SCHEDULE':
videoDuration = item.videoDuration
videoName = item.mediaName
scheduleVideoUrl = item.mediaUrl
videoType = item.mediaType
break
default:
break
}
return item
})
setCoverUrl(coverUrl);
setVideoName(videoName);
setScheduleVideoUrl(scheduleVideoUrl);
setCourseName(courseName);
setCategoryName(categoryName);
setCourseChapterList(courseChapterVOList);
})
}
async function handleScanFileModal(fileType: string = "MP4", fileObj:any){
const {fileVersionId,name,mediaUrl,id} = fileObj;
if(fileType === "VIDEO"){
const scanFileModal = (
<ScanFileModal
fileType={fileType}
item={fileObj}
close={() => {
setScanFileModal(null);
}}
/>
);
setScanFileModal(scanFileModal)
}else{
if(!fileVersionId){
setShowPreviewModal(true);
previewing = true;
setPreviewStatus('UPLOAD');
const uploadParams ={
fileUrl:mediaUrl,
yoZoTypeEnum:'UPLOAD'
}
const uploadSign:any = await BaseService.getYoZoSign(uploadParams);
BaseService.yoZoUpload(mediaUrl,YZ_APPId,uploadSign).then(async function (response){
const saveParams ={
fileVersionId:response.data.data.fileVersionId,
courseChapterId:id
}
BaseService.saveFileVersionIdByCourseChapter(saveParams);
if(previewing){
const previewParams ={
fileVersionId:response.data.data.fileVersionId,
yoZoTypeEnum:'VIEW',
htmlTitle:name
}
const previewSign = await BaseService.getYoZoSign(previewParams);
const url = `${YZ_PREVIEW_URL}?fileVersionId=${response.data.data.fileVersionId}&appId=${YZ_APPId}&sign=${previewSign}&htmlTitle=${name}`
setPreviewStatus('UPLOAD_SUCCESS');
setUrl(url)
}
})
}else{
const previewParams ={
fileVersionId,
yoZoTypeEnum:'VIEW',
htmlTitle:name
}
const previewSign = await BaseService.getYoZoSign(previewParams);
const url = `${YZ_PREVIEW_URL}?fileVersionId=${fileVersionId}&appId=${YZ_APPId}&sign=${previewSign}&htmlTitle=${name}`
const a = document.createElement('a');
document.body.appendChild(a);
a.setAttribute('href', url);
a.setAttribute('target', '_blank');
a.click();
document.body.removeChild(a)
}
}
}
// function previewingSet(){
// return new Promise(async (resolve) => {
// setShowPreviewModal(true);
// setPreviewing(true);
// setPreviewStatus('UPLOAD');
// resolve(true);
// })
// }
function cancelPreview(){
setShowPreviewModal(false);
// setPreviewing(false);
previewing = false;
setPreviewStatus('UPLOAD');
}
return <div className="page video-course-detail">
<Breadcrumbs
navList="课程详情"
goBack={() => {
window.RCHistory.goBack();
}}
/>
<div className="box">
<div className="course-detail">
<div className='course-detail__img'>
{/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */}
<img src={coverUrl || `https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png`} alt='' />
</div>
<div className="course-detail__info">
<div className="info__title">{courseName}</div>
<div className="info__category">{`分类:${categoryName}`}</div>
<div className="info__chapterNum">{`课节数量:${courseChapterList.length}`}</div>
</div>
</div>
<div className="course-chapter">
<div className="course-chapter__total">{`共${courseChapterList.length}个课节`}</div>
<div className="course-chapter__list">
<If condition={courseChapterList.length > 0}>
{
underscore.map(courseChapterList,(item: any,index: number) => {
return <div className='course-ware' onClick={()=>{handleScanFileModal(item.mediaType,item)}} key={index}>
<div className="course-ware__index">{index < 9 ? `0${index + 1 } ` : index + 1}</div>
<img className='course-ware__img' src={FileTypeIconMap[item.mediaType]} alt='' />
<div className='course-ware__name'>{item.name}</div>
</div>
})
}
</If>
</div>
</div>
</div>
{ showPreviewModal &&
<PreviewFileModal onCancel={()=>cancelPreview()} previewStatus={previewStatus} url={url}/>
}
{scanFileModal}
</div>
}
export default withRouter(VideoCourseDetail);
\ No newline at end of file
...@@ -6,30 +6,23 @@ ...@@ -6,30 +6,23 @@
* @Description: 添加直播-简介 * @Description: 添加直播-简介
*/ */
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import { Col, message, Row, Switch } from 'antd';
import React from 'react'; import React from 'react';
import { Input, message, Upload, Radio, Row, Col, Button, Popover, Switch } from 'antd';
import Service from '@/common/js/service';
import GraphicsEditor from '../../components/GraphicsEditor'; import GraphicsEditor from '../../components/GraphicsEditor';
import User from '@/common/js/user'; import { filter } from 'underscore';
import UploadOss from '@/core/upload';
import './AddVideoIntro.less'; import './AddVideoIntro.less';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import { DISK_MAP } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components';
const { TextArea } = Input;
const defaultCover = 'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599635741526.png'; const defaultCover = 'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599635741526.png';
class AddVideoIntro extends React.Component { class AddVideoIntro extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
warmUrl: defaultCover, warmUrl: defaultCover,
showSelectFileModal: false, showSelectFileModal: false,
diskList: [], diskList: [],
selectType:null selectType: null,
} };
} }
// 上传封面图 // 上传封面图
...@@ -40,34 +33,34 @@ class AddVideoIntro extends React.Component { ...@@ -40,34 +33,34 @@ class AddVideoIntro extends React.Component {
imageFile, imageFile,
showCutModal: true, showCutModal: true,
}); });
} };
// 选择暖场资源 // 选择暖场资源
handleSelectVideo = (file) => { handleSelectVideo = (file) => {
const { selectType } = this.state; const { selectType } = this.state;
this.setState({ this.setState({
showSelectFileModal: false showSelectFileModal: false,
}) });
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file; const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
if(selectType === 'WARMUP'){ if (selectType === 'WARMUP') {
const liveCourseWarmMedia = { const liveCourseWarmMedia = {
contentType: 'WARMUP', contentType: 'WARMUP',
mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE', mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE',
mediaContent: resourceId, mediaContent: resourceId,
mediaUrl: ossUrl, mediaUrl: ossUrl,
mediaName: folderName, mediaName: folderName,
size: folderSize size: folderSize,
} };
this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia); this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia);
}else{ } else {
// 最多添加九图片 // 最多添加九图片
const { liveCourseMediaRequests } = this.props.data; const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => { const list = filter(liveCourseMediaRequests, (item) => {
return item.mediaType == "PICTURE"; return item.mediaType === 'PICTURE';
}); });
if (list.length > 8) { if (list.length > 8) {
message.warning("最多添加9张图片"); message.warning('最多添加9张图片');
return; return;
} }
liveCourseMediaRequests.push({ liveCourseMediaRequests.push({
...@@ -80,49 +73,56 @@ class AddVideoIntro extends React.Component { ...@@ -80,49 +73,56 @@ class AddVideoIntro extends React.Component {
}); });
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests); this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
} }
};
}
changeIntro = (value) => { changeIntro = (value) => {
this.props.onChange('introduce', value); this.props.onChange('introduce', value);
} };
handleUpload = (Blob) => { handleUpload = (Blob) => {
this.setState({ this.setState({
showSelectFileModal: true, showSelectFileModal: true,
selectType:'INTRO' selectType: 'INTRO',
}) });
} };
whetherVisitorsJoinChange = ()=>{ whetherVisitorsJoinChange = () => {
if(this.props.data.whetherVisitorsJoin==="NO"){ if (this.props.data.whetherVisitorsJoin === 'NO') {
this.props.onChange('whetherVisitorsJoin','YES') this.props.onChange('whetherVisitorsJoin', 'YES');
}else{ } else {
this.props.onChange('whetherVisitorsJoin','NO') this.props.onChange('whetherVisitorsJoin', 'NO');
} }
} };
shelfStateChange = ()=>{ shelfStateChange = () => {
if(this.props.data.shelfState==="NO"){ if (this.props.data.shelfState === 'NO') {
this.props.onChange('shelfState','YES') this.props.onChange('shelfState', 'YES');
}else{ } else {
this.props.onChange('shelfState','NO') this.props.onChange('shelfState', 'NO');
} }
} };
render() { render() {
const {data: { whetherVisitorsJoin,liveCourseMediaRequests = [], shelfState, id, introduce, loadintroduce } } = this.props; const {
const {showSelectFileModal,selectType} = this.state data: { whetherVisitorsJoin, shelfState, id, introduce, loadintroduce },
} = this.props;
const { showSelectFileModal, selectType } = this.state;
return ( return (
<div className="add-video__intro-info"> <div className='add-video__intro-info'>
<div className="allow-tourist-join"> <div className='allow-tourist-join'>
<span className="label">观看设置:</span> <span className='label'>观看设置:</span>
<div className="content"> <div className='content'>
<div> <div>
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/> <Switch checked={whetherVisitorsJoin==="NO"? true:false} onChange={this.whetherVisitorsJoinChange}/>
</div> </div>
<div> <div>
<div className="desc"> <div className="desc">
<div>开启:允许未绑定手机号的学员观看</div> <Choose>
<div>关闭:仅限绑定了手机号的学员可以进入观看视频</div> <When condition={whetherVisitorsJoin==="NO"}>
<div>已开启,学员需绑定手机号才可观看</div>
</When>
<Otherwise>
<div>已关闭,学员无需绑定手机号即可观看</div>
</Otherwise>
</Choose>
</div> </div>
</div> </div>
</div> </div>
...@@ -136,48 +136,57 @@ class AddVideoIntro extends React.Component { ...@@ -136,48 +136,57 @@ class AddVideoIntro extends React.Component {
</Col> </Col>
<Col span={21}> <Col span={21}>
<div className="desc"> <div className="desc">
<div>开启:此视频将在学员学院的视频列表中出现</div> <Choose>
<div>关闭:此视频将在学员学院的视频列表中隐藏</div> <When condition={shelfState==="YES"}>
<div>已开启,课程将在该学院的学员课程列表中显示</div>
</When>
<Otherwise>
<div>已关闭,课程将在该学院的学员课程列表中隐藏</div>
</Otherwise>
</Choose>
</div> </div>
</Col> </Col>
</Row> </Row>
</div> </div>
</div> </div>
<div className="introduce"> <div className="introduce">
<span className="label">视频课简介:</span> <span className="label">课程简介:</span>
<div className="content"> <div className="content">
<div className="intro-list"> <div className="intro-list">
<div className="intro-list__item introduce-editor"> <div className="intro-list__item introduce-editor">
{(!id || loadintroduce) && {(!id || loadintroduce) && (
<GraphicsEditor <GraphicsEditor
maxLimit={1000}
id="intro" id="intro"
isIntro={true} isIntro={true}
detail={{ detail={{
content: introduce content: introduce,
}}
onChange={(val) => {
this.changeIntro(val);
}} }}
onChange={(val) => { this.changeIntro(val) }}
/> />
} )}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* 选择暖场图文件弹窗 */} {/* 选择暖场图文件弹窗 */}
{ showSelectFileModal && {showSelectFileModal && (
<SelectPrepareFileModal <SelectPrepareFileModal
operateType="select" operateType='select'
accept={selectType==="INTRO"?"image/jpeg,image/png,image/jpg":"video/mp4,image/jpeg,image/png,image/jpg"} accept={selectType === 'INTRO' ? 'image/jpeg,image/png,image/jpg' : 'video/mp4,image/jpeg,image/png,image/jpg'}
selectTypeList={ selectType==="INTRO" ? ['JPG', 'JPEG', 'PNG']: ['MP4', 'JPG', 'JPEG', 'PNG'] } selectTypeList={selectType === 'INTRO' ? ['JPG', 'JPEG', 'PNG'] : ['MP4', 'JPG', 'JPEG', 'PNG']}
tooltip={ selectType==="INTRO"?'支持文件类型:jpg、jpeg、png':'支持文件类型:jpg、jpeg、png、mp4'} tooltip={selectType === 'INTRO' ? '支持文件类型:jpg、jpeg、png' : '支持文件类型:jpg、jpeg、png、mp4'}
isOpen={showSelectFileModal} isOpen={showSelectFileModal}
onClose={() => { onClose={() => {
this.setState({ showSelectFileModal: false }) this.setState({ showSelectFileModal: false });
}} }}
onSelect={this.handleSelectVideo} onSelect={this.handleSelectVideo}
/> />
} )}
</div> </div>
) );
} }
} }
......
...@@ -32,14 +32,17 @@ ...@@ -32,14 +32,17 @@
} }
.store-show{ .store-show{
display:flex; display:flex;
margin-top:16px; margin-top:24px;
margin-bottom:16px; margin-bottom:24px;
.desc{ .desc{
margin-left:16px; margin-left:11px;
font-size:14px; font-size:14px;
color:#999; color:#999;
display:inline-block; display:inline-block;
} }
.content {
width: 400px;
}
} }
.radio { .radio {
display: block; display: block;
......
import React from 'react';
import './ChapterList.less';
import { FileTypeIcon } from '@/common/constants/academic/lessonEnum'
const FileTypeIconMap = FileTypeIcon;
function ChapterList(props){
const { courseChapterList } = props;
return <div className='chapter-list-component'>
<If condition={courseChapterList.length > 0}>
{
_.map(courseChapterList,(item,index) => {
return <div className='course-ware'>
<img className='course-ware__img' src={FileTypeIconMap[item.mediaType]} alt='' />
<div className='course-ware__index'>{index < 9 ? `0${index + 1 }` : `${index + 1 }`}</div>
<div className="course-ware__detail">
<div className='course-ware__detail__name'>{item.mediaName}</div>
{ item.mediaType === 'VIDEO' &&
<div className='course-ware__detail__duration'>{window.formatDuration(item.videoDuration)}</div>
}
</div>
</div>
})
}
</If>
</div>
}
export default ChapterList;
\ No newline at end of file
.chapter-list-component {
.course-ware {
display: flex;
padding: 10px 0;
border-bottom: 1px dashed #EEEEEE;
&:last-child {
border-bottom: none;
}
&__index {
width: 18px;
height: 18px;
font-size: 13px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #999999;
line-height: 18px;
white-space: nowrap;
margin-left: 8px;
margin-right: 6px;
line-height: 20px;
}
&__img {
width: 20px;
height: 20px;
}
&__detail {
display: flex;
flex-direction: column;
&__name {
font-size: 13px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
margin-bottom: 4px;
}
&__duration {
color: #999999;
}
}
}
}
\ No newline at end of file
import User from "@/common/js/user";
import CourseService from "@/domains/course-domain/CourseService";
import React from "react";
import { Modal } from "antd";
import { FileTypeIcon } from '@/common/constants/academic/lessonEnum'
import "./LearningDetailModal.less";
class LearningDetailModal extends React.Component {
constructor(props) {
super(props);
this.state = {
courseChapterList: [],
};
}
componentDidMount() {
this.handleFetchDataList();
}
// 获取观看视频数据列表
handleFetchDataList = () => {
const { courseId, data } = this.props;
const params = {
courseId,
storeId: User.getStoreId(),
storeCustomerId: data.storeCustomerId,
};
CourseService.lineDetailWatchInfo(params).then((res) => {
const { result = [] } = res;
this.setState({
courseChapterList: result
});
});
};
render() {
const { title, onClose, onOk } = this.props;
const { courseChapterList } = this.state;
return (
<Modal
footer={null}
visible={true}
title={title}
width={680}
maskClosable={false}
closeIcon={
<span className="icon iconfont modal-close-icon">&#xe6ef;</span>
}
onCancel={onClose}
onOk={onOk}
className="learning-detail-modal"
>
<div className="course-chapter__list">
<If condition={courseChapterList.length > 0}>
{_.map(courseChapterList, (item, index) => {
return (
<div className="course-wrap">
<div className="course-ware" key={index}>
<div className="course-ware__index">{`${
index < 9 ? `0${index + 1}` : index + 1
} `}</div>
<img className='course-ware__img' src={FileTypeIcon[item.mediaType]} alt='' />
<div className="course-ware__name">{item.courseChapterName && item.courseChapterName.replace('.MP4','')}</div>
</div>
<div className={`progress ${item.progress === 100 ? 'finish' :''}`}>{`${item.progress === 100 ? '已完成' : `${item.progress}%`}`}</div>
</div>
);
})}
</If>
</div>
</Modal>
);
}
}
export default LearningDetailModal;
.learning-detail-modal {
.course-chapter {
padding-top: 24px;
&__list {
.course-wrap {
display: flex;
justify-content: space-between;
.course-ware {
display: flex;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 28px;
&__index {
width: 20px;
}
&__img {
width: 20px;
height: 20px;
margin-left: 12px;
margin-right: 8px;
}
&__name {
width: 500px;
}
}
.progress {
&.finish {
color: RGBA(101, 202, 168, 1);
}
}
}
}
}
}
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-08-05 10:11:57 * @Date: 2020-08-05 10:11:57
* @LastEditors: fusanqiasng * @LastEditors: yuananting
* @LastEditTime: 2021-05-28 20:14:37 * @LastEditTime: 2021-07-12 14:14:47
* @Description: 视频课-搜索模块 * @Description: 线上课-搜索模块
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -56,22 +56,17 @@ class VideoCourseFilter extends React.Component { ...@@ -56,22 +56,17 @@ class VideoCourseFilter extends React.Component {
this.getTeacherList() this.getTeacherList()
this.queryCategoryTree() this.queryCategoryTree()
} }
// 查询分类树 // 查询分类树
queryCategoryTree = (categoryName = '') => { queryCategoryTree = ()=> {
let query = { const params= {
bizType: 'QUESTION', storeId:User.getStoreId()
source: 2,
categoryName,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
count: false,
} }
AidToolService.queryExternalCategoryTree(query).then((res) => { StoreService.getStoreDetail(params).then((res) => {
const { categoryList = [] } = res.result const { result = {} } = res;
console.log(this.renderTreeNodes(categoryList)) result.coursePackageId && AidToolService.queryCategoryTreeByPackage({coursePackageId: result.coursePackageId}).then((res) => {
this.setState({ const { result = [] } = res;
categoryList: this.renderTreeNodes(categoryList), this.setState({categoryList: this.renderTreeNodes(result)})
categoryName,
}) })
}) })
} }
...@@ -192,10 +187,10 @@ class VideoCourseFilter extends React.Component { ...@@ -192,10 +187,10 @@ class VideoCourseFilter extends React.Component {
<Row type='flex' justify='space-between' align='top'> <Row type='flex' justify='space-between' align='top'>
<div className='search-condition'> <div className='search-condition'>
<div className='search-condition__item'> <div className='search-condition__item'>
<span className='search-name'>视频课名称:</span> <span className='search-name'>线上课名称:</span>
<Search <Search
value={courseName} value={courseName}
placeholder='搜索视频课名称' placeholder='搜索线上课名称'
onChange={(e) => { onChange={(e) => {
this.handleChangeQuery('courseName', e.target.value) this.handleChangeQuery('courseName', e.target.value)
}} }}
......
import { Dropdown, message, Modal, Switch, Tooltip } from 'antd'; import React from "react"
import User from '@/common/js/user'; import { Modal, message, Tooltip, Switch, Dropdown } from "antd"
import { PageControl } from '@/components'; import _ from "underscore"
import { LIVE_SHARE } from '@/domains/course-domain/constants'; import { PageControl } from "@/components"
import CourseService from '@/domains/course-domain/CourseService'; import { LIVE_SHARE } from "@/domains/course-domain/constants"
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal'; import { Route, withRouter } from 'react-router-dom';
import React from 'react'; import ShareLiveModal from "@/modules/course-manage/modal/ShareLiveModal"
import RelatedPlanModal from '../../modal/RelatedPlanModal'; import CourseService from "@/domains/course-domain/CourseService"
import WatchDataModal from '../modal/WatchDataModal'; import RelatedPlanModal from "../../modal/RelatedPlanModal"
import User from "@/common/js/user"
import VideoCourseDetail from '../VideoCourseDetail';
import WatchData from "./WatchData";
import { XMTable } from '@/components'; import { XMTable } from '@/components';
import college from '@/common/lottie/college'; import college from '@/common/lottie/college';
import './VideoCourseList.less'; import "./VideoCourseList.less"
class VideoCourseList extends React.Component { class VideoCourseList extends React.Component {
constructor(props) { constructor(props) {
...@@ -32,21 +35,20 @@ class VideoCourseList extends React.Component { ...@@ -32,21 +35,20 @@ class VideoCourseList extends React.Component {
} }
} }
// 跳转课程详情页
handleLinkToCourseDetail = (courseId) => {
const { match } = this.props;
window.RCHistory.push(`${match.url}/video-course-detail?courseId=${courseId}`)
}
// 观看数据弹窗 // 观看数据弹窗
handleShowWatchDataModal = (record) => { handleShowWatchDataModal = (item) => {
const watchDataModal = ( const { match } = this.props;
<WatchDataModal window.RCHistory.push({
type='videoCourseList' pathname: `${match.url}/course-data?courseName=${item.courseName}&courseId=${item.id}`
data={record} })
close={() => { }
this.setState({
watchDataModal: null,
});
}}
/>
);
this.setState({ watchDataModal });
};
// 请求表头 // 请求表头
parseColumns = () => { parseColumns = () => {
...@@ -54,23 +56,21 @@ class VideoCourseList extends React.Component { ...@@ -54,23 +56,21 @@ class VideoCourseList extends React.Component {
const { ShelfLoading } = this.state; const { ShelfLoading } = this.state;
const columns = [ const columns = [
{ {
title: '视频课', title: "线上课",
key: 'scheduleName', key: "scheduleName",
dataIndex: 'scheduleName', dataIndex: "scheduleName",
width: 321, width: 321,
fixed: 'left', fixed: 'left',
render: (val, record) => { render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record; const { coverUrl } = record;
return ( return (
<div className='record__item'> <div className='record__item'>
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img <img
className='course-cover' className='course-cover'
src={ src={
coverUrl || coverUrl || 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'
(type === 'internal' ? `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast` : 'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png')
} }
alt='' alt='封面图'
/> />
<Choose> <Choose>
<When condition={record.courseName.length > 25}> <When condition={record.courseName.length > 25}>
...@@ -99,9 +99,9 @@ class VideoCourseList extends React.Component { ...@@ -99,9 +99,9 @@ class VideoCourseList extends React.Component {
</If> </If>
</span> </span>
), ),
key: 'categoryName', key: "categoryName",
dataIndex: 'categoryName', dataIndex: "categoryName",
width: 200, width: 150,
render: (val, record) => { render: (val, record) => {
return ( return (
<Choose> <Choose>
...@@ -119,21 +119,15 @@ class VideoCourseList extends React.Component { ...@@ -119,21 +119,15 @@ class VideoCourseList extends React.Component {
}, },
}, },
{ {
title: '创建人', title: '课节数',
key: 'createName', key: 'chapterNum',
dataIndex: 'createName', dataIndex: 'chapterNum',
className: "chapterNum",
width: 100, width: 100,
render: (val) => { align: 'right',
return ( render: (val,item) => {
<div> return <div onClick={()=>this.handleLinkToCourseDetail(item.id)}>{val || 1}</div>
{val && ( }
<Tooltip title={val}>
<div>{val.length > 4 ? `${val.slice(0, 4)}...` : val}</div>
</Tooltip>
)}
</div>
);
},
}, },
{ {
title: ( title: (
...@@ -159,6 +153,7 @@ class VideoCourseList extends React.Component { ...@@ -159,6 +153,7 @@ class VideoCourseList extends React.Component {
render: (val, item, index) => { render: (val, item, index) => {
return ( return (
<Switch <Switch
size='small'
loading={ShelfLoading} loading={ShelfLoading}
checked={item.shelfState === 'YES'} checked={item.shelfState === 'YES'}
defaultChecked={item.shelfState} defaultChecked={item.shelfState}
...@@ -170,13 +165,31 @@ class VideoCourseList extends React.Component { ...@@ -170,13 +165,31 @@ class VideoCourseList extends React.Component {
}, },
}, },
{ {
title: '观看学员数', title: "观看学员数",
width: 110, width: 150,
key: 'watchUserCount', key: "watchUserCount",
dataIndex: 'watchUserCount', dataIndex: "watchUserCount",
align: 'right',
render: (val, item) => { render: (val, item) => {
return <div className='watchUserCount'>{val || 0}</div>; return <div className='watchUserCount' onClick={() => this.handleShowWatchDataModal(item)}>{val || 0}</div>
}
}, },
{
title: "创建人",
key: "createName",
dataIndex: "createName",
width: 100,
render: (val) => {
return (
<div>
{val && (
<Tooltip title={val}>
<div>{val.length > 4 ? `${val.slice(0, 4)}...` : val}</div>
</Tooltip>
)}
</div>
)
}
}, },
{ {
title: '创建时间', title: '创建时间',
...@@ -230,22 +243,17 @@ class VideoCourseList extends React.Component { ...@@ -230,22 +243,17 @@ class VideoCourseList extends React.Component {
title: '操作', title: '操作',
key: 'operate', key: 'operate',
dataIndex: 'operate', dataIndex: 'operate',
width: 210, width: 160,
fixed: 'right', fixed: 'right',
render: (val, record) => { render: (val, record) => {
return ( return (
<div className='operate'> <div className='operate'>
<div className='operate__item' onClick={() => this.handleShowWatchDataModal(record)}> <If condition={type === "internal"}>
观看数据
</div>
<If condition={type === 'internal'}>
<span className='operate__item split'> | </span>
<div className='operate__item' onClick={() => this.handleShowShareModal(record)}> <div className='operate__item' onClick={() => this.handleShowShareModal(record)}>
分享 分享
</div> </div>
</If>
<span className='operate__item split'> | </span> <span className='operate__item split'> | </span>
</If>
<Dropdown overlay={this.renderMoreOperate(record)}> <Dropdown overlay={this.renderMoreOperate(record)}>
<span className='more-operate'> <span className='more-operate'>
<span className='operate-text'>更多</span> <span className='operate-text'>更多</span>
...@@ -260,7 +268,7 @@ class VideoCourseList extends React.Component { ...@@ -260,7 +268,7 @@ class VideoCourseList extends React.Component {
}, },
]; ];
type !== 'internal' && columns.splice(2, 1); type !== 'internal' && columns.splice(5, 1);
return columns; return columns;
}; };
...@@ -327,7 +335,7 @@ class VideoCourseList extends React.Component { ...@@ -327,7 +335,7 @@ class VideoCourseList extends React.Component {
}); });
}; };
// 删除视频课 // 删除线上课
handleDeleteVideoCourse = (scheduleId) => { handleDeleteVideoCourse = (scheduleId) => {
Modal.confirm({ Modal.confirm({
title: '你确定要删除此视频课吗?', title: '你确定要删除此视频课吗?',
...@@ -369,7 +377,7 @@ class VideoCourseList extends React.Component { ...@@ -369,7 +377,7 @@ class VideoCourseList extends React.Component {
data={shareData} data={shareData}
type='videoClass' type='videoClass'
courseDivision={type} courseDivision={type}
title='视频课' title='线上课'
close={() => { close={() => {
this.setState({ this.setState({
shareLiveModal: null, shareLiveModal: null,
...@@ -446,7 +454,7 @@ class VideoCourseList extends React.Component { ...@@ -446,7 +454,7 @@ class VideoCourseList extends React.Component {
); );
}; };
render() { render() {
const { dataSource = [], totalCount, query, type } = this.props; const { dataSource = [], totalCount, query, type, match } = this.props;
const { current, size } = query; const { current, size } = query;
const { RelatedPlanModalVisible, selectPlanList, selectCourseId } = this.state; const { RelatedPlanModalVisible, selectPlanList, selectCourseId } = this.state;
return ( return (
...@@ -465,16 +473,6 @@ class VideoCourseList extends React.Component { ...@@ -465,16 +473,6 @@ class VideoCourseList extends React.Component {
scroll={{ x: 1500 }} scroll={{ x: 1500 }}
className='video-list-table' className='video-list-table'
/> />
{/* <Table
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
onChange={this.handleChangeTable}
pagination={false}
scroll={{ x: 1500 }}
bordered
className='video-list-table'
/> */}
<div className='box-footer'> <div className='box-footer'>
<PageControl <PageControl
...@@ -499,9 +497,11 @@ class VideoCourseList extends React.Component { ...@@ -499,9 +497,11 @@ class VideoCourseList extends React.Component {
)} )}
{this.state.shareLiveModal} {this.state.shareLiveModal}
{this.state.watchDataModal} {this.state.watchDataModal}
<Route path={`${match.url}/video-course-detail`} component={VideoCourseDetail} />
<Route path={`${match.url}/course-data`} component={WatchData} />
</div> </div>
); );
} }
} }
export default VideoCourseList; export default withRouter(VideoCourseList);
...@@ -24,11 +24,17 @@ ...@@ -24,11 +24,17 @@
} }
} }
} }
.chapterNum {
color: #2966ff;
cursor: pointer;
}
} }
} }
.watchUserCount { .watchUserCount {
text-align: right; text-align: right;
padding: 16px; padding: 16px;
color: #2966ff;
cursor: pointer;
} }
.operate-text { .operate-text {
color: #2966FF; color: #2966FF;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:15 * @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan * @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27 * @LastEditTime: 2020-12-26 16:07:27
* @Description: 视频课-操作模块 * @Description: 线上课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有 * @Copyright: 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -22,7 +22,7 @@ export default function VideoCourseOpt() { ...@@ -22,7 +22,7 @@ export default function VideoCourseOpt() {
RCHistory.push('/create-video-course?type=add'); RCHistory.push('/create-video-course?type=add');
}} }}
className="mr12" className="mr12"
>新建视频课</Button> >新建线上课</Button>
</div> </div>
); );
} }
import User from "@/common/js/user";
import college from "@/common/lottie/college";
import { PageControl, XMTable } from "@/components";
import Breadcrumbs from "@/components/Breadcrumbs";
import CourseService from "@/domains/course-domain/CourseService";
import { Input, Select } from "antd";
import React from "react";
import { withRouter } from "react-router-dom";
import LearningDetailModal from "./LearningDetailModal";
import "./WatchData.less";
const { Search } = Input;
class WatchData extends React.Component {
constructor(props) {
super(props);
const courseId = window.getParameterByName("courseId");
const courseName = window.getParameterByName("courseName");
this.state = {
courseName,
courseId,
visible: true,
dataSource: [],
query: {
current: 1,
size: 10,
sourceEnum: null,
},
totalCount: 0,
learningDetailModal: null,
};
}
componentDidMount() {
this.handleFetchDataList();
}
// 获取观看视频数据列表
handleFetchDataList = () => {
const { query, courseId } = this.state;
const params = {
...query,
courseId: courseId,
storeId: User.getStoreId(),
};
CourseService.videoWatchInfo(params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total),
});
});
};
parseColumns = () => {
const SOURCENUM = {
WE_CHAT: "微信",
WORK_WE_CHAT: "企业微信",
};
const columns = [
{
title: "观看学员",
key: "name",
dataIndex: "name",
render: (val, item) => {
return (
<div className="watcher">
<div className="watcher__name">{val}</div>
<div className={`watcher__type ${item.sourceEnum}`}>
@{SOURCENUM[item.sourceEnum]}
</div>
</div>
);
},
},
{
title: "手机号",
key: "phone",
dataIndex: "phone",
},
{
title: "首次观看时间",
key: "firstWatch",
dataIndex: "firstWatch",
render: (val) => {
return formatDate("YYYY-MM-DD H:i", val);
},
},
{
title: "学习进度",
key: "progress",
dataIndex: "progress",
render: (val) => {
return <span>{val}%</span>;
},
},
{
title: "操作",
key: "operate",
dataIndex: "operate",
render: (val, record) => {
return (
<div className="operate">
<div
className="operate__item"
onClick={() => this.ShowLearningDetailModal(record)}
>
学习详情
</div>
</div>
);
},
},
];
return columns;
};
// 显示学员数据详情弹窗
ShowLearningDetailModal = (item) => {
const learningDetailModal = (
<LearningDetailModal
data={item}
title="学习详情"
onClose={() => {
this.setState({
learningDetailModal: null
});
}}
courseId={this.state.courseId}
/>
);
this.setState({ learningDetailModal });
};
// 搜索学员姓名或手机号
handleChangNickname = (value) => {
const isPhone = (value || "").match(/^\d+$/);
const { query } = this.state;
let _query = { ...query };
if (isPhone) {
_query.phone = value;
_query.nickName = null;
} else {
_query.nickName = value;
_query.phone = null;
}
_query.current = 1;
this.setState(
{
query: _query,
},
this.handleFetchDataList
);
};
// 搜索学员类型
handleWatchTypeSelect = (sourceEnum) => {
const { query } = this.state;
let _query = { ...query };
_query.sourceEnum = sourceEnum;
this.setState(
{
query: _query,
},
this.handleFetchDataList
);
};
render() {
const { dataSource, totalCount, query, courseName } = this.state;
const { current, size, sourceEnum } = query;
return (
<div className="page video-course-watch-data">
<Breadcrumbs
navList="观看学员数据"
goBack={() => {
window.RCHistory.goBack();
}}
/>
<div className="box">
<div className="box-header">
<div className="course-detail">
<div className="detail__line"></div>
<div className="detail__name">{courseName}</div>
</div>
<div className="filter-box">
<div className="watcher-name">
<span className="label">学员:</span>
<Search
placeholder="搜索学员姓名/手机号"
style={{ width: 200 }}
onChange={(e) => {
this.handleChangNickname(e.target.value);
}}
onSearch={() => {
this.handleFetchDataList();
}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="watch-type">
<span className="label">学员类型:</span>
<Select
showSearch
style={{ width: 200 }}
allowClear
onPopupScroll={this.handleFetchMore}
placeholder="请选择学员类型"
value={sourceEnum}
onChange={this.handleWatchTypeSelect}
>
<Select.Option tyle={{ textAlign: "center" }} value="WE_CHAT">
微信
</Select.Option>
<Select.Option
tyle={{ textAlign: "center" }}
value="WORK_WE_CHAT"
>
企业微信
</Select.Option>
</Select>
</div>
</div>
</div>
<XMTable
renderEmpty={{
image: college,
description: "暂无数据",
}}
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
className="video-list-table"
/>
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = { ...query, current: page + 1 };
this.setState(
{
query: _query,
},
() => {
this.handleFetchDataList();
}
);
}}
/>
</div>
</div>
{this.state.learningDetailModal}
</div>
);
}
}
export default withRouter(WatchData);
.video-course-watch-data {
.watcher {
display: flex;
&__type {
margin-left: 8px;
&.WE_CHAT {
color:rgba(28, 172, 27, 1);
}
&.WORK_WE_CHAT {
color: rgba(41, 102, 255, 1);
}
}
}
.filter-box {
display: flex;
margin-bottom: 4px;
.watcher-name {
margin-right: 40px;
}
}
.course-detail {
display: flex;
align-items: center;
margin-bottom: 16px;
.detail__line {
width: 4px;
height: 12px;
background-image: linear-gradient(#2966ff 83.5%, #0acca4 16.5%);
margin-right: 8px;
}
.detail__name {
width: 880px;
height: 26px;
font-size: 19px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
}
}
}
\ No newline at end of file
...@@ -15,18 +15,18 @@ class VideoCourse extends React.Component { ...@@ -15,18 +15,18 @@ class VideoCourse extends React.Component {
current: 1, current: 1,
storeId: User.getStoreId(), storeId: User.getStoreId(),
}, },
dataSource: [], // 视频课列表 dataSource: [], // 线上课列表
totalCount: 0, // 视频课数据总条数 totalCount: 0, // 线上课数据总条数
currentTabKey: 'internal', currentTabKey: 'internal',
}; };
} }
componentWillMount() { componentWillMount() {
// 获取视频课列表 // 获取线上课列表
this.handleFetchScheduleList(); this.handleFetchScheduleList();
} }
// 获取视频课列表 // 获取线上课列表
handleFetchScheduleList = (_query = {}) => { handleFetchScheduleList = (_query = {}) => {
const { currentTabKey } = this.state; const { currentTabKey } = this.state;
const query = { const query = {
...@@ -81,7 +81,7 @@ class VideoCourse extends React.Component { ...@@ -81,7 +81,7 @@ class VideoCourse extends React.Component {
const { dataSource, totalCount, query, currentTabKey } = this.state; const { dataSource, totalCount, query, currentTabKey } = this.state;
return ( return (
<div className='page video-course-page'> <div className='page video-course-page'>
<div className='content-header'>视频课</div> <div className='content-header'>线上课</div>
<div className='box'> <div className='box'>
<Tabs onChange={this.currenTabChange} activeKey={currentTabKey}> <Tabs onChange={this.currenTabChange} activeKey={currentTabKey}>
...@@ -95,7 +95,7 @@ class VideoCourse extends React.Component { ...@@ -95,7 +95,7 @@ class VideoCourse extends React.Component {
<If condition={currentTabKey === 'internal'}> <If condition={currentTabKey === 'internal'}>
<VideoCourseOpt /> <VideoCourseOpt />
</If> </If>
{/* 视频课列表模块 */} {/* 线上课列表模块 */}
<VideoCourseList <VideoCourseList
type={currentTabKey} type={currentTabKey}
query={query} query={query}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @Date: 2020-05-19 11:01:31 * @Date: 2020-05-19 11:01:31
* @Last Modified by: 吴文洁 * @Last Modified by: 吴文洁
* @Last Modified time: 2020-05-25 16:50:47 * @Last Modified time: 2020-05-25 16:50:47
* @Description 余额异常弹窗 * @Description 学员观看数据弹窗
*/ */
import User from '@/common/js/user'; import User from '@/common/js/user';
import { PageControl, XMTable } from '@/components'; import { PageControl, XMTable } from '@/components';
...@@ -128,65 +128,52 @@ class WatchDataModal extends React.Component { ...@@ -128,65 +128,52 @@ class WatchDataModal extends React.Component {
const { visible, size, dataSource, totalCount, query } = this.state; const { visible, size, dataSource, totalCount, query } = this.state;
return ( return (
<Modal <Modal
title='视频课观看数据' title="线上课观看数据"
visible={visible} visible={visible}
footer={null} footer={null}
onCancel={this.onClose} onCancel={this.onClose}
maskClosable={false} maskClosable={false}
className='watch-data-modal' className="watch-data-modal"
closable={true} closable={true}
width={800} width={800}
closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}> closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
<div className='search-container'> >
<Search <div className="search-container">
placeholder='搜索学员姓名/手机号' <Search placeholder="搜索学员姓名/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} enterButton={<span className="icon iconfont">&#xe832;</span>}/>
style={{ width: 200 }}
onChange={(e) => {
this.handleChangNickname(e.target.value);
}}
onSearch={() => {
this.handleFetchDataList();
}}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/>
</div> </div>
<div> <div>
<XMTable <XMTable
renderEmpty={{ renderEmpty={{
image: college, image: college,
description: '暂无数据', description: '暂无数据',
}} }}
rowKey={(record) => record.id} rowKey={record => record.id}
dataSource={dataSource} dataSource={dataSource}
columns={this.parseColumns()} columns={this.parseColumns()}
pagination={false} pagination={false}
bordered bordered
/> />
{dataSource.length > 0 && ( {dataSource.length >0 &&
<div className='box-footer'> <div className="box-footer">
<PageControl <PageControl
current={query.current - 1} current={query.current - 1}
pageSize={size} pageSize={size}
total={totalCount} total={totalCount}
size='small' size="small"
toPage={(page) => { toPage={(page) => {
const _query = { ...query, current: page + 1 }; const _query = {...query, current: page + 1};
this.setState( this.setState({
{ query:_query
query: _query, },()=>{ this.handleFetchDataList()})
},
() => {
this.handleFetchDataList();
}
);
}} }}
onShowSizeChange={this.onShowSizeChange} onShowSizeChange={this.onShowSizeChange}
/> />
</div> </div>
)} }
</div> </div>
</Modal> </Modal>
); )
} }
} }
......
...@@ -343,7 +343,7 @@ class Home extends React.Component { ...@@ -343,7 +343,7 @@ class Home extends React.Component {
</div> </div>
</div> </div>
<div className='course-item'> <div className='course-item'>
<div className='course-title'>视频课</div> <div className='course-title'>线上课</div>
<div className='data'> <div className='data'>
<span className='course-number'>{videoCourseNum}</span> <span className='course-number'>{videoCourseNum}</span>
{incVideoCourseNum > 0 && <span className='icon iconfont'>&#xe635;</span>} {incVideoCourseNum > 0 && <span className='icon iconfont'>&#xe635;</span>}
...@@ -409,9 +409,8 @@ class Home extends React.Component { ...@@ -409,9 +409,8 @@ class Home extends React.Component {
</span> </span>
<span <span
className={`tab${scheduleType === 'VOICE' ? ' selected' : ''}`} className={`tab${scheduleType === 'VOICE' ? ' selected' : ''}`}
onClick={() => this.setState({ scheduleType: 'VOICE' }, () => this.getHotCourse())}> onClick={() => this.setState({ scheduleType: 'VOICE' }, () => this.getHotCourse())}
视频课 >线上课</span>
</span>
<span <span
className={`tab${scheduleType === 'PICTURE' ? ' selected' : ''}`} className={`tab${scheduleType === 'PICTURE' ? ' selected' : ''}`}
onClick={() => this.setState({ scheduleType: 'PICTURE' }, () => this.getHotCourse())}> onClick={() => this.setState({ scheduleType: 'PICTURE' }, () => this.getHotCourse())}>
......
...@@ -55,7 +55,7 @@ const ENUM = { ...@@ -55,7 +55,7 @@ const ENUM = {
CourseTypeEnum: { CourseTypeEnum: {
LIVE: "直播课", LIVE: "直播课",
VOICE: "视频课", VOICE: "线上课",
PICTURE: "图文课", PICTURE: "图文课",
FOLDER: "学习资料", FOLDER: "学习资料",
}, },
......
...@@ -188,7 +188,7 @@ class Classification extends Component { ...@@ -188,7 +188,7 @@ class Classification extends Component {
<div className='sider-title'>知识分类</div> <div className='sider-title'>知识分类</div>
<Search <Search
className='sider-search' className='sider-search'
placeholder='搜索名称分类' placeholder='搜索分类名称'
onSearch={(value) => { onSearch={(value) => {
this.queryCategoryTree(value); this.queryCategoryTree(value);
}} }}
......
...@@ -65,10 +65,11 @@ class KnowledgeBaseFilter extends React.Component { ...@@ -65,10 +65,11 @@ class KnowledgeBaseFilter extends React.Component {
<Row type="flex" justify="space-between" align="top"> <Row type="flex" justify="space-between" align="top">
<div className="search-condition"> <div className="search-condition">
<div className="search-condition__item"> <div className="search-condition__item">
<span className="search-name">课程名称:</span> <span className="search-name">资料名称:</span>
<Search <Search
value={name} value={name}
placeholder="搜索课程名称" // allowClear
placeholder="搜索资料名称"
onChange={(e) => { onChange={(e) => {
this.handleChangeQuery("name", e.target.value, false); this.handleChangeQuery("name", e.target.value, false);
}} }}
...@@ -79,33 +80,6 @@ class KnowledgeBaseFilter extends React.Component { ...@@ -79,33 +80,6 @@ class KnowledgeBaseFilter extends React.Component {
enterButton={<span className="icon iconfont">&#xe832;</span>} enterButton={<span className="icon iconfont">&#xe832;</span>}
/> />
</div> </div>
<div className="search-condition__item">
<span className="shelf-status">课程类型:</span>
<Select
style={{ width: "calc(100% - 84px)" }}
placeholder="请选择课程类型"
allowClear={true}
value={type}
onChange={(value) => {
this.handleChangeQuery("type", value);
}}
suffixIcon={
<span
className="icon iconfont"
style={{ fontSize: "12px", color: "#BFBFBF" }}
>
&#xe835;
</span>
}
>
{Reflect.ownKeys(ENUM.CourseTypeEnum).map((item) => {
return (
<Option key={item} value={item}>{ENUM.CourseTypeEnum[item]}</Option>
);
})}
</Select>
</div>
</div> </div>
<div className="reset-fold-area"> <div className="reset-fold-area">
<Tooltip title="清空筛选"> <Tooltip title="清空筛选">
......
...@@ -2,13 +2,14 @@ ...@@ -2,13 +2,14 @@
* @Description: * @Description:
* @Author: zangsuyun * @Author: zangsuyun
* @Date: 2021-03-12 14:49:40 * @Date: 2021-03-12 14:49:40
* @LastEditors: Please set LastEditors * @LastEditors: yuananting
* @LastEditTime: 2021-06-11 16:44:59 * @LastEditTime: 2021-07-15 14:07:42
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import React from "react"; import React from "react";
import { Modal, message, Tooltip, Switch, Dropdown, Button } from "antd"; import { Modal, message, Tooltip, Switch, Dropdown, Button } from "antd";
import { FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum';
import { Route, withRouter } from "react-router-dom"; import { Route, withRouter } from "react-router-dom";
import { PageControl, XMTable } from "@/components"; import { PageControl, XMTable } from "@/components";
import { LIVE_SHARE_MAP } from "@/common/constants/academic/cloudClass"; import { LIVE_SHARE_MAP } from "@/common/constants/academic/cloudClass";
...@@ -181,161 +182,20 @@ class KnowledgeBaseList extends React.Component { ...@@ -181,161 +182,20 @@ class KnowledgeBaseList extends React.Component {
const { current, size } = query const { current, size } = query
const columns = [ const columns = [
{ {
title: "课程名称", title: "名称",
key: "name", key: "name",
dataIndex: "name", dataIndex: "name",
width: 391,
fixed: "left",
render: (val, record) => { render: (val, record) => {
const { coverUrl, mediaCourseUrl, courseDivision } = record.source const { coverUrl, mediaCourseUrl, courseDivision } = record.source
let hasCover = false let hasCover = false
const type = record.type const type = record.type
return ( return (
<div>
{type === "LIVE" && (
<div className='record__item'>
{record.source &&
record.source.courseMediaVOS.map((item, index) => {
if (item.contentType === "COVER") {
hasCover = true
return <img className='course-cover' key={index} src={item.mediaUrl} />
}
})}
{!hasCover && (
<img
className="course-cover"
src={"https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png"}
/>
)}
<div>
{val.length > 17 ? (
<Tooltip title={val}>
<div className='course-name'>{val}</div>
</Tooltip>
) : (
<div className='course-name'>{val}</div>
)}
<div>
<span className='course-time'>
{formatDate("YYYY-MM-DD H:i", parseInt(record.source.startTime))}~{formatDate("H:i", parseInt(record.source.endTime))}
</span>
<span
className='course-status'
style={{
color: ENUM.courseStateShow[record.source.courseState].color,
border: `1px solid ${ENUM.courseStateShow[record.source.courseState].color}`
}}>
{ENUM.courseStateShow[record.source.courseState].title}
</span>
{record.hideToUser && (
<Tooltip title={<div>课程未成功开课,已在学员知识列表中隐藏</div>}>
<i
className='icon iconfont'
style={{
marginLeft: "5px",
cursor: "pointer",
color: "#FF4F4F",
fontSize: "14px"
}}>
&#xe61d;
</i>
</Tooltip>
)}
</div>
<div className='teacher-assistant'>
{record.source.teacherName.length > 4 ? (
<Tooltip title={record.source.teacherName}>
<span className='teacher'>讲师:{record.source.teacherName}</span>
</Tooltip>
) : (
<span className='teacher'>讲师:{record.source.teacherName}</span>
)}
{record.source.admins.length > 0 && (
<>
<span className='split'> | </span>
{this.handleAdminName(record.source.admins).length > 4 ? (
<Tooltip title={this.handleAdminName(record.source.admins)}>
<span className='assistant'>
助教:
{record.source.admins.map((item, index) => {
return (
<span>
{item.adminName} {index < record.source.admins.length - 1 && <span> 、</span>}{" "}
</span>
)
})}
</span>
</Tooltip>
) : (
<span className='assistant'>
助教:
{record.source.admins.map((item, index) => {
return (
<span key={index}>
{item.adminName} {index < record.source.admins.length - 1 && <span> 、</span>}{" "}
</span>
)
})}
</span>
)}
</>
)}
</div>
</div>
</div>
)}
{type === "VOICE" && (
<div className='record__item'>
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img
className='course-cover'
src={
coverUrl
? coverUrl
: courseDivision !== "EXTERNAL"
? `${mediaCourseUrl}?x-oss-process=video/snapshot,t_0,m_fast`
: "https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png"
}
alt=''
/>
{val.length > 25 ? (
<Tooltip title={val}>
<div className='course-name clamp'>{val}</div>
</Tooltip>
) : (
<div className='course-name clamp'>{val}</div>
)}
</div>
)}
{type === "PICTURE" && (
<div className="record__item">
<img
className="course-cover"
src={
coverUrl ||
"https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png"
}
/>
{val.length > 25 ? (
<Tooltip title={val}>
<div className='course-name clamp'>{val}</div>
</Tooltip>
) : (
<div className='course-name clamp'>{val}</div>
)}
</div>
)}
{type === "FOLDER" && (
<div <div
className='record__item' className='record__item'
onClick={() => { onClick={() => {
this.handleScanFile(record.source) this.handleScanFile(record.source)
}}> }}>
<div className={`folder-type ${record.source && record.source.folderFormat}`} /> <img src={FileTypeIcon[record.source.folderFormat] } style={{width:24,height:24}} alt='' className='item-img' />
{val.length > 25 ? ( {val.length > 25 ? (
<Tooltip title={val}> <Tooltip title={val}>
<div className='course-name clamp'>{val}</div> <div className='course-name clamp'>{val}</div>
...@@ -344,22 +204,11 @@ class KnowledgeBaseList extends React.Component { ...@@ -344,22 +204,11 @@ class KnowledgeBaseList extends React.Component {
<div className='course-name clamp'>{val}</div> <div className='course-name clamp'>{val}</div>
)} )}
</div> </div>
)}
</div>
) )
} }
}, },
{ {
title: "课程类型",
key: "type",
dataIndex: "type",
align: "center",
// width: 100,
render: (val, record) => {
return <div className=''>{val ? ENUM.CourseTypeEnum[val] : "-"}</div>
}
},
{
title: "创建人", title: "创建人",
key: "createName", key: "createName",
dataIndex: "createName", dataIndex: "createName",
...@@ -393,7 +242,7 @@ class KnowledgeBaseList extends React.Component { ...@@ -393,7 +242,7 @@ class KnowledgeBaseList extends React.Component {
}, },
{ {
title: "", title: "",
width: 48
}, },
{ {
title: "操作", title: "操作",
...@@ -452,7 +301,6 @@ class KnowledgeBaseList extends React.Component { ...@@ -452,7 +301,6 @@ class KnowledgeBaseList extends React.Component {
const { match } = this.props; const { match } = this.props;
localStorage.setItem("WatchData_CourseName", item.name); localStorage.setItem("WatchData_CourseName", item.name);
window.RCHistory.push({ window.RCHistory.push({
// pathname: `${match.url}/course-data?type=${item.courseType}&id=${item.liveCourseId}`,
pathname: `${match.url}/course-data?type=${item.type}&id=${item.id}` pathname: `${match.url}/course-data?type=${item.type}&id=${item.id}`
}) })
} }
...@@ -501,7 +349,7 @@ class KnowledgeBaseList extends React.Component { ...@@ -501,7 +349,7 @@ class KnowledgeBaseList extends React.Component {
dataSource={dataSource} dataSource={dataSource}
columns={this.parseColumns()} columns={this.parseColumns()}
pagination={false} pagination={false}
scroll={{ x: 900 }}
bordered bordered
className="knowledge-list-table" className="knowledge-list-table"
renderEmpty={{ renderEmpty={{
......
...@@ -109,7 +109,10 @@ ...@@ -109,7 +109,10 @@
.knowledge-base-list { .knowledge-base-list {
.record__item { .record__item {
display: flex; display: flex;
// align-items: center; align-items: center;
.item-img{
margin-right: 4px;
}
.course-cover { .course-cover {
min-width: 107px; min-width: 107px;
max-width: 90px; max-width: 90px;
...@@ -153,13 +156,7 @@ ...@@ -153,13 +156,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
height: 20px; height: 20px;
&.clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
white-space: normal;
height: 40px;
}
} }
.course-time { .course-time {
font-size: 12px; font-size: 12px;
......
...@@ -9,10 +9,8 @@ ...@@ -9,10 +9,8 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Button, Menu, Dropdown,message, Modal } from "antd"; import { Button, Menu, Dropdown, message, Modal } from "antd";
import SelectPrepareFileModal from "@/modules/prepare-lesson/modal/SelectPrepareFileModal"; import SelectPrepareFileModal from "@/modules/prepare-lesson/modal/SelectPrepareFileModal";
import { DownOutlined } from "@ant-design/icons";
import AddCourse from "../modal/AddCourse";
import User from "@/common/js/user"; import User from "@/common/js/user";
import Service from "@/common/js/service"; import Service from "@/common/js/service";
import KnowledgeAPI from "@/data-source/knowledge/request-api"; import KnowledgeAPI from "@/data-source/knowledge/request-api";
...@@ -31,16 +29,7 @@ export default function KnowledgeBaseOpt({ ...@@ -31,16 +29,7 @@ export default function KnowledgeBaseOpt({
const [openMoveModal, setOpenMoveModal] = useState(false); const [openMoveModal, setOpenMoveModal] = useState(false);
const [data, setData] = useState([]); const [data, setData] = useState([]);
const menu = (
<Menu>
<Menu.Item key="1" style={{ textAlign: "center" }}>
<span onClick={handAddCourse}>添加课程</span>
</Menu.Item>
<Menu.Item key="2" style={{ textAlign: "center" }}>
<span onClick={handleAddFile}>添加资料</span>
</Menu.Item>
</Menu>
);
useEffect(() => { useEffect(() => {
queryCategoryTree(); queryCategoryTree();
...@@ -76,19 +65,6 @@ export default function KnowledgeBaseOpt({ ...@@ -76,19 +65,6 @@ export default function KnowledgeBaseOpt({
return newTreeData; return newTreeData;
}; };
function handAddCourse() {
let modal = (
<AddCourse
onClose={() => {
setModal(null);
}}
onChange={onChange}
categoryId={categoryId}
updateCategoryTree={updateCategoryTree}
></AddCourse>
);
setModal(modal);
}
function handUpload(refIds) { function handUpload(refIds) {
const params = { const params = {
...@@ -118,7 +94,7 @@ export default function KnowledgeBaseOpt({ ...@@ -118,7 +94,7 @@ export default function KnowledgeBaseOpt({
isOpen={true} isOpen={true}
accept=".ppt,.pptx,.doc,.docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls" accept=".ppt,.pptx,.doc,.docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls"
tooltip="支持文件类型:ppt、word、excel、pdf、jpg、jpeg、png" tooltip="支持文件类型:ppt、word、excel、pdf、jpg、jpeg、png"
selectTypeList={["JPG", "JPEG", "PNG",'DOC','PDF','EXCEL','application/msword','application/vnd.ms-powerpoint']} // DOC 包含 .pptx,.docx,.xls.XLSX,WORD:DOC selectTypeList={["JPG", "JPEG", "PNG", 'DOC', 'PDF', 'EXCEL', 'application/msword', 'application/vnd.ms-powerpoint']} // DOC 包含 .pptx,.docx,.xls.XLSX,WORD:DOC
onClose={() => { onClose={() => {
setModal(null); setModal(null);
}} }}
...@@ -205,12 +181,9 @@ export default function KnowledgeBaseOpt({ ...@@ -205,12 +181,9 @@ export default function KnowledgeBaseOpt({
return ( return (
<div className="knowledge-course-opt"> <div className="knowledge-course-opt">
{_.isEmpty(selectedRowKeys) ? {_.isEmpty(selectedRowKeys) ?
(categoryId !== '0' && <Dropdown overlay={menu}> categoryId !== '0' && <Button type="primary" onClick={handleAddFile} className="mr8">
<Button type="primary" className="mr8"> 添加资料
添加知识
<DownOutlined />
</Button> </Button>
</Dropdown>)
: <div className="select-container"> : <div className="select-container">
<span className="con"> <span className="con">
<div> <div>
......
...@@ -107,11 +107,11 @@ class AddCourse extends React.Component { ...@@ -107,11 +107,11 @@ class AddCourse extends React.Component {
selectVideo: { selectVideo: {
external: [], external: [],
internal: [], internal: [],
}, //弹窗内已选择的视频课程 }, //弹窗内已选择的线上课程
currentVideoCourseListData: { currentVideoCourseListData: {
external: [], external: [],
internal: [], internal: [],
}, //页面中已关联的视频课程 }, //页面中已关联的线上课程
pictureDataSource: [], pictureDataSource: [],
pictureSize: 10, pictureSize: 10,
...@@ -201,7 +201,7 @@ class AddCourse extends React.Component { ...@@ -201,7 +201,7 @@ class AddCourse extends React.Component {
}); });
}; };
// 获取视频课列表 // 获取线上课列表
handleFetchVideoList = () => { handleFetchVideoList = () => {
const { videoQuery, videoSize, videoCourseDivision, videoDataSource, videoTotalCount } = this.state; const { videoQuery, videoSize, videoCourseDivision, videoDataSource, videoTotalCount } = this.state;
...@@ -421,7 +421,7 @@ class AddCourse extends React.Component { ...@@ -421,7 +421,7 @@ class AddCourse extends React.Component {
coverUrl || coverUrl ||
(videoCourseDivision === 'internal' (videoCourseDivision === 'internal'
? `${mediaCourseUrl}?x-oss-process=video/snapshot,t_0,m_fast` ? `${mediaCourseUrl}?x-oss-process=video/snapshot,t_0,m_fast`
: 'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png') : 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png')
} }
alt='' alt=''
/> />
...@@ -866,7 +866,7 @@ class AddCourse extends React.Component { ...@@ -866,7 +866,7 @@ class AddCourse extends React.Component {
)} )}
</div> </div>
</TabPane> </TabPane>
<TabPane tab='视频课' key='VIDEO'> <TabPane tab='线上课' key='VIDEO'>
<Radio.Group value={videoCourseDivision} onChange={this.videoCourseDivisionChange} style={{ marginBottom: 8 }}> <Radio.Group value={videoCourseDivision} onChange={this.videoCourseDivisionChange} style={{ marginBottom: 8 }}>
<Radio.Button value='internal'>内部课程</Radio.Button> <Radio.Button value='internal'>内部课程</Radio.Button>
<Radio.Button value='external'>外部课程</Radio.Button> <Radio.Button value='external'>外部课程</Radio.Button>
......
...@@ -25,7 +25,7 @@ class VideoList extends React.Component { ...@@ -25,7 +25,7 @@ class VideoList extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
id: "", // 视频课ID id: "", // 线上课ID
studentIds: [], studentIds: [],
selectedRowKeys: [], selectedRowKeys: [],
query: { query: {
...@@ -33,8 +33,8 @@ class VideoList extends React.Component { ...@@ -33,8 +33,8 @@ class VideoList extends React.Component {
current: 1, current: 1,
storeId: User.getStoreId(), storeId: User.getStoreId(),
}, },
dataSource: [], // 视频课列表 dataSource: [], // 线上课列表
totalCount: 0, // 视频课数据总条数 totalCount: 0, // 线上课数据总条数
}; };
} }
...@@ -50,11 +50,11 @@ class VideoList extends React.Component { ...@@ -50,11 +50,11 @@ class VideoList extends React.Component {
} }
componentWillMount() { componentWillMount() {
// 获取视频课列表 // 获取线上课列表
this.handleFetchScheduleList(); this.handleFetchScheduleList();
} }
// 获取视频课列表 // 获取线上课列表
handleFetchScheduleList = (_query = {}) => { handleFetchScheduleList = (_query = {}) => {
const query = { const query = {
...this.state.query, ...this.state.query,
......
/* /*
* @Author: zhangleyuan * @Author: zhangleyuan
* @Date: 2021-02-20 16:13:39 * @Date: 2021-02-20 16:13:39
* @LastEditors: wufan * @LastEditors: yuananting
* @LastEditTime: 2021-05-30 20:39:16 * @LastEditTime: 2021-07-20 17:17:12
* @Description: 描述一下 * @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react';
import { Button, message, Modal } from 'antd' import { Button, message, Modal } from 'antd';
import ShowTips from '@/components/ShowTips' import ShowTips from '@/components/ShowTips';
import Breadcrumbs from '@/components/Breadcrumbs' import Breadcrumbs from '@/components/Breadcrumbs';
import BasicInfo from './components/BasicInfo' import BasicInfo from './components/BasicInfo';
import TrainingTask from './components/TrainingTask' import TrainingTask from './components/TrainingTask';
import ExpiredCourseList from './components/ExpiredCourseList' import ExpiredCourseList from './components/ExpiredCourseList';
import PlanService from '@/domains/plan-domain/planService' import PlanService from '@/domains/plan-domain/planService';
import User from '@/common/js/user' import User from '@/common/js/user';
import _ from 'underscore' import _ from 'underscore';
import './AddPlan.less' import './AddPlan.less';
import Bus from '@/core/bus';
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png'; const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
const defaultBasicData = { const defaultBasicData = {
planName: '', planName: '',
...@@ -28,37 +29,43 @@ const defaultBasicData = { ...@@ -28,37 +29,43 @@ const defaultBasicData = {
operateType: 'All_Operate', operateType: 'All_Operate',
percentCompleteLive: 80, percentCompleteLive: 80,
percentCompleteVideo: 80, percentCompleteVideo: 80,
percentCompletePicture: 100 percentCompletePicture: 100,
} };
const defaultTaskList = [] const defaultTaskList = [];
function AddPlan() { function AddPlan() {
const id = getParameterByName('id') const id = getParameterByName('id');
const type = getParameterByName('type') const type = getParameterByName('type');
const [basicData, setBasicData] = useState(defaultBasicData) const [basicData, setBasicData] = useState(defaultBasicData);
const [taskList, setTaskList] = useState(defaultTaskList) const [taskList, setTaskList] = useState(defaultTaskList);
const [expiredCourseList, setExpiredCourseList] = useState([]) const [expiredCourseList, setExpiredCourseList] = useState([]);
const [hasGetDetail, setHasGetDetail] = useState(false) const [hasGetDetail, setHasGetDetail] = useState(false);
const [submitDisabled, setSubmitDisabled] = useState(false) const [submitDisabled, setSubmitDisabled] = useState(false);
const [editorTextLength, setEditorTextLength] = useState(0);
useEffect(() => { useEffect(() => {
if (type === 'edit') { if (type === 'edit') {
getPlanDetail() getPlanDetail();
getPlanCustomerState() getPlanCustomerState();
} }
}, id) Bus.bind('editorLimit', (editorTextLength) => {
setEditorTextLength(editorTextLength);
});
}, id);
function getPlanCustomerState() { function getPlanCustomerState() {
PlanService.getTrainingCourseAutoCancel({ PlanService.getTrainingCourseAutoCancel({
planId: id planId: id,
}).then((res) => { }).then((res) => {
const expiredCourseList = res.result const expiredCourseList = res.result;
setExpiredCourseList(expiredCourseList) setExpiredCourseList(expiredCourseList);
}) });
} }
function getPlanDetail() { function getPlanDetail() {
PlanService.getTrainingPlanDetail({ PlanService.getTrainingPlanDetail({
planId: id planId: id,
}).then((res) => { }).then((res) => {
const { const {
planId,
planName, planName,
enableState, enableState,
operateType, operateType,
...@@ -67,35 +74,38 @@ function AddPlan() { ...@@ -67,35 +74,38 @@ function AddPlan() {
percentCompleteVideo, percentCompleteVideo,
percentCompletePicture, percentCompletePicture,
courseMediaVOS, courseMediaVOS,
trainingTaskList trainingTaskList,
} = res.result } = res.result;
let coverId let coverId;
let coverUrl let coverUrl;
let instro let instro;
let hasIntro = false;
courseMediaVOS.map((item) => { courseMediaVOS.map((item) => {
switch (item.contentType) { switch (item.contentType) {
case 'COVER': case 'COVER':
coverId = item.mediaContent coverId = item.mediaContent;
coverUrl = item.mediaUrl coverUrl = item.mediaUrl;
break break;
case 'INTRO': case 'INTRO':
instro = item.mediaContent hasIntro = true;
break instro = item.mediaContent;
break;
default: default:
break break;
} }
return item return item;
}) });
let _selectOperatorList = [] let _selectOperatorList = [];
if (operateIds) { if (operateIds) {
_selectOperatorList = operateIds.map((item, index) => { _selectOperatorList = operateIds.map((item, index) => {
let _item = {} let _item = {};
_item.id = item _item.id = item;
return _item return _item;
}) });
} }
setTaskList(trainingTaskList) setTaskList(trainingTaskList);
setBasicData({ setBasicData({
planId,
planName, planName,
coverUrl: coverUrl || defaultCover, coverUrl: coverUrl || defaultCover,
coverId, coverId,
...@@ -105,20 +115,20 @@ function AddPlan() { ...@@ -105,20 +115,20 @@ function AddPlan() {
operateType, operateType,
percentCompleteLive, percentCompleteLive,
percentCompleteVideo, percentCompleteVideo,
percentCompletePicture percentCompletePicture,
}) });
setHasGetDetail(true) setHasGetDetail(true);
}) });
} }
function handleChangeBasicInfo(field, value, option) { function handleChangeBasicInfo(field, value, option) {
setBasicData({ setBasicData({
...basicData, ...basicData,
[field]: value [field]: value,
}) });
} }
function handleChangeTaskInfo(value) { function handleChangeTaskInfo(value) {
setTaskList(value) setTaskList(value);
} }
function submitInfo() { function submitInfo() {
...@@ -132,37 +142,37 @@ function AddPlan() { ...@@ -132,37 +142,37 @@ function AddPlan() {
percentCompleteVideo, percentCompleteVideo,
percentCompletePicture, percentCompletePicture,
coverId, coverId,
coverUrl coverUrl,
} = basicData } = basicData;
let input = /^[\s]*$/ let input = /^[\s]*$/;
if (!planName || input.test(planName)) { if (!planName || input.test(planName)) {
message.warning('请输入的培训计划名称') message.warning('请输入的培训计划名称');
return return;
} }
if (operateType === 'Assign_Operate' && selectOperatorList.length === 0) { if (operateType === 'Assign_Operate' && selectOperatorList.length === 0) {
message.warning('请选择指定运营师') message.warning('请选择指定运营师');
return return;
} }
if (!percentCompleteLive && percentCompleteLive !== 0) { if (!percentCompleteLive && percentCompleteLive !== 0) {
message.warning('请输入完成标准') message.warning('请输入完成标准');
return return;
} }
if (!percentCompleteVideo && percentCompleteVideo !== 0) { if (!percentCompleteVideo && percentCompleteVideo !== 0) {
message.warning('请输入完成标准') message.warning('请输入完成标准');
return return;
} }
if (!percentCompletePicture && percentCompletePicture !== 0) { if (!percentCompletePicture && percentCompletePicture !== 0) {
message.warning('请输入完成标准') message.warning('请输入完成标准');
return return;
} }
if (taskList.length === 0) { if (taskList.length === 0) {
message.warning('请输入培训计划内容') message.warning('请输入培训计划内容');
return return;
} }
for (let i = 0; i < taskList.length; i++) { for (let i = 0; i < taskList.length; i++) {
if (input.test(taskList[i].taskName)) { if (input.test(taskList[i].taskName)) {
message.warning('培训任务名称不能为空') message.warning('培训任务名称不能为空');
return false return false;
} }
if (taskList[i].courseList.length === 0) { if (taskList[i].courseList.length === 0) {
Modal.confirm({ Modal.confirm({
...@@ -170,28 +180,33 @@ function AddPlan() { ...@@ -170,28 +180,33 @@ function AddPlan() {
content: '每个任务下至少关联一个课程', content: '每个任务下至少关联一个课程',
okText: '确定', okText: '确定',
cancelText: '取消', cancelText: '取消',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span> icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
}) });
return false return false;
}
} }
if (editorTextLength > 1000) {
message.warning('简介超过字数限定');
return;
} }
let scheduleMediaRequests = [] let scheduleMediaRequests = [];
let coverObj = { let coverObj = {
contentType: 'COVER', contentType: 'COVER',
mediaContent: coverId, mediaContent: coverId,
mediaType: 'PICTURE', mediaType: 'PICTURE',
mediaUrl: coverUrl mediaUrl: coverUrl,
} };
if (coverId) { if (coverId) {
scheduleMediaRequests = [...scheduleMediaRequests, coverObj] scheduleMediaRequests = [...scheduleMediaRequests, coverObj];
} }
let instroObj = { let instroObj = {
contentType: 'INTRO', contentType: 'INTRO',
mediaType: 'TEXT', mediaType: 'TEXT',
mediaContent: instro mediaContent: instro,
} };
if (instro) { if (instro) {
scheduleMediaRequests = [...scheduleMediaRequests, instroObj] scheduleMediaRequests = [...scheduleMediaRequests, instroObj];
} }
const params = { const params = {
...@@ -205,43 +220,43 @@ function AddPlan() { ...@@ -205,43 +220,43 @@ function AddPlan() {
planName, planName,
scheduleMediaRequests, scheduleMediaRequests,
storeId: User.getStoreId(), storeId: User.getStoreId(),
trainingTaskList: handleSubmitTaskData(taskList) trainingTaskList: handleSubmitTaskData(taskList),
} };
if (type === 'add') { if (type === 'add') {
PlanService.createTrainingPlan(params).then((res) => { PlanService.createTrainingPlan(params).then((res) => {
if (res.success) { if (res.success) {
message.success('新建成功') message.success('新建成功');
setSubmitDisabled(true) setSubmitDisabled(true);
window.RCHistory.goBack() window.RCHistory.goBack();
} }
}) });
} else { } else {
const _params = { const _params = {
...params, ...params,
id id,
} };
PlanService.updateTrainingPlan(_params).then((res) => { PlanService.updateTrainingPlan(_params).then((res) => {
if (res.success) { if (res.success) {
message.success('更新成功') message.success('更新成功');
window.RCHistory.goBack() window.RCHistory.goBack();
} }
}) });
} }
} }
function handleSubmitTaskData(taskData) { function handleSubmitTaskData(taskData) {
return taskData.map((item, index) => { return taskData.map((item, index) => {
let _item = {} let _item = {};
_item.taskId = item.taskId _item.taskId = item.taskId;
_item.taskName = item.taskName _item.taskName = item.taskName;
_item.courseList = item.courseList.map((childItem, index) => { _item.courseList = item.courseList.map((childItem, index) => {
let _childItem = {} let _childItem = {};
_childItem.courseId = childItem.courseId _childItem.courseId = childItem.courseId;
_childItem.courseName = childItem.courseName _childItem.courseName = childItem.courseName;
_childItem.courseType = childItem.courseType _childItem.courseType = childItem.courseType;
return _childItem return _childItem;
}) });
return _item return _item;
}) });
} }
// 取消编辑并返回上一级路由 // 取消编辑并返回上一级路由
function handleGoBack() { function handleGoBack() {
...@@ -253,11 +268,11 @@ function AddPlan() { ...@@ -253,11 +268,11 @@ function AddPlan() {
cancelText: '留在本页', cancelText: '留在本页',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>, icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => { onOk: () => {
window.RCHistory.goBack() window.RCHistory.goBack();
} },
}) });
} else { } else {
window.RCHistory.goBack() window.RCHistory.goBack();
} }
} }
...@@ -293,6 +308,6 @@ function AddPlan() { ...@@ -293,6 +308,6 @@ function AddPlan() {
</Button> </Button>
</div> </div>
</div> </div>
) );
} }
export default AddPlan export default AddPlan;
/* /*
* @Author: yuananting
* @Date: 2021-07-05 10:48:08
* @LastEditors: yuananting
* @LastEditTime: 2021-07-20 17:23:13
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
/*
* @Author: zhangleyuan * @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51 * @Date: 2021-02-20 16:45:51
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
...@@ -7,17 +16,17 @@ ...@@ -7,17 +16,17 @@
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import React from 'react'; import React from 'react';
import { Button, Input, Switch, Radio, Row, Col, Modal, message, Tooltip } from 'antd'; import { Button, Input, Switch, Radio, Row, Col, message, Tooltip } from 'antd';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import SelectOperatorModal from '../modal/SelectOperatorModal'; import SelectOperatorModal from '../modal/SelectOperatorModal';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal'; import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import Upload from '@/core/upload'; import Upload from '@/core/upload';
import GraphicsEditor from '@/modules/course-manage/components/GraphicsEditor';
import ImgClipModal from '@/components/ImgClipModal' import ImgClipModal from '@/components/ImgClipModal'
import './BasicInfo.less'; import './BasicInfo.less';
const { TextArea } = Input; const { TextArea } = Input;
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png'; const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
let cutFlag = false;
class BasicInfo extends React.Component { class BasicInfo extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -74,7 +83,7 @@ class BasicInfo extends React.Component { ...@@ -74,7 +83,7 @@ class BasicInfo extends React.Component {
handleSelectCover = (file) => { handleSelectCover = (file) => {
this.setState({ this.setState({
visible: true, visible: true,
imageFile:file imageFile: file,
}); });
}; };
//获取resourceId //获取resourceId
...@@ -125,7 +134,7 @@ class BasicInfo extends React.Component { ...@@ -125,7 +134,7 @@ class BasicInfo extends React.Component {
}; };
render() { render() {
const { operatorModalVisible, showSelectFileModal, visible, hasImgReady, cutImageBlob,imageFile} = this.state; const { operatorModalVisible, showSelectFileModal, visible, imageFile } = this.state;
const { data } = this.props; const { data } = this.props;
const { planName, coverUrl, instro, enableState, operateType, selectOperatorList, percentCompleteLive, percentCompleteVideo, percentCompletePicture } = const { planName, coverUrl, instro, enableState, operateType, selectOperatorList, percentCompleteLive, percentCompleteVideo, percentCompletePicture } =
data; data;
...@@ -148,10 +157,6 @@ class BasicInfo extends React.Component { ...@@ -148,10 +157,6 @@ class BasicInfo extends React.Component {
<div className='cover'> <div className='cover'>
<span className='label'>封面图:</span> <span className='label'>封面图:</span>
<div className='cover__wrap'> <div className='cover__wrap'>
<div className='img-content'>
{isDefaultCover && <span className='tag'>默认图</span>}
<img src={coverUrl} width='690' alt='' />
</div>
<div className='opt-btns'> <div className='opt-btns'>
<Button <Button
onClick={() => { onClick={() => {
...@@ -166,6 +171,10 @@ class BasicInfo extends React.Component { ...@@ -166,6 +171,10 @@ class BasicInfo extends React.Component {
</span> </span>
<div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div> <div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div> </div>
<div className='img-content'>
{isDefaultCover && <span className='tag'>默认图</span>}
<img src={coverUrl} width='690' alt='' />
</div>
</div> </div>
</div> </div>
<div className='introduction'> <div className='introduction'>
...@@ -182,20 +191,13 @@ class BasicInfo extends React.Component { ...@@ -182,20 +191,13 @@ class BasicInfo extends React.Component {
<div className='wether-use'> <div className='wether-use'>
<span className='label'>是否启用:</span> <span className='label'>是否启用:</span>
<div className='content'> <div className='content'>
<div>
<Switch <Switch
checked={enableState === 'YES' ? true : false} checked={enableState === 'YES' ? true : false}
onChange={() => { onChange={() => {
this.enableStateChange(); this.enableStateChange();
}} }}
/> />
</div> <div className='instro-text'>{enableState === 'YES' ? '已开启,培训计划可正常分享给学员学习' : '已关闭,培训计划暂不进行分享和学习'}</div>
<div>
<div className='instro-text'>
<div>开启:此培训计划可以分享给学员进行学习</div>
<div>关闭:此培训计划暂不可分享给学员进行学习,后续可开启</div>
</div>
</div>
</div> </div>
</div> </div>
<div className='view-range'> <div className='view-range'>
...@@ -213,7 +215,7 @@ class BasicInfo extends React.Component { ...@@ -213,7 +215,7 @@ class BasicInfo extends React.Component {
onChange={(e) => { onChange={(e) => {
this.props.onChange('operateType', e.target.value); this.props.onChange('operateType', e.target.value);
}}> }}>
<Row style={{ marginBottom: '5px' }}> <Row style={{ marginBottom: '16px' }}>
<Col span={24}> <Col span={24}>
<Radio value='All_Operate'> <Radio value='All_Operate'>
所有运营师 所有运营师
...@@ -239,7 +241,7 @@ class BasicInfo extends React.Component { ...@@ -239,7 +241,7 @@ class BasicInfo extends React.Component {
选择运营师 选择运营师
</Button> </Button>
<span> <span>
已选择<span>{selectOperatorList.length}</span>名运营师 已选择 <span style={{ color: '#2966FF' }}>{selectOperatorList.length}</span> 名运营师
</span> </span>
</div> </div>
)} )}
...@@ -269,7 +271,7 @@ class BasicInfo extends React.Component { ...@@ -269,7 +271,7 @@ class BasicInfo extends React.Component {
<div className='live-standard-info'> <div className='live-standard-info'>
<span className='icon iconfont'>&#xe864;</span> <span className='icon iconfont'>&#xe864;</span>
<span className='instro'> <span className='instro'>
视频课单个课程,学员学习进度达到 线上课单个课节,学员学习进度达到
<Input <Input
width='40' width='40'
value={percentCompleteVideo} value={percentCompleteVideo}
...@@ -279,7 +281,7 @@ class BasicInfo extends React.Component { ...@@ -279,7 +281,7 @@ class BasicInfo extends React.Component {
onBlur={(e) => this.percentCompleteBlur(e, 'percentCompleteVideo')} onBlur={(e) => this.percentCompleteBlur(e, 'percentCompleteVideo')}
className='input-box' className='input-box'
/> />
%,即视为"已完成"学习 %,即课节视为"已完成"学习
</span> </span>
</div> </div>
<div className='live-standard-info'> <div className='live-standard-info'>
...@@ -325,9 +327,16 @@ class BasicInfo extends React.Component { ...@@ -325,9 +327,16 @@ class BasicInfo extends React.Component {
onSelect={this.handleSelectCover} onSelect={this.handleSelectCover}
/> />
)} )}
{ visible && {visible && (
<ImgClipModal visible={visible} imgUrl={imageFile.ossUrl} onConfirm={this.getSignature} onClose={()=>{this.setState({ visible: false });}}/> <ImgClipModal
} visible={visible}
imgUrl={imageFile.ossUrl}
onConfirm={this.getSignature}
onClose={() => {
this.setState({ visible: false });
}}
/>
)}
</div> </div>
); );
} }
......
.plan-basic-info{ .plan-basic-info {
.label { .label {
width: 110px; width: 110px;
text-align: right; text-align: right;
display:inline-block; display: inline-block;
font-size:14px; font-size: 14px;
color:#666; color: #666;
.require { .require {
color: #EC4B35; color: #ec4b35;
} }
.iconfont{ .iconfont {
font-size:14px; font-size: 14px;
color:#BFBFBF; color: #bfbfbf;
} }
} }
.cover { .cover {
display: flex; display: flex;
margin-top: 16px; margin-top: 24px;
&__wrap { &__wrap {
position: relative; position: relative;
display: flex;
.tag { .tag {
border-radius: 2px; border-radius: 2px;
background: #D6D6D6; background: #d6d6d6;
font-size: 12px; font-size: 12px;
height: 18px; height: 18px;
width: 52px; width: 52px;
text-align: center; text-align: center;
color: #FFF; color: #fff;
position: absolute; position: absolute;
top: 8px; top: 8px;
left: 8px; left: 8px;
} }
} }
.img-content { .img-content {
margin-right: 20px; position: relative;
margin-top: 8px;
width: 299px; width: 299px;
height: 169px; height: 169px;
img { img {
...@@ -42,90 +42,94 @@ ...@@ -42,90 +42,94 @@
object-fit: contain; object-fit: contain;
} }
} }
.opt-btns{ .opt-btns {
.default-btn { .default-btn {
margin:0 8px; margin-left: 14px;
color: #2966FF; color: #2966ff;
cursor: pointer; cursor: pointer;
&.disabled { &.disabled {
color: #CCC; color: #ccc;
cursor: not-allowed; cursor: not-allowed;
} }
} }
.tips{ .tips {
margin-top:8px; margin-top: 8px;
font-size:14px; font-size: 14px;
color:#999; color: #999;
} }
} }
} }
.introduction{ .introduction {
margin-top:16px; display: flex;
.instro-textarea{ margin-top: 24px;
.instro-textarea {
vertical-align: top; vertical-align: top;
} }
} }
.wether-use{ .wether-use {
display:flex; display: flex;
margin-top:16px; margin-top: 34px;
.instro-text{ .instro-text {
color:#999; color: #999;
margin-left:12px; margin-left: 12px;
} }
.content{ .content {
display:flex; display: flex;
} }
} }
.view-range{ .view-range {
display:flex; display: flex;
margin-top:16px; margin-top: 24px;
.label{ .label {
margin-top:2px; margin-top: 2px;
} }
.instro-text{ .instro-text {
color:#999; color: #999;
margin-left:12px; margin-left: 12px;
} }
.choose-business{ .choose-business {
margin-top:16px; margin-top: 12px;
margin-left: 24px;
.ant-btn {
margin-right: 12px;
} }
.playback__text{
margin-left:12px;
color:#999999;
} }
.playback__text {
margin-left: 12px;
color: #999999;
} }
.done-standard{ }
.done-standard {
display: flex; display: flex;
margin-top:22px; margin-top: 22px;
.standard-label{ .standard-label {
margin-top:3px; margin-top: 3px;
} }
.live-standard-info{ .live-standard-info {
margin-bottom:10px; margin-bottom: 10px;
} }
input{ input {
display:inline-block; display: inline-block;
width:90px; width: 90px;
height:32px; height: 32px;
} }
.icon{ .icon {
color:#A0A0A0; color: #a0a0a0;
font-size:14px; font-size: 14px;
margin-right:4px; margin-right: 4px;
} }
.instro{ .instro {
color:#333333; color: #333333;
font-size:14px; font-size: 14px;
} }
.input-box{ .input-box {
width: 60px; width: 60px;
height: 32px; height: 32px;
border-radius: 4px; border-radius: 4px;
border: 1px solid #E8E8E8; border: 1px solid #e8e8e8;
color:#333333; color: #333333;
font-size:14px; font-size: 14px;
margin:0 2px; margin: 0 2px;
} }
} }
} }
...@@ -18,7 +18,7 @@ function ExpiredCourseList(props) { ...@@ -18,7 +18,7 @@ function ExpiredCourseList(props) {
<span>直播课</span> <span>直播课</span>
} }
{ item.courseType === "VOICE " && { item.courseType === "VOICE " &&
<span>视频课</span> <span>线上课</span>
} }
</div> </div>
<div className="course-instro"> <div className="course-instro">
......
/* /*
* @Author: zhangleyuan * @Author: zhangleyuan
* @Date: 2021-02-20 16:46:46 * @Date: 2021-02-20 16:46:46
* @LastEditors: fusanqiasng * @LastEditors: wufan
* @LastEditTime: 2021-06-01 11:45:34 * @LastEditTime: 2021-07-16 11:29:13
* @Description: 描述一下 * @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -52,7 +52,6 @@ function PlanList(props) { ...@@ -52,7 +52,6 @@ function PlanList(props) {
title: '课程总数量', title: '课程总数量',
key: 'courseNum', key: 'courseNum',
dataIndex: 'courseNum', dataIndex: 'courseNum',
width: 110,
render: (val, record) => { render: (val, record) => {
return <div className='course-number'>{val}</div>; return <div className='course-number'>{val}</div>;
}, },
...@@ -64,6 +63,7 @@ function PlanList(props) { ...@@ -64,6 +63,7 @@ function PlanList(props) {
render: (val, item, index) => { render: (val, item, index) => {
return ( return (
<Switch <Switch
size='small'
checked={item.enableState === 'NO' ? false : true} checked={item.enableState === 'NO' ? false : true}
onChange={() => changeEnableState(item)} onChange={() => changeEnableState(item)}
disabled={User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager' ? false : true} disabled={User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager' ? false : true}
......
.plan-list { .plan-list {
margin-top: 12px; margin-top: 12px;
.course-number { .course-number {
text-align: right;
margin-right: 45px; margin-right: 45px;
} }
......
/* /*
* @Author: yuananting
* @Date: 2021-07-05 10:49:01
* @LastEditors: yuananting
* @LastEditTime: 2021-07-05 11:02:04
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
/*
* @Author: zhangleyuan * @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51 * @Date: 2021-02-20 16:45:51
* @LastEditors: fusanqiasng * @LastEditors: yuananting
* @LastEditTime: 2021-05-24 15:15:06 * @LastEditTime: 2021-07-01 17:16:50
* @Description: 描述一下 * @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
import React from 'react' import React from 'react';
import { Input, Form, Modal } from 'antd' import { Input, Form, Modal } from 'antd';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc' import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import arrayMove from 'array-move' import arrayMove from 'array-move';
import RelatedCourseModal from '../modal/relatedCourseModal' import RelatedCourseModal from '../modal/relatedCourseModal';
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom';
import './TrainingTask.less' import './TrainingTask.less';
const { confirm } = Modal const { confirm } = Modal;
const CourseType = { const CourseType = {
LIVE: { LIVE: {
text: '直播课' text: '直播课',
}, },
VOICE: { VOICE: {
text: '视频课' text: '线上课',
}, },
RECORD: { RECORD: {
text: '录播课' text: '录播课',
}, },
PICTURE: { PICTURE: {
text: '图文课' text: '图文课',
} },
} };
const courseStateShow = { const courseStateShow = {
UN_START: { UN_START: {
title: '待开播' title: '待开播',
}, },
STARTING: { STARTING: {
title: '直播中' title: '直播中',
}, },
FINISH: { FINISH: {
title: '回放' title: '回放',
}, },
EXPIRED: { EXPIRED: {
title: '未成功开课' title: '未成功开课',
} },
} };
const DragHandle = sortableHandle(() => ( const DragHandle = sortableHandle(() => (
<span className='operate__item'> <span className='operate__item'>
<span className='icon iconfont'>&#xe7cd;</span> <span className='icon iconfont'>&#xe7cd;</span>
<span className='text'>移动</span> <span className='text'>移动</span>
</span> </span>
)) ));
const SortableTaskItem = sortableElement((props) => <div {...props}>{props.taskitem}</div>) const SortableTaskItem = sortableElement((props) => <div {...props}>{props.taskitem}</div>);
const SortableTaskContainer = sortableContainer((props) => <div {...props}></div>) const SortableTaskContainer = sortableContainer((props) => <div {...props}></div>);
const SortableCourseItem = sortableElement((props) => <div {...props}>{props.courseitem}</div>) const SortableCourseItem = sortableElement((props) => <div {...props}>{props.courseitem}</div>);
const SortableCourseContainer = sortableContainer((props) => <div {...props}></div>) const SortableCourseContainer = sortableContainer((props) => <div {...props}></div>);
class TrainingTask extends React.Component { class TrainingTask extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
dataSource: this.props.data, dataSource: this.props.data,
selectedTaskIndex: 0, selectedTaskIndex: 0,
relatedCourseModalVisible: false relatedCourseModalVisible: false,
} };
} }
componentWillMount() {} componentWillMount() {}
componentWillReceiveProps(nextProps) {} componentWillReceiveProps(nextProps) {}
onTaskSortEnd = ({ oldIndex, newIndex }) => { onTaskSortEnd = ({ oldIndex, newIndex }) => {
const { dataSource } = this.state const { dataSource } = this.state;
if (oldIndex !== newIndex) { if (oldIndex !== newIndex) {
const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter((el) => !!el) const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter((el) => !!el);
this.setState( this.setState(
{ {
dataSource: newData dataSource: newData,
}, },
() => { () => {
this.props.onChange(newData) this.props.onChange(newData);
}
)
} }
);
} }
};
onCourseSortEnd = ({ oldIndex, newIndex }, parentIndex) => { onCourseSortEnd = ({ oldIndex, newIndex }, parentIndex) => {
const { dataSource } = this.state const { dataSource } = this.state;
const _dataSource = [...dataSource] const _dataSource = [...dataSource];
if (oldIndex !== newIndex) { if (oldIndex !== newIndex) {
_dataSource[parentIndex].courseList = arrayMove([].concat(dataSource[parentIndex].courseList), oldIndex, newIndex).filter((el) => !!el) _dataSource[parentIndex].courseList = arrayMove([].concat(dataSource[parentIndex].courseList), oldIndex, newIndex).filter((el) => !!el);
this.setState( this.setState(
{ {
dataSource: _dataSource dataSource: _dataSource,
}, },
() => { () => {
this.props.onChange(_dataSource) this.props.onChange(_dataSource);
}
)
} }
);
} }
};
addTask = () => { addTask = () => {
const { dataSource } = this.state const { dataSource } = this.state;
const taskObj = { const taskObj = {
taskName: '', taskName: '',
index: dataSource.length, index: dataSource.length,
type: 'input', type: 'input',
open: true, open: true,
courseList: [] courseList: [],
} };
const newData = [...dataSource, taskObj] const newData = [...dataSource, taskObj];
this.setState( this.setState(
{ {
dataSource: newData dataSource: newData,
}, },
() => { () => {
this.props.onChange(newData) this.props.onChange(newData);
}
)
} }
);
};
handleRenameTaskName = (e, record) => { handleRenameTaskName = (e, record) => {
const { value } = e.target const { value } = e.target;
const { dataSource } = this.state const { dataSource } = this.state;
record.taskName = value record.taskName = value;
this.setState( this.setState(
{ {
dataSource dataSource,
}, },
() => { () => {
this.props.onChange(dataSource) this.props.onChange(dataSource);
}
)
} }
);
};
handleTaskNameBlur = (e, record) => { handleTaskNameBlur = (e, record) => {
const { value } = e.target const { value } = e.target;
const { dataSource } = this.state const { dataSource } = this.state;
let input = /^[\s]*$/ let input = /^[\s]*$/;
if (value && !input.test(value)) { if (value && !input.test(value)) {
record.type = 'text' record.type = 'text';
this.setState( this.setState(
{ {
dataSource dataSource,
}, },
() => { () => {
this.props.onChange(dataSource) this.props.onChange(dataSource);
}
)
} }
);
} }
};
handleRenameCourseName = (e, record) => { handleRenameCourseName = (e, record) => {
const { value } = e.target const { value } = e.target;
const { dataSource } = this.state const { dataSource } = this.state;
record.courseName = value record.courseName = value;
this.setState( this.setState(
{ {
dataSource dataSource,
}, },
() => { () => {
this.props.onChange(dataSource) this.props.onChange(dataSource);
}
)
} }
);
};
handleCourseNameBlur = (e, record) => { handleCourseNameBlur = (e, record) => {
const { value } = e.target const { value } = e.target;
const { dataSource } = this.state const { dataSource } = this.state;
let input = /^[\s]*$/ let input = /^[\s]*$/;
if (value && !input.test(value)) { if (value && !input.test(value)) {
record.type = 'text' record.type = 'text';
this.setState( this.setState(
{ {
dataSource dataSource,
}, },
() => { () => {
this.props.onChange(dataSource) this.props.onChange(dataSource);
}
)
} }
);
} }
};
handleDeleteTask = (index) => { handleDeleteTask = (index) => {
return confirm({ return confirm({
title: '删除任务', title: '删除任务',
...@@ -191,23 +200,23 @@ class TrainingTask extends React.Component { ...@@ -191,23 +200,23 @@ class TrainingTask extends React.Component {
okType: 'danger', okType: 'danger',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
this.handleConfirmDeleteTask(index) this.handleConfirmDeleteTask(index);
} },
}) });
} };
handleConfirmDeleteTask = (index) => { handleConfirmDeleteTask = (index) => {
const { dataSource } = this.state const { dataSource } = this.state;
const newData = [...dataSource] const newData = [...dataSource];
newData.splice(index, 1) newData.splice(index, 1);
this.setState( this.setState(
{ {
dataSource: newData dataSource: newData,
}, },
() => { () => {
this.props.onChange(newData) this.props.onChange(newData);
}
)
} }
);
};
handleDeleteCourse = (parentIndex, index) => { handleDeleteCourse = (parentIndex, index) => {
return confirm({ return confirm({
...@@ -218,82 +227,82 @@ class TrainingTask extends React.Component { ...@@ -218,82 +227,82 @@ class TrainingTask extends React.Component {
okType: 'danger', okType: 'danger',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
this.handleConfirmDeleteCourse(parentIndex, index) this.handleConfirmDeleteCourse(parentIndex, index);
} },
}) });
} };
handleConfirmDeleteCourse = (parentIndex, index) => { handleConfirmDeleteCourse = (parentIndex, index) => {
const { dataSource } = this.state const { dataSource } = this.state;
const newData = [...dataSource] const newData = [...dataSource];
const selectData = [...newData[parentIndex].courseList] const selectData = [...newData[parentIndex].courseList];
selectData.splice(index, 1) selectData.splice(index, 1);
newData[parentIndex].courseList = selectData newData[parentIndex].courseList = selectData;
this.setState( this.setState(
{ {
dataSource: newData dataSource: newData,
}, },
() => { () => {
this.props.onChange(newData) this.props.onChange(newData);
}
)
} }
);
};
showRelatedCourseModal = (index) => { showRelatedCourseModal = (index) => {
this.setState({ this.setState({
selectedTaskIndex: index, selectedTaskIndex: index,
relatedCourseModalVisible: true relatedCourseModalVisible: true,
}) });
} };
closeRelatedCourseModal = (index) => { closeRelatedCourseModal = (index) => {
this.setState({ this.setState({
relatedCourseModalVisible: false relatedCourseModalVisible: false,
}) });
} };
confirmSelectCourse = (selectList) => { confirmSelectCourse = (selectList) => {
console.log('selectList', selectList) console.log('selectList', selectList);
const { selectedTaskIndex } = this.state const { selectedTaskIndex } = this.state;
const { dataSource } = this.state const { dataSource } = this.state;
const newData = [...dataSource] const newData = [...dataSource];
const selectData = [...newData[selectedTaskIndex].courseList] const selectData = [...newData[selectedTaskIndex].courseList];
const _selectData = [...selectData, ...selectList] const _selectData = [...selectData, ...selectList];
newData[selectedTaskIndex].courseList = _selectData newData[selectedTaskIndex].courseList = _selectData;
this.setState( this.setState(
{ {
relatedCourseModalVisible: false, relatedCourseModalVisible: false,
dataSource: newData dataSource: newData,
}, },
() => { () => {
this.props.onChange(newData) this.props.onChange(newData);
}
)
} }
);
};
openOrCloseTask = (index) => { openOrCloseTask = (index) => {
const { dataSource } = this.state const { dataSource } = this.state;
const newData = [...dataSource] const newData = [...dataSource];
newData[index].open = !newData[index].open newData[index].open = !newData[index].open;
this.setState( this.setState(
{ {
dataSource: newData dataSource: newData,
}, },
() => { () => {
this.props.onChange(newData) this.props.onChange(newData);
}
)
} }
);
};
handleValidatorTaskName = (rule, value) => { handleValidatorTaskName = (rule, value) => {
let input = /^[\s]*$/ let input = /^[\s]*$/;
if (input.test(value) || !value) { if (input.test(value) || !value) {
return Promise.reject(new Error('请输入任务名称')) return Promise.reject(new Error('请输入任务名称'));
}
return Promise.resolve()
} }
return Promise.resolve();
};
handleValidatorCourseName = (rule, value) => { handleValidatorCourseName = (rule, value) => {
let input = /^[\s]*$/ let input = /^[\s]*$/;
if (input.test(value) || !value) { if (input.test(value) || !value) {
return Promise.reject(new Error('请输入课程名称')) return Promise.reject(new Error('请输入课程名称'));
}
return Promise.resolve()
} }
return Promise.resolve();
};
renderTaskItem = (record, index) => { renderTaskItem = (record, index) => {
return ( return (
...@@ -314,8 +323,8 @@ class TrainingTask extends React.Component { ...@@ -314,8 +323,8 @@ class TrainingTask extends React.Component {
name={['taskName']} name={['taskName']}
rules={[ rules={[
{ {
validator: (rule, value) => this.handleValidatorTaskName(rule, value) validator: (rule, value) => this.handleValidatorTaskName(rule, value),
} },
]}> ]}>
<Input <Input
className='task-name-input' className='task-name-input'
...@@ -323,10 +332,10 @@ class TrainingTask extends React.Component { ...@@ -323,10 +332,10 @@ class TrainingTask extends React.Component {
placeholder='请输入任务名称(20字以内)' placeholder='请输入任务名称(20字以内)'
maxLength={20} maxLength={20}
onChange={(e) => { onChange={(e) => {
this.handleRenameTaskName(e, record) this.handleRenameTaskName(e, record);
}} }}
onBlur={(e) => { onBlur={(e) => {
this.handleTaskNameBlur(e, record) this.handleTaskNameBlur(e, record);
}} }}
/> />
</Form.Item> </Form.Item>
...@@ -346,9 +355,9 @@ class TrainingTask extends React.Component { ...@@ -346,9 +355,9 @@ class TrainingTask extends React.Component {
<span <span
className='operate__item' className='operate__item'
onClick={() => { onClick={() => {
const { dataSource } = this.state const { dataSource } = this.state;
record.type = 'input' record.type = 'input';
this.setState({ dataSource }) this.setState({ dataSource });
}}> }}>
<span className='icon iconfont'>&#xe6f5;</span> <span className='icon iconfont'>&#xe6f5;</span>
<span className='text'>重命名</span> <span className='text'>重命名</span>
...@@ -356,7 +365,7 @@ class TrainingTask extends React.Component { ...@@ -356,7 +365,7 @@ class TrainingTask extends React.Component {
<span <span
className='operate__item' className='operate__item'
onClick={() => { onClick={() => {
this.handleDeleteTask(index) this.handleDeleteTask(index);
}}> }}>
<span className='icon iconfont'>&#xe6f6;</span> <span className='icon iconfont'>&#xe6f6;</span>
<span className='text'>删除</span> <span className='text'>删除</span>
...@@ -377,7 +386,7 @@ class TrainingTask extends React.Component { ...@@ -377,7 +386,7 @@ class TrainingTask extends React.Component {
<span <span
className='add-course-btn-disabled' className='add-course-btn-disabled'
onClick={() => { onClick={() => {
this.showRelatedCourseModal(index) this.showRelatedCourseModal(index);
}}> }}>
<span>+</span> <span>+</span>
<span>关联课程</span> <span>关联课程</span>
...@@ -387,7 +396,7 @@ class TrainingTask extends React.Component { ...@@ -387,7 +396,7 @@ class TrainingTask extends React.Component {
<span <span
className='add-course-btn' className='add-course-btn'
onClick={() => { onClick={() => {
this.showRelatedCourseModal(index) this.showRelatedCourseModal(index);
}}> }}>
<span>+</span> <span>+</span>
<span>关联课程</span> <span>关联课程</span>
...@@ -398,9 +407,10 @@ class TrainingTask extends React.Component { ...@@ -398,9 +407,10 @@ class TrainingTask extends React.Component {
</div> </div>
)} )}
</div> </div>
) );
} };
renderCourseItem = (record, index, parentIndex) => { renderCourseItem = (record, index, parentIndex) => {
console.log(record);
return ( return (
<div className='plan-course-sort-item'> <div className='plan-course-sort-item'>
<div className='course-info'> <div className='course-info'>
...@@ -414,8 +424,8 @@ class TrainingTask extends React.Component { ...@@ -414,8 +424,8 @@ class TrainingTask extends React.Component {
name={['courseName']} name={['courseName']}
rules={[ rules={[
{ {
validator: (rule, value) => this.handleValidatorCourseName(rule, value) validator: (rule, value) => this.handleValidatorCourseName(rule, value),
} },
]}> ]}>
<Input <Input
className='course-name-input' className='course-name-input'
...@@ -423,10 +433,10 @@ class TrainingTask extends React.Component { ...@@ -423,10 +433,10 @@ class TrainingTask extends React.Component {
placeholder='请输入课程名称(40字以内)' placeholder='请输入课程名称(40字以内)'
maxLength={40} maxLength={40}
onChange={(e) => { onChange={(e) => {
this.handleRenameCourseName(e, record) this.handleRenameCourseName(e, record);
}} }}
onBlur={(e) => { onBlur={(e) => {
this.handleCourseNameBlur(e, record) this.handleCourseNameBlur(e, record);
}} }}
/> />
</Form.Item> </Form.Item>
...@@ -441,6 +451,7 @@ class TrainingTask extends React.Component { ...@@ -441,6 +451,7 @@ class TrainingTask extends React.Component {
{record.courseState === 'EXPIRED' && <span className='icon iconfont tip'>&#xe834;</span>} {record.courseState === 'EXPIRED' && <span className='icon iconfont tip'>&#xe834;</span>}
{record.courseType === 'LIVE' && <span className='course-state'>{courseStateShow[record.courseState].title}</span>} {record.courseType === 'LIVE' && <span className='course-state'>{courseStateShow[record.courseState].title}</span>}
{record.courseType === 'VOICE' && <span>(共{record.courseChapterNum || 1}小节)</span>}
</div> </div>
<div className='course-operate'> <div className='course-operate'>
<DragHandle /> <DragHandle />
...@@ -448,18 +459,18 @@ class TrainingTask extends React.Component { ...@@ -448,18 +459,18 @@ class TrainingTask extends React.Component {
<span <span
className='operate__item' className='operate__item'
onClick={() => { onClick={() => {
this.handleDeleteCourse(parentIndex, index) this.handleDeleteCourse(parentIndex, index);
}}> }}>
<span className='icon iconfont'>&#xe6f6;</span> <span className='icon iconfont'>&#xe6f6;</span>
<span className='text'>删除</span> <span className='text'>删除</span>
</span> </span>
</div> </div>
</div> </div>
) );
} };
render() { render() {
const { dataSource, selectedTaskIndex, relatedCourseModalVisible } = this.state const { dataSource, selectedTaskIndex, relatedCourseModalVisible } = this.state;
console.log('dataSource', dataSource) console.log('dataSource', dataSource);
return ( return (
<div className='training-task'> <div className='training-task'>
<SortableTaskContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={this.onTaskSortEnd} className='plan-task-sort-container'> <SortableTaskContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={this.onTaskSortEnd} className='plan-task-sort-container'>
...@@ -494,8 +505,8 @@ class TrainingTask extends React.Component { ...@@ -494,8 +505,8 @@ class TrainingTask extends React.Component {
/> />
)} )}
</div> </div>
) );
} }
} }
export default withRouter(TrainingTask) export default withRouter(TrainingTask);
...@@ -8,11 +8,11 @@ ...@@ -8,11 +8,11 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { Row, Input, Select ,Tooltip} from 'antd'; import { Row, Input, Select, Tooltip } from 'antd';
import RangePicker from "@/modules/common/DateRangePicker"; import RangePicker from '@/modules/common/DateRangePicker';
import moment from 'moment'; import moment from 'moment';
import StoreService from "@/domains/store-domain/storeService"; import StoreService from '@/domains/store-domain/storeService';
import User from '@/common/js/user' import User from '@/common/js/user';
import Bus from '@/core/bus'; import Bus from '@/core/bus';
import './UserLearningDataFilter.less'; import './UserLearningDataFilter.less';
...@@ -22,55 +22,52 @@ const userRole = User.getUserRole(); ...@@ -22,55 +22,52 @@ const userRole = User.getUserRole();
const DEFAULT_QUERY = { const DEFAULT_QUERY = {
customerName: null, customerName: null,
startTime: null, startTime: null,
endTime:null, endTime: null,
learnState:null, learnState: null,
operateId: null, operateId: null,
} };
const defaultCreatorQuery = { const defaultCreatorQuery = {
size: 10, size: 10,
current: 1, current: 1,
nickName:null nickName: null,
} };
function UserLearningDataFilter(props) { function UserLearningDataFilter(props) {
const [expandFilter, setExpandFilter] = useState(false); const [expandFilter, setExpandFilter] = useState(false);
const [query,setQuery] = useState(DEFAULT_QUERY); const [query, setQuery] = useState(DEFAULT_QUERY);
const [operateName,setOperateName] = useState(null) const [operateName, setOperateName] = useState(null);
const [hasNext,setHasNext] = useState(false); const [hasNext, setHasNext] = useState(false);
const [creatorQuery,setCreatorQuery] = useState(defaultCreatorQuery); const [creatorQuery, setCreatorQuery] = useState(defaultCreatorQuery);
const [creatorList,setCreatorList] = useState([]); const [creatorList, setCreatorList] = useState([]);
useEffect(() => { useEffect(() => {
Bus.bind('watchDataView',(record) => handleChangeCreatorQuery(record)) Bus.bind('watchDataView', (record) => handleChangeCreatorQuery(record));
}); }, []);
useEffect(() => { useEffect(() => {
getCreatorList(); getCreatorList();
}, []); }, []);
function handleChangeCreatorQuery (record){ function handleChangeCreatorQuery(record) {
const _creatorQuery = {...creatorQuery}; const _creatorQuery = { ...creatorQuery };
_creatorQuery.operateId = record.storeUserId; _creatorQuery.operateId = record.storeUserId;
setCreatorQuery(_creatorQuery); setCreatorQuery(_creatorQuery);
handleChangeQuery('operateId', record.storeUserId,record.storeUserName) handleChangeQuery('operateId', record.storeUserId, record.storeUserName);
} }
// 改变搜索条件 // 改变搜索条件
function handleChangeQuery(field,value,optionValue){ function handleChangeQuery(field, value, optionValue) {
const _query ={ const _query = {
...query, ...query,
[field]: value, [field]: value,
current: 1, current: 1,
} };
setQuery(_query); setQuery(_query);
if(field === 'operateId'){ if (field === 'operateId') {
setOperateName(optionValue) setOperateName(optionValue);
} }
if (field === 'customerName') return; if (field === 'customerName') return;
props.onChange( _query); props.onChange(_query);
} }
function handleChangeDates(dates) {
function handleChangeDates (dates){
const _query = _.clone(query); const _query = _.clone(query);
if (_.isEmpty(dates)) { if (_.isEmpty(dates)) {
delete _query.startTime; delete _query.startTime;
...@@ -79,27 +76,27 @@ function UserLearningDataFilter(props) { ...@@ -79,27 +76,27 @@ function UserLearningDataFilter(props) {
_query.startTime = dates[0].valueOf(); _query.startTime = dates[0].valueOf();
_query.endTime = dates[1].valueOf(); _query.endTime = dates[1].valueOf();
} }
const param ={ const param = {
..._query, ..._query,
current: 1, current: 1,
} };
setQuery(param); setQuery(param);
props.onChange(param); props.onChange(param);
} }
// 重置搜索条件 // 重置搜索条件
function handleReset(){ function handleReset() {
setQuery(DEFAULT_QUERY); setQuery(DEFAULT_QUERY);
props.onChange(DEFAULT_QUERY); props.onChange(DEFAULT_QUERY);
} }
function getCreatorList(current = 1, selectList){ function getCreatorList(current = 1, selectList) {
const _query = { const _query = {
...creatorQuery, ...creatorQuery,
current, current,
size:10 size: 10,
}; };
StoreService.getStoreUserBasicPage( _query).then((res) => { StoreService.getStoreUserBasicPage(_query).then((res) => {
const { result = {} } = res; const { result = {} } = res;
const { records = [], total = 0, hasNext } = result; const { records = [], total = 0, hasNext } = result;
const list = current > 1 ? creatorList.concat(records) : records; const list = current > 1 ? creatorList.concat(records) : records;
...@@ -109,119 +106,148 @@ function UserLearningDataFilter(props) { ...@@ -109,119 +106,148 @@ function UserLearningDataFilter(props) {
} }
// 滑动加载更多讲师列表 // 滑动加载更多讲师列表
function handleScrollCreatorList(e){ function handleScrollCreatorList(e) {
const container = e.target; const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop; const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) { if (scrollToBottom && hasNext) {
const _creatorQuery = {...creatorQuery}; const _creatorQuery = { ...creatorQuery };
_creatorQuery.current = creatorQuery.current + 1; _creatorQuery.current = creatorQuery.current + 1;
setCreatorQuery(_creatorQuery); setCreatorQuery(_creatorQuery);
getCreatorList(creatorQuery.current + 1); getCreatorList(creatorQuery.current + 1);
} }
} }
return ( return (
<div className="user-learn-data-filter"> <div className='user-learn-data-filter'>
<Row> <Row>
<div className="search-condition"> <div className='search-condition'>
<div className="search-condition__item"> <div className='search-condition__item'>
<span className="label customer-label">学员:</span> <span className='label customer-label'>学员:</span>
<Search <Search
value={query.customerName} value={query.customerName}
placeholder="搜索学员名称" placeholder='搜索学员名称'
onChange={(e) => { handleChangeQuery('customerName', e.target.value)}} onChange={(e) => {
onSearch={ () => { props.onChange(query) } } handleChangeQuery('customerName', e.target.value);
style={{ width: "calc(100% - 70px)" }} }}
enterButton={<span className="icon iconfont">&#xe832;</span>} onSearch={() => {
props.onChange(query);
}}
style={{ width: 'calc(100% - 70px)' }}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/> />
</div> </div>
{(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")&& {(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && (
<div className="search-condition__item"> <div className='search-condition__item'>
<span className="label lead-label">负责人:</span> <span className='label lead-label'>负责人:</span>
<Select <Select
id="leadSelect" id='leadSelect'
placeholder="请选择员工" placeholder='请选择员工'
style={{width:"calc(100% - 70px)"}} style={{ width: 'calc(100% - 70px)' }}
showSearch showSearch
allowClear allowClear
filterOption={(input, option) => option} filterOption={(input, option) => option}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>} suffixIcon={
<span className='icon iconfont' style={{ fontSize: '12px', color: '#BFBFBF' }}>
&#xe835;
</span>
}
onPopupScroll={handleScrollCreatorList} onPopupScroll={handleScrollCreatorList}
value={operateName} value={operateName}
onChange={(value,option) => { onChange={(value, option) => {
if(option){ if (option) {
handleChangeQuery('operateId',value,option.children) handleChangeQuery('operateId', value, option.children);
}else{ } else {
handleChangeQuery('operateId',value,"") handleChangeQuery('operateId', value, '');
} }
}} }}
onSearch={(value) => { onSearch={(value) => {
creatorQuery.nickName = value creatorQuery.nickName = value;
setCreatorQuery(creatorQuery) setCreatorQuery(creatorQuery);
getCreatorList(); getCreatorList();
} }}
} onClear={(value) => {
onClear ={(value)=>{
setCreatorQuery({ setCreatorQuery({
size: 10, size: 10,
current: 1, current: 1,
nickName:null nickName: null,
}) });
getCreatorList() getCreatorList();
} }}>
}
>
{_.map(creatorList, (item, index) => { {_.map(creatorList, (item, index) => {
return ( return (
<Select.Option value={item.id} key={item.id}>{item.nickName}</Select.Option> <Select.Option value={item.id} key={item.id}>
{item.nickName}
</Select.Option>
); );
})} })}
</Select> </Select>
</div> </div>
} )}
<div className="search-condition__item"> <div className='search-condition__item'>
<span className="label learn-date-label">最近学习日期:</span> <span className='label learn-date-label'>最近学习日期:</span>
<RangePicker <RangePicker
id="course_date_picker" id='course_date_picker'
allowClear={false} allowClear={false}
value={ query.startTime ? [moment(query.startTime), moment(query.endTime)] : null } value={query.startTime ? [moment(query.startTime), moment(query.endTime)] : null}
format={"YYYY-MM-DD"} format={'YYYY-MM-DD'}
onChange={(dates) => { handleChangeDates(dates) }} onChange={(dates) => {
style={{ width: "calc(100% - 98px)" }} handleChangeDates(dates);
}}
style={{ width: 'calc(100% - 98px)' }}
/> />
</div> </div>
{ ((expandFilter && (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")) || User.getUserRole === "CloudOperator")&& {((expandFilter && (User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager')) || User.getUserRole === 'CloudOperator') && (
<div className="search-condition__item"> <div className='search-condition__item'>
<span className="label learn-status-label">学习状态:</span> <span className='label learn-status-label'>学习状态:</span>
<Select <Select
style={{ width: "calc(100% - 70px)" }} style={{ width: 'calc(100% - 70px)' }}
placeholder="请选择当前状态" placeholder='请选择当前状态'
allowClear={true} allowClear={true}
value={query.learnState} value={query.learnState}
onChange={(value) => { handleChangeQuery('learnState', value) }} onChange={(value) => {
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>} handleChangeQuery('learnState', value);
> }}
<Option value="UN_PLAY">未开始</Option> suffixIcon={
<Option value="UNDER_WAY">进行中</Option> <span className='icon iconfont' style={{ fontSize: '12px', color: '#BFBFBF' }}>
<Option value="FINISH">已完成</Option> &#xe835;
</span>
}>
<Option value='UN_PLAY'>未开始</Option>
<Option value='UNDER_WAY'>进行中</Option>
<Option value='FINISH'>已完成</Option>
</Select> </Select>
</div> </div>
} )}
</div> </div>
{( User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")&& {(User.getUserRole() === 'CloudManager' || User.getUserRole() === 'StoreManager') && (
<div className="reset-fold-area"> <div className='reset-fold-area'>
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={handleReset}>&#xe61b; </span></Tooltip> <Tooltip title='清空筛选'>
<span style={{ cursor: 'pointer' }} className="fold-btn" onClick={() => { <span className='resetBtn iconfont icon' onClick={handleReset}>
setExpandFilter(!expandFilter) &#xe61b;{' '}
}}>{expandFilter ? <span><span>收起</span><span className="iconfont icon fold-icon" >&#xe82d; </span> </span> : <span>展开<span className="iconfont icon fold-icon" >&#xe835; </span></span>}</span> </span>
</Tooltip>
<span
style={{ cursor: 'pointer' }}
className='fold-btn'
onClick={() => {
setExpandFilter(!expandFilter);
}}>
{expandFilter ? (
<span>
<span>收起</span>
<span className='iconfont icon fold-icon'>&#xe82d; </span>{' '}
</span>
) : (
<span>
展开<span className='iconfont icon fold-icon'>&#xe835; </span>
</span>
)}
</span>
</div> </div>
} )}
</Row> </Row>
</div> </div>
) );
} }
export default withRouter(UserLearningDataFilter); export default withRouter(UserLearningDataFilter);
/*
* @Author: yuananting
* @Date: 2021-07-05 10:50:30
* @LastEditors: yuananting
* @LastEditTime: 2021-07-14 14:32:55
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react'; import React from 'react';
import {Table, Modal,Input} from 'antd'; import { Table, Modal, Input } from 'antd';
import { PageControl } from "@/components"; import PlanService from '@/domains/plan-domain/planService';
import PlanService from '@/domains/plan-domain/planService' import User from '@/common/js/user';
import User from '@/common/js/user'
import './UserLearnDetailModal.less'; import './UserLearnDetailModal.less';
import _ from "underscore"; import _ from 'underscore';
const { Search } = Input; import { FileTypeIcon } from '@/common/constants/academic/lessonEnum'
const defaultCover = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png'; const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
const CourseType = { const CourseType = {
LIVE: { LIVE: {
text: "直播课" text: '直播课',
}, },
VOICE : { VOICE: {
text:"视频课" text: '线上课',
}, },
RECORD : { RECORD: {
text:'录播课' text: '录播课',
},
PICTURE: {
text: '图文课',
}, },
PICTURE:{
text:'图文课'
}
}; };
const courseStateShow = { const courseStateShow = {
UN_START: { UN_START: {
title: "待开播", title: '待开播',
}, },
STARTING: { STARTING: {
title: "直播中", title: '直播中',
}, },
FINISH: { FINISH: {
title: "回放", title: '回放',
}, },
EXPIRED: { EXPIRED: {
title: "未成功开课", title: '未成功开课',
}, },
}; };
class UserLearnDetailModal extends React.Component { class UserLearnDetailModal extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
planDataSource:{}, planDataSource: {},
taskDataSource:[], taskDataSource: [],
taskSize:10, taskSize: 10,
taskQuery: { taskQuery: {
current: 1, current: 1,
}, },
taskTotalCount:0, taskTotalCount: 0,
courseDataSource:[], courseDataSource: [],
storeCustomerName:'', storeCustomerName: '',
storeCustomerPhone:'' storeCustomerPhone: '',
}; };
} }
componentDidMount() { componentDidMount() {
this.getPlanCustomerDetail(); this.getPlanCustomerDetail();
} }
getPlanCustomerDetail = ()=>{ getPlanCustomerDetail = () => {
PlanService.getPlanCustomerDetail({ PlanService.getPlanCustomerDetail({
planId:getParameterByName("id"), planId: getParameterByName('id'),
storeCustomerId:this.props.storeCustomerId, storeCustomerId: this.props.storeCustomerId,
storeId:User.getStoreId() storeId: User.getStoreId(),
}).then((res) => { }).then((res) => {
const { const { storeCustomerName, storeCustomerPhone, planName, learnFinishPercentage, taskCustomerVOList, courseMediaVOS } = res.result;
storeCustomerName,
storeCustomerPhone,
planName,
learnFinishPercentage,
taskCustomerVOList,
courseMediaVOS,
}=res.result;
let coverUrl; let coverUrl;
courseMediaVOS.map((item) => { courseMediaVOS.map((item) => {
if(item.contentType === "COVER"){ if (item.contentType === 'COVER') {
coverUrl = item.mediaUrl; coverUrl = item.mediaUrl;
} }
return item; return item;
}) });
const planDataSource = { const planDataSource = {
planName, planName,
learnFinishPercentage, learnFinishPercentage,
coverUrl:coverUrl || defaultCover coverUrl: coverUrl || defaultCover,
} };
this.setState({ this.setState({
storeCustomerName, storeCustomerName,
storeCustomerPhone, storeCustomerPhone,
planDataSource, planDataSource,
taskDataSource:taskCustomerVOList taskDataSource: taskCustomerVOList,
}) });
}) });
} };
parsePlanDataColumns = () => {
const columns = [
{
title: '培训计划名称',
key: 'planName',
dataIndex: 'planName',
width: '77%',
render: (val, record, index) => {
return (
<div className='record-name'>
<div className='img-con'>
<img src={record.coverUrl} />
</div>
<div>{record.planName}</div>
</div>
);
},
},
{
title: '学习进度',
key: 'learnFinishPercentage',
dataIndex: 'learnFinishPercentage',
render: (val, record, index) => {
return <div>{val}%</div>;
},
},
];
return columns;
};
parseTaskColumns = () => { parseTaskColumns = () => {
const columns = [ const columns = [
...@@ -95,133 +126,169 @@ class UserLearnDetailModal extends React.Component { ...@@ -95,133 +126,169 @@ class UserLearnDetailModal extends React.Component {
title: '培训任务', title: '培训任务',
key: 'taskName', key: 'taskName',
dataIndex: 'taskName', dataIndex: 'taskName',
render: (val, record,index) => { width: '68%',
render: (val, record, index) => {
return ( return (
<div className="taskName"> <div className='taskName'>
{index + 1}.{record.taskName} {index + 1}.{record.taskName}
</div> </div>
) );
} },
}, },
{ {
title: '学习进度', title: '学习进度',
key: 'learnFinishPercentage', key: 'learnFinishPercentage',
dataIndex: 'learnFinishPercentage', dataIndex: 'learnFinishPercentage',
width:167,
render: (val, record) => { render: (val, record) => {
return ( return <div className='task-learn-percentage'>{val === 100 ? <span>已完成</span> : <span>{val}%</span>}</div>;
<div className="task-learn-percentage"> },
{ val === 100 ?<span>已完成</span>:<span>{val}%</span>} },
</div>
)
}
}
]; ];
return columns; return columns;
} };
parseCoursecolumns = (parentIndex) => {
parseCourseColumns = (parentIndex) => {
const columns = [ const columns = [
{ {
title: '课程', title: '课程',
key: 'courseName', key: 'courseName',
dataIndex: 'courseName', dataIndex: 'courseName',
render: (val, record,index) => { width: '70%',
render: (val, record, index) => {
return ( return (
<div className="course-info"> <div className='course-info'>
<div> <div>
<span className="course-type">{CourseType[record.courseType].text}</span> <span className='course-type'>{CourseType[record.courseType].text}</span>
<span>{parentIndex + 1}.{index + 1}&nbsp;</span> <span>
{parentIndex + 1}.{index + 1}&nbsp;
</span>
</div> </div>
<div className="name-and-state"> <div className='name-and-state'>
<span className="course-name">{record.courseName}</span> <span className='course-name'>{record.courseName}</span>
{ record.courseType==="LIVE" && {record.courseType === 'LIVE' && <span className='course-state'>{courseStateShow[record.courseState].title}</span>}
<span className="course-state">{courseStateShow[record.courseState].title}</span>
}
</div> </div>
</div> </div>
) );
} },
}, },
{ {
title: '学习进度', title: '学习进度',
key: 'learnFinishPercentage', key: 'learnFinishPercentage',
dataIndex: 'learnFinishPercentage', dataIndex: 'learnFinishPercentage',
width:152,
render: (val, record) => { render: (val, record) => {
return <div className='course-learn-percentage'>{record.learnState === 'FINISH' ? <span>已完成</span> : <span>{val}%</span>}</div>;
},
},
];
return columns;
};
parseChapterColumns = (chapterIndex) => {
const columns = [
{
title: '课节名称',
key: 'name',
dataIndex: 'name',
width: '80%',
render: (val, record, index) => {
return ( return (
<div className="course-learn-percentage"> <div className='chapter-record'>
{ record.learnState === "FINISH" ?<span>已完成</span>:<span>{val}%</span>} <span>
{index < 9 ? `0${index + 1} ` : index + 1}
</span>
<img className='chapter-img' src={FileTypeIcon[record.mediaType]} />
<span className='chapter-name'>{record.name}</span>
</div> </div>
) );
} },
} },
{
title: '学习进度',
key: 'watchProgress',
dataIndex: 'watchProgress',
render: (val, record) => {
return <span>{val}%</span>;
},
},
]; ];
return columns; return columns;
} };
render() { render() {
const {storeCustomerName,storeCustomerPhone,planDataSource,taskDataSource,taskQuery,taskTotalCount} = this.state; const { storeCustomerName, storeCustomerPhone, planDataSource, taskDataSource, taskQuery, taskTotalCount } = this.state;
const { visible } = this.props; const { visible } = this.props;
return ( return (
<Modal <Modal
title="学员学习详情" title='学员学习详情'
onCancel={this.props.onClose} onCancel={this.props.onClose}
onOk={this.props.onClose} onOk={this.props.onClose}
maskClosable={false} maskClosable={false}
visible={visible} visible={visible}
className="user-Learn-modal" className='user-Learn-modal'
closable={true} closable={true}
width={800} width={800}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>} closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}>
> <div className='customer-info'>
<div className="customer-info"> <span className='customer-name'>
<span className="customer-name">
<span>学员:</span> <span>学员:</span>
<span>{storeCustomerName}</span> <span>{storeCustomerName}</span>
</span> </span>
<span className="customer-phone"> <span className='customer-phone'>
<span>手机号:</span> <span>手机号:</span>
<span>{storeCustomerPhone}</span> <span>{storeCustomerPhone}</span>
</span> </span>
</div> </div>
<div className="plan-instro"> <div className='table-box'>
<div className="img-con"> <Table className='plan-table' dataSource={[planDataSource]} columns={this.parsePlanDataColumns()} pagination={false} />
<img src={planDataSource.coverUrl}/>
</div>
<div>
<div className="plan-name">{planDataSource.planName}</div>
<div className="task-learn-percentage">
<span>学习进度: {planDataSource.learnFinishPercentage}%</span>
</div>
</div>
</div>
<div>
<Table <Table
className='task-table'
rowKey={(record) => record.taskId} rowKey={(record) => record.taskId}
className="task-table"
dataSource={taskDataSource} dataSource={taskDataSource}
columns={this.parseTaskColumns()} columns={this.parseTaskColumns()}
pagination={false} pagination={false}
expandedRowRender={(record,index) => { expandable={{
if(!record.courseVOList){ expandedRowRender: (record, index) => {
return return (
} <div>
if (record.courseVOList.length !== 0 ){
return <div>
<Table <Table
pagination={false} showHeader={false}
className='child-table'
rowKey={(record) => record.courseId}
dataSource={record.courseVOList} dataSource={record.courseVOList}
columns={this.parseCoursecolumns(index)} columns={this.parseCourseColumns(index)}
className="child-table" pagination={false}
expandable={{
expandedRowRender: (chapterRecord, chapterIndex) => {
return (
<div>
<Table
showHeader={false}
dataSource={chapterRecord.courseChapterList}
columns={this.parseChapterColumns(chapterIndex)}
pagination={false}
/>
</div>
);
},
rowExpandable: (record) => record.courseChapterList && record.courseChapterList.length > 1,
}}
/> />
</div> </div>
);
},
rowExpandable: (record) => record.courseVOList && record.courseVOList.length !== 0,
}}
rowClassName={(record, index) => {
if (index % 2 === 0) {
return 'odd-row';
} else {
return 'even-row';
} }
}} }}
rowClassName={(record,index)=>{if(index%2===0){return 'odd-row'}else{ return 'even-row'}}}
/> />
</div> </div>
</Modal> </Modal>
) );
} }
} }
......
.user-Learn-modal{ .user-Learn-modal {
.customer-info {
.customer-info{ margin-bottom: 16px;
margin-bottom:16px; .customer-name {
.customer-name{ font-size: 14px;
font-size:14px; color: #333;
color:#333; margin-right: 32px;
margin-right:32px;
} }
.customer-phone{ .customer-phone {
font-size:14px; font-size: 14px;
color:#333; color: #333;
} }
} }
.plan-instro{ .plan-instro {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom:16px; margin-bottom: 16px;
.img-con{ .img-con {
margin-right:8px; margin-right: 8px;
img{ img {
width: 97px; width: 97px;
height: 54px; height: 54px;
display: inline-block; display: inline-block;
border-radius:4px; border-radius: 4px;
}
}
.plan-name{
color:#333;
font-size:16px;
}
.plan-learn-percentage{
color:#333;
font-size:14px;
}
}
.task-table{
.ant-table-thead{
tr {
th{
padding:9px 16px;
} }
} }
.plan-name {
color: #333;
font-size: 16px;
} }
tr{ .plan-learn-percentage {
td{ color: #333;
padding:14px 16px; font-size: 14px;
}
}
.taskName{
color:#333;
font-size:14px;
}
.task-learn-percentage{
color:#333;
font-size:14px;
}
.course-info{
display:flex;
margin-left:57px;
align-items: center;
.course-type{
font-size:11px;
color:#666666;
padding:0px 6px;
border: 1px solid #999999;
margin-right:4px;
border-radius: 2px;
line-height: 16px;
}
.name-and-state{
flex:1;
.course-name{
color:#666666;
font-size:14px;
margin-right:8px;
}
.tip{
font-size:14px;
color:#FF4F4F;
margin-right:2px;
}
.course-state{
color:#999;
font-size:14px;
} }
} }
.table-box {
.child-table {
margin-left: 40px;
tr > td {
&:first-child {
padding-right: 0 !important;
} }
.ant-table-expanded-row{ &:nth-child(2) {
td{ padding-left: 0 !important;
padding:0 16px;
}
}
.ant-table-content{
border:1px solid #e8e8e8;
tr{
td{
border:none;
}
.child-table{
.ant-table-content{
border:none;
thead{
display:none;
}
tbody tr {
td{
border-bottom:none;
padding:14px 16px;
}
}
}
}
}
.odd-row{
background:transparent;
td{
background: #FFF;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FFF;
}
}
&:hover{
& + .ant-table-expanded-row{
background:transparent;
td{
background: #F3f6fa !important;
}
} }
} }
.course-info {
display: flex;
.course-type {
border-radius: 2px;
border: 1px solid #999999;
font-size: 11px;
color: #666666;
padding: 1px 8px;
margin-right: 6px;
} }
.even-row{
background:transparent;
td{
background: #FAFAFA;
} }
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FAFAFA;
} }
.chapter-record {
display: flex;
align-items: center;
.chapter-img {
width: 18px;
height: 18px;
margin: 0 10px;
} }
&:hover{ .chapter-name {
& + .ant-table-expanded-row{ overflow: hidden;
background:transparent; white-space: nowrap;
td{ text-overflow: ellipsis;
background: #F3f6fa !important; width: 400px;
} }
} }
.record-name {
display: flex;
align-items: center;
.img-con {
margin-right: 8px;
img {
width: 97px;
height: 54px;
border-radius: 4px;
} }
} }
} }
......
/*
* @Author: yuananting
* @Date: 2021-07-05 10:50:10
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-07-18 10:47:02
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react'; import React from 'react';
import _ from 'underscore'; import _ from 'underscore';
import { Table, Radio, Tabs, Modal, Input, message, Button, Tooltip } from 'antd'; import { Radio, Tabs, Modal, Input, message, Button, Tooltip } from 'antd';
import { PageControl, XMTable } from '@/components'; import { PageControl, XMTable } from '@/components';
import college from '@/common/lottie/college'; import college from '@/common/lottie/college';
...@@ -79,11 +88,11 @@ class SelectOperatorModal extends React.Component { ...@@ -79,11 +88,11 @@ class SelectOperatorModal extends React.Component {
selectVideo: { selectVideo: {
external: [], external: [],
internal: [], internal: [],
}, //弹窗内已选择的视频课程 }, //弹窗内已选择的线上课程
currentVideoCourseListData: { currentVideoCourseListData: {
external: [], external: [],
internal: [], internal: [],
}, //页面中已关联的视频课程 }, //页面中已关联的线上课程
pictureDataSource: [], pictureDataSource: [],
pictureSize: 10, pictureSize: 10,
...@@ -91,10 +100,10 @@ class SelectOperatorModal extends React.Component { ...@@ -91,10 +100,10 @@ class SelectOperatorModal extends React.Component {
current: 1, current: 1,
}, },
pictureTotalCount: 0, pictureTotalCount: 0,
selectPicture: [], //弹窗内已选择的视频课程 selectPicture: [], //弹窗内已选择的线上课程
currentPictureCourseListData: [], //页面中已关联的视频课程 currentPictureCourseListData: [], //页面中已关联的线上课程
activeKey: 'video', activeKey: 'live',
currentTaskCourseData: this.props.data[this.props.selectedTaskIndex].courseList || [], currentTaskCourseData: this.props.data[this.props.selectedTaskIndex].courseList || [],
}; };
} }
...@@ -136,7 +145,7 @@ class SelectOperatorModal extends React.Component { ...@@ -136,7 +145,7 @@ class SelectOperatorModal extends React.Component {
}); });
}; };
// 获取视频课列表 // 获取线上课列表
handleFetchVideoDataList = () => { handleFetchVideoDataList = () => {
const { videoQuery, videoSize, videoDataSource, videoTotalCount, videoCourseDivision } = this.state; const { videoQuery, videoSize, videoDataSource, videoTotalCount, videoCourseDivision } = this.state;
...@@ -370,7 +379,6 @@ class SelectOperatorModal extends React.Component { ...@@ -370,7 +379,6 @@ class SelectOperatorModal extends React.Component {
// 请求表头 // 请求表头
parseVideoColumns = () => { parseVideoColumns = () => {
const { videoCourseDivision } = this.state;
const columns = [ const columns = [
{ {
title: ( title: (
...@@ -387,33 +395,23 @@ class SelectOperatorModal extends React.Component { ...@@ -387,33 +395,23 @@ class SelectOperatorModal extends React.Component {
dataIndex: 'course', dataIndex: 'course',
width: '60%', width: '60%',
render: (val, record) => { render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record; const { coverUrl } = record;
return ( return (
<div className='course-info'> <div className='course-info'>
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */} <img className='course-cover' src={coverUrl || 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'} alt='' />
<img
className='course-cover'
src={
coverUrl ||
(videoCourseDivision === 'internal'
? `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`
: 'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png')
}
alt=''
/>
<div className='course-name'>{record.courseName}</div> <div className='course-name'>{record.courseName}</div>
</div> </div>
); );
}, },
}, },
{ {
title: '课程时长', title: '课节数',
key: 'courseTime', key: 'courseChapterNum',
dataIndex: 'courseTime', dataIndex: 'courseChapterNum',
width: '20%', width: '20%',
align: 'right',
render: (val, record) => { render: (val, record) => {
return <span className='course-status'>{dealTimeDuration(record.videoDuration)}</span>; return <span>{val || 1}</span>;
}, },
}, },
{ {
...@@ -555,6 +553,7 @@ class SelectOperatorModal extends React.Component { ...@@ -555,6 +553,7 @@ class SelectOperatorModal extends React.Component {
_item.courseId = item.id; _item.courseId = item.id;
_item.courseType = 'VOICE'; _item.courseType = 'VOICE';
_item.courseName = item.courseName; _item.courseName = item.courseName;
_item.courseChapterNum = item.courseChapterNum
return _item; return _item;
}); });
...@@ -616,7 +615,7 @@ class SelectOperatorModal extends React.Component { ...@@ -616,7 +615,7 @@ class SelectOperatorModal extends React.Component {
className='link-create-course' className='link-create-course'
href={window.location.origin + window.location.pathname + '#/create-video-course?type=add'} href={window.location.origin + window.location.pathname + '#/create-video-course?type=add'}
onClick={this.props.onClose}> onClick={this.props.onClose}>
没有找到需要的视频课?<span>去创建</span> 没有找到需要的线上课?<span>去创建</span>
</a> </a>
); );
break; break;
...@@ -792,7 +791,7 @@ class SelectOperatorModal extends React.Component { ...@@ -792,7 +791,7 @@ class SelectOperatorModal extends React.Component {
</div> </div>
</TabPane> </TabPane>
<TabPane tab='视频课' key='video'> <TabPane tab='线上课' key='video'>
<Radio.Group value={videoCourseDivision} onChange={this.videoCourseDivisionChange} style={{ marginBottom: 16 }}> <Radio.Group value={videoCourseDivision} onChange={this.videoCourseDivisionChange} style={{ marginBottom: 16 }}>
<Radio.Button value='internal'>内部课程</Radio.Button> <Radio.Button value='internal'>内部课程</Radio.Button>
<Radio.Button value='external'>外部课程</Radio.Button> <Radio.Button value='external'>外部课程</Radio.Button>
......
...@@ -101,7 +101,7 @@ class SelectPrepareFileModal extends React.Component { ...@@ -101,7 +101,7 @@ class SelectPrepareFileModal extends React.Component {
// 获取文件列表 // 获取文件列表
handleFetchFolderList = (params = {}, loadMore = false) => { handleFetchFolderList = (params = {}, loadMore = false) => {
// 根据当前根节点获取文件列表 // 根据当前根节点获取文件列表
const { selectTypeList } = this.props; const { selectTypeList,queryTypeEnum} = this.props;
const { query, folderList, currentRootDisk } = this.state; const { query, folderList, currentRootDisk } = this.state;
const _params = { const _params = {
...query, ...query,
...@@ -114,6 +114,9 @@ class SelectPrepareFileModal extends React.Component { ...@@ -114,6 +114,9 @@ class SelectPrepareFileModal extends React.Component {
if (selectTypeList) { if (selectTypeList) {
_params.folderFileType = selectTypeList _params.folderFileType = selectTypeList
} }
if(queryTypeEnum){
_params.queryTypeEnum = queryTypeEnum
}
Service.Hades(FOLDERLIST_URL_MAP[currentRootDisk.disk], _params).then((res) => { Service.Hades(FOLDERLIST_URL_MAP[currentRootDisk.disk], _params).then((res) => {
const { result = {} } = res; const { result = {} } = res;
...@@ -221,6 +224,7 @@ class SelectPrepareFileModal extends React.Component { ...@@ -221,6 +224,7 @@ class SelectPrepareFileModal extends React.Component {
maxSelectLength maxSelectLength
} = this.props; } = this.props;
console.log('selectedFileList',selectedFileList)
if (multiple) { if (multiple) {
const currSelectedFileList = [...(prevSelectedFileList || []), ...selectedFileList]; const currSelectedFileList = [...(prevSelectedFileList || []), ...selectedFileList];
// 最多选择20个文件,判断是否超出了20个 // 最多选择20个文件,判断是否超出了20个
...@@ -286,7 +290,8 @@ class SelectPrepareFileModal extends React.Component { ...@@ -286,7 +290,8 @@ class SelectPrepareFileModal extends React.Component {
const { fileListRef } = this.refs; const { fileListRef } = this.refs;
// const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight === fileListRef.scrollHeight; // const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight === fileListRef.scrollHeight;
const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight > fileListRef.scrollHeight - 1; const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight > fileListRef.scrollHeight - 10;
console.log("fileListRef.scrollTop + fileListRef.clientHeight",hasReachBottom,fileListRef.scrollTop , fileListRef.clientHeight,fileListRef.scrollHeight)
if (!hasReachBottom || !hasMore) return; if (!hasReachBottom || !hasMore) return;
const currentFolder = folderPathList[folderPathList.length - 1]; const currentFolder = folderPathList[folderPathList.length - 1];
...@@ -321,14 +326,12 @@ class SelectPrepareFileModal extends React.Component { ...@@ -321,14 +326,12 @@ class SelectPrepareFileModal extends React.Component {
// 上传文件 // 上传文件
handleUpload = (event) => { handleUpload = (event) => {
const { selectType } = this.props;
const fileList = event.target.files; const fileList = event.target.files;
// 判断文件的大小是否超出了限制 // 判断文件的大小是否超出了限制
const nonCompliantFileList = []; const nonCompliantFileList = [];
const _fileList = [...fileList]; const _fileList = [...fileList];
_fileList.map((file, index) => { _fileList.map((file, index) => {
console.log('file',file);
let { size, type, name } = file; let { size, type, name } = file;
if (!type) { if (!type) {
type = getFileTypeByName(name); type = getFileTypeByName(name);
...@@ -569,7 +572,8 @@ class SelectPrepareFileModal extends React.Component { ...@@ -569,7 +572,8 @@ class SelectPrepareFileModal extends React.Component {
} }
</div> </div>
{ {
!_.isEmpty(folderList) ? <Choose>
<When condition={!_.isEmpty(folderList)}>
<div> <div>
<div className="file-list" <div className="file-list"
onScrollCapture={() => this.handleScrollEvent()} onScrollCapture={() => this.handleScrollEvent()}
...@@ -607,8 +611,7 @@ class SelectPrepareFileModal extends React.Component { ...@@ -607,8 +611,7 @@ class SelectPrepareFileModal extends React.Component {
// 文件禁止点击的情况(移动、直播场景下文件为Excel、文件已经被关联了、文件不合法) // 文件禁止点击的情况(移动、直播场景下文件为Excel、文件已经被关联了、文件不合法)
const disabled = hiddenVideo || (!isFolder && operateType === 'move') || (scene === 'liveCourse' && folder.folderFormat === 'EXCEL') || !!hasRelation || (!isFolder && !FILE_SUFFIX_LIST.includes(suffix)); const disabled = hiddenVideo || (!isFolder && operateType === 'move') || (scene === 'liveCourse' && folder.folderFormat === 'EXCEL') || !!hasRelation || (!isFolder && !FILE_SUFFIX_LIST.includes(suffix));
// console.log('currentFile',currentFile);
// console.log('folder',folder);
let currentFileCheck = false; let currentFileCheck = false;
if(currentFile){ if(currentFile){
currentFileCheck = (currentFile.id===folder.id) currentFileCheck = (currentFile.id===folder.id)
...@@ -656,12 +659,16 @@ class SelectPrepareFileModal extends React.Component { ...@@ -656,12 +659,16 @@ class SelectPrepareFileModal extends React.Component {
}) })
} }
</div> </div>
</div> : </div>
</When>
<Otherwise>
<LottieIcon <LottieIcon
title={<span className="desc">这个文件夹是空的</span>} title={<span className="desc">这个文件夹是空的</span>}
type="college" type="college"
size={150} size={150}
/> />
</Otherwise>
</Choose>
} }
<UploadProgressModal <UploadProgressModal
......
...@@ -34,10 +34,10 @@ class ScanFileModal extends React.Component { ...@@ -34,10 +34,10 @@ class ScanFileModal extends React.Component {
style={{ width: 632, objectFit: "cover" }} style={{ width: 632, objectFit: "cover" }}
/> />
)} )}
{fileType === "MP4" && ( {fileType === "VIDEO" && (
<div> <div>
<Player <Player
src={item.ossAddress || item.ossUrl} src={item.mediaUrl || item.ossAddress || item.ossUrl}
fluid={false} fluid={false}
height={306} height={306}
width={"100%"} width={"100%"}
......
...@@ -10,4 +10,7 @@ ...@@ -10,4 +10,7 @@
height: 60px; height: 60px;
} }
} }
.video-react .video-react-play-progress:after {
width: max-content;
}
} }
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
*/ */
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { ConfigProvider, message, Layout } from 'antd'; import { ConfigProvider, message, Layout, DatePicker } from 'antd';
import Header from './Header' import Header from './Header'
import Menu from './Menu' import Menu from './Menu'
import Main from './Main' import Main from './Main'
import zhCN from 'antd/es/locale/zh_CN' import zhCN from 'antd/lib/locale/zh_CN'
import User from '@/common/js/user'; import User from '@/common/js/user';
import BaseService from "@/domains/basic-domain/baseService"; import BaseService from "@/domains/basic-domain/baseService";
import moment from 'moment'; import moment from 'moment';
......
...@@ -158,7 +158,10 @@ export default class CreateCollege extends React.Component { ...@@ -158,7 +158,10 @@ export default class CreateCollege extends React.Component {
<Button <Button
className="button" className="button"
type="primary" type="primary"
onClick={() => this.submit()} onClick={_.debounce(() => this.submit(), 3000, {
leading: true,
trailing: false
})}
>确定创建</Button> >确定创建</Button>
</div> </div>
<input <input
......
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2019-09-10 18:26:03 * @Date: 2019-09-10 18:26:03
* @LastEditors: yuananting * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-07-06 14:37:49 * @LastEditTime: 2021-07-20 17:39:52
* @Description: * @Description:
*/ */
import React, { useRef, useContext, useEffect, useState } from 'react'; import React, { useRef, useContext, useEffect, useState } from 'react';
...@@ -36,7 +36,7 @@ function Header(props) { ...@@ -36,7 +36,7 @@ function Header(props) {
const [openDropdown, setOpenDropdown] = useState(false); const [openDropdown, setOpenDropdown] = useState(false);
const [instScroll, setInstScroll] = useState(false); const [instScroll, setInstScroll] = useState(false);
const ctx = useContext(XMContext); const ctx = useContext(XMContext);
const htmlUrl = `${LIVE_SHARE}store/index?id=${User.getStoreId()}&userId=${User.getUserId()}&from=work_weixin`; const htmlUrl = `${LIVE_SHARE}store/index?id=${User.getStoreId()}&userId=${User.getUserId()}`;
const helpCenterUrl = 'https://www.yuque.com/wangzhong-zkqw0/qixue'; // 帮助中心 const helpCenterUrl = 'https://www.yuque.com/wangzhong-zkqw0/qixue'; // 帮助中心
const storeUserId = User.getStoreUserId(); const storeUserId = User.getStoreUserId();
const enterpriseId = User.getEnterpriseId(); const enterpriseId = User.getEnterpriseId();
...@@ -79,6 +79,7 @@ function Header(props) { ...@@ -79,6 +79,7 @@ function Header(props) {
setNickName(nickName); setNickName(nickName);
setPhone(phone); setPhone(phone);
setAvatar(res.result.avatar); setAvatar(res.result.avatar);
User.setAvatar(res.result.avatar);
}); });
} }
...@@ -207,8 +208,9 @@ function Header(props) { ...@@ -207,8 +208,9 @@ function Header(props) {
} }
function handleConvertShortUrl() { function handleConvertShortUrl() {
//提供给企业微信下的人员使用的码,用微信方式扫时页时获取企业微信的身份
CourseService.getQrcode({ CourseService.getQrcode({
urls: [htmlUrl], urls: [`${htmlUrl}&source=workWechat`],
}).then((res) => { }).then((res) => {
const { result = [] } = res; const { result = [] } = res;
const qrcodeWrapDom = document.querySelector('#h5-qrcode'); const qrcodeWrapDom = document.querySelector('#h5-qrcode');
...@@ -335,7 +337,7 @@ function Header(props) { ...@@ -335,7 +337,7 @@ function Header(props) {
<div <div
className='url-link' className='url-link'
onClick={() => { onClick={() => {
window.open(htmlUrl); window.open(`${LIVE_SHARE}store/index?id=${window.currentStoreUserInfo.storeId||User.getStoreId()}&userId=${window.currentStoreUserInfo.userId||User.getUserId()}&enterpriseId=${window.currentStoreUserInfo.enterpriseId||User.getEnterpriseId()}&from=admin&avatar=${avatar}`);
}}> }}>
{'立即前往 >'} {'立即前往 >'}
</div> </div>
...@@ -343,7 +345,7 @@ function Header(props) { ...@@ -343,7 +345,7 @@ function Header(props) {
<div className='h5-url'> <div className='h5-url'>
<div className='name'>手机端学院</div> <div className='name'>手机端学院</div>
<div id='h5-qrcode'></div> <div id='h5-qrcode'></div>
<div className='tip'>微信扫码,打开学院</div> <div className='tip'>企业员工扫码登录学院</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -378,7 +378,7 @@ ...@@ -378,7 +378,7 @@
.name, .name,
.tip { .tip {
width: 70px; width: 70px;
font-size: 14px; font-size: 13px;
color: #333333; color: #333333;
line-height: 52px; line-height: 52px;
margin: 0 auto; margin: 0 auto;
......
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
margin: 6px 8px; margin: 6px 8px;
width: calc(100% - 15px); width: calc(100% - 15px);
&:hover { &:hover {
background: #f3f6fa; background: rgba(41, 102, 255, .05);
border-radius: 2px; border-radius: 2px;
color: #333; color: #333;
} }
...@@ -106,8 +106,10 @@ ...@@ -106,8 +106,10 @@
background-color: @active-color; background-color: @active-color;
color: #fff; color: #fff;
border-radius: 2px; border-radius: 2px;
&:hover { &.single-menu {
color: #fff; background: rgba(41, 102, 255, .1) !important;
color: @active-color;
font-weight:500;
} }
} }
.ant-menu-submenu { .ant-menu-submenu {
...@@ -117,6 +119,9 @@ ...@@ -117,6 +119,9 @@
} }
.ant-menu-item { .ant-menu-item {
padding-left: 46px !important; padding-left: 46px !important;
&:hover {
color: #333!important;
}
} }
} }
...@@ -124,14 +129,22 @@ ...@@ -124,14 +129,22 @@
color: @active-color; color: @active-color;
.ant-menu-item-selected { .ant-menu-item-selected {
color: #fff; color: @active-color;
font-weight: 500;
&:hover {
color: @active-color!important;
}
.listType { .listType {
background: @active-color; background: @active-color;
} }
} }
.ant-menu-submenu-arrow {
right: 10px!important;
color: @active-color;
}
} }
.ant-menu-submenu-arrow { .ant-menu-submenu-arrow {
right: 22px; right: 10px!important;
color: #5e606a; color: #5e606a;
} }
} }
...@@ -266,16 +279,30 @@ ...@@ -266,16 +279,30 @@
.shink-footer { .shink-footer {
left: 74px; left: 74px;
} }
// &:hover{ }
// .menu-type-icon{
// display:inline-block;
// }
// }
.ant-menu-inline .ant-menu-item, .ant-menu-inline .ant-menu-submenu-title {
width: calc(100% - 15px)!important;
} }
.ant-menu-submenu:hover {
// background: rgba(41, 102, 255, .05) !important;
&.ant-menu-submenu-title:hover {
color: #333333!important;
}
.ant-menu-submenu-title > .ant-menu-submenu-arrow {
color: #5e606a!important;
}
&.icon-img-box {
background-color: rgba(41, 102, 255, .05)!important;
}
}
.ant-menu-light .ant-menu-submenu-title:hover {
color: #333!important;
background: rgba(41, 102, 255, .05) !important;
}
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background: @active-color !important; background: rgba(41, 102, 255, .1) !important;
} }
.ant-menu.ant-menu-dark, .ant-menu.ant-menu-dark,
.ant-menu-dark .ant-menu-sub, .ant-menu-dark .ant-menu-sub,
......
...@@ -239,10 +239,10 @@ function Aside(props: any) { ...@@ -239,10 +239,10 @@ function Aside(props: any) {
icon={ icon={
!menuType && item.groupCode === selectKeyParent ? ( !menuType && item.groupCode === selectKeyParent ? (
<div className='icon-img-box' style={{ backgroundColor: '#2966FF', width: '40px', height: '40px' }}> <div className='icon-img-box' style={{ backgroundColor: '#2966FF', width: '40px', height: '40px' }}>
<img src={item.selectImg} className='icon-img' /> <img src={item.img} className='icon-img' />
</div> </div>
) : ( ) : (
<div className='icon-img-box' style={{ backgroundColor: '#fff', width: '40px', height: '40px', display: 'inline-block' }}> <div className='icon-img-box' style={{ width: '40px', height: '40px', display: 'inline-block' }}>
<img src={item.img} className='icon-img' /> <img src={item.img} className='icon-img' />
</div> </div>
) )
...@@ -269,6 +269,7 @@ function Aside(props: any) { ...@@ -269,6 +269,7 @@ function Aside(props: any) {
} else { } else {
return ( return (
<Menu.Item <Menu.Item
className="single-menu"
onClick={() => { onClick={() => {
toggleMenu(item, item.groupCode); toggleMenu(item, item.groupCode);
}} }}
...@@ -276,11 +277,11 @@ function Aside(props: any) { ...@@ -276,11 +277,11 @@ function Aside(props: any) {
icon={ icon={
!menuType && item.groupCode === selectKeyParent ? ( !menuType && item.groupCode === selectKeyParent ? (
<div className='icon-img-box' style={{ backgroundColor: '#2966FF' }}> <div className='icon-img-box' style={{ backgroundColor: '#2966FF' }}>
<img src={item.selectImg} className='icon-img' /> <img src={item.img} className='icon-img' />
</div> </div>
) : ( ) : (
<div className='icon-img-box'> <div className='icon-img-box'>
<img src={selectKey === item.groupCode ? item.selectImg : item.img} className='icon-img' /> <img src={item.img} className='icon-img' />
</div> </div>
) )
}> }>
......
...@@ -539,15 +539,15 @@ class CourseCategoryManage extends Component { ...@@ -539,15 +539,15 @@ class CourseCategoryManage extends Component {
return ( return (
<div className='page course-category-manage'> <div className='page course-category-manage'>
{['aid', 'knowledge'].includes(getParameterByName('from')) ? ( {['aid', 'knowledge'].includes(getParameterByName('from')) ? (
<Breadcrumbs navList='课程分类' goBack={() => window.RCHistory.goBack()} /> <Breadcrumbs navList='分类管理' goBack={() => window.RCHistory.goBack()} />
) : ( ) : (
<div className='content-header'>课程分类</div> <div className='content-header'>分类管理</div>
)} )}
<div className='box'> <div className='box'>
<div className='search-condition'> <div className='search-condition'>
<span className='search-label'>搜索名称:</span> <span className='search-label'>搜索名称:</span>
<Search <Search
placeholder='请输入名称' placeholder='搜索分类名称'
style={{ width: '300px' }} style={{ width: '300px' }}
onSearch={(value) => this.queryCategoryTree('init', value)} onSearch={(value) => this.queryCategoryTree('init', value)}
className='search-input' className='search-input'
......
/* /*
* @Author: yuananting * @Author: yuananting
* @Date: 2021-02-22 10:59:43 * @Date: 2021-02-22 10:59:43
* @LastEditors: fusanqiasng * @LastEditors: yuananting
* @LastEditTime: 2021-06-15 11:20:48 * @LastEditTime: 2021-07-13 11:54:21
* @Description: 助学工具-侧边课程分类树 * @Description: 助学工具-侧边课程分类树
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -20,7 +20,7 @@ class CourseCategorySiderTree extends Component { ...@@ -20,7 +20,7 @@ class CourseCategorySiderTree extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
selectedKeys: ['QUESTION_INDEX', 'PAPER_INDEX'].includes(props.fromModule) ? [getParameterByName('categoryId') || 'null'] : ['null'], selectedKeys: ['null'],
treeData: props.treeData || [], treeData: props.treeData || [],
autoExpandParent: false, autoExpandParent: false,
}; };
...@@ -189,7 +189,7 @@ class CourseCategorySiderTree extends Component { ...@@ -189,7 +189,7 @@ class CourseCategorySiderTree extends Component {
)} )}
<Search <Search
className='sider-search' className='sider-search'
placeholder='搜索名称分类' placeholder='搜索分类名称'
onSearch={(value) => { onSearch={(value) => {
this.queryCategoryTree('init', value); this.queryCategoryTree('init', value);
}} }}
......
...@@ -36,12 +36,14 @@ function AddExam(props: any) { ...@@ -36,12 +36,14 @@ function AddExam(props: any) {
const [getData, setGetData] = useState(false); const [getData, setGetData] = useState(false);
const [preview, setPreview] = useState(false); const [preview, setPreview] = useState(false);
const [examTotal, setExamTotal] = useState(0); const [examTotal, setExamTotal] = useState(0);
const [examList, setExamList] = useState([]);
const request = useRef(false); const request = useRef(false);
const { match } = props; const { match } = props;
const [examDuration, setExamDuration] = useState(undefined); const [examDuration, setExamDuration] = useState(undefined);
useEffect(() => { useEffect(() => {
queryExamList();
switch (props.type) { switch (props.type) {
case "copy": // 考试列表-复制考试进入 case "copy": // 考试列表-复制考试进入
case "edit": // 考试列表-编辑考试进入 case "edit": // 考试列表-编辑考试进入
...@@ -52,6 +54,7 @@ function AddExam(props: any) { ...@@ -52,6 +54,7 @@ function AddExam(props: any) {
case "editPaperToAddExam": // 组卷页面-编辑保存试卷并组织考试 case "editPaperToAddExam": // 组卷页面-编辑保存试卷并组织考试
setGetData(true); setGetData(true);
setPaperInfo(props.paperInfo); setPaperInfo(props.paperInfo);
setExamName(props.paperInfo?.paperName)
break; break;
} }
}, []) }, [])
...@@ -72,6 +75,21 @@ function AddExam(props: any) { ...@@ -72,6 +75,21 @@ function AddExam(props: any) {
return current && current < moment().startOf('day'); return current && current < moment().startOf('day');
} }
function queryExamList() {
let param = {
current: 1,
size: 9999,
order: 'EXAM_START_TIME_DESC',
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0,
}
Service.Hades('public/hades/queryExamPageList', param).then(res=> {
const { result = {} } = res;
setExamList(result.records || [])
})
}
function queryExamDetail() { function queryExamDetail() {
Service.Hades("public/hades/queryExamDetail", { Service.Hades("public/hades/queryExamDetail", {
examId: match.params.id, examId: match.params.id,
...@@ -137,6 +155,11 @@ function AddExam(props: any) { ...@@ -137,6 +155,11 @@ function AddExam(props: any) {
return return
} }
if(checkExist(param.examName)) {
message.warning('此考试名称已存在');
return
}
if (!paperId) { if (!paperId) {
message.warning('请选择试卷'); message.warning('请选择试卷');
return return
...@@ -250,6 +273,26 @@ function AddExam(props: any) { ...@@ -250,6 +273,26 @@ function AddExam(props: any) {
}) })
} }
// 校验考试名称是否存在
function checkExist(examName: any) {
var result:any = null;
examList.length > 0 && examList.forEach((item:any) => {
if (result != null) {
return result;
}
if (props.type === 'edit') {
if (item.examName === examName && item.examId !== match.params.id) {
result = item;
}
} else {
if (item.examName === examName) {
result = item;
}
}
});
return result;
};
let title = ''; let title = '';
switch (props.type) { switch (props.type) {
case 'add': case 'add':
...@@ -281,8 +324,8 @@ function AddExam(props: any) { ...@@ -281,8 +324,8 @@ function AddExam(props: any) {
layout="horizontal" layout="horizontal"
> >
<Form.Item label="考试名称" <Form.Item label="考试名称"
validateStatus={(check && (!examName ? '请输入考试名称' : (examName.length > 40) && '考试名称最多40字')) ? 'error' : ''} validateStatus={(check && (!examName || (examName.length > 40 || checkExist(examName)) )) ? 'error' : ''}
help={check && (!examName ? '请输入考试名称' : (examName.length > 40) && '考试名称最多40字')} help={check && (!examName ? '请输入考试名称' : (examName.length > 40 ? '考试名称最多40字' : (checkExist(examName) && '此考试名称已存在')))}
required> required>
<Input placeholder='请输入考试名称(40字以内)' maxLength={40} value={examName} onChange={(e) => { <Input placeholder='请输入考试名称(40字以内)' maxLength={40} value={examName} onChange={(e) => {
...@@ -410,24 +453,25 @@ function AddExam(props: any) { ...@@ -410,24 +453,25 @@ function AddExam(props: any) {
<div className="title" style={{ marginTop: 40 }}>考试设置</div> <div className="title" style={{ marginTop: 40 }}>考试设置</div>
<Form.Item label="身份验证" required> <Form.Item label="身份验证" required>
<div style={{ display: 'flex', marginLeft: 4, }}> <div style={{ display: 'flex', marginLeft: 4, }}>
<Switch style={{ position: 'relative', top: 6 }} <Switch
checked={needPhone == 'NEED_PHONE_VERIFY'} checked={needPhone == 'NEED_PHONE_VERIFY'}
onChange={(val) => { setNeedPhone(val ? 'NEED_PHONE_VERIFY' : 'DO_NOT_NEED_PHONE_VERIFY') }} onChange={(val) => { setNeedPhone(val ? 'NEED_PHONE_VERIFY' : 'DO_NOT_NEED_PHONE_VERIFY') }}
></Switch> ></Switch>
<div style={{ position: 'relative', top: 3, left: 8, color: "#999" }}><p>开启:需要绑定手机号的学员才能参加考试</p> <div style={{ position: 'relative', left: 8, color: "#999" }}>
<p>关闭:微信/企业微信登陆直接参加考试</p></div> {needPhone == 'NEED_PHONE_VERIFY' ? '已开启,学员需绑定手机号才可参与考试' : '已关闭,学员无需绑定手机号即可参与考试'}
</div>
</div> </div>
</Form.Item> </Form.Item>
<Form.Item label="选项乱序" required> <Form.Item label="选项乱序" required>
<div style={{ display: 'flex', marginLeft: 4, }}> <div style={{ display: 'flex', marginLeft: 4, }}>
<Switch style={{ position: 'relative', top: 6 }} <Switch
checked={needOptionDisorder == 'OPTION_RANDOM'} checked={needOptionDisorder == 'OPTION_RANDOM'}
onChange={(val) => { setNeedOptionDisorder(val ? 'OPTION_RANDOM' : 'OPTION_SORT') }} onChange={(val) => { setNeedOptionDisorder(val ? 'OPTION_RANDOM' : 'OPTION_SORT') }}
></Switch> ></Switch>
<div style={{ position: 'relative', top: 3, left: 8, color: "#999" }}><p>开启:选择题的选项随机排序</p> <div style={{ position: 'relative', left: 8, color: "#999" }}>
<p>关闭:选择题按题目原有顺序展示</p></div> {needOptionDisorder == 'OPTION_RANDOM' ? '已开启,选项随机排序' : '已关闭,选项按设置顺序排序'}
</div>
</div> </div>
</Form.Item> </Form.Item>
......
/* /*
* @Author: yuananting * @Author: yuananting
* @Date: 2021-03-27 16:15:13 * @Date: 2021-03-27 16:15:13
* @LastEditors: fusanqiasng * @LastEditors: yuananting
* @LastEditTime: 2021-07-01 16:30:38 * @LastEditTime: 2021-07-13 12:01:37
* @Description: 助学工具-新建/复制/编辑试卷 * @Description: 助学工具-新建/复制/编辑试卷
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -118,7 +118,7 @@ class OperatePaper extends Component { ...@@ -118,7 +118,7 @@ class OperatePaper extends Component {
portionScore: 0, portionScore: 0,
totalQuestion: 0, totalQuestion: 0,
}, },
] ],
}; };
} }
...@@ -352,20 +352,6 @@ class OperatePaper extends Component { ...@@ -352,20 +352,6 @@ class OperatePaper extends Component {
return result; return result;
}; };
validatePaperName = (paperName) => {
if (this.state.check && !paperName) {
return '请输入试卷名称';
}
if (this.checkExist(paperName)) {
return '该试卷名称已存在';
}
if (paperName && paperName.length > 40) {
return '试卷名称最多40字';
}
};
// 保存试卷 // 保存试卷
savePaper = (saveType) => { savePaper = (saveType) => {
this.setState({ check: true }); this.setState({ check: true });
...@@ -373,8 +359,17 @@ class OperatePaper extends Component { ...@@ -373,8 +359,17 @@ class OperatePaper extends Component {
const categoryId = getParameterByName('categoryId'); const categoryId = getParameterByName('categoryId');
const { match } = this.props; const { match } = this.props;
let questionList = []; let questionList = [];
if (!formData.passRate || !formData.paperName || this.checkExist(formData.paperName) || (formData.paperName && formData.paperName.length > 40)) { if(!formData.paperName) {
return; return message.warning('请输入试卷名称')
}
if(formData.paperName.length > 40) {
return message.warning('试卷名称最多40字')
}
if (this.checkExist(formData.paperName)) {
return message.warning('该试卷名称已存在')
}
if(!formData.passRate) {
return message.warning('请输入及格线')
} }
if (selectQuestionList.length === 0) { if (selectQuestionList.length === 0) {
return message.warning('请选择题目'); return message.warning('请选择题目');
...@@ -410,18 +405,14 @@ class OperatePaper extends Component { ...@@ -410,18 +405,14 @@ class OperatePaper extends Component {
}); });
} else { } else {
message.success(currentOperate === 'new' ? '新建成功' : '复制成功'); message.success(currentOperate === 'new' ? '新建成功' : '复制成功');
window.RCHistory.push({ window.RCHistory.goBack();
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length); Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length);
Bus.trigger('queryCategoryTree', 'remain'); Bus.trigger('queryCategoryTree', 'remain');
} }
} }
}) })
.catch((e) => { .catch((e) => {
window.RCHistory.push({ window.RCHistory.goBack();
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length); Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length);
Bus.trigger('queryCategoryTree', 'remain'); Bus.trigger('queryCategoryTree', 'remain');
}); });
...@@ -438,17 +429,13 @@ class OperatePaper extends Component { ...@@ -438,17 +429,13 @@ class OperatePaper extends Component {
}); });
} else { } else {
message.success('编辑成功'); message.success('编辑成功');
window.RCHistory.push({ window.RCHistory.goBack();
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length); Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length);
} }
} }
}) })
.catch((e) => { .catch((e) => {
window.RCHistory.push({ window.RCHistory.goBack();
pathname: `/paper-manage-index?categoryId=${categoryId}`,
});
Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length); Bus.trigger('queryPaperPageList', categoryId, selectQuestionList.length);
}); });
} }
...@@ -482,9 +469,7 @@ class OperatePaper extends Component { ...@@ -482,9 +469,7 @@ class OperatePaper extends Component {
cancelText: '留在本页', cancelText: '留在本页',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>, icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => { onOk: () => {
window.RCHistory.push({ window.RCHistory.goBack()
pathname: `/paper-manage-index?categoryId=${getParameterByName('categoryId')}`,
});
Bus.trigger('queryCategoryTree', 'remain'); Bus.trigger('queryCategoryTree', 'remain');
Bus.trigger('queryPaperPageList', getParameterByName('categoryId'), 0); Bus.trigger('queryPaperPageList', getParameterByName('categoryId'), 0);
}, },
...@@ -822,8 +807,8 @@ class OperatePaper extends Component { ...@@ -822,8 +807,8 @@ class OperatePaper extends Component {
name='paperName' name='paperName'
label='试卷名称:' label='试卷名称:'
required required
validateStatus={this.validatePaperName(paperName) ? 'error' : ''} validateStatus={(check && (!paperName || paperName.length > 40 || this.checkExist(paperName))) ? 'error' : ''}
help={this.validatePaperName(paperName)}> help={check && (!paperName ? '请输入试卷名称' : (paperName.length > 40 ? '试卷名称最多40字' : (this.checkExist(paperName) && '该试卷名称已存在')))}>
<Input <Input
value={paperName} value={paperName}
autoComplete='off' autoComplete='off'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: yuananting * @Author: yuananting
* @Date: 2021-02-25 11:23:47 * @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting * @LastEditors: yuananting
* @LastEditTime: 2021-06-03 17:13:30 * @LastEditTime: 2021-07-13 15:16:52
* @Description: 助学工具-题库-试卷列表数据 * @Description: 助学工具-题库-试卷列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -362,9 +362,9 @@ class PaperList extends Component { ...@@ -362,9 +362,9 @@ class PaperList extends Component {
{isPermiss && ( {isPermiss && (
<div <div
className="record-operate__item" className="record-operate__item"
onClick={() => this.copyPaper(record)} onClick={() => this.organizeExam(record)}
> >
复制 组织考试
</div> </div>
)} )}
{isPermiss && ( {isPermiss && (
...@@ -391,7 +391,7 @@ class PaperList extends Component { ...@@ -391,7 +391,7 @@ class PaperList extends Component {
<span onClick={() => this.editPaper(item)}>编辑</span> <span onClick={() => this.editPaper(item)}>编辑</span>
</Menu.Item> </Menu.Item>
<Menu.Item key="organizeExam"> <Menu.Item key="organizeExam">
<span onClick={() => this.organizeExam(item)}>组织考试</span> <span onClick={() => this.copyPaper(item)}>复制</span>
</Menu.Item> </Menu.Item>
<Menu.Item key="del"> <Menu.Item key="del">
<span onClick={() => this.confirmDeletePaper(item)}>删除</span> <span onClick={() => this.confirmDeletePaper(item)}>删除</span>
......
/* /*
* @Author: yuananting * @Author: yuananting
* @Date: 2021-02-25 13:46:35 * @Date: 2021-02-25 13:46:35
* @LastEditors: wufan * @LastEditors: yuananting
* @LastEditTime: 2021-05-24 11:32:47 * @LastEditTime: 2021-07-13 11:51:56
* @Description: 助学工具-题库-新建/编辑题目 * @Description: 助学工具-题库-新建/编辑题目
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -161,9 +161,7 @@ class OperateQuestion extends Component { ...@@ -161,9 +161,7 @@ class OperateQuestion extends Component {
<span className="icon iconfont default-confirm-icon">&#xe6f4;</span> <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
), ),
onOk: () => { onOk: () => {
window.RCHistory.push({ window.RCHistory.goBack();
pathname: `/question-manage-index?categoryId=${getParameterByName("categoryId")}`,
});
Bus.trigger("queryCategoryTree", "remain"); Bus.trigger("queryCategoryTree", "remain");
Bus.trigger("queryQuestionPageList", getParameterByName("categoryId")); Bus.trigger("queryQuestionPageList", getParameterByName("categoryId"));
}, },
...@@ -248,9 +246,7 @@ class OperateQuestion extends Component { ...@@ -248,9 +246,7 @@ class OperateQuestion extends Component {
this.handleRest(type); this.handleRest(type);
} }
if (next === "close") { if (next === "close") {
window.RCHistory.push({ window.RCHistory.goBack();
pathname: `/question-manage-index?categoryId=${categoryId}`,
});
Bus.trigger("queryCategoryTree", "remain") Bus.trigger("queryCategoryTree", "remain")
Bus.trigger("queryQuestionPageList", categoryId) Bus.trigger("queryQuestionPageList", categoryId)
} }
...@@ -271,9 +267,7 @@ class OperateQuestion extends Component { ...@@ -271,9 +267,7 @@ class OperateQuestion extends Component {
this.handleRest(type); this.handleRest(type);
} }
if (next === "close") { if (next === "close") {
window.RCHistory.push({ window.RCHistory.goBack();
pathname: `/question-manage-index?categoryId=${categoryId}`,
});
Bus.trigger("queryCategoryTree", "remain") Bus.trigger("queryCategoryTree", "remain")
Bus.trigger("queryQuestionPageList", categoryId) Bus.trigger("queryQuestionPageList", categoryId)
} }
......
...@@ -81,7 +81,7 @@ const mainRoutes = [ ...@@ -81,7 +81,7 @@ const mainRoutes = [
{ {
path: '/video-course', path: '/video-course',
component: VideoCoursePage, component: VideoCoursePage,
name: '视频课', name: '线上课',
}, },
{ {
path: '/graphics-course', path: '/graphics-course',
...@@ -101,7 +101,7 @@ const mainRoutes = [ ...@@ -101,7 +101,7 @@ const mainRoutes = [
{ {
path: '/create-video-course', path: '/create-video-course',
component: AddVideoCoursePage, component: AddVideoCoursePage,
name: '创建视频课', name: '创建线上课',
}, },
{ {
path: '/knowledge-base', path: '/knowledge-base',
...@@ -163,7 +163,7 @@ const mainRoutes = [ ...@@ -163,7 +163,7 @@ const mainRoutes = [
{ {
path: '/create-plan', path: '/create-plan',
component: AddPlanPage, component: AddPlanPage,
name: '创建视频课', name: '创建线上课',
}, },
{ {
path: '/store-info', path: '/store-info',
......
/* /*
* @Author: yuananting * @Author: yuananting
* @Date: 2021-02-21 15:53:31 * @Date: 2021-02-21 15:53:31
* @LastEditors: Please set LastEditors * @LastEditors: wufan
* @LastEditTime: 2021-06-08 16:46:15 * @LastEditTime: 2021-07-05 10:29:23
* @Description: 描述一下咯 * @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -28,8 +28,8 @@ export const menuList: any = [ ...@@ -28,8 +28,8 @@ export const menuList: any = [
link: '/live-course', link: '/live-course',
}, },
{ {
groupName: '视频课', groupName: "线上课",
groupCode: 'CourseVideoClass', groupCode: "CourseVideoClass",
link: '/video-course', link: '/video-course',
}, },
{ {
......
...@@ -7,104 +7,82 @@ ...@@ -7,104 +7,82 @@
*/ */
import mainRoutes from './config/mainRoutes'; import mainRoutes from './config/mainRoutes';
import redirectRoutes from './config/redirectRoutes'; import redirectRoutes from './config/redirectRoutes';
import React from 'react' import React from 'react';
import { Redirect,HashRouter as Router,Route ,Switch} from 'react-router-dom'; import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import { createHashHistory } from 'history'; import { createHashHistory } from 'history';
import App from '../modules/root/App';
import AppContext from '@/modules/root/AppContent'; import AppContext from '@/modules/root/AppContent';
import Login from '../modules/root/Login'; import Login from '../modules/root/Login';
import CollegeManagePage from '../modules/root/CollegeManagePage'; import CollegeManagePage from '../modules/root/CollegeManagePage';
import CreateCollege from '../modules/root/CreateCollege'; import CreateCollege from '../modules/root/CreateCollege';
import _ from 'underscore'; import _ from 'underscore';
import { asyncComponent } from 'react-async-component'
import SwitchRoute from '@/modules/root/SwitchRoute'; import SwitchRoute from '@/modules/root/SwitchRoute';
import ErrorCollege from '@/modules/root/ErrorCollege'; import ErrorCollege from '@/modules/root/ErrorCollege';
const history = createHashHistory(); const history = createHashHistory();
window.RCHistory = _.extend({}, history, { window.RCHistory = _.extend({}, history, {
push: (obj: any) => { push: (obj: any) => {
history.push(obj) history.push(obj);
}, },
pushState: (obj: any) => { pushState: (obj: any) => {
history.push(obj) history.push(obj);
}, },
pushStateWithStatus: (obj: any) => { pushStateWithStatus: (obj: any) => {
history.push(obj) history.push(obj);
}, },
goBack: history.goBack, goBack: history.goBack,
location: history.location, location: history.location,
replace: (obj: any) => { replace: (obj: any) => {
history.replace(obj) history.replace(obj);
}
});
const cache:any = {
path: '',
component: null
}
function dynamic (component:any) {
const resolveComponent = component
return asyncComponent({
resolve: () => {
const ts = resolveComponent()
return ts
}, },
}) });
}
export const RootRouter = () => { export const RootRouter = () => {
return ( return (
<Router {...history}> <Router {...history}>
<Switch> <Switch>
<Route key="1" exact path="/login" render={() => <Login />} /> <Route key='1' exact path='/login' render={() => <Login />} />
<Route key="2" exact path="/switch-route" render={() => <SwitchRoute />} /> <Route key='2' exact path='/switch-route' render={() => <SwitchRoute />} />
<Route key="3" exact path="/college-manage" render={() => <CollegeManagePage />} /> <Route key='3' exact path='/college-manage' render={() => <CollegeManagePage />} />
<Route key="4" exact path="/college-manage/create" render={() => <CreateCollege />} /> <Route key='4' exact path='/college-manage/create' render={() => <CreateCollege />} />
<Route key="6" exact path="/error-college" render={() => <ErrorCollege />} /> <Route key='6' exact path='/error-college' render={() => <ErrorCollege />} />
<Route key="5" path="/" render={() => <AppContext />} /> <Route key='5' path='/' render={() => <AppContext />} />
</Switch> </Switch>
</Router> </Router>
) );
} };
export const MainRoutes = () => { export const MainRoutes = () => {
return ( return (
<Switch> <Switch>
{ {_.map(mainRoutes, ({ path, component }, key) => {
_.map(mainRoutes, ({ return (
path, <Route
component,
}, key) => {
return <Route
key={key} key={key}
path={path} path={path}
render={() => { render={() => {
const Component = component; const Component = component;
return <Component /> return <Component />;
}} }}
/> />
}) );
} })}
</Switch> </Switch>
) );
} };
export const RedirectRoutes = () => { export const RedirectRoutes = () => {
return ( return (
<Switch> <Switch>
{ {_.map(redirectRoutes, ({ path, component }, key) => {
_.map(redirectRoutes, ({ return (
path, <Route
component,
}, key) => {
return <Route
key={key} key={key}
path={path} path={path}
render={() => { render={() => {
const Component = component; const Component = component;
return <Component /> return <Component />;
}} }}
/> />
}) );
} })}
</Switch> </Switch>
) );
} };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment