Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
fbbe6b9e09 | |||
4f1f8bd3d1 | |||
d904797db3 | |||
b6da6e8e3e | |||
b20c4dc376 | |||
a3823b4735 | |||
b8070d96e8 | |||
51da660509 | |||
2103d3fcc1 | |||
0d5a7d6d5d | |||
d24052d24b | |||
926c1ef774 | |||
a2f0392fed | |||
2d987351bc |
10
.babelrc
10
.babelrc
@ -1,13 +1,9 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", { "modules": false }],
|
||||
"stage-2",
|
||||
["es2015", { "modules": false }]
|
||||
["@babel/preset-env", { "modules": false }]
|
||||
],
|
||||
"plugins": ["transform-object-rest-spread",["component", [
|
||||
{
|
||||
"plugins": [["@babel/plugin-transform-object-rest-spread", {}], ["component", {
|
||||
"libraryName": "mint-ui",
|
||||
"style": true
|
||||
}
|
||||
]]]
|
||||
}]]
|
||||
}
|
||||
|
52
README.md
52
README.md
@ -2,7 +2,7 @@
|
||||
|
||||
## 项目简介
|
||||
|
||||
JT360是一个基于Vue.js 2.x的前端项目,使用Webpack 3.x作为构建工具。项目集成了Mint-UI组件库,支持移动端开发。
|
||||
JT360是一个基于Vue.js 2.x的前端项目,使用Webpack 5.x作为构建工具。项目集成了Mint-UI组件库,支持移动端开发。
|
||||
|
||||
## 技术栈
|
||||
|
||||
@ -10,19 +10,22 @@ JT360是一个基于Vue.js 2.x的前端项目,使用Webpack 3.x作为构建工
|
||||
- **Vue Router 3.0.1** - 路由管理
|
||||
- **Vuex 3.0.1** - 状态管理
|
||||
- **Mint-UI 2.2.13** - 移动端UI组件库
|
||||
- **Webpack 3.6.0** - 构建工具
|
||||
- **Webpack 5.88.2** - 构建工具
|
||||
- **Axios 0.17.1** - HTTP客户端
|
||||
- **Sass** - CSS预处理器
|
||||
- **buffer@6.0.3**、**process@0.11.10** - Node.js核心模块polyfill
|
||||
|
||||
## 环境要求
|
||||
|
||||
### Node.js版本要求
|
||||
**严格要求使用 Node.js 12.22.12 版本**
|
||||
support Node.js 18.x 版本
|
||||
|
||||
> 注意:使用Node.js 18.x版本时,可能会遇到node-sass弃用警告和pkcs7包兼容性警告,但不影响项目运行。
|
||||
|
||||
```bash
|
||||
# 检查Node.js版本
|
||||
node -v
|
||||
# 应该显示: v12.22.12
|
||||
# 推荐显示: v18.12.1 或更高版本
|
||||
```
|
||||
|
||||
### 其他要求
|
||||
@ -39,21 +42,36 @@ cd jt360front
|
||||
|
||||
### 2. 安装依赖
|
||||
```bash
|
||||
npm install
|
||||
# 或者使用yarn(推荐使用yarn)
|
||||
yarn install
|
||||
npm install --legacy-peer-deps
|
||||
# 或者使用yarn
|
||||
# yarn install
|
||||
```
|
||||
|
||||
### 3. 开发环境运行
|
||||
```bash
|
||||
yarn dev
|
||||
npm run dev
|
||||
# 或者使用yarn
|
||||
# yarn dev
|
||||
```
|
||||
|
||||
### 4. 生产环境构建
|
||||
```bash
|
||||
yarn build
|
||||
npm run build
|
||||
# 或者使用yarn
|
||||
# yarn build
|
||||
```
|
||||
|
||||
## Webpack 5 升级说明
|
||||
|
||||
项目已从Webpack 3.x升级到Webpack 5.x,主要解决了以下问题:
|
||||
1. 添加了Node.js核心模块(如Buffer、process)的polyfill
|
||||
2. 配置了fallback解析路径
|
||||
3. 使用ProvidePlugin全局提供对象
|
||||
|
||||
升级后可能存在的警告:
|
||||
- node-sass弃用警告(建议未来迁移到sass/sass-embedded)
|
||||
- pkcs7包引擎版本不兼容警告(建议未来更新此包)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
@ -85,7 +103,10 @@ jt360front/
|
||||
|
||||
## 开发注意事项
|
||||
|
||||
1. **Node.js版本**:必须使用12.22.12版本,其他版本可能导致依赖包兼容性问题
|
||||
1. **Node.js版本**:支持Node.js 18.x版本
|
||||
2. **浏览器兼容性**:支持现代浏览器,不支持IE8及以下版本
|
||||
3. **移动端适配**:项目主要针对移动端开发,使用了Mint-UI组件库
|
||||
4. **依赖安装**:建议使用`npm install --legacy-peer-deps`安装依赖以避免兼容性问题
|
||||
2. **浏览器兼容性**:支持现代浏览器,不支持IE8及以下版本
|
||||
3. **移动端适配**:项目主要针对移动端开发,使用了Mint-UI组件库
|
||||
|
||||
@ -102,15 +123,18 @@ jt360front/
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 为什么必须使用Node.js 12.22.12?
|
||||
A: 项目依赖的某些包(如node-sass 4.12.0)对Node.js版本有严格要求,使用其他版本可能导致编译错误。
|
||||
### Q: 为什么使用Node.js 18.x版本会出现警告?
|
||||
A: 主要是node-sass和pkcs7包的兼容性问题,这些警告不影响项目运行。未来计划迁移到sass/sass-embedded并更新pkcs7包。
|
||||
|
||||
### Q: 如何解决依赖安装冲突?
|
||||
A: 建议使用`npm install --legacy-peer-deps`命令安装依赖。
|
||||
|
||||
### Q: 如何切换Node.js版本?
|
||||
A: 推荐使用nvm(Node Version Manager)来管理Node.js版本:
|
||||
```bash
|
||||
# 安装指定版本
|
||||
nvm install 12.22.12
|
||||
nvm use 12.22.12
|
||||
nvm install 18.12.1
|
||||
nvm use 18.12.1
|
||||
```
|
||||
|
||||
## 联系方式
|
||||
|
@ -18,17 +18,24 @@ rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
spinner.stop()
|
||||
if (err) throw err
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
|
||||
if (err) {
|
||||
console.log(chalk.red(' Webpack configuration error:'))
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
process.stdout.write(stats.toString({ colors: true,
|
||||
modules: true,
|
||||
children: true,
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n')
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
console.log(chalk.red(' Build failed with errors.\n'))
|
||||
console.log(chalk.red(' Error details:'))
|
||||
stats.toJson().errors.forEach(error => {
|
||||
console.error(error)
|
||||
})
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const config = require('../config')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const packageConfig = require('../package.json')
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
@ -45,10 +45,7 @@ exports.cssLoaders = function (options) {
|
||||
// Extract CSS when that option is specified
|
||||
// (which is the case during production build)
|
||||
if (options.extract) {
|
||||
return ExtractTextPlugin.extract({
|
||||
use: loaders,
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
return [MiniCssExtractPlugin.loader].concat(loaders)
|
||||
} else {
|
||||
return ['vue-style-loader'].concat(loaders)
|
||||
}
|
||||
|
@ -2,7 +2,10 @@
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const vueLoaderConfig = require('./vue-loader.conf')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const webpack = require('webpack')
|
||||
const Buffer = require('buffer').Buffer
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
@ -20,21 +23,27 @@ module.exports = {
|
||||
filename: '[name].js',
|
||||
publicPath: process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsPublicPath
|
||||
: config.dev.assetsPublicPath
|
||||
: config.dev.assetsPublicPath,
|
||||
clean: true
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('src'),
|
||||
},
|
||||
fallback: {
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
buffer: require.resolve('buffer/'),
|
||||
vm: require.resolve('vm-browserify')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
@ -68,16 +77,21 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
// prevent webpack from injecting useless setImmediate polyfill because Vue
|
||||
// source contains it (although only uses it if it's native).
|
||||
setImmediate: false,
|
||||
// prevent webpack from injecting mocks to Node native modules
|
||||
// that does not make sense for the client
|
||||
dgram: 'empty',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty'
|
||||
}
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
})
|
||||
]
|
||||
// Node.js polyfills are no longer included by default in Webpack 5
|
||||
// If needed, use resolve.fallback or providePlugin
|
||||
// node: {
|
||||
// setImmediate: false,
|
||||
// dgram: 'empty',
|
||||
// fs: 'empty',
|
||||
// net: 'empty',
|
||||
// tls: 'empty',
|
||||
// child_process: 'empty'
|
||||
// }
|
||||
}
|
||||
|
@ -2,18 +2,18 @@
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const { merge } = require('webpack-merge')
|
||||
const path = require('path')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
mode: 'development',
|
||||
module: {
|
||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
|
||||
},
|
||||
@ -22,35 +22,36 @@ const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
|
||||
// these devServer options should be customized in /config/index.js
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
client: {
|
||||
logging: 'warn',
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
},
|
||||
historyApiFallback: {
|
||||
rewrites: [
|
||||
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
|
||||
],
|
||||
},
|
||||
hot: true,
|
||||
contentBase: false, // since we use CopyWebpackPlugin.
|
||||
static: false, // since we use CopyWebpackPlugin.
|
||||
compress: true,
|
||||
host: HOST || config.dev.host,
|
||||
port: PORT || config.dev.port,
|
||||
open: config.dev.autoOpenBrowser,
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
publicPath: config.dev.assetsPublicPath,
|
||||
proxy: config.dev.proxyTable,
|
||||
quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
watchOptions: {
|
||||
watchFiles: {
|
||||
options: {
|
||||
poll: config.dev.poll,
|
||||
},
|
||||
disableHostCheck:true
|
||||
},
|
||||
allowedHosts: 'all'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/dev.env')
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
@ -59,13 +60,17 @@ const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
inject: true
|
||||
}),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.dev.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
globOptions: {
|
||||
ignore: ['**/.*']
|
||||
}
|
||||
])
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
@ -80,16 +85,6 @@ module.exports = new Promise((resolve, reject) => {
|
||||
// add port to devServer config
|
||||
devWebpackConfig.devServer.port = port
|
||||
|
||||
// Add FriendlyErrorsPlugin
|
||||
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
|
||||
compilationSuccessInfo: {
|
||||
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
|
||||
},
|
||||
onErrors: config.dev.notifyOnErrors
|
||||
? utils.createNotifierCallback()
|
||||
: undefined
|
||||
}))
|
||||
|
||||
resolve(devWebpackConfig)
|
||||
}
|
||||
})
|
||||
|
@ -3,59 +3,69 @@ const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const { merge } = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
|
||||
const env = require('../config/prod.env')
|
||||
|
||||
const webpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
extract: true,
|
||||
usePostCSS: true
|
||||
})
|
||||
rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true, usePostCSS: true })
|
||||
},
|
||||
devtool: config.build.productionSourceMap ? config.build.devtool : false,
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||
filename: utils.assetsPath('js/[name].[contenthash].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[contenthash].js')
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
parallel: true
|
||||
}),
|
||||
new CssMinimizerPlugin({
|
||||
minimizerOptions: {
|
||||
preset: [
|
||||
'default',
|
||||
{
|
||||
discardComments: { removeAll: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
],
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: 'vendor',
|
||||
chunks: 'all'
|
||||
}
|
||||
}
|
||||
},
|
||||
runtimeChunk: 'single'
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
parallel: true
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
new MiniCssExtractPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css'),
|
||||
// Setting the following option to `false` will not extract CSS from codesplit chunks.
|
||||
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
|
||||
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
|
||||
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
|
||||
allChunks: true,
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: config.build.productionSourceMap
|
||||
? { safe: true, map: { inline: false } }
|
||||
: { safe: true }
|
||||
chunkFilename: utils.assetsPath('css/[id].[contenthash].css')
|
||||
}),
|
||||
// generate dist index.html with correct asset hash for caching.
|
||||
// you can customize output by editing /index.html
|
||||
@ -71,51 +81,23 @@ const webpackConfig = merge(baseWebpackConfig, {
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
chunksSortMode: 'auto'
|
||||
}),
|
||||
// keep module.id stable when vendor modules does not change
|
||||
new webpack.HashedModuleIdsPlugin(),
|
||||
// enable scope hoisting
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks (module) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
)
|
||||
}
|
||||
}),
|
||||
// extract webpack runtime and module manifest to its own file in order to
|
||||
// prevent vendor hash from being updated whenever app bundle is updated
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest',
|
||||
minChunks: Infinity
|
||||
}),
|
||||
// This instance extracts shared chunks from code splitted chunks and bundles them
|
||||
// in a separate chunk, similar to the vendor chunk
|
||||
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'app',
|
||||
async: 'vendor-async',
|
||||
children: true,
|
||||
minChunks: 3
|
||||
}),
|
||||
new webpack.ids.HashedModuleIdsPlugin(),
|
||||
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
globOptions: {
|
||||
ignore: ['**/.*']
|
||||
}
|
||||
])
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const { merge } = require('webpack-merge')
|
||||
const prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"',
|
||||
// BASE_API: '"http://spadmin.jiangtang360.com/api"'
|
||||
BASE_API: '"http://admin.jiangtang360.com/api"',
|
||||
BASE_API: '"https://adminssl.jiangtang360.com/api"',
|
||||
// BASE_API: '"http://zywsadmin.jiangtang360.com/api"'
|
||||
})
|
||||
|
@ -12,7 +12,7 @@ module.exports = {
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {
|
||||
'/api': {
|
||||
// target: 'http://admin.jiangtang360.com/admin',
|
||||
// target: 'https://adminssl.jiangtang360.com/admin',
|
||||
target: 'http://sz-test.shengzhevictor.com', //设置调用接口域名和端口号别忘了加http///请求的第三方接口
|
||||
changeOrigin: true,///在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,这样服务端和服务端进行数据的交互就不会有跨域问题
|
||||
pathRewrite: {
|
||||
@ -36,8 +36,7 @@ module.exports = {
|
||||
*/
|
||||
|
||||
// https://webpack.js.org/configuration/devtool/#development
|
||||
// devtool: 'cheap-module-eval-source-map',
|
||||
devtool: '#source-map',
|
||||
devtool: 'eval-cheap-module-source-map',
|
||||
|
||||
// If you have problems debugging vue-files in devtools,
|
||||
// set this to false - it *may* help
|
||||
@ -66,7 +65,7 @@ module.exports = {
|
||||
|
||||
productionSourceMap: true,
|
||||
// https://webpack.js.org/configuration/devtool/#production
|
||||
devtool: '#source-map',
|
||||
devtool: 'source-map',
|
||||
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict'
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
// BASE_API: '"http://spadmin.jiangtang360.com/api"'
|
||||
BASE_API: '"https://admins.jiangtang360.com/api"'
|
||||
BASE_API: '"https://adminssl.jiangtang360.com/api"'
|
||||
// BASE_API: '"http://zywsadmin.jiangtang360.com/api"'
|
||||
}
|
||||
|
28002
package-lock.json
generated
28002
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@ -5,9 +5,9 @@
|
||||
"author": "王觉贤 <615297896@qq.com>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 0.0.0.0",
|
||||
"dev": "webpack serve --config build/webpack.dev.conf.js --host 0.0.0.0",
|
||||
"start": "npm run dev",
|
||||
"build": "node build/build.js"
|
||||
"build": "node build/build.js --display-error-details"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
@ -19,10 +19,12 @@
|
||||
"console": "^0.7.2",
|
||||
"core-js": "^3.39.0",
|
||||
"crypto": "^1.0.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"exif-js": "^2.3.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"mint-ui": "^2.2.13",
|
||||
"qnweb-rtc": "^4.3.1",
|
||||
"qrcodejs2": "^0.0.2",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"simple-peer": "^9.11.1",
|
||||
@ -34,9 +36,11 @@
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.9",
|
||||
"@babel/preset-env": "^7.22.9",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-loader": "^8.3.0",
|
||||
"babel-plugin-component": "^1.1.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
@ -45,35 +49,42 @@
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^2.0.1",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"crypto-browserify": "^3.12.1",
|
||||
"css-loader": "^2.1.1",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"css-minimizer-webpack-plugin": "^7.0.2",
|
||||
"file-loader": "^1.1.4",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"node-notifier": "^5.1.2",
|
||||
"node-sass": "^4.12.0",
|
||||
"node-sass": "^9.0.0",
|
||||
"optimize-css-assets-webpack-plugin": "^2.0.0",
|
||||
"ora": "^1.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"postcss-import": "^11.0.0",
|
||||
"postcss-loader": "^2.0.8",
|
||||
"postcss-url": "^7.2.1",
|
||||
"process": "^0.11.10",
|
||||
"rimraf": "^2.6.0",
|
||||
"sass-loader": "^6.0.6",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"uglifyjs-webpack-plugin": "^1.0.0",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-loader": "^13.3.0",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"vue-loader": "^15.10.1",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-bundle-analyzer": "^3.3.2",
|
||||
"webpack-cli": "^3.3.1",
|
||||
"webpack-dev-server": "^2.9.4",
|
||||
"webpack-merge": "^4.1.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-merge": "^5.9.0",
|
||||
"weixin-js-sdk": "^1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -11,6 +11,15 @@
|
||||
<img src="../../static/lead/8.png" class="img8" v-if="step == 8"/>
|
||||
<img src="../../static/lead/9.png" class="img9" v-if="step == 9"/>
|
||||
</div> -->
|
||||
<div class="cover" v-if="haveLive">
|
||||
<div class="live_box" v-for="live in liveData" @click="goLive(live.room)">
|
||||
<div>{{ live.title }}</div>
|
||||
<br />
|
||||
<div class="stream_info">
|
||||
<p>開始時間:{{ live.start_time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<common-top></common-top>
|
||||
<div class="all">
|
||||
<div class="top">
|
||||
@ -37,16 +46,18 @@
|
||||
<div class="kecheng">
|
||||
<div class="kecheng_biaoti">
|
||||
<p class="kecheng_biaotiL">课程展示</p>
|
||||
<p class="kecheng_biaotiR" @click="goUrl('/courseAll')">查看全部 <img class="fr" src="../../static/imgs/header_icon_right.svg" alt=""></p>
|
||||
<p class="kecheng_biaotiR" @click="goUrl('/courseAll')">查看全部 <img class="fr"
|
||||
src="../../static/imgs/header_icon_right.svg" alt=""></p>
|
||||
</div>
|
||||
|
||||
<div class="kechenginfo" v-for="(item,index) in course" :key="index">
|
||||
<div class="kechenginfo" v-for="(item, index) in course" :key="index">
|
||||
<img :src="item.thumbnail" alt="">
|
||||
<div class="kechenginfoR">
|
||||
<p class="kechenginfoR_bt">{{item.name}}</p>
|
||||
<p class="kechenginfoR_xbt">{{item.description}}</p>
|
||||
<p class="kechenginfoR_bt">{{ item.name }}</p>
|
||||
<p class="kechenginfoR_xbt">{{ item.description }}</p>
|
||||
<div class="kechenginfoR_bot">
|
||||
<div class="kechenginfoR_botL"><img src="../../static/img/yanjing.png" alt="" class="yanjing">{{item.page_view}}人</div>
|
||||
<div class="kechenginfoR_botL"><img src="../../static/img/yanjing.png" alt="" class="yanjing">{{
|
||||
item.page_view }}人</div>
|
||||
<div class="kechenginfoR_botR" @click="goCourseDetail(item.id)">进入课程</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -59,172 +70,224 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonTop from './common/CommonTop'
|
||||
import CommonFooter from "./common/CommonFooter";
|
||||
import { getStore, setStore } from '@/utils/storage'
|
||||
import { Toast } from "mint-ui";
|
||||
export default {
|
||||
import CommonTop from './common/CommonTop'
|
||||
import CommonFooter from "./common/CommonFooter";
|
||||
import { getStore, setStore } from '@/utils/storage'
|
||||
import { Toast } from "mint-ui";
|
||||
export default {
|
||||
name: "Homes",
|
||||
components:{ CommonTop,CommonFooter },
|
||||
data(){
|
||||
return{
|
||||
company:{},
|
||||
course:[],
|
||||
step: 0
|
||||
components: { CommonTop, CommonFooter },
|
||||
data() {
|
||||
return {
|
||||
company: {},
|
||||
course: [],
|
||||
step: 0,
|
||||
haveLive: false,
|
||||
liveData: [],
|
||||
}
|
||||
},
|
||||
created(){
|
||||
created() {
|
||||
},
|
||||
mounted(){
|
||||
mounted() {
|
||||
|
||||
this.getPageData();
|
||||
this.getLiveStreams();
|
||||
},
|
||||
methods:{
|
||||
goCourseDetail (id){
|
||||
this.$router.push({path: '/courseDetail?id=' + id});
|
||||
methods: {
|
||||
goCourseDetail(id) {
|
||||
this.$router.push({ path: '/courseDetail?id=' + id });
|
||||
},
|
||||
goUrl (url){
|
||||
this.$router.push({path: url});
|
||||
goUrl(url) {
|
||||
this.$router.push({ path: url });
|
||||
},
|
||||
getPageData(){
|
||||
getPageData() {
|
||||
let is_lead = getStore('is_lead1');
|
||||
// console.log(is_lead)
|
||||
if(is_lead==0 || is_lead==null){
|
||||
if (is_lead == 0 || is_lead == null) {
|
||||
this.step = 1;
|
||||
}
|
||||
this.getData('/Home/home',{token:getStore('token')}).then(
|
||||
data=>{
|
||||
if(data.code==1){
|
||||
this.getData('/Home/home', { token: getStore('token') }).then(
|
||||
data => {
|
||||
if (data.code == 1) {
|
||||
this.company = data.data.company;
|
||||
//替换图片为https地址
|
||||
this.company.banner = this.company.banner.replace(
|
||||
"http://img",
|
||||
"https://imgs"
|
||||
);
|
||||
this.company.thumbnail = this.company.thumbnail.replace(
|
||||
"http://img",
|
||||
"https://imgs"
|
||||
);
|
||||
this.course = data.data.course;
|
||||
var courseName = this.course.map(v => {return v.name})
|
||||
//替换图片为https地址
|
||||
for (let index = 0; index < this.course.length; index++) {
|
||||
this.course[index].thumbnail = this.course[index].thumbnail.replace(
|
||||
"http://img",
|
||||
"https://imgs"
|
||||
);
|
||||
}
|
||||
var courseName = this.course.map(v => { return v.name })
|
||||
//证书需要课程名称,所以用localStorage缓存
|
||||
localStorage.setItem("courseName", JSON.stringify(courseName));
|
||||
}else{
|
||||
} else {
|
||||
Toast(data.msg);
|
||||
}
|
||||
},
|
||||
err=>{ })
|
||||
err => { })
|
||||
},
|
||||
nextLead(){
|
||||
if (this.step == 9){
|
||||
getLiveStreams() {
|
||||
this.getData("/Membervideo/getLiveStreams", { token: getStore("token") }).then(
|
||||
(data) => {
|
||||
if (data.code == 1) {
|
||||
if (data.data.length > 0) {
|
||||
this.haveLive = true;
|
||||
this.liveData = data.data;
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => {}
|
||||
);
|
||||
},
|
||||
goLive(room) {
|
||||
this.$router.push({ path: "/liveview?roomName=" + room });
|
||||
},
|
||||
nextLead() {
|
||||
if (this.step == 9) {
|
||||
this.step = 0
|
||||
// setStore('is_lead1', 1);
|
||||
}else{
|
||||
} else {
|
||||
this.step++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.all{
|
||||
.all {
|
||||
padding: 5rem 0 6.5rem;
|
||||
}
|
||||
.top{
|
||||
}
|
||||
|
||||
.top {
|
||||
width: 30rem;
|
||||
margin: auto;
|
||||
height: 18rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img{
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 15rem;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.daohang{
|
||||
}
|
||||
|
||||
.daohang {
|
||||
width: 90%;
|
||||
height: 6rem;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.daohang1{
|
||||
|
||||
.daohang1 {
|
||||
width: 25%;
|
||||
border-right: 1px solid #CCCCCC;
|
||||
font-size:1.35rem;
|
||||
&:last-child{
|
||||
font-size: 1.35rem;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.liuyan1{
|
||||
|
||||
.liuyan1 {
|
||||
border-right: none;
|
||||
}
|
||||
.tupian{
|
||||
|
||||
.tupian {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
.kecheng{
|
||||
}
|
||||
|
||||
.kecheng {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
.kecheng_biaoti{
|
||||
|
||||
.kecheng_biaoti {
|
||||
margin: 1rem 0;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.kecheng_biaotiL{
|
||||
font-size:1.56rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:bold;
|
||||
|
||||
.kecheng_biaotiL {
|
||||
font-size: 1.56rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: bold;
|
||||
}
|
||||
.kecheng_biaotiR{
|
||||
font-size:1.25rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:400;
|
||||
color:#C5C5C5;
|
||||
.fr{
|
||||
|
||||
.kecheng_biaotiR {
|
||||
font-size: 1.25rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: 400;
|
||||
color: #C5C5C5;
|
||||
|
||||
.fr {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.kechenginfo{
|
||||
.kechenginfo {
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #CCCCCC;
|
||||
>img{
|
||||
|
||||
>img {
|
||||
width: 13rem;
|
||||
height: 10rem;
|
||||
object-fit: cover;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
.kechenginfoR{
|
||||
|
||||
.kechenginfoR {
|
||||
width: 56%;
|
||||
height: 10rem;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
.kechenginfoR_bt{
|
||||
|
||||
.kechenginfoR_bt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size:1.67rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:400;
|
||||
font-size: 1.67rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.kechenginfoR_xbt{
|
||||
|
||||
.kechenginfoR_xbt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size:1.25rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:400;
|
||||
color:#5E5E5E;
|
||||
font-size: 1.25rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: 400;
|
||||
color: #5E5E5E;
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.kechenginfoR_bot{
|
||||
|
||||
.kechenginfoR_bot {
|
||||
height: 4rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -233,29 +296,52 @@
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.kechenginfoR_botL{
|
||||
|
||||
.kechenginfoR_botL {
|
||||
color: #CCCCCC;
|
||||
font-size:1.25rem;
|
||||
font-size: 1.25rem;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.yanjing{
|
||||
width:1.5rem;
|
||||
height:1.5rem;
|
||||
|
||||
.yanjing {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.kechenginfoR_botR{
|
||||
width:8rem;
|
||||
height:3rem;
|
||||
background:#f2f2f2;
|
||||
border-radius:2rem;
|
||||
|
||||
.kechenginfoR_botR {
|
||||
width: 8rem;
|
||||
height: 3rem;
|
||||
background: #f2f2f2;
|
||||
border-radius: 2rem;
|
||||
text-align: center;
|
||||
line-height: 3rem;
|
||||
color: #D4A458;
|
||||
font-size:1.25rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.cover {
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.live_box {
|
||||
background-color: white;
|
||||
width: 25rem;
|
||||
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);
|
||||
margin: 3rem;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
@ -32,7 +32,7 @@
|
||||
data-vidtype="1"
|
||||
style="position: relative; z-index: 1; height: 35em; width: 31em"
|
||||
scrolling="no"
|
||||
src="http://img.jiangtang360.com/1666599415204.mp4"
|
||||
src="https://imgs.jiangtang360.com/1666599415204.mp4"
|
||||
allowfullscreen="1"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
|
@ -30,7 +30,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
createQRCode(num) {
|
||||
axios.get("http://admin.jiangtang360.com/api/Card/getTime").then(
|
||||
axios.get("https://adminssl.jiangtang360.com/api/Card/getTime").then(
|
||||
(res) => {
|
||||
if (num === 1) {
|
||||
var redirect_uri = encodeURIComponent(this.redirect_uri)+res.data;
|
||||
|
@ -77,7 +77,7 @@ export default {
|
||||
var params = new URLSearchParams(query);
|
||||
//扫描二维码传入的时间
|
||||
var paramValue = params.get("nowTime");
|
||||
axios.get("http://admin.jiangtang360.com/api/Card/getTime").then(
|
||||
axios.get("https://adminssl.jiangtang360.com/api/Card/getTime").then(
|
||||
(res) => {
|
||||
if (
|
||||
Number(paramValue) + 1000 * 60 * 5 > res.data &&
|
||||
@ -99,7 +99,7 @@ export default {
|
||||
getOpenid(code) {
|
||||
let formData = new FormData();
|
||||
formData.append("code", code);
|
||||
axios.post("http://admin.jiangtang360.com/api/Signin/getOpenId", formData).then(
|
||||
axios.post("https://adminssl.jiangtang360.com/api/Signin/getOpenId", formData).then(
|
||||
(res) => {
|
||||
if (res.data.code == 200) {
|
||||
this.openid = res.data.data;
|
||||
@ -120,7 +120,7 @@ export default {
|
||||
let formData = new FormData();
|
||||
formData.append("openid", openid);
|
||||
axios
|
||||
.post("http://admin.jiangtang360.com/api/Signin/isTodaySignedIn", formData)
|
||||
.post("https://adminssl.jiangtang360.com/api/Signin/isTodaySignedIn", formData)
|
||||
.then(
|
||||
(res) => {
|
||||
if (res.status == 200) {
|
||||
@ -141,7 +141,7 @@ export default {
|
||||
formData.append("openid", this.openid);
|
||||
axios
|
||||
.post(
|
||||
"http://admin.jiangtang360.com/api/Signin/getLastSubmissionByOpenid",
|
||||
"https://adminssl.jiangtang360.com/api/Signin/getLastSubmissionByOpenid",
|
||||
formData
|
||||
)
|
||||
.then(
|
||||
@ -169,7 +169,7 @@ export default {
|
||||
formData.append("openid", this.openid);
|
||||
axios
|
||||
.post(
|
||||
"http://admin.jiangtang360.com/api/Signin/saveUserCheckinInfo",
|
||||
"https://adminssl.jiangtang360.com/api/Signin/saveUserCheckinInfo",
|
||||
formData
|
||||
)
|
||||
.then(
|
||||
|
@ -116,7 +116,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</mt-popup>
|
||||
<mt-popup v-model="popupVisible2" popup-transition="popup-fade" style="background-color: #5e5e5e; border-radius: 20px;">
|
||||
<mt-popup v-model="popupVisible2" popup-transition="popup-fade"
|
||||
style="background-color: #5e5e5e; border-radius: 20px;">
|
||||
<div style=" top: 5px; font-size: 20px; font-weight: bold; margin: 20px 0; color: white;">请选择要考试的课程</div>
|
||||
<div style=" margin: 30px; width: 250px;" v-for="(item, index) in courseData" :key="index">
|
||||
<mt-button @click="btn_courseID(item.id)" type="default" class=""
|
||||
@ -143,6 +144,7 @@ import { getStore } from "@/utils/storage";
|
||||
import { Toast } from "mint-ui";
|
||||
import { MessageBox } from "mint-ui";
|
||||
import WXTake from "../study/weixinTake.vue";
|
||||
import { decrypt } from "@/utils/decrypt";
|
||||
export default {
|
||||
name: "LiuYan",
|
||||
components: { Back, WXTake },
|
||||
@ -185,11 +187,11 @@ export default {
|
||||
token: getStore("token"),
|
||||
}).then((data) => {
|
||||
this.courseData = data.data;
|
||||
if(this.courseData.length === 1){
|
||||
if (this.courseData.length === 1) {
|
||||
this.btn_courseID(this.courseData[0].id);
|
||||
}else if(this.courseData.length > 1){
|
||||
} else if (this.courseData.length > 1) {
|
||||
this.popupVisible2 = true;
|
||||
}else {
|
||||
} else {
|
||||
Toast("暂无考试");
|
||||
}
|
||||
});
|
||||
@ -267,19 +269,20 @@ export default {
|
||||
var current_time_stamp = new Date().getTime();
|
||||
|
||||
//调用接口,获得正式考试的相关数据
|
||||
this.getData("/Question/getExamQuestions", {
|
||||
// this.getData("/Question/getExamQuestions", {
|
||||
this.getData("/Question/getExamQuestionsSecurity", {
|
||||
token: getStore("token"),
|
||||
course_id: this.courseID,
|
||||
}).then(
|
||||
(data) => {
|
||||
// console.log("data=>",data.data.exam_data);
|
||||
if (data.code == 1) {
|
||||
const questions = decrypt(data.data);
|
||||
let _this = this;
|
||||
this.is_start = 1;
|
||||
this.id = data.data.id;
|
||||
this.questions = data.data.question_data;
|
||||
this.current_question_data = data.data.question_data[0];
|
||||
this.exam_data = data.data.exam_data;
|
||||
this.id = questions.id;
|
||||
this.questions = questions.question_data;
|
||||
this.current_question_data = questions.question_data[0];
|
||||
this.exam_data = questions.exam_data;
|
||||
this.title = "正式考试";
|
||||
this.monishow = false;
|
||||
var count = 1;
|
||||
@ -873,6 +876,7 @@ export default {
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mask-content {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
@ -881,6 +885,7 @@ export default {
|
||||
width: 80%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.mask-content p {
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
|
@ -590,7 +590,7 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
console.log(this.loginInfo);
|
||||
// console.log(this.loginInfo);
|
||||
|
||||
this.start();
|
||||
},
|
||||
|
@ -2,12 +2,7 @@
|
||||
<div class="all">
|
||||
<back title="已学课程"></back>
|
||||
<div class="kecheng">
|
||||
<div
|
||||
class="kechenginfo"
|
||||
@click="goCourseDetail(item.id)"
|
||||
v-for="(item, index) in course"
|
||||
:key="index"
|
||||
>
|
||||
<div class="kechenginfo" @click="goCourseDetail(item.id)" v-for="(item, index) in course" :key="index">
|
||||
<img :src="item.thumbnail" alt="" />
|
||||
<div class="kechenginfoR">
|
||||
<p class="kechenginfoR_bt">{{ item.name }}</p>
|
||||
@ -41,7 +36,7 @@ export default {
|
||||
course: [],
|
||||
};
|
||||
},
|
||||
created() {},
|
||||
created() { },
|
||||
mounted() {
|
||||
this.getPageData();
|
||||
},
|
||||
@ -54,11 +49,17 @@ export default {
|
||||
(data) => {
|
||||
if (data.code == 1) {
|
||||
this.course = data.data;
|
||||
for (let index = 0; index < this.course.length; index++) {
|
||||
this.course[index].thumbnail = this.course[index].thumbnail.replace(
|
||||
"http://img",
|
||||
"https://imgs"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Toast(data.msg);
|
||||
}
|
||||
},
|
||||
(err) => {}
|
||||
(err) => { }
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -70,45 +71,53 @@ export default {
|
||||
padding: 5.5rem 0;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
|
||||
.kecheng_biaoti {
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.kecheng_biaotiL {
|
||||
font-size: 1.56rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.kecheng_biaotiR {
|
||||
font-size: 1.25rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: 400;
|
||||
color: #c5c5c5;
|
||||
|
||||
.fr {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.kechenginfo {
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
> img {
|
||||
|
||||
>img {
|
||||
width: 11rem;
|
||||
height: 15rem;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.kechenginfoR {
|
||||
width: 56%;
|
||||
height: 15rem;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.kechenginfoR_bt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@ -119,6 +128,7 @@ export default {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.kechenginfoR_xbt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@ -131,10 +141,12 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
height: 7rem;
|
||||
}
|
||||
|
||||
.kechenginfoR_end {
|
||||
font-size: 1rem;
|
||||
color: #0083ff;
|
||||
}
|
||||
|
||||
.kechenginfoR_bot {
|
||||
height: 4rem;
|
||||
display: flex;
|
||||
@ -144,6 +156,7 @@ export default {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kechenginfoR_botL {
|
||||
color: #cccccc;
|
||||
font-size: 1.25rem;
|
||||
@ -151,11 +164,13 @@ export default {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.yanjing {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.kechenginfoR_botR {
|
||||
width: 8rem;
|
||||
height: 3rem;
|
||||
|
@ -3,13 +3,14 @@
|
||||
<back title="所有课程"></back>
|
||||
<div class="kecheng">
|
||||
|
||||
<div class="kechenginfo" @click="goCourseDetail(item.id)" v-for="(item,index) in course" :key="index">
|
||||
<div class="kechenginfo" @click="goCourseDetail(item.id)" v-for="(item, index) in course" :key="index">
|
||||
<img :src="item.thumbnail" alt="">
|
||||
<div class="kechenginfoR">
|
||||
<p class="kechenginfoR_bt">{{item.name}}</p>
|
||||
<p class="kechenginfoR_xbt">{{item.description}}</p>
|
||||
<p class="kechenginfoR_bt">{{ item.name }}</p>
|
||||
<p class="kechenginfoR_xbt">{{ item.description }}</p>
|
||||
<div class="kechenginfoR_bot">
|
||||
<div class="kechenginfoR_botL"><img src="../../../static/img/yanjing.png" alt="" class="yanjing">{{item.page_view}}人</div>
|
||||
<div class="kechenginfoR_botL"><img src="../../../static/img/yanjing.png" alt=""
|
||||
class="yanjing">{{ item.page_view }}人</div>
|
||||
<div class="kechenginfoR_botR">进入课程</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -21,108 +22,121 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Back from "../common/Back";
|
||||
import CommonFooter from "../common/CommonFooter";
|
||||
import { getStore } from '@/utils/storage'
|
||||
import { Toast } from "mint-ui";
|
||||
export default {
|
||||
import Back from "../common/Back";
|
||||
import CommonFooter from "../common/CommonFooter";
|
||||
import { getStore } from '@/utils/storage'
|
||||
import { Toast } from "mint-ui";
|
||||
export default {
|
||||
name: "courseAll",
|
||||
components:{Back,CommonFooter},
|
||||
data(){
|
||||
return{
|
||||
course:[]
|
||||
components: { Back, CommonFooter },
|
||||
data() {
|
||||
return {
|
||||
course: []
|
||||
}
|
||||
},
|
||||
created(){
|
||||
created() {
|
||||
},
|
||||
mounted(){
|
||||
mounted() {
|
||||
this.getPageData();
|
||||
},
|
||||
methods:{
|
||||
goCourseDetail (id){
|
||||
this.$router.push({path: '/courseDetail?id=' + id});
|
||||
methods: {
|
||||
goCourseDetail(id) {
|
||||
this.$router.push({ path: '/courseDetail?id=' + id });
|
||||
},
|
||||
getPageData(){
|
||||
this.getData('/Home/getCourseList',{token:getStore('token'), type: 1}).then(
|
||||
data=>{
|
||||
if(data.code==1){
|
||||
getPageData() {
|
||||
this.getData('/Home/getCourseList', { token: getStore('token'), type: 1 }).then(
|
||||
data => {
|
||||
if (data.code == 1) {
|
||||
this.course = data.data;
|
||||
}else{
|
||||
for (let index = 0; index < this.course.length; index++) {
|
||||
this.course[index].thumbnail = this.course[index].thumbnail.replace('http://img', 'https://imgs');
|
||||
}
|
||||
} else {
|
||||
Toast(data.msg);
|
||||
}
|
||||
},
|
||||
err=>{ })
|
||||
err => { })
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.kecheng{
|
||||
.kecheng {
|
||||
padding: 5.5rem 0;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
.kecheng_biaoti{
|
||||
|
||||
.kecheng_biaoti {
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.kecheng_biaotiL{
|
||||
font-size:1.56rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:bold;
|
||||
|
||||
.kecheng_biaotiL {
|
||||
font-size: 1.56rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: bold;
|
||||
}
|
||||
.kecheng_biaotiR{
|
||||
font-size:1.25rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:400;
|
||||
color:#C5C5C5;
|
||||
.fr{
|
||||
|
||||
.kecheng_biaotiR {
|
||||
font-size: 1.25rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: 400;
|
||||
color: #C5C5C5;
|
||||
|
||||
.fr {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
.kechenginfo{
|
||||
|
||||
.kechenginfo {
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #CCCCCC;
|
||||
>img{
|
||||
|
||||
>img {
|
||||
width: 13rem;
|
||||
height: 10rem;
|
||||
object-fit: cover;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
.kechenginfoR{
|
||||
|
||||
.kechenginfoR {
|
||||
width: 56%;
|
||||
height: 10rem;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
.kechenginfoR_bt{
|
||||
|
||||
.kechenginfoR_bt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size:1.67rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:400;
|
||||
font-size: 1.67rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.kechenginfoR_xbt{
|
||||
|
||||
.kechenginfoR_xbt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size:1.25rem;
|
||||
font-family:MicrosoftYaHei;
|
||||
font-weight:400;
|
||||
color:#5E5E5E;
|
||||
font-size: 1.25rem;
|
||||
font-family: MicrosoftYaHei;
|
||||
font-weight: 400;
|
||||
color: #5E5E5E;
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.kechenginfoR_bot{
|
||||
|
||||
.kechenginfoR_bot {
|
||||
height: 4rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -131,28 +145,30 @@
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.kechenginfoR_botL{
|
||||
|
||||
.kechenginfoR_botL {
|
||||
color: #CCCCCC;
|
||||
font-size:1.25rem;
|
||||
font-size: 1.25rem;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.yanjing{
|
||||
width:1.5rem;
|
||||
height:1.5rem;
|
||||
|
||||
.yanjing {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.kechenginfoR_botR{
|
||||
width:8rem;
|
||||
height:3rem;
|
||||
background:#f2f2f2;
|
||||
border-radius:2rem;
|
||||
|
||||
.kechenginfoR_botR {
|
||||
width: 8rem;
|
||||
height: 3rem;
|
||||
background: #f2f2f2;
|
||||
border-radius: 2rem;
|
||||
text-align: center;
|
||||
line-height: 3rem;
|
||||
color: #D4A458;
|
||||
font-size:1.25rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
@ -161,6 +161,7 @@
|
||||
<div v-for="(item, index) in questions" :key="index" @click="goQ(index)" v-bind:class="{
|
||||
error: item.is_wrong == 1 && current_question != index,
|
||||
active: current_question == index,
|
||||
done: item.response != '' && !is_end
|
||||
}">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
@ -253,6 +254,7 @@ export default {
|
||||
}
|
||||
},
|
||||
toggleTabs(tabText) {
|
||||
// this.is_end = false;
|
||||
this.choose = tabText;
|
||||
if (tabText == 3) {
|
||||
let is_lead = getStore("is_lead1");
|
||||
@ -270,7 +272,21 @@ export default {
|
||||
(data) => {
|
||||
if (data.code == 1) {
|
||||
this.courseData = data.data;
|
||||
this.courseData.thumbnail = this.courseData.thumbnail.replace(
|
||||
"http://img",
|
||||
"https://imgs"
|
||||
);
|
||||
this.section = data.data.section;
|
||||
for (let index = 0; index < this.section.length; index++) {
|
||||
this.section[index].thumbnail = this.section[index].thumbnail.replace(
|
||||
"http://img",
|
||||
"https://imgs"
|
||||
);
|
||||
this.section[index].video_url = this.section[index].video_url.replace(
|
||||
"http://img",
|
||||
"https://imgs"
|
||||
);
|
||||
}
|
||||
this.changeSection(data.data.recent_section);
|
||||
} else {
|
||||
Toast(data.msg);
|
||||
@ -341,8 +357,8 @@ export default {
|
||||
// 定时器
|
||||
function timer() {
|
||||
// console.log("被调用了");
|
||||
|
||||
let curTime = document.getElementById("video").currentTime;
|
||||
|
||||
let apartTime = curTime - _this.current_sign;
|
||||
if (apartTime > 2 && _this.section[_this.choose_section_index].is_end != 1) {
|
||||
document.getElementById("video").currentTime = _this.current_sign;
|
||||
@ -411,7 +427,9 @@ export default {
|
||||
//最后2s调用结束接口
|
||||
// 获取视频总时长
|
||||
let duration = document.getElementById("video").duration;
|
||||
if (curTime >= (duration - 2)) {
|
||||
// console.log(curTime, _this.current_sign, duration, "-----")
|
||||
if (curTime >= (duration - 2) && _this.current_sign >= (duration - 10)) {
|
||||
|
||||
if (_this.last30sTimer && _this.section[_this.choose_section_index].is_end != 1) {
|
||||
// console.log("最后2秒我运行了11111111111111111");
|
||||
_this.last30sTimer = false;
|
||||
@ -431,22 +449,36 @@ export default {
|
||||
_this.current_sign = _this.record;
|
||||
Toast("从上次记录的地方开始播放");
|
||||
setTimeout(function () {
|
||||
// 先清除旧定时器
|
||||
if (_this.video_timer) {
|
||||
clearInterval(_this.video_timer);
|
||||
}
|
||||
_this.video_timer = setInterval(timer, 100);
|
||||
}, 1000);
|
||||
}
|
||||
function onPlayerEnded() {
|
||||
const duration = document.getElementById("video").duration;
|
||||
// console.log(duration, "视频播放完啦,真的吗?");
|
||||
|
||||
if (_this.current_sign >= duration - 10) {
|
||||
document
|
||||
.getElementById("video")
|
||||
.removeEventListener("ended", onPlayerEnded, false);
|
||||
_this.onPlayerEnded();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
console.log("视频播放进度:", _this.record);
|
||||
if (_this.record > 0.1) {
|
||||
_this.current_sign = _this.record;
|
||||
document.getElementById("video").addEventListener("play", plays, false);
|
||||
} else {
|
||||
// vd.play();
|
||||
// 先清除旧定时器
|
||||
if (_this.video_timer) {
|
||||
clearInterval(_this.video_timer);
|
||||
}
|
||||
_this.video_timer = setInterval(timer, 100);
|
||||
}
|
||||
// 监听视频播放完成
|
||||
@ -852,6 +884,16 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
// 确保离开路由时清除定时器
|
||||
// console.log("离开路由时清除定时器:", this.video_timer);
|
||||
|
||||
if (this.video_timer) {
|
||||
clearInterval(this.video_timer);
|
||||
this.video_timer = null; // 重置定时器变量
|
||||
}
|
||||
next();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.video_timer) {
|
||||
clearInterval(this.video_timer);
|
||||
@ -1103,6 +1145,10 @@ export default {
|
||||
&.error {
|
||||
background: red;
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: #1fff00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
1174
src/components/study/liveView.vue
Normal file
1174
src/components/study/liveView.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -191,7 +191,7 @@ export default {
|
||||
type: type,
|
||||
}).then(
|
||||
(data) => {
|
||||
if (data.code == 1) {
|
||||
if (data.code == 1 && data.data != null) {
|
||||
let _this = this;
|
||||
// console.log(data.data)
|
||||
this.questions = data.data;
|
||||
|
@ -74,7 +74,7 @@
|
||||
[
|
||||
{
|
||||
key:1,
|
||||
img:'../../../static/img/shezhi.png',
|
||||
img:'/static/img/shezhi.png',
|
||||
desc:'修改密码',
|
||||
border:false,
|
||||
link:'/user/changepwd'
|
||||
|
@ -23,7 +23,7 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.cardphotopath =
|
||||
"http://admin.jiangtang360.com/static/admin/cards/" +
|
||||
"https://adminssl.jiangtang360.com/static/admin/cards/" +
|
||||
this.$refs.getValue.value +
|
||||
".jpg" +
|
||||
"?ran=" +
|
||||
|
@ -151,7 +151,7 @@ export default {
|
||||
axios
|
||||
.post(
|
||||
// "/uploadcardimage",formData
|
||||
"http://admin.jiangtang360.com/api/card/uploadcardimage",
|
||||
"https://adminssl.jiangtang360.com/api/card/uploadcardimage",
|
||||
formData,
|
||||
config
|
||||
// Qs.stringify(formData)
|
||||
@ -188,13 +188,13 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.photopath =
|
||||
"http://admin.jiangtang360.com/static/admin/cards/" +
|
||||
"https://adminssl.jiangtang360.com/static/admin/cards/" +
|
||||
this.$refs.getValue.value +
|
||||
".jpg" +
|
||||
"?ran=" +
|
||||
Math.random();
|
||||
this.ImgObj.src =
|
||||
"http://admin.jiangtang360.com/static/admin/cards/" +
|
||||
"https://adminssl.jiangtang360.com/static/admin/cards/" +
|
||||
this.$refs.getValue.value +
|
||||
".jpg" +
|
||||
"?ran=" +
|
||||
|
@ -25,7 +25,6 @@ import "regenerator-runtime/runtime";
|
||||
let wx = require('weixin-js-sdk')
|
||||
Vue.config.productionTip = false
|
||||
|
||||
|
||||
// mintui模块
|
||||
Vue.component(Button.name, Button)
|
||||
Vue.component(Swipe.name, Swipe);
|
||||
|
@ -121,6 +121,11 @@ export default new Router({
|
||||
name:'wxerror',
|
||||
component: resolve => require(['../components/login/wxError.vue'], resolve)
|
||||
},
|
||||
{
|
||||
path:'/liveview',
|
||||
name:'liveview',
|
||||
component: resolve => require(['../components/study/liveView.vue'], resolve)
|
||||
},
|
||||
// 用户
|
||||
{
|
||||
path:'/user',
|
||||
|
29
src/utils/decrypt.js
Normal file
29
src/utils/decrypt.js
Normal file
@ -0,0 +1,29 @@
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
const SECRET_KEY = 'mIS*fo4T2ioFSw91Flaovn@ofiq89Fqe';
|
||||
|
||||
|
||||
export function decrypt(encryptedResponse) {
|
||||
// 1. Base64解码
|
||||
const iv = CryptoJS.enc.Base64.parse(encryptedResponse.iv)
|
||||
const payload = CryptoJS.enc.Base64.parse(encryptedResponse.payload)
|
||||
|
||||
// 2. 执行AES解密
|
||||
const decrypted = CryptoJS.AES.decrypt(
|
||||
{ ciphertext: payload },
|
||||
CryptoJS.enc.Utf8.parse(SECRET_KEY),
|
||||
{
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
}
|
||||
)
|
||||
|
||||
// 3. 转为UTF-8字符串并解析JSON
|
||||
try {
|
||||
return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8))
|
||||
} catch (e) {
|
||||
console.error("解密失败: ", e)
|
||||
return null
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ const state={
|
||||
const mutations={
|
||||
addInfo(state,loginInfo){
|
||||
state.loginInfo = loginInfo;
|
||||
state.loginInfo.header = state.loginInfo.header.replace('http://img','https://imgs');
|
||||
}
|
||||
|
||||
}
|
||||
|
231
webpack-upgrade-plan.md
Normal file
231
webpack-upgrade-plan.md
Normal file
@ -0,0 +1,231 @@
|
||||
# Webpack 3 升级到 Webpack 5 详细计划
|
||||
|
||||
## 升级背景
|
||||
当前项目使用 Webpack 3.6.0,需要升级到 Webpack 5 以支持 Node.js 18,并提升构建性能和安全性。
|
||||
|
||||
## 升级前准备
|
||||
1. 备份项目代码和 `package.json` 文件
|
||||
2. 确保已安装 Node.js 18+(当前计划使用版本)
|
||||
3. 了解 Webpack 3 与 Webpack 5 的主要差异
|
||||
|
||||
## 升级步骤
|
||||
|
||||
### 1. 卸载旧依赖
|
||||
```bash
|
||||
npm uninstall webpack webpack-cli webpack-dev-server webpack-merge extract-text-webpack-plugin
|
||||
```
|
||||
|
||||
### 2. 安装新依赖
|
||||
```bash
|
||||
npm install webpack@5.88.2 webpack-cli@5.1.4 webpack-dev-server@4.15.1 webpack-merge@5.9.0 mini-css-extract-plugin@2.7.6 --save-dev
|
||||
npm install html-webpack-plugin@5.5.3 --save-dev
|
||||
npm install vue-loader@15.10.1 --save-dev
|
||||
```
|
||||
|
||||
### 3. 更新 package.json 脚本
|
||||
修改 `package.json` 中的 scripts 部分:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "webpack serve --config build/webpack.dev.conf.js --host 0.0.0.0",
|
||||
"build": "node build/build.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 修改 Webpack 配置文件
|
||||
|
||||
#### 4.1 修改 webpack.base.conf.js
|
||||
- 添加 VueLoaderPlugin
|
||||
- 更新 module.rules 配置
|
||||
- 调整输出配置
|
||||
|
||||
#### 4.2 修改 webpack.dev.conf.js
|
||||
- 更新 devServer 配置
|
||||
- 修改插件配置
|
||||
|
||||
#### 4.3 修改 webpack.prod.conf.js
|
||||
- 替换 extract-text-webpack-plugin 为 mini-css-extract-plugin
|
||||
- 更新优化配置
|
||||
- 移除过时插件
|
||||
|
||||
### 5. 解决可能的兼容性问题
|
||||
- 处理 Babel 配置
|
||||
- 更新其他 loader 版本
|
||||
- 解决依赖冲突
|
||||
|
||||
## 主要变化点
|
||||
1. Webpack 5 内置更多功能,减少对第三方插件的依赖
|
||||
2. 开发服务器配置变化 (webpack-dev-server -> webpack serve)
|
||||
3. 提取 CSS 的插件变化 (extract-text-webpack-plugin -> mini-css-extract-plugin)
|
||||
4. 模块解析和优化配置的变化
|
||||
5. 缓存机制改进
|
||||
|
||||
## 可能遇到的问题及解决方案
|
||||
1. **Node.js 版本兼容问题**:确保使用 Node.js 18+
|
||||
2. **插件不兼容**:逐一替换为 Webpack 5 兼容版本
|
||||
3. **配置语法变化**:更新配置文件以符合 Webpack 5 语法
|
||||
4. **依赖冲突**:使用 npm ls 检查并解决冲突
|
||||
|
||||
## 测试计划
|
||||
1. 升级后运行 `npm run dev` 测试开发环境
|
||||
2. 运行 `npm run build` 测试生产构建
|
||||
3. 检查应用功能是否正常
|
||||
4. 验证构建输出文件大小和性能
|
||||
|
||||
## 实际升级问题及解决方案
|
||||
|
||||
在实际升级过程中,我们遇到了以下问题并成功解决:
|
||||
|
||||
### 1. Node.js 核心模块未定义问题 (Buffer, process)
|
||||
|
||||
**问题描述**:升级到 Webpack 5 后,浏览器中出现 `Buffer is not defined` 和 `process is not defined` 错误。
|
||||
|
||||
**原因**:Webpack 5 不再自动 polyfill Node.js 核心模块,需要手动添加。
|
||||
|
||||
**解决方案**:
|
||||
1. 安装必要的 polyfill 包:
|
||||
```bash
|
||||
npm install buffer@6.0.3 process@0.11.10 vm-browserify@1.1.2 --save
|
||||
```
|
||||
|
||||
2. 在 webpack.base.conf.js 中添加以下配置:
|
||||
```javascript
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
// ... 其他配置
|
||||
resolve: {
|
||||
fallback: {
|
||||
buffer: require.resolve('buffer/'),
|
||||
process: require.resolve('process/'),
|
||||
vm: require.resolve('vm-browserify')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
// ... 其他插件
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
process: 'process/browser'
|
||||
})
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Node.js 18 与 node-sass 兼容性问题
|
||||
|
||||
**问题描述**:在 Node.js 18 环境下,使用旧版本 node-sass 导致编译错误。
|
||||
|
||||
**原因**:node-sass 4.x 不兼容 Node.js 18。
|
||||
|
||||
**解决方案**:
|
||||
1. 升级 node-sass 到最新版本:
|
||||
```bash
|
||||
npm install node-sass@9.0.0 --save-dev --legacy-peer-deps
|
||||
```
|
||||
|
||||
2. 使用 `--legacy-peer-deps` 标志解决与 webpack 5 的依赖冲突。
|
||||
|
||||
> 注意:node-sass 已被弃用,建议未来迁移到 sass 或 sass-embedded。
|
||||
|
||||
### 3. pkcs7 包引擎版本不兼容警告
|
||||
|
||||
**问题描述**:安装依赖时出现 pkcs7 包要求 node@^0.10 的警告。
|
||||
|
||||
**原因**:项目依赖的 pkcs7 包版本过旧,与 Node.js 18 不兼容。
|
||||
|
||||
**解决方案**:
|
||||
- 目前可以忽略此警告,不影响项目运行。
|
||||
- 建议未来更新此包到兼容版本。
|
||||
|
||||
### 4. 依赖安装冲突
|
||||
|
||||
**问题描述**:安装新依赖时出现多个包版本冲突。
|
||||
|
||||
**原因**:不同包对同一个依赖的版本要求不一致。
|
||||
|
||||
**解决方案**:
|
||||
- 使用 `npm install --legacy-peer-deps` 命令安装依赖:
|
||||
```bash
|
||||
npm install --legacy-peer-deps
|
||||
```
|
||||
|
||||
### 5. webpack-dev-server 配置变化
|
||||
|
||||
**问题描述**:升级后开发服务器无法正常启动。
|
||||
|
||||
**原因**:webpack-dev-server 4.x 配置方式有较大变化。
|
||||
|
||||
**解决方案**:
|
||||
1. 更新 webpack.dev.conf.js 中的 devServer 配置:
|
||||
```javascript
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
port: 8080,
|
||||
client: {
|
||||
overlay: { warnings: false, errors: true },
|
||||
},
|
||||
historyApiFallback: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://sz-test.shengzhevictor.com',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. 更新 package.json 中的 dev 脚本:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "webpack serve --config build/webpack.dev.conf.js --host 0.0.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. resolve.fallback 配置引号问题
|
||||
|
||||
**问题描述**:Webpack 5 配置验证错误,提示 resolve.fallback 配置中的属性名不能带引号。
|
||||
|
||||
**原因**:Webpack 5 对配置格式有更严格的要求,resolve.fallback 中的属性名应该是标识符而非字符串。
|
||||
|
||||
**解决方案**:
|
||||
修改 webpack.base.conf.js 文件,移除 resolve.fallback 配置中各属性名的单引号:
|
||||
```javascript
|
||||
resolve: {
|
||||
fallback: {
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
buffer: require.resolve('buffer/'),
|
||||
process: require.resolve('process/'),
|
||||
vm: require.resolve('vm-browserify')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. devtool 配置格式问题
|
||||
|
||||
**问题描述**:Webpack 5 配置验证错误,提示 configuration.devtool 不符合模式 "^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map(-debugids)?$"
|
||||
|
||||
**原因**:Webpack 5 对 devtool 配置格式有严格要求,不再支持以 "#" 开头的格式。
|
||||
|
||||
**解决方案**:
|
||||
修改 config/index.js 文件,将 build.devtool 的值从 "#source-map" 改为 "source-map":
|
||||
```javascript
|
||||
build: {
|
||||
// ... 其他配置
|
||||
devtool: 'source-map',
|
||||
// ... 其他配置
|
||||
}
|
||||
```
|
||||
|
||||
## 升级总结
|
||||
|
||||
1. 成功将 Webpack 从 3.6.0 升级到 5.88.2
|
||||
2. 解决了 Node.js 18 兼容性问题,使项目能够在现代 Node.js 环境下运行
|
||||
3. 添加了必要的 Node.js 核心模块 polyfill
|
||||
4. 解决了依赖冲突和插件兼容性问题
|
||||
5. 更新了构建配置以符合 Webpack 5 语法
|
||||
|
||||
虽然升级过程中遇到了一些挑战,但通过逐一解决问题,最终成功完成了升级,为项目后续的维护和性能优化奠定了基础。
|
8743
yarn-error.log
8743
yarn-error.log
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user