提交 f5384052 authored 作者: 王鹏飞's avatar 王鹏飞

merge

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "dev",
"program": "${workspaceFolder}/server/server/distRun.js",
"env": {
"SERVER_PORT": "4006",
"NODE_ENV": "test"
}
},
{
"type": "node",
"request": "launch",
"name": "production",
"program": "${workspaceFolder}/server/server/distRun.js",
"env": {
"SERVER_PORT": "4006",
"NODE_ENV": "production"
}
}
]
}
......@@ -3,7 +3,8 @@ module.exports = {
url: 'https://wmp-learning2.ezijing.com/api',
apiBaseURL: '/',
others: {
url: '/login/is-login'
url: '/app/learn/course',
loginUrl: 'https://login2.ezijing.com/wmp/login/index'
},
webpack: {
externals: {
......
......@@ -3,22 +3,21 @@ module.exports = {
DesDir: '../client-dist',
apiBaseURL: '/',
others: {
url: '/login/is-login'
url: '/app/learn/course',
loginUrl: 'https://login.ezijing.com/wmp/login/index'
},
webpack: {
externals: {
'CKEDITOR': 'window.CKEDITOR',
'VideoJs': 'window.swfobject',
'Base64': 'window.Base64',
'md5': 'window.md5',
'regeneratorRuntime': 'window.regeneratorRuntime',
'$': 'window.$',
'Aliplayer': 'window.Aliplayer',
'AliPlayerComponent': 'window.AliPlayerComponent'
CKEDITOR: 'window.CKEDITOR',
VideoJs: 'window.swfobject',
Base64: 'window.Base64',
md5: 'window.md5',
regeneratorRuntime: 'window.regeneratorRuntime',
$: 'window.$',
Aliplayer: 'window.Aliplayer',
AliPlayerComponent: 'window.AliPlayerComponent'
// 'sentNotify': 'window.sentNotify',
}
},
ProvidePlugin: {
}
ProvidePlugin: {}
}
......@@ -3,23 +3,22 @@ module.exports = {
DesDir: '../client-dist',
apiBaseURL: '/',
others: {
url: '/login/is-login'
url: '/app/learn/course',
loginUrl: 'https://login2.ezijing.com/wmp/login/index'
},
isUploadStatic: false,
webpack: {
externals: {
'CKEDITOR': 'window.CKEDITOR',
'VideoJs': 'window.swfobject',
'Base64': 'window.Base64',
'md5': 'window.md5',
'regeneratorRuntime': 'window.regeneratorRuntime',
'$': 'window.$',
'Aliplayer': 'window.Aliplayer',
'AliPlayerComponent': 'window.AliPlayerComponent'
CKEDITOR: 'window.CKEDITOR',
VideoJs: 'window.swfobject',
Base64: 'window.Base64',
md5: 'window.md5',
regeneratorRuntime: 'window.regeneratorRuntime',
$: 'window.$',
Aliplayer: 'window.Aliplayer',
AliPlayerComponent: 'window.AliPlayerComponent'
// 'sentNotify': 'window.sentNotify',
}
},
ProvidePlugin: {
}
ProvidePlugin: {}
}
var semver = require('semver')
var requiredVersion = require('../package.json').engines.node
function checkNodeVersion (wanted, id) {
if (!semver.satisfies(process.version, wanted)) {
console.log('\x1b[91m%s\x1B[0m',
'You are using Node ' + process.version + ', but this version of ' + id +
' requires Node ' + wanted + '.\nPlease upgrade your Node version.'
)
process.exit(1)
}
}
checkNodeVersion(requiredVersion, '`node uploadAliyunCDN.js`')
const isDev = process.env.NODE_ENV
const RegStrs = require('./regExpStr.js')
let config = {
isDev: isDev,
......@@ -12,6 +13,8 @@ let config = {
isHttps: true,
isEnableToIphoneDebugger: false,
CDN_BASE: 'https://zws-imgs-pub.ezijing.com/',
CDN_DIR: 'static/build/learn-enterprise/',
isUploadStatic: false,
......@@ -19,10 +22,12 @@ let config = {
webpack: {}
}
config.RegStrs = RegStrs
let vueClientConfig = {}
config.domain = 'dev.ezijing.com'
config.domain = ''
if (config.isDev === 'development') {
config.url = 'http://dev.ezijing.com:12002'
config.url = 'http://' + config.domain + ':12002'
try {
vueClientConfig = require('../.config.dev.js')
} catch (error) {
......
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAgddAmdjWL+4JUXvmiR/lr9cbdoctEGLwdLpnIrRlL/oVKubZ
TrASed5pA2kHKUbbV9i64iNIzn668ziytivudRiuGL4wBTPdtlpkSty9ij0ZHZXe
23oWQ73fV3FNROp3ekLmIYHqo2ep5hhfq1A4Sey7IM1Z0A9OF6MV7Tgqm+8a6Fy4
eNbgx99f0n7nquGjYZ3RcnU0j6xzfQhOqOYmhk4flQqSx6Mb6sSmWM7V4/h/554L
QyJNV3ngNfT3ZFmAmhIZt7n+o3svzIqwghiZSj0K9jyE7zbSZjVUBEQztFUcfYgP
ZQfZFFsdgIkFSvR3cXIpIiN8oPf8xK6naApOXwIDAQABAoIBAHKlKKJvZvgdO1ca
ir1rT7jKu7IVu4GritvWtzhahrotHEGsYhZru3SmIJ1lQHB+4zAW6zS/qQrDtbkK
yCm4cq4sI5UvYWFGoa7g84tyi12jgyiva37ptv0Li0g/f9WpHePzbBYMC61K3CaS
QO8YPwtvHu/gwjvHN9IBK1wxatYRriAfR6CF2+pfZXhlq3nprc0z9YypExLg2Swa
ML1uQGLObDHAFKheVFYOgWOncMbG5eXNV9mdfzoUuc+mkENDtynQUTzI2ZM4OZFr
uMjto7y4RLFu463Rg0ijg2MXL4FbygzclaePNL7YBS6wed0Z8cE26FvbG5vM//Qc
qYK+WBECgYEA0FdJ7tPzgBvn4VsvBBdA8gmmFmmD/TJxehrxAwn/x0QkcZa6ngxJ
ScgcicJ2OSM049zId35KA4OV7aGUC6QKdG6JbL00Wqg9xWbsHiiB9XzV1iDghTug
35cDWhRN8SnMgBhQ85l5ps8NJuLS/fqLBK0/yeE54mkWKqvD4JS39KcCgYEAn4rl
Uc4PQ/xkgCKCTOCIYmzvDUuZcS0pA0MU/uJ/aQIwaJFanIehczs6Wg/Gb0YhKx0h
cMDuvZYY2XjbCic8eDNE7ED0hUHBAa8VAQZwS22ds/qNoooCO5UcHPRevenb/dIk
oKQTrI+4e5izQtuV3YLJMeH5Ba9bUP45iaZDt4kCgYEAnBGnmrc/46oD7HdoIwJg
bm/38TYd3+CXzUa9YO9uohFT05t8NMUzaYf0iOYZtfe/uSo9KfZ672L1P0wZIRdD
lbDwVXru6zK5A1V0b6scn49iiMOcLXJbsuLnaeVn9c7AGP0eNz2zOdhFG+oy8Htt
BJXcARktSYQ7TL/bPjNqEmUCgYB54o8XVCltcyEEk1igitkm+LoYmiz7vdibWWBs
6XUVMErzWDi1ZRj/A7ysmWisEhO88GBf18WMqWMKob4Vn6we2GxLYcRtGbLuKmgN
hHG97lyQ51XVW0IhauUzaa4HwOYEn8rDvxYYuyPhqOMqrL9tn0E+DrlEkpdc5Rvo
AVGyoQKBgGYvAeoKrdkfLUUnvRBIju9/bg5H6t1MY8lYi4WEnL+aPn056LV43LU9
+gF7+S7f8Er6WiJCCb7laYeDyC5adqnF/fI//4iNsarid86blirXfJMSfTG+GkZM
OEBHd/sX0IbRUt6MCxiG3Znmdpny3/MG4CegPMtdohQktQEAS2yJ
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIFiDCCBHCgAwIBAgIQAepAKb4wAxmrtof7BwPQSDANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
RFYgVExTIENBIC0gRzEwHhcNMTkxMTA4MDAwMDAwWhcNMjAxMTA3MTIwMDAwWjAa
MRgwFgYDVQQDEw9kZXYuZXppamluZy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQCB10CZ2NYv7glRe+aJH+Wv1xt2hy0QYvB0umcitGUv+hUq5tlO
sBJ53mkDaQcpRttX2LriI0jOfrrzOLK2K+51GK4YvjAFM922WmRK3L2KPRkdld7b
ehZDvd9XcU1E6nd6QuYhgeqjZ6nmGF+rUDhJ7LsgzVnQD04XoxXtOCqb7xroXLh4
1uDH31/Sfueq4aNhndFydTSPrHN9CE6o5iaGTh+VCpLHoxvqxKZYztXj+H/nngtD
Ik1XeeA19PdkWYCaEhm3uf6jey/MirCCGJlKPQr2PITvNtJmNVQERDO0VRx9iA9l
B9kUWx2AiQVK9HdxcikiI3yg9/zErqdoCk5fAgMBAAGjggJ0MIICcDAfBgNVHSME
GDAWgBRVdE+yck/1YLpQ0dfmUVyaAYca1zAdBgNVHQ4EFgQU28ndXe6qIDlhPWX5
+gzJoRhaQQowGgYDVR0RBBMwEYIPZGV2LmV6aWppbmcuY29tMA4GA1UdDwEB/wQE
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTAYDVR0gBEUwQzA3
BglghkgBhv1sAQIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
Y29tL0NQUzAIBgZngQwBAgEwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx
LmNydDAJBgNVHRMEAjAAMIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHYApLkJkLQY
WBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BAAAAFuSnTaJgAABAMARzBFAiEAtYCW
PLYE6pylBOsB1MmETgxGpYrG64Osn/XXyJlh+/UCIEnttMCBvNif4hpJfAQibP/Q
bJ9w0VYC59hi8Tmcf/mfAHcAXqdz+d9WwOe1Nkh90EngMnqRmgyEoRIShBh1loFx
RVgAAAFuSnTZSQAABAMASDBGAiEAsMgDWdSJ5d2jiXoNyxJ5FY7+3PET59vgvxi9
Eev9MwECIQC3sfR8sRWxJg82xH7lIA9sN87p7fLmr+KyhnuAK+2bcjANBgkqhkiG
9w0BAQsFAAOCAQEAOXMhz5dapVgYoLe23i+rEbBeO648c3cAO11qubqE0b5ie2bY
4DuatptwiLA47xfSVbFF0Y44cPL1b0zHe+Ki9TpcFP+TQ/+cPD2bPrqovI2uh8Qi
1RU7baLoYO9t7NxaPXh9RtRLUufJHas7HcWtLw/nPvVi+SuhgiiPytWdVM64dIPz
+nP9YY6wZhp4S/vNw5T7LARaw28xrEPzgCzWoXBUDyLB1slU3A2Uu+vl4lilcVeF
B/hl/75PWIdlxeRsD2V4TGCg796eL1BTVYEh7+mjvvaft+1/jwofKtGxg34YZHfl
6M22MuuP1pLviPZEE4ZlPXvltUV/Qq47LvZkTg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
-----END CERTIFICATE-----
const fs = require('fs')
const path = require('path')
const request = require('request')
const _conf = require('./config')
fs.writeFileSync(path.join(__dirname, _conf.domain + '.key'), '', { encoding: 'utf8', mode: 0o755 })
fs.writeFileSync(path.join(__dirname, _conf.domain + '.pem'), '', { encoding: 'utf8', mode: 0o755 })
request('https://ssl.godzyx.com/' + _conf.domain + '/' + _conf.domain + '.key?get=allow').pipe(fs.createWriteStream(path.join(__dirname, _conf.domain + '.key')))
request('https://ssl.godzyx.com/' + _conf.domain + '/' + _conf.domain + '.pem?get=allow').pipe(fs.createWriteStream(path.join(__dirname, _conf.domain + '.pem')))
/* 由于正则表达式,不能进行跟 gulpfile 中 去注释 在一起 */
module.exports = {
regExp_static: /('|"|`)[\.|\/]*?static\/(.*?\.(png|jpe?g|gif|svg|ico)(\?.*)?)\1/g
}
const _conf = require('./config')
module.exports = function (source) {
if (_conf.isDev !== 'development') {
return source
.replace(_conf.RegStrs.regExp_static, '$1' + _conf.CDN_BASE + _conf.CDN_DIR + 'static/$2$1')
} else {
return source
}
}
......@@ -25,6 +25,7 @@ let fileCount = 1
async function uploadFile (prefixPath, dirFileName) {
const upFilePath = prefixPath.replace(new RegExp(DIR_PATH, 'gi'), '') + path.basename(dirFileName)
try {
const upFilePath = prefixPath.replace(new RegExp(DIR_PATH, 'gi'), '') + path.basename(dirFileName)
const result = await client.put(prefixPath + path.basename(dirFileName), dirFileName, {
headers,
timeout: 600000
......
......@@ -7,6 +7,7 @@ const _conf = require('./config')
const $GLOBAL = {
isDev: _conf.isDev,
ResDir: _conf.ResDir,
'isEnableToIphoneDebugger': _conf.isEnableToIphoneDebugger,
templatePath: path.resolve(__dirname, '../' + _conf.HtmlPath),
icoPath: _conf.IcoPath,
......@@ -21,9 +22,12 @@ const $GLOBAL = {
EntryStaticPath: path.resolve(__dirname, '../' + 'static'),
OutputStaticPath: path.resolve(__dirname, '../' + _conf.DesDir + '/static'),
RegStrs: _conf.RegStrs,
webConf: {
'isDev': _conf.isDev,
'serverPort': process.env.SERVER_PORT || 8000,
'isEnableToIphoneDebugger': _conf.isEnableToIphoneDebugger,
'domain': _conf.domain,
'url': _conf.url,
'isHttps': _conf.isHttps,
......@@ -50,7 +54,8 @@ $GLOBAL.BaseConfig = {
'@api': path.resolve(__dirname, '../' + $GLOBAL.ResDir + '/api'),
'@action': path.resolve(__dirname, '../' + $GLOBAL.ResDir + '/action'),
'@tool': path.resolve(__dirname, '../' + $GLOBAL.ResDir + '/tool')
}
},
extensions: ['.js', '.json', '.jsx', '.vue']
},
module: {
rules: [
......@@ -60,6 +65,12 @@ $GLOBAL.BaseConfig = {
exclude: /node_modules/,
enforce: 'pre'
},
{
test: /\.(vue|js|jsx)|((sa|sc|c)ss)$/,
loader: './build/stringReplaceLoader.js',
exclude: /node_modules/,
enforce: 'pre'
},
{
test: /\.vue$/,
loader: 'vue-loader'
......@@ -69,10 +80,32 @@ $GLOBAL.BaseConfig = {
exclude: /(node_modules)|(dist)/,
loader: 'babel-loader',
options: {
presets: ['env', 'stage-2'],
presets: [[
'@babel/preset-env',
{
"targets": {
"browsers": [
"last 2 versions",
"ie >= 11"
],
},
corejs: '3.6.5',
useBuiltIns: 'usage'
}
]],
plugins: [
'transform-vue-jsx',
'syntax-dynamic-import'
'@babel/plugin-syntax-jsx',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
helpers: true,
regenerator: true,
useESModules: false
}
]
]
}
},
......
......@@ -9,7 +9,9 @@ const VueLoaderPlugin = require('vue-loader/lib/plugin')
const AutoPrefixer = require('autoprefixer')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const HtmlReplaceWebpackPlugin = require('html-replace-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const VconsoleWebpackPlugin = require('vconsole-webpack-plugin')
const $GLOBAL = require('./webpack.base.conf')
......@@ -26,9 +28,10 @@ if ($GLOBAL.isDev === 'development') {
port: $GLOBAL.webConf.serverPort,
disableHostCheck: false,
host: $GLOBAL.webConf.domain || 'localhost',
http2: false,
https: $GLOBAL.webConf.isHttps && {
key: fs.readFileSync(path.join(__dirname, '../static/cert/dev.ezijing.com.key')),
cert: fs.readFileSync(path.join(__dirname, '../static/cert/dev.ezijing.com.pem'))
key: fs.readFileSync(path.join(__dirname, $GLOBAL.webConf.domain + '.key')),
cert: fs.readFileSync(path.join(__dirname, $GLOBAL.webConf.domain + '.pem'))
},
overlay: {
errors: true
......@@ -77,7 +80,7 @@ if ($GLOBAL.isDev === 'development') {
}
},
{
loader: 'sass-loader', // 不再使用本身默认 node-sass,部分mac安装有问题。
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
......@@ -92,6 +95,10 @@ if ($GLOBAL.isDev === 'development') {
template: $GLOBAL.templatePath,
inject: true,
favicon: $GLOBAL.icoPath
}),
new VconsoleWebpackPlugin({
filter: [],
enable: $GLOBAL.isEnableToIphoneDebugger
})
]
})
......@@ -100,7 +107,7 @@ if ($GLOBAL.isDev === 'development') {
mode: 'production',
entry: {
app: $GLOBAL.EntryPath,
vendor: ['vue', 'vue-router']
vendor: ['vue', 'vue-router', 'vue-i18n']
},
module: {
rules: [{
......@@ -128,7 +135,7 @@ if ($GLOBAL.isDev === 'development') {
}
},
{
loader: 'sass-loader', // 不再使用本身默认 node-sass,部分mac安装有问题。
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
......@@ -152,6 +159,12 @@ if ($GLOBAL.isDev === 'development') {
collapseWhitespace: true
}
}),
new HtmlReplaceWebpackPlugin([
{
pattern: $GLOBAL.RegStrs.regExp_static,
replacement: '$1' + $GLOBAL.webConf.CDN_PATH + 'static/$2$1'
}
]),
new CopyWebpackPlugin([
{
from: $GLOBAL.EntryStaticPath,
......@@ -161,7 +174,11 @@ if ($GLOBAL.isDev === 'development') {
return content
}
}
])
]),
new VconsoleWebpackPlugin({
filter: [],
enable: $GLOBAL.isEnableToIphoneDebugger
})
],
optimization: {
runtimeChunk: {
......@@ -170,27 +187,19 @@ if ($GLOBAL.isDev === 'development') {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 400000, // 大于400kb 会再进行拆分,可以进行优化时,添加
maxSize: 400000,
minChunks: 1,
maxAsyncRequests: 5, // 按需加载块时并行请求的最大数量
maxInitialRequests: 3, // 初始页面加载时并行请求的最大数量
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: false,
cacheGroups: {
vendor: { // 将所有node_modules中模块 js打包到一起,并拆分
vendor: {
name: 'vendor',
chunks: 'initial',
priority: -10,
reuseExistingChunk: false,
test: /node_modules\/(.*)\.js/
},
// styles: { // 将所有node_modules中模块 css、scss打包到一起,并拆分,暂时没用,还在研究
// name: 'styles',
// test: /node_modules\/\.(sa|sc|c)ss$/,
// chunks: 'all',
// minChunks: 1,
// reuseExistingChunk: true,
// enforce: true
// }
}
}
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "@god/vue-client",
"version": "3.0.5",
"description": "enterprise - 学习系统",
"version": "3.0.14",
"description": "适应于公司全系统的纯客户端开发模型",
"main": "index.js",
"scripts": {
"lint": "eslint --ext .js --ext .jsx --ext .vue src/",
"lint:fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/",
"dev": "cross-env NODE_ENV=development SERVER_PORT=3005 webpack-dev-server --inline --progress --config build/webpack.client.conf.js",
"build:test": "cross-env NODE_ENV=test webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=test n use 8.11.3 ./build/uploadAliyunCDN.js",
"build:pro": "cross-env NODE_ENV=production webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=production n use 8.11.3 ./build/uploadAliyunCDN.js"
"dev": "npm run check:node && cross-env NODE_ENV=development node build/getSSL.js && cross-env NODE_ENV=development SERVER_PORT=3001 webpack-dev-server --inline --progress --config build/webpack.client.conf.js",
"build:test": "npm run check:node && cross-env NODE_ENV=test webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=test node ./build/uploadAliyunCDN.js",
"build:pro": "npm run check:node && cross-env NODE_ENV=production webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=production node ./build/uploadAliyunCDN.js",
"check:node": "node build/checkNodeVersion.js"
},
"repository": {
"type": "git",
......@@ -24,43 +25,51 @@
"node_modules/",
"assets/font-icons/"
],
"engines": {
"node": ">=8.9"
},
"devDependencies": {
"acorn": "^7.1.1",
"ali-oss": "^6.5.1",
"autoprefixer": "^9.7.5",
"babel-core": "^6.26.3",
"ali-oss": "^6.11.2",
"autoprefixer": "^9.8.6",
"babel-eslint": "^10.1.0",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^7.1.5",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-vue-jsx": "^3.7.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-2": "^6.24.1",
"clean-webpack-plugin": "^1.0.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.2",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^1.0.1",
"copy-webpack-plugin": "^5.1.2",
"css-loader": "^4.3.0",
"dart-sass": "^1.25.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-loader": "^3.0.4",
"eslint-plugin-html": "^6.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-html": "^6.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.0.4",
"file-loader": "^6.1.1",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.3.1",
"style-loader": "^1.1.3",
"url-loader": "^4.0.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2"
"sass-loader": "^10.0.3",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2",
"html-replace-webpack-plugin": "^2.5.6",
"request": "^2.88.2",
"semver": "^1.1.4",
"vconsole-webpack-plugin": "^1.5.2",
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/runtime-corejs3": "^7.11.2"
},
"dependencies": {
"axios": "^0.19.2",
......@@ -68,14 +77,14 @@
"element-ui": "^2.13.0",
"js-cookie": "^2.2.1",
"lodash": "^4.17.15",
"promise.prototype.finally": "^3.1.2",
"regenerator": "^0.14.4",
"qs": "^6.9.4",
"vue": "^2.6.11",
"vue-i18n": "^8.16.0",
"vue-loader": "^15.9.1",
"vue-meta-info": "^0.1.7",
"vue-router": "^3.1.6",
"vue-template-compiler": "^2.6.11",
"vuex": "^3.1.3"
"vuex": "^3.1.3",
"core-js": "^3.6.5"
}
}
......@@ -16,4 +16,6 @@ export default class AffairsAction extends BaseACTION {
updateAffairs (rid, obj) { return Affairs.updateAffairs(rid, obj).then(res => res) }
/* 重修图片上传 */
uploadFile (obj) { return Affairs.uploadFile(obj).then(res => res) }
/* 获取成绩 */
obtainReport (obj) { return Affairs.obtainReport(obj).then(res => res) }
}
......@@ -105,8 +105,22 @@ export default class CourseAction extends BaseACTION {
unit: item.lecturer_title || ''
})
}
const findChapter = function (id, list) {
for (const item of list) {
if (item.resource_id === id) {
return item
}
if (item.children && item.children.length) {
const found = findChapter(id, item.children)
if (found) {
return found
}
}
}
}
/* 课程内容 */
json.tabs1ChapterList = {
currentChapter: findChapter(cur.latest_play, data.chapters),
currentChapterId: cur.latest_play || '',
currentVideoProvider: cur.latest_play_type || '1',
course: cur.chapters.map((_, i) => {
......@@ -123,18 +137,19 @@ export default class CourseAction extends BaseACTION {
_homework.semester_id = data.semester_id
}
if (__.live && __.live.id) {
__.live.live_status = parseInt(__.live.live_status)
let str = ''
switch (__.live.live_status) {
case 0: str = '直播未开始'; break
case 1: str = '正在直播'; break
case 2: str = '直播结束'; break
case 101: str = '直播结束,视频剪辑中'; break // 录制开始
case 102: str = '直播结束,视频剪辑中'; break // 录制结束
case 103: str = '观看回放'; break
case 1: str = '直播未开始'; break
case 2: str = '正在直播'; break
case 3: str = '直播结束'; break
case 4: str = '即将开始'; break
case 5: str = '直播未开始'; break
case 11: str = '直播结束'; break
default: str = '直播未开始'
}
// 5分钟内显示“即将开始”,5~1小时内“N分钟后开始”,1~24小时内“N小时后开始”,1天以上“N天后开始”天就显示年月日
if (__.live.live_status === 0 && __.live.start_time) {
if (__.live.live_status === 1 && __.live.start_time) {
const time = (new Date(__.live.start_time).getTime() - new Date().getTime()) / 1000
if (time <= 5 * 60) {
str = '即将开始'
......@@ -146,8 +161,8 @@ export default class CourseAction extends BaseACTION {
str = parseInt(time / (24 * 60 * 60)) + '天后开始'
}
}
if (__.live.live_status === 103 && __.live.enable_record !== undefined && __.live.enable_record !== null && !__.live.enable_record) {
str = ''
if (__.live.live_status === 3 && __.live.enable_record && __.live.record_url) {
str = '观看回放'
}
__.live.statusStr = str
}
......@@ -199,10 +214,11 @@ export default class CourseAction extends BaseACTION {
title: '课程考试',
isUp: true,
chapters: [],
type: 'exam',
id: 'course_exam',
sid: sid,
cid: cid,
examId: cur.course_examination
examId: cur.course_examination,
type: 102
})
}
/* 课程考核 考核标准文案读取 */
......
......@@ -49,7 +49,7 @@ export default class OtherAction extends BaseACTION {
*/
uploadFile (obj) { return Other.uploadFile(obj).then(res => res) }
/**
* 调用个人信心 - 修改密码
* 修改密码
*/
updatePwd (obj) { return Other.updatePwd(obj).then(res => res) }
/**
......@@ -64,4 +64,8 @@ export default class OtherAction extends BaseACTION {
* 注册
*/
register (obj) { return Other.register(obj).then(res => res) }
/**
* 修改用户信息
*/
updateUser (obj) { return Other.updateUser(obj).then(res => res) }
}
......@@ -88,14 +88,12 @@ export default class PlayerAction extends BaseACTION {
}
}
if (__.live && __.live.id) {
__.live.live_status = parseInt(__.live.live_status)
let str = ''
switch (__.live.live_status) {
case 0: str = '直播未开始'; break
case 1: str = '正在直播'; break
case 2: str = '直播结束'; break
case 101: str = '直播结束,视频剪辑中'; break // 录制开始
case 102: str = '直播结束,视频剪辑中'; break // 录制结束
case 103: str = '观看回放'; break
default: str = '直播未开始'
}
// 5分钟内显示“即将开始”,5~1小时内“N分钟后开始”,1~24小时内“N小时后开始”,1天以上“N天后开始”天就显示年月日
......@@ -111,8 +109,8 @@ export default class PlayerAction extends BaseACTION {
str = parseInt(time / (24 * 60 * 60)) + '天后开始'
}
}
if (__.live.live_status === 103 && __.live.enable_record !== undefined && __.live.enable_record !== null && !__.live.enable_record) {
str = ''
if (__.live.live_status === 2 && __.live.enable_record && __.live.record_url) {
str = '观看回放'
}
__.live.statusStr = str
}
......@@ -122,6 +120,7 @@ export default class PlayerAction extends BaseACTION {
video_provider: (__.video && __.video.video_provider) || '',
time: (__.video && tools.convertTime.durationToTimeString(__.video.video_length)) || '',
name: __.name,
chapterId: __.id, // 需要chapterId 用来 提交 作业或问题 , 这个 chapterId 是 每个章节下 对应课程的 id,不是 章节id
type: __.type,
work_type: (__.homework && __.homework.work_type) || '',
homework: _homework,
......@@ -194,7 +193,8 @@ export default class PlayerAction extends BaseACTION {
json: json,
courseInfo: _res.files || [],
courseWork: _res.curriculum || {},
curJson: curJson
curJson: curJson,
rawResponse: _res
}
})
}
......@@ -339,51 +339,51 @@ export default class PlayerAction extends BaseACTION {
exam.id = _res.id
exam.title = _res.title
exam.score = {}
exam.radioList = _res.examination.radioList
for (let i = 0; i < exam.radioList.length; i++) {
exam.radioList[i].user_answer = ''
exam.radioList[i].right_answer = ''
exam.radioList[i].get_score = -1
}
exam.checkboxList = _res.examination.checkboxList
for (let i = 0; i < exam.checkboxList.length; i++) {
exam.checkboxList[i].user_answer = []
exam.checkboxList[i].right_answer = []
exam.checkboxList[i].get_score = -1
}
exam.shortAnswerList = _res.examination.shortAnswerList
for (let i = 0; i < exam.shortAnswerList.length; i++) {
exam.shortAnswerList[i].user_answer = ''
exam.shortAnswerList[i].get_score = -1
exam.shortAnswerList[i].attachments = []
exam.shortAnswerList[i].upload = {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true,
headers: {
tenant: 'sofia'
}
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M。</p>
</div>
`
exam.examination = _res.examination.map(exam => {
for (let i = 0; i < exam.radioList.length; i++) {
exam.radioList[i].user_answer = ''
exam.radioList[i].right_answer = ''
exam.radioList[i].get_score = -1
}
}
for (let i = 0; i < exam.checkboxList.length; i++) {
exam.checkboxList[i].user_answer = []
exam.checkboxList[i].right_answer = []
exam.checkboxList[i].get_score = -1
}
for (let i = 0; i < exam.shortAnswerList.length; i++) {
exam.shortAnswerList[i].user_answer = ''
exam.shortAnswerList[i].get_score = -1
exam.shortAnswerList[i].attachments = []
exam.shortAnswerList[i].upload = {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true,
headers: {
tenant: 'sofia'
}
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M。</p>
</div>
`
}
}
return exam
})
return exam
})
}
/* 获取考卷结果 */
getExamAnswer (cid, sid, eid) {
return Player.getExamAnswer(cid, sid, eid).then(_res => {
getExamAnswer (cid, sid, eid, obj) {
return Player.getExamAnswer(cid, sid, eid, obj).then(_res => {
if (_res.code) { return _res }
const exam = {}
let tmp = null
......@@ -393,46 +393,50 @@ export default class PlayerAction extends BaseACTION {
exam.score = _res.score
exam.isPublished = _res.is_published || ''
exam.submitted_time = _res.submitted_time
exam.radioList = _res.sheet.radioList
for (let i = 0; i < exam.radioList.length; i++) {
tmp = exam.radioList[i]
if (!tmp.user_answer) tmp.user_answer = ''
if (!tmp.right_answer) tmp.right_answer = ''
if (!tmp.get_score) tmp.get_score = -1
}
exam.checkboxList = _res.sheet.checkboxList
for (let i = 0; i < exam.checkboxList.length; i++) {
tmp = exam.checkboxList[i]
if (!tmp.user_answer || !tmp.user_answer.length) tmp.user_answer = []
if (!tmp.right_answer || !tmp.right_answer.length) tmp.right_answer = []
if (!tmp.get_score) tmp.get_score = -1
}
exam.shortAnswerList = _res.sheet.shortAnswerList
for (let i = 0; i < exam.shortAnswerList.length; i++) {
tmp = exam.shortAnswerList[i]
tmp.user_answer = Base64.decode(tmp.user_answer.replace(/ /gi, '+'))
if (!tmp.attachments || !tmp.attachments.length) tmp.attachments = []
tmp.upload = {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true,
headers: {
tenant: 'sofia'
}
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M。</p>
</div>
`
exam.examination = _res.sheet.map(exam => {
exam.radioList = exam.radioList || []
for (let i = 0; i < exam.radioList.length; i++) {
tmp = exam.radioList[i]
if (!tmp.user_answer) tmp.user_answer = ''
if (!tmp.right_answer) tmp.right_answer = ''
if (!tmp.get_score) tmp.get_score = -1
}
}
exam.checkboxList = exam.checkboxList || []
for (let i = 0; i < exam.checkboxList.length; i++) {
tmp = exam.checkboxList[i]
if (!tmp.user_answer || !tmp.user_answer.length) tmp.user_answer = []
// if (!tmp.right_answer || !tmp.right_answer.length) tmp.right_answer = []
tmp.right_answer = tmp.right_answer || []
if (!tmp.get_score) tmp.get_score = -1
}
exam.shortAnswerList = exam.shortAnswerList || []
for (let i = 0; i < exam.shortAnswerList.length; i++) {
tmp = exam.shortAnswerList[i]
tmp.user_answer = tmp.user_answer ? Base64.decode(tmp.user_answer.replace(/ /gi, '+')) : ''
if (!tmp.attachments || !tmp.attachments.length) tmp.attachments = []
tmp.upload = {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true,
headers: {
tenant: 'sofia'
}
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M。</p>
</div>
`
}
}
return exam
})
return exam
})
}
......
......@@ -29,4 +29,6 @@ export default class AffairsAPI extends BaseAPI {
updateAffairs = (rid, obj = {}) => this.post(`/api/lms/v2/lobby/processes/${rid}`, obj, { headers: { 'Content-Type': 'multipart/form-data' } })
/* 重修图片提交 */
uploadFile = (obj = {}) => this.post('/api/lms/v2/lobby/tools/upload', obj, { headers: { 'Content-Type': 'multipart/form-data' } })
/* 获取成绩单 */
obtainReport = (type) => this.get(`/api/lms/v2/lobby/havereport?type=${type}`, {})
}
import axios from 'axios'
import _ from 'lodash'
import qs from 'qs'
import { MessageBox, Message } from 'element-ui'
......@@ -38,24 +39,13 @@ export default class API {
const beforeFail = _config.beforeFail ? _config.beforeFail : this._reqFail
const headers = {
tenant: 'wmp',
version: window.G.VERSION
version: window.G.VERSION,
'Content-Type': 'application/x-www-form-urlencoded'
}
_config.headers = _.assignIn(_config.headers, headers)
_config.headers = _.assignIn(headers, _config.headers)
/* 判别 传输方式 */
if (
_config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
) {
let str = ''
const _obj = _config.data || _config.params
for (const key in _obj) {
str += key + '=' + _obj[key] + '&'
}
str = str.substr(0, str.length - 1)
if (_config.data) {
_config.data = str
} else {
_config.params = str
}
if (_config.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
_config.data = qs.stringify(_config.data)
}
if (_config.headers['Content-Type'] === 'multipart/form-data') {
let fr = new FormData() // eslint-disable-line
......@@ -66,10 +56,7 @@ export default class API {
_config.data = fr
}
/* 创建并根据参数发起请求 */
return this._axios(_config).then(
beforeSuccess.bind(this),
beforeFail.bind(this)
)
return this._axios(_config).then(beforeSuccess.bind(this), beforeFail.bind(this))
}
setConfirm(titleStr, btnStr, msgStr) {
......@@ -89,10 +76,9 @@ export default class API {
_vIn.$router.go(0)
} else {
_vIn.$router.push({
path:
'/login/index?rd=' +
encodeURIComponent(href.replace(/.*?\/\/.*?\//gi, '/'))
path: '/login/index?rd=' + encodeURIComponent(href.replace(/.*?\/\/.*?\//gi, '/'))
})
// window.location.href = `${webConf.others.loginUrl}'?rd=${encodeURIComponent(window.location.href)}`
}
}
......@@ -104,7 +90,6 @@ export default class API {
_reqSuccess(res) {
/* 单独处理考试接口 */
if (/util\/kaosx/gi.test(res.config.url)) {
console.log(res.data)
return res.data
}
const { data } = res
......@@ -158,16 +143,12 @@ export default class API {
/* 重新实现 get请求 */
get(url, data, config) {
return this._request(
_.assignIn({ url, method: 'GET', params: data }, config)
)
return this._request(_.assignIn({ url, method: 'GET', params: data }, config))
}
/* 重新实现 post请求 */
post(url, data, config) {
return this._request(
_.assignIn({ url, method: 'POST', data: data }, config)
)
return this._request(_.assignIn({ url, method: 'POST', data: data }, config))
}
/* 重新实现 put请求 */
......@@ -177,8 +158,6 @@ export default class API {
/* 重新实现 delete请求 */
delete(url, data, config) {
return this._request(
_.assignIn({ url, method: 'DELETE', params: data }, config)
)
return this._request(_.assignIn({ url, method: 'DELETE', params: data }, config))
}
}
......@@ -7,11 +7,7 @@ export default class OtherAPI extends BaseAPI {
* /user_center/get_user_info
*/
getInfo = () =>
this.get(
'/api/lms/user/check-access',
{},
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
this.get('/api/lms/user/check-access', {}, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
/**
* 当前登录用户,检测是否该系统有权限
......@@ -30,7 +26,7 @@ export default class OtherAPI extends BaseAPI {
getMyMsg = (obj = {}) => this.get('/api/lms/v2/education/message/my', obj, {})
/* 支持三方登录 */
examAutoLogin = (obj) =>
examAutoLogin = obj =>
this.post('/api/lms/util/kaosx', obj, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
......@@ -38,15 +34,14 @@ export default class OtherAPI extends BaseAPI {
/**
* 标记已读未读
*/
setMyMsg = (rid) => this.post(`/api/lms/v2/education/message/${rid}`, {})
setMyMsg = rid => this.post(`/api/lms/v2/education/message/${rid}`, {})
getNavMsg = (rid) =>
this.get(`/api/lms/v2/education/message/num?v=${new Date().getTime()}`, {})
getNavMsg = rid => this.get(`/api/lms/v2/education/message/num?v=${new Date().getTime()}`, {})
/**
* wmp标记已读未读
*/
setMsgWmp = (rid) => this.get(`/api/lms/v2/education/message/read/${rid}`, {})
setMsgWmp = rid => this.get(`/api/lms/v2/education/message/read/${rid}`, {})
/**
* 调用退出登录
......@@ -69,11 +64,9 @@ export default class OtherAPI extends BaseAPI {
* @param {[string]} obj.service 这里 一直是定值 h5.ezijing.com
*/
updatePwd = (obj = {}) =>
this.post(
'/api/usercenter/user/change-pwd-by-cookie',
_.assignIn(obj, { service: 'h5.ezijing.com' }),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
this.post('/api/usercenter/user/change-pwd-by-cookie', _.assignIn(obj, { service: 'h5.ezijing.com' }), {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
/**
* 个人信息 - 上传头像
......@@ -98,4 +91,9 @@ export default class OtherAPI extends BaseAPI {
this.post('/api/lms/user/register', obj, {
headers: { 'Content-Type': 'multipart/form-data' }
})
updateUser = (obj = {}) =>
this.post('/api/usercenter/user/update-user', obj, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
......@@ -65,7 +65,7 @@ export default class PlayerAPI extends BaseAPI {
* 文件提交
* @param {[object]} obj
*/
uploadFile = (obj = {}) => this.post('/api/sso/util/upload-file', obj, { headers: { 'Content-Type': 'multipart/form-data' } })
uploadFile = (obj = {}) => this.post('/api/lms/util/upload-file', obj, { headers: { 'Content-Type': 'multipart/form-data' } })
/**
* 提交课程 作业或问题
*/
......
......@@ -22,6 +22,7 @@ export default class Before {
path: '/login/index',
query: { rd: encodeURIComponent(to.fullPath) }
})
// window.location.href = `${webConf.others.loginUrl}?rd=${encodeURIComponent(window.location.href)}`
return
}
// 移动端
......
......@@ -6,7 +6,7 @@
<li class="tabs-hd">{{item.name}}</li>
<template v-for="(item1, index1) in item.arrItem">
<li v-bind:key="index1">
<div :class="['tab', (item.selectIndex == index1 ? 'on' : '')]" @click="selFindSelect" :data-index='index1' :data-i="index" :data-key='item.key' :data-val='item1.val'>{{item1.name}}</div>
<div :class="['tab', (item.selectIndex == index1 ? 'on' : '')]" @click="selFindSelect($event, item1)" :data-index='index1' :data-i="index" :data-key='item.key' :data-val='item1.val'>{{item1.name}}</div>
</li>
</template>
</ul>
......@@ -39,7 +39,7 @@ export default {
}
},
methods: {
selFindSelect (e) {
selFindSelect (e, item) {
const _data = e.currentTarget.dataset
const index = _data.index
const json = this.tapParam
......@@ -48,7 +48,8 @@ export default {
// json[i].isShow = false
const param = {
index: index,
id: _data.val
id: _data.val,
name: item.name
}
this.$emit('tapParam', param)
}
......
......@@ -10,7 +10,7 @@ export default {
name: 'VEditor',
props: {
value: { type: String },
disabled: { type: Boolean, default: false }
disabled: { type: Boolean, default: null }
},
data() {
return {
......@@ -18,12 +18,25 @@ export default {
ckEditor: null
}
},
watch: {
value(val) {
if (this.ckEditor && this.ckEditor.getData() !== val) {
this.ckEditor.setData(val)
}
},
disabled(val) {
if (this.ckEditor && this.ckEditor.instanceReady) {
this.ckEditor.setReadOnly(val)
}
}
},
methods: {
createEditor() {
const editor = (this.ckEditor = CKEDITOR.replace(this.textareaElementId, {
const config = {
height: 400,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
filebrowserImageUploadUrl: '/api/ck/form/ckeditor-upload',
fileTools_requestHeaders: { tenant: 'sofia' },
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: ['Source', '-', 'Save', 'NewPage', 'Preview'] },
......@@ -71,10 +84,18 @@ export default {
{ name: 'links', items: ['Link', 'Unlink', 'Anchor'] },
{ name: 'insert', items: ['Image', 'Table', 'HorizontalRule'] }
]
}))
}
if (this.disabled !== null) {
config.readOnly = this.disabled
}
const editor = (this.ckEditor = CKEDITOR.replace(
this.textareaElementId,
config
))
editor.on('instanceReady', () => {
const data = this.value
editor.fire('lockSnapshot')
editor.setData(data, {
......
......@@ -204,11 +204,11 @@ export default {
},
/* 退出登录 - 跳转方法 */
goOutLogin () {
// this.$router.push({ path: '/login/index' })
cAction.Other.outLogin()
.then(str => {
window.G.UserInfo = {}
this.$router.push({ path: '/login/index' })
// window.location.href = webConf.others.loginUrl
})
.catch(e => {
this.$message.error(e.message)
......@@ -288,6 +288,7 @@ export default {
width: 70px;
height: 70px;
border-radius: 50%;
object-fit: cover;
}
&:hover {
.set-pic {
......
<template>
<div class="upload">
<el-upload action :show-file-list="false" :http-request="httpRequest">
<slot></slot>
<el-button type="text" size="small" icon="el-icon-upload">点击上传</el-button>
<template v-slot:tip>
<div class="el-upload__tips">
<slot name="tip"></slot>
</div>
</template>
</el-upload>
<div class="file-list" v-if="value">
<div class="file-list-item">
<a :href="value" target="_blank">
<i class="el-icon-document"></i>
{{ fileName }}
</a>
<a :href="value" :download="fileName" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
</div>
</div>
</div>
<el-upload
class="avatar-uploader"
action
:accept="accept"
:show-file-list="showFileList"
:http-request="httpRequest"
v-bind="$attrs"
>
<slot>
<img v-if="value" :src="value" class="avatar" />
<i v-else class="el-icon-plus uploader-icon"></i>
</slot>
</el-upload>
</template>
<script>
......@@ -31,7 +20,9 @@ import cAction from '@action'
export default {
name: 'VUpload',
props: {
value: { type: String }
value: { type: String },
accept: { type: String, default: 'image/*' },
showFileList: { type: Boolean, default: false }
},
data() {
return {}
......@@ -52,30 +43,34 @@ export default {
.catch(error => {
console.log(error)
})
},
handleRemove() {
this.$emit('input', '')
}
}
}
</script>
<style lang="scss" scoped>
.file-list-item {
display: flex;
margin-bottom: 10px;
padding: 0 10px;
justify-content: space-between;
line-height: 30px;
background-color: #fff;
border-radius: 4px;
a {
text-decoration: none;
color: #333;
&:hover {
color: #b49441;
}
}
<style lang="scss">
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #b80037;
}
.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 220px;
height: 220px;
line-height: 220px;
text-align: center;
}
.avatar {
width: 220px;
height: 220px;
display: block;
object-fit: contain;
}
</style>
......@@ -27,8 +27,7 @@
<script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/static/common/runtime.js"></script>
<!-- 直接引入aliyun播放插件 JS -->
<script type="text/javascript" charset="utf-8" src="https://g.alicdn.com/de/prismplayer/2.8.8/aliplayer-min.js"></script>
<script type="text/javascript" charset="utf-8" src="https://player.alicdn.com/aliplayer/presentation/js/aliplayercomponents.min.js"></script>
<!-- 解决iframe嵌套,CC视频在safri中打开免登陆兼容问题 -->
<script type="text/javascript" charset="utf-8" src="https://player.alicdn.com/aliplayer/presentation/js/aliplayercomponents.min.js"></script> <!-- 解决iframe嵌套,CC视频在safri中打开免登陆兼容问题 -->
<script src="//view.csslcloud.net/js/_fix_.js"></script>
<script src="//view.csslcloud.net/js/jquery-1.9.0.min.js" type="text/javascript"></script>
<script src="//view.csslcloud.net/js/sdk/3.1.0/liveSDK.js" type="text/javascript"></script>
......
......@@ -14,10 +14,8 @@ import modules from './modules'
import createBefore from './components/beforeEnter'
import store from './store'
/* 兼容处理 start */
/* 处理低版本浏览器支持axios finally问题 */
require('promise.prototype.finally').shim()
/* 兼容处理 end */
import _ from 'lodash'
Vue.prototype.$_ = _
const VueRouterPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (to) {
......@@ -40,6 +38,7 @@ Vue.use(modules, { i18n })
window.G = Vue.prototype.$GLOBAL = {
VERSION: 'PC-1.0.0'
}
// window.Promise = Promise
/**
* 定义全局变量 - 用作全局事件发布订阅
* 使用 vue自带事件 $emit 和 $on 进行发布订阅
......@@ -56,5 +55,5 @@ window.G.$instance_vue = new Vue({
router,
i18n,
store,
render: (h) => h(App)
render: h => h(App)
}).$mount('#app')
......@@ -66,12 +66,12 @@ export default {
},
methods: {
loadAjax () {
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
// const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
api[this.params.request](this.params)
.then(json => {
this.discussList = json
})
.catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
.catch(e => { this.$message.error(e.message) }).finally(() => { /* loading.close() */ })
},
/**
* 跳转到对应 问题详情页
......
......@@ -8,7 +8,9 @@ const httpRequest = new BaseAPI(webConf)
* @param {string} courseId 课程ID
*/
export function getCourse(semesterId, courseId) {
return httpRequest.get(`/v2/education/courses/${semesterId}/${courseId}`)
return httpRequest.get(
`/api/lms/v2/education/courses/${semesterId}/${courseId}`
)
}
/**
......@@ -17,7 +19,7 @@ export function getCourse(semesterId, courseId) {
*/
export function getChapterVideo(vid) {
return httpRequest.post(
'/v2/education/video-streaming',
'/api/lms/v2/education/video-streaming',
{ vid },
{ headers: { 'Content-Type': 'application/json' } }
)
......@@ -29,29 +31,62 @@ export function getChapterVideo(vid) {
*/
export function getChapterVideoAliyun(vid) {
return httpRequest.post(
'/v2/education/aliyun-video-streaming',
'/api/lms/v2/education/aliyun-video-streaming',
{ vid },
{ headers: { 'Content-Type': 'application/json' } }
)
}
/**
* 获取答题信息
* 获取章节视频播放进度
* @param {string} semesterId 学期ID
* @param {string} resourseId 章节的资源ID
* @param {Object} params
*/
export function getChapterVideoProgress(semesterId, resourseId, params) {
return httpRequest.get(
`/api/lms/v2/education/video/${semesterId}/${resourseId}/device`,
params
)
}
/**
* 更新章节视频播放进度
* @param {Object} params
*/
export function updateChapterVideoProgress(params) {
return httpRequest.get('/api/lms/v2/analytics/upload-video', params)
}
/**
* 获取章节作业
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} resourseId 章节的资源ID
*/
export function getChapterExam(semesterId, courseId, resourseId) {
export function getChapterHomework(semesterId, courseId, resourseId) {
return httpRequest.get(
`/api/lms/v2/education/homeworks/${semesterId}/${courseId}/${resourseId}`
)
}
/**
* 获取提交作业截止时间
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} chapterId 章节ID
*/
export function getChapterHomeworkDeadline(semesterId, courseId, chapterId) {
return httpRequest.get(
`/v2/education/homeworks/${semesterId}/${courseId}/${resourseId}`
`/api/lms/v2/education/homeworks/${semesterId}/${courseId}/${chapterId}/deadline`
)
}
/**
* 提交考试
*/
export function sbumitChapterExam(params) {
return httpRequest.post('/v2/education/homeworks', params, {
export function sbumitChapterHomework(params) {
return httpRequest.post('/api/lms/v2/education/homeworks', params, {
headers: { 'Content-Type': 'application/json' }
})
}
......@@ -60,7 +95,7 @@ export function sbumitChapterExam(params) {
* 上传文件
*/
export function uploadFile(data) {
return httpRequest.post('/util/upload-file', data, {
return httpRequest.post('/api/lms/util/upload-file', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
......@@ -72,7 +107,7 @@ export function uploadFile(data) {
*/
export function getCourseWork(semesterId, courseId) {
return httpRequest.get(
`/v2/education/courses/${semesterId}/${courseId}/essay`
`/api/lms/v2/education/courses/${semesterId}/${courseId}/essay`
)
}
......@@ -83,7 +118,7 @@ export function getCourseWork(semesterId, courseId) {
*/
export function updateCourseWork(semesterId, courseId, data) {
return httpRequest.post(
`/v2/education/courses/${semesterId}/${courseId}/essay`,
`/api/lms/v2/education/courses/${semesterId}/${courseId}/essay`,
data,
{ headers: { 'Content-Type': 'multipart/form-data' } }
)
......@@ -95,7 +130,9 @@ export function updateCourseWork(semesterId, courseId, data) {
* @param {string} courseId 课程ID
*/
export function getCourseExam(semesterId, courseId) {
return httpRequest.get(`/v2/education/${semesterId}/${courseId}/examination`)
return httpRequest.get(
`/api/lms/v2/education/${semesterId}/${courseId}/examination`
)
}
/**
......@@ -106,7 +143,7 @@ export function getCourseExam(semesterId, courseId) {
*/
export function getCourseExamStatus(semesterId, courseId, examId) {
return httpRequest.get(
`/v2/education/${semesterId}/${courseId}/examination/${examId}/status`
`/api/lms/v2/education/${semesterId}/${courseId}/examination/${examId}/status`
)
}
......@@ -118,7 +155,7 @@ export function getCourseExamStatus(semesterId, courseId, examId) {
*/
export function submitCourseExam(semesterId, courseId, examId, data) {
return httpRequest.post(
`/v2/education/${semesterId}/${courseId}/examination/${examId}/sheet`,
`/api/lms/v2/education/${semesterId}/${courseId}/examination/${examId}/sheet`,
data,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
......@@ -130,8 +167,9 @@ export function submitCourseExam(semesterId, courseId, examId, data) {
* @param {string} courseId 课程ID
* @param {string} examId 试题ID
*/
export function getCourseExamResult(semesterId, courseId, examId) {
export function getCourseExamResult(semesterId, courseId, examId, params) {
return httpRequest.get(
`/v2/education/${semesterId}/${courseId}/examination/${examId}/sheet`
`/api/lms/v2/education/${semesterId}/${courseId}/examination/${examId}/sheet`,
params
)
}
<template>
<ul class="chapter-list">
<li class="chapter-item" v-for="item in data" :key="item.id">
<h4>{{item.name}}</h4>
<li class="chapter-item" v-for="item in chapters" :key="item.id">
<h4>{{ item.name }}</h4>
<ul class="chapter-item-list">
<li
v-for="subItem in item.children"
:key="subItem.id"
@click="onClick(subItem)"
:class="{'is-active': subItem.id === (active ? active.id : '')}"
:class="{ 'is-active': subItem.id === (active ? active.id : '') }"
>
<span class="chapter-item-list__name">{{subItem.name | showName(subItem.type)}}</span>
<span class="chapter-item-list__name">{{ subItem.name | showName(subItem) }}</span>
<i class="el-icon" :class="genIconClass(subItem.type)"></i>
</li>
</ul>
......@@ -20,7 +20,14 @@
<script>
export default {
props: {
data: { type: Array, default: () => [] },
// 课程详情接口返回的数据
data: {
type: Object,
default() {
return {}
}
},
chapters: { type: Array, default: () => [] },
// 当前选中的章节
active: {
type: Object,
......@@ -30,26 +37,13 @@ export default {
}
},
data() {
return {
otherList: [
{
name: '大作业及资料',
children: [
{ name: '课程大作业', id: 'course_work' },
{ name: '课程资料', id: 'course_info' },
{ name: '教学评估', id: 'teach_evaluation' }
]
}
]
}
},
computed: {
list() {
return this.data.concat(this.otherList)
}
return {}
},
filters: {
showName(name, type) {
showName(name, data) {
if (data.type === 5 && data.live) {
return `${name}(${data.live.start_time})`
}
return name
}
},
......@@ -63,20 +57,26 @@ export default {
return map[type] || 'el-icon-self-cc-book'
},
onClick(data) {
if (data.type === 1) {
return
}
// zoom直播
if (data.type === 8) {
window.open(data.live.record_url || data.live.join_url)
return
}
// 课程大作业
// if (data.id === 'course_work') {
// this.$router.push({ name: 'viewerCourseWork' })
// return
// }
// 课程资料
// if (data.id === 'course_info') {
// this.$router.push({ name: 'viewerCourseFile' })
// return
// }
this.$router.push({
name: 'viewerCourseChapter',
params: { id: data.id }
})
if (data.id === 'course_work' && !this.data.survey) {
this.$message('请先填写教学评估,然后完成大作业。')
return
}
// 教学评估
if (data.id === 'teach_evaluation') {
const { sid, cid } = this.$route.params
this.$router.push({ name: 'survey', params: { sid, cid } })
return
}
this.$router.push({ name: 'viewerCourseChapter', params: { id: data.id } })
}
}
}
......
<template>
<div class="course-viewer-aside">
<aside class="course-viewer-aside">
<el-tabs v-model="activeName">
<el-tab-pane label="章节" name="0">
<div class="tab-pane">
<aside-chapter :data="chapters" :active="active"></aside-chapter>
<aside-chapter :data="data" :chapters="chapters" :active="active"></aside-chapter>
</div>
</el-tab-pane>
<!-- <el-tab-pane label="讲义" name="1">
<div class="tab-pane">
<aside-lecture :data="ppts" :pptIndex="pptIndex" v-on="$listeners"></aside-lecture>
<aside-lecture :ppts="ppts" :pptIndex="pptIndex" v-on="$listeners"></aside-lecture>
</div>
</el-tab-pane> -->
</el-tabs>
</div>
</aside>
</template>
<script>
......@@ -21,6 +21,13 @@ import AsideLecture from './lecture.vue'
export default {
props: {
// 课程详情接口返回的数据
data: {
type: Object,
default() {
return {}
}
},
// 章节
chapters: { type: Array, default: () => [] },
// 讲义
......
<template>
<ul class="lecture-list">
<li
v-for="(item, index) in data"
v-for="(item, index) in ppts"
:key="item.id"
@click="onClick(index)"
:class="{'is-active': index === activeIndex}"
......@@ -16,7 +16,7 @@ export default {
props: {
// 当前选择的PPT
pptIndex: { type: Number, default: 0 },
data: { type: Array, default: () => [] }
ppts: { type: Array, default: () => [] }
},
data() {
return {
......
......@@ -10,7 +10,7 @@ export default {
name: 'VEditor',
props: {
value: { type: String },
disabled: { type: Boolean, default: false }
disabled: { type: Boolean, default: null }
},
data() {
return {
......@@ -18,12 +18,25 @@ export default {
ckEditor: null
}
},
watch: {
value(val) {
if (this.ckEditor && this.ckEditor.getData() !== val) {
this.ckEditor.setData(val)
}
},
disabled(val) {
if (this.ckEditor && this.ckEditor.instanceReady) {
this.ckEditor.setReadOnly(val)
}
}
},
methods: {
createEditor() {
const editor = (this.ckEditor = CKEDITOR.replace(this.textareaElementId, {
const config = {
height: 400,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
filebrowserImageUploadUrl: '/api/ck/form/ckeditor-upload',
fileTools_requestHeaders: { tenant: 'sofia' },
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: ['Source', '-', 'Save', 'NewPage', 'Preview'] },
......@@ -71,10 +84,17 @@ export default {
{ name: 'links', items: ['Link', 'Unlink', 'Anchor'] },
{ name: 'insert', items: ['Image', 'Table', 'HorizontalRule'] }
]
}))
}
if (this.disabled !== null) {
config.readOnly = this.disabled
}
const editor = (this.ckEditor = CKEDITOR.replace(
this.textareaElementId,
config
))
editor.on('instanceReady', () => {
const data = this.value
editor.fire('lockSnapshot')
editor.setData(data, {
......
<template>
<div>
<ul class="file-list" v-if="files.length">
<li class="file-list-item" v-for="file in files" :key="file.id">
<a :href="file.file_url" target="_blank">
<i class="el-icon-document"></i>
<div v-html="file.file_name"></div>
</a>
<span v-if="file.file_size">{{ file.file_size }}</span>
<a :href="file.file_url" :download="file.file_name" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
</li>
</ul>
<div class="empty" v-else>
<slot name="empty">暂无课程资料</slot>
</div>
</div>
</template>
<script>
export default {
name: 'FilePanel',
props: {
// 标题
title: { type: String, default: '课程资料' },
// 文件列表
files: { type: Array, default: () => [] }
}
}
</script>
<style lang="scss" scoped>
.file-list {
padding: 0;
}
.file-list-item {
display: flex;
font-size: 16px;
padding: 20px 30px;
margin-bottom: 10px;
background-color: #fff;
list-style: none;
border-radius: 32px;
justify-content: space-between;
a {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
white-space: nowrap;
&:hover {
color: #b49441;
}
::v-deep * {
margin: 0;
padding: 0;
}
}
}
.empty {
font-size: 18px;
line-height: 80px;
background-color: #fff;
text-align: center;
border-radius: 40px;
}
</style>
<template>
<div class="upload">
<el-upload action :show-file-list="false" :http-request="httpRequest">
<el-upload action :disabled="disabled" :show-file-list="false" :http-request="httpRequest">
<slot></slot>
<el-button type="text" icon="el-icon-upload">点击上传</el-button>
<template v-slot:tip>
......@@ -15,11 +15,23 @@
<i class="el-icon-document"></i>
{{ fileUrl | fileName }}
</a>
<a :href="fileUrl" :download="fileUrl | fileName" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
<div>
<a
href="javascript:;"
@click="handleRemove(index)"
style="margin-right:10px;"
v-if="!disabled"
>
<el-tooltip effect="dark" content="删除">
<i class="el-icon-delete"></i>
</el-tooltip>
</a>
<a :href="fileUrl" :download="fileUrl | fileName" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
</div>
</div>
</div>
</div>
......@@ -31,7 +43,8 @@ import * as api from '../../api'
export default {
name: 'VUpload',
props: {
value: { type: [String, Array] }
value: { type: [String, Array] },
disabled: { type: Boolean, default: false }
},
data() {
return {
......@@ -39,9 +52,18 @@ export default {
}
},
watch: {
value(value) {
if (value) {
this.fileList = Array.isArray(value) ? value : [value]
value: {
immediate: true,
handler(value) {
if (value) {
if (Array.isArray(value)) {
this.fileList = value.map(item => {
return item.url || item
})
} else {
this.fileList = [value]
}
}
}
}
},
......@@ -63,6 +85,10 @@ export default {
}
}
})
},
handleRemove(index) {
this.fileList.splice(index, 1)
this.$emit('input', Array.isArray(this.value) ? this.fileList : '')
}
}
}
......
<template>
<component
:is="currentCompoent"
:chapter="chapter"
v-bind="$attrs"
v-on="$listeners"
v-if="chapter"
:key="pid"
/>
<component :is="currentCompoent" :chapter="chapter" v-bind="$attrs" v-on="$listeners" v-if="chapter" :key="pid" />
</template>
<script>
// components
import ChapterPlayer from './player/ChapterPlayer.vue' // 章节视频
import ChapterPlayer from './player/chapterPlayer.vue' // 章节视频
import ChapterWork from './work/index.vue' // 章节作业
import ChapterExam from './work/chapterExam.vue' // 章节考试
import ChapterRead from './read/chapterRead.vue' // 章节资料
import ChapterLive from './live/chapterLive.vue' // 章节直播
import CourseWork from './work/courseWork.vue' // 课程大作业
......@@ -25,6 +19,7 @@ export default {
ChapterPlayer,
ChapterWork,
ChapterRead,
ChapterExam,
ChapterLive,
CourseWork,
CourseRead,
......@@ -44,7 +39,9 @@ export default {
2: 'ChapterPlayer', // 视频
3: 'ChapterWork', // 作业
4: 'ChapterRead', // 资料
5: 'ChapterLive', // 直播
5: 'ChapterLive', // CC直播
8: 'ChapterLive', // CC直播
9: 'ChapterExam', // 考试
99: 'CourseWork', // 课程大作业
100: 'CourseRead', // 课程资料
101: 'CourseExam' // 课程考试
......
<template>
<div style="width:100%;height:100%;">
<div style="width: 100%; height: 100%">
<div class="course-viewer-content" v-if="isLiveEnd && !hasRecord">
<div class="empty">直播已结束</div>
</div>
<iframe
:src="iframeUrl"
frameborder="0"
width="100%"
height="100%"
allow="autoplay;geolocation;microphone;camera;midi;encrypted-media;"
v-else
></iframe>
</div>
</template>
......@@ -37,19 +41,52 @@ export default {
nickName() {
return this.user.personal_name || '匿名'
},
live() {
const live = this.chapter.live || {}
live.live_status = parseInt(live.live_status)
return live
},
// 是否直播结束
isLiveEnd() {
return this.live.live_status === 2
},
// 是否有回放
hasRecord() {
// enable_record 0:不启用回放 1:开启回放
return this.live.enable_record === 1 && this.live.record_url
},
iframeUrl() {
const live = this.chapter.live
const liveStatus = live.live_status
live.viewer_name = live.viewer_name || this.nickName
if (liveStatus === 103 && live.enable_record === 1) {
// enable_record 0:不启用回放 1:开启回放
if (this.live.type === 5) {
return this.ccUrl
}
if (this.live.type === 8) {
return this.zoomUrl
}
},
// cc直播
ccUrl() {
const live = this.live
if (this.isLiveEnd && this.hasRecord) {
// 查看回放
return `https://view.csslcloud.net/api/view/callback?recordid=${live.record_id}&roomid=${live.room_id}&userid=${live.user_id}&autoLogin=true&viewername=${live.viewer_name}&viewertoken=${live.viewer_token}`
return live.record_url
} else {
// 直播
return `https://view.csslcloud.net/api/view/index?roomid=${live.room_id}&userid=${live.user_id}&autoLogin=true&viewername=${live.viewer_name}&viewertoken=${live.viewer_token}`
live.user_name = live.user_name || this.nickName
return `https://view.csslcloud.net/api/view/index?roomid=${live.room_id}&userid=${live.account_id}&autoLogin=true&viewername=${live.user_name}&viewertoken=${live.play_pass}`
}
},
// zoom直播
zoomUrl() {
return this.live.record_url || this.live.join_url
}
}
}
</script>
<style scoped>
.empty {
padding: 100px;
font-size: 30px;
text-align: center;
}
</style>
......@@ -5,17 +5,21 @@
<!-- 视频 -->
<video-player
:isSkip="isSkip"
:skipTime="skipTime"
:video="chatperResources.video"
@timeupdate="onTimeupdate"
@ready="onReady"
ref="videoPlayer"
></video-player>
</div>
<div class="player-column" v-if="pptVisible">
<!-- ppt -->
<ppt-player
:index="pptIndex"
:ppts="chatperResources.ppts"
@close="pptVisible = false"
@close="onPPTClose"
@fullscreen="onPPTFullscreen"
@videoSyncTime="onVideoSyncTime"
></ppt-player>
</div>
</div>
......@@ -30,6 +34,8 @@
</template>
<script>
import Cookies from 'js-cookie'
import { throttle } from 'lodash'
// api
import * as api from '../../api'
// components
......@@ -42,23 +48,47 @@ export default {
props: {
// 当前章节
chapter: { type: Object },
// 是否是PPT播放跳转
isSeek: { type: Boolean, default: false },
// PPT当前选中的索引
pptIndex: { type: Number, default: 0 }
},
data() {
// 是否跳过片头
const isSkip = window.localStorage.getItem('isSkip') === 'true'
return {
videoVisible: true,
pptVisible: false,
isSkip: false,
chatperResources: null
isSkip,
skipTime: 6,
chatperResources: null,
throttled: null,
throttleWait: 5, // 秒
progress: {
cpt: 0, // 当前播放时间
mpt: 0, // 当前播放最大时间
progress: 0, // 进度
pt: 0 // 累计观看时间
},
player: null,
watchedTime: 0,
watchedTimePoint: [] // 视频观看的时间点
}
},
watch: {
pptIndex(index) {
this.updateVideoCurrentTime(index)
this.isSeek && this.updateVideoCurrentTime(index)
}
},
computed: {
// 学期ID
sid() {
return this.$route.params.sid
},
// 课程ID
cid() {
return this.$route.params.cid
},
// 视频资源ID
resourceId() {
return this.chapter.resource_id
......@@ -95,25 +125,58 @@ export default {
// 始终跳过片头
toggleSkip() {
this.isSkip = !this.isSkip
window.localStorage.setItem('isSkip', this.isSkip)
},
// 关闭PPT
onPPTClose() {
this.pptVisible = false
this.videoVisible = true
},
// PPT全屏
onPPTFullscreen(value) {
this.videoVisible = !value
},
// 设置视频时间为当前PPT时间
onVideoSyncTime(time) {
this.player.seek(time)
},
// 播放器ready
onReady(player) {
this.player = player
// 跳转播放进度
if (this.progress.cpt) {
this.player.seek(this.progress.cpt)
}
},
// 当前播放时间更新
onTimeupdate(time) {
time = Math.floor(time)
const ppts = this.chatperResources.ppts || []
let index = this.chatperResources.ppts.findIndex(
item => item.ppt_point > time
)
let index = this.chatperResources.ppts.findIndex(item => item.ppt_point > time)
index = index !== -1 ? index - 1 : ppts.length - 1
this.$emit('change-ppt', index)
const durations = this.player.getDuration()
// 更新当前播放时间
this.progress.cpt = time
// 观看的最大点
this.progress.mpt = Math.max(time, this.progress.mpt)
const hasTimePoint = this.watchedTimePoint.includes(this.progress.cpt)
if (!hasTimePoint) {
this.watchedTimePoint.push(this.progress.cpt)
}
// 更新视频观看总时长
this.updateWatchTime(time)
// 更新视频进度,10秒更新一次
if (this.throttled) {
this.throttled(time, durations)
} else {
this.throttled = throttle(this.updateChapterVideoProgress, this.throttleWait * 1000, { leading: false })
}
},
// 更新视频当前播放时间
updateVideoCurrentTime() {
const player = this.$refs.videoPlayer.player
const ppt = this.chatperResources.ppts[this.pptIndex]
ppt && player.seek(ppt.ppt_point) // 增加2秒
ppt && this.player.seek(ppt.ppt_point) // 增加2秒
},
// 获取章节视频详情
getChapterVideo() {
......@@ -125,14 +188,79 @@ export default {
})
} else {
api.getChapterVideo(this.resourceId).then(response => {
this.chatperResources = response
Array.isArray(response.ppts) && this.$emit('pptupdate', response.ppts)
let { video, audio, ppts } = response
video = video.reduce(
(result, item) => {
if (item.quality === '10') {
result.LD = item.playurl
}
if (item.quality === '20') {
result.SD = item.playurl
}
return result
},
{ LD: '', SD: '' }
)
this.chatperResources = { video, audio, ppts }
Array.isArray(ppts) && this.$emit('pptupdate', ppts)
})
}
},
// 获取章节视频进度
getChapterVideoProgress() {
api
.getChapterVideoProgress(this.sid, this.resourceId, {
device_id: Cookies.get('_idt')
})
.then(response => {
this.progress = response
// 跳转播放进度
if (this.player && response.cpt) {
this.player.seek(response.cpt)
}
})
},
// 更新章节视频进度
updateChapterVideoProgress(time, durations) {
// 登录用户信息
const user = window.G.UserInfo
const params = {
sid: user.student_info.id,
uid: user.id,
d: Cookies.get('_idt'),
i: Cookies.get('_idt'),
c: this.cid, // 课程ID
s: this.sid, // 学期ID
v: this.resourceId, // 视频资源ID
_p: this.progress.pt, // 累计时间
_m: this.progress.mpt, // 当前播放最大时间
_c: this.progress.cpt, // 当前播放位置
ps: this.watchedTimePoint.join(',') // 播放时,统计帧
}
api.updateChapterVideoProgress(params)
// 清空已经上传过的观看时间点
this.watchedTimePoint = []
},
// 更新观看总时长
updateWatchTime(time) {
if (time === this.watchedTime) {
return
}
this.watchedTime = time
// 增加跳过片头时间
if (this.isSkip && !this.progress.pt) {
this.progress.pt = this.skipTime + 20
}
// 默认增加时间
this.progress.pt = this.progress.pt || 20
this.progress.pt++
}
},
beforeMount() {
// 获取视频
this.getChapterVideo()
// 获取视频进度
this.getChapterVideoProgress()
}
}
</script>
......@@ -148,6 +276,7 @@ export default {
.player-main {
display: flex;
flex: 1;
overflow: hidden;
}
.player-column {
flex: 1;
......
......@@ -19,10 +19,18 @@
<span>{{ppts.length}}</span>
</div>
<div class="ppt-player-controls__tools">
<i :class="['el-icon-self-xuexiao', (currentSync ? 'active' : '')]" @click="onToggleSync"></i>
<i class="el-icon-self-quanping" @click="fullscreen"></i>
<i class="el-icon-self-shipin" @click="setVideoTime"></i>
<i class="el-icon-self-guanbi" @click="$emit('close')"></i>
<el-tooltip content="PPT同步视频播放">
<i :class="['el-icon-self-xuexiao', (isSync ? 'active' : '')]" @click="onToggleSync"></i>
</el-tooltip>
<el-tooltip content="放大PPT">
<i class="el-icon-self-quanping" @click="fullscreen"></i>
</el-tooltip>
<el-tooltip content="切换视频到当前PPT页">
<i class="el-icon-self-shipin" @click="setVideoTime"></i>
</el-tooltip>
<el-tooltip content="关闭PPT">
<i class="el-icon-self-guanbi" @click="$emit('close')"></i>
</el-tooltip>
</div>
</div>
</template>
......@@ -34,20 +42,21 @@ export default {
name: 'ppt-player',
props: {
ppts: { type: Array },
index: { type: Number, default: 0 },
isSync: { type: Boolean, default: false }
index: { type: Number, default: 0 }
},
data() {
return {
currentIndex: this.index,
currentSync: this.isSync,
isSync: true,
isFullscreen: false
}
},
watch: {
index: {
handler(value) {
this.currentIndex = value
if (this.isSync) {
this.currentIndex = value
}
}
}
},
......@@ -67,20 +76,18 @@ export default {
},
prev() {
this.currentIndex = this.getIndex(this.currentIndex - 1)
this.currentSync = false
this.isSync = false
},
next(e) {
this.currentIndex = this.getIndex(this.currentIndex + 1)
this.currentSync = false
this.isSync = false
},
onToggleSync(e) {
this.currentSync = !this.currentSync
this.currentIndex = this.currentSync
? this.currentIndex
: this.currentIndex
this.isSync = !this.isSync
},
setVideoTime(e) {
this.$emit('onVideoSyncTime', this.ppts[this.currentIndex].ppt_point)
this.isSync = true
this.$emit('videoSyncTime', this.ppts[this.currentIndex].ppt_point)
},
// 全屏
fullscreen() {
......
......@@ -5,7 +5,11 @@
<script>
export default {
name: 'VideoPlayer',
props: { isSkip: Boolean, video: Object },
props: {
isSkip: Boolean,
video: Object,
autoplay: { type: Boolean, default: false }
},
data() {
return { player: null }
},
......@@ -13,34 +17,40 @@ export default {
createPlayer() {
const _this = this
const { FD, LD, SD } = this.video
/*
"OD" : "原画"
"FD" : "流畅"
"LD" : "标清"
"SD" : "高清"
"HD" : "超清"
"2K" : "2K"
"4K" : "4K"
*/
this.player = new Aliplayer(
{
id: 'player',
source: JSON.stringify({ FD, LD, SD }),
width: '100%',
height: '100%',
autoplay: true,
autoplay: this.autoplay,
isLive: false,
controlBarVisibility: 'always',
components: [
{
name: 'QualityComponent',
type: AliPlayerComponent.QualityComponent
}
]
definition: 'FD,LD,SD',
defaultDefinition: 'LD',
useHlsPluginForSafari: true
},
function(player) {
player.on('sourceloaded', function(params) {
const paramData = params.paramData
const desc = paramData.desc
const definition = paramData.definition
player
.getComponent('QualityComponent')
.setCurrentQuality(desc, definition)
player.on('ready', function() {
// 跳过片头
_this.isSkip && player.seek(6)
_this.$emit('ready', player)
})
player.on('timeupdate', function(event) {
_this.$emit('timeupdate', player.getCurrentTime())
})
player.on('error', function(event) {
console.log(event)
})
}
)
}
......
......@@ -8,7 +8,7 @@
<script>
// components
import Container from '../common/container.vue'
import FileList from '../common/fileList.vue'
import FileList from './fileList.vue'
// 章节阅读资料
export default {
......
......@@ -8,7 +8,7 @@
<script>
// components
import Container from '../common/container.vue'
import FileList from '../common/fileList.vue'
import FileList from './fileList.vue'
// 课程阅读资料
export default {
......
......@@ -4,9 +4,9 @@
<li class="file-list-item" v-for="file in files" :key="file.id">
<a :href="file.file_url" target="_blank">
<i class="el-icon-document"></i>
{{ file.file_name }}
<div v-html="file.file_name"></div>
</a>
<span v-if="file.file_size">{{ file.file_size }}</span>
<!-- <span v-if="file.file_size">{{ file.file_size }}</span> -->
<a :href="file.file_url" :download="file.file_name" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
......@@ -46,11 +46,18 @@ export default {
border-radius: 32px;
justify-content: space-between;
a {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
white-space: nowrap;
&:hover {
color: #b49441;
}
::v-deep * {
margin: 0;
padding: 0;
}
}
}
.empty {
......
<template>
<container :title="chapter.name" v-loading="loading">
<template v-slot:header-aside v-if="isSubmited">正确率:{{detail.score}}%</template>
<container :title="detail.paper_title" v-loading="loading">
<template v-slot:header-aside v-if="isExamComplete">分数:{{exam.score.total}}</template>
<div class="exam">
<div class="exam-form">
<el-form :disabled="isSubmited">
<exam-item
v-for="(item, index) in unorderedQuestions"
:index="index"
:type="item.question_type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
<div class="exam-buttons">
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" @click="onSubmit">{{submitText}}</el-button>
</el-tooltip>
</div>
</el-form>
</div>
<template v-if="isSubmited && !isExamComplete">
<div class="no-exam">试卷批改中,请耐心等待</div>
</template>
<template v-else>
<!-- 考试期间,未开始考试 -->
<div class="exam-welcome" v-if="!isStartExam">
<div v-if="detail.paper_deadline">考试截止时间:{{detail.paper_deadline}}</div>
<el-button
type="primary"
:disabled="!isExamTime"
@click="onStartExam"
>{{startExamButtonText}}</el-button>
</div>
<!-- 考试试题 -->
<div class="exam-form" v-if="isStartExam">
<el-form :disabled="isSubmited">
<template v-for="items in questions">
<exam-item
v-for="(item, index) in items"
:index="index"
:type="item.type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
</template>
<div class="exam-buttons">
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" :loading="submitLoading" @click="onSubmit">{{submitText}}</el-button>
</el-tooltip>
</div>
</el-form>
</div>
</template>
</div>
</container>
</template>
<script>
// libs
import { shuffle } from 'lodash'
import Base64 from 'Base64'
// components
import Container from '../common/container.vue'
import ExamItem from './examItem.vue'
......@@ -44,23 +61,34 @@ export default {
default() {
return {}
}
},
// 课程详情接口返回的数据
data: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
loading: false,
detail: null,
questions: [], // 问题列表
startTime: new Date().getTime(), // 进入时间
messageInstance: null
detail: {},
questions: [],
messageInstance: null,
exam: {},
isStartExam: false, // 是否开始考试
autoSubmitTimer: null, // 自动提交定时器
submitLoading: false
}
},
watch: {
chapter: {
immediate: true,
handler(data) {
this.questions = data.homework
? this.genQuenstions(data.homework.questions)
this.detail = data.paper
this.questions = data.paper
? this.genQuestions(data.paper.examination)
: []
}
}
......@@ -78,19 +106,28 @@ export default {
pid() {
return this.$route.params.id
},
// 资源ID
resourceId() {
return this.chapter.resource_id
// 是否是考试时间
isExamTime() {
if (!this.detail.paper_deadline) {
return true
}
// 大于开始时间,小于结束时间
const endTime = +new Date(this.exam.paper_deadline)
const currentTime = new Date().getTime()
return currentTime < endTime
},
// 考试按钮
startExamButtonText() {
return this.isExamTime ? '开始考试' : '考试结束'
},
// 打乱顺序的问题列表
unorderedQuestions() {
const ids = this.questions.map(item => item.id)
const sortIds = shuffle(ids)
return sortIds.map(id => this.questions.find(item => item.id === id))
// 考试完成
isExamComplete() {
// 考试完成,批改完成并且公布成绩
return this.exam.is_published === 1 && this.exam.type === 2
},
// 是否提交
isSubmited() {
return this.detail ? !!this.detail.work_contents : false
return this.exam.type === 1 || this.exam.type === 2
},
// 提交按钮文本
submitText() {
......@@ -98,86 +135,78 @@ export default {
}
},
methods: {
// 获取测试答题详情
getDetail() {
this.loading = true
api
.getChapterExam(this.sid, this.cid, this.resourceId)
.then(response => {
this.detail = Array.isArray(response) ? null : response
if (this.detail) {
const parseAnswers = JSON.parse(this.detail.work_contents)
// 设置答案
this.questions = this.questions.map(item => {
const found = parseAnswers.find(
answer => answer.question_id === item.id
)
if (found) {
const selectedIds = found.options.reduce((result, item) => {
item.selected && result.push(item.id)
return result
}, [])
item.user_answer =
item.question_type === 2 ? selectedIds : selectedIds[0]
}
return item
})
this.questions = this.genQuenstions(this.questions)
}
})
.finally(() => {
this.loading = false
})
// 开始考试
onStartExam() {
this.isStartExam = true
// 自动提交答题
this.autoSubmit()
},
// 组装问题数据
genQuenstions(list) {
genQuestions(list) {
if (!list) {
return []
}
return list.map(item => {
let temp = null
if (item.question_type === 1) {
// 单选
temp = {
return list.map(data => {
let { radioList, checkboxList, shortAnswerList } = data
// 单选
radioList = radioList.map(item => {
const temp = {
type: 1,
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
} else if (item.question_type === 2) {
// 多选
temp = {
return Object.assign({}, item, temp)
})
// 多选
checkboxList = checkboxList.map(item => {
const temp = {
type: 2,
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
} else if (item.question_type === 3) {
// 简答
temp = {
return Object.assign({}, item, temp)
})
// 问答
shortAnswerList = shortAnswerList.map(item => {
const temp = {
type: 3,
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer)
? Base64.decode(item.user_answer.replace(/ /gi, '+'))
: '',
attachments: item.attachments || ''
attachments: item.attachments || []
}
}
}
return Object.assign(
{},
item,
{
content: item.question_content,
options: item.question_options
? JSON.parse(item.question_options)
: []
},
temp
)
return Object.assign({}, item, temp)
})
return [...radioList, ...checkboxList, ...shortAnswerList]
})
},
// 获取考试结果
getExamResult() {
api
.getCourseExamResult(this.sid, this.cid, this.pid, { paper_type: 0 })
.then(response => {
// 设置问题列表数据
if (response.code !== 8001) {
this.isStartExam = true
this.exam = response
this.questions = this.genQuestions(response.sheet)
// 自动提交
if (this.isStartExam && !this.isSubmited && !this.isExamComplete) {
this.autoSubmit()
}
}
})
},
// 提交校验
checkSubmit() {
const quenstions = this.questions
for (let i = 0; i < quenstions.length; i++) {
const value = quenstions[i].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
for (let i = 0; i < this.questions.length; i++) {
const questions = this.questions[i]
for (let k = 0; k < questions.length; k++) {
const value = questions[k].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
}
}
}
return true
......@@ -190,74 +219,84 @@ export default {
this.messageInstance = this.$message.error('还有题目未做,不能提交')
return
}
// 计算答题时间
const duration = Math.floor(
(new Date().getTime() - this.startTime) / 1000
)
// 答案数据
const data = this.handleSubmitData()
// 计算分数
const score = data.reduce((result, item) => {
item.is_correct && result++
return result
}, 0)
const total = this.questions.length
const params = {
semester_id: this.sid,
course_id: this.cid,
chapter_id: this.pid,
work_id: this.resourceId,
work_contents: JSON.stringify(data),
duration,
score: ((score / total) * 100).toFixed(1)
}
// 提交的答案数据
const answers = this.handleSubmitData()
// 提交参数
const params = { answers: JSON.stringify(answers), type: 1 }
// 请求接口
this.handleSubmitRequest(params)
},
// 提交的答案数据
// 自动提交
autoSubmit() {
// 10秒提交一次
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.autoSubmitTimer = setInterval(() => {
// 提交的答案数据
const answers = this.handleSubmitData()
const params = { answers: JSON.stringify(answers), type: 0 }
// 请求接口
this.handleSubmitRequest(params)
}, 10000)
},
// 处理请求接口答案数据
handleSubmitData() {
const result = this.questions.map(item => {
// 设置提交选中状态
let isCorrect = true
const options = item.options.map(option => {
// 选择的项
const answers = item.formModel.user_answer
// 是否选中该项
const selected = Array.isArray(answers)
? answers.includes(option.id)
: option.id === answers
// 是否选择正确
if (option.checked !== selected && isCorrect) {
isCorrect = false
}
return {
id: option.id,
checked: option.checked,
option: option.option,
selected
}
})
return {
question_id: item.id,
is_correct: isCorrect ? 1 : 0,
options
}
return this.questions.map(questions => {
return questions.reduce(
(result, item) => {
// 单选题
if (item.type === 1) {
result.radioList.push(item.formModel)
}
// 多选题
if (item.type === 2) {
result.checkboxList.push(item.formModel)
}
// 简答题
if (item.type === 3) {
const formModel = Object.assign({}, item.formModel, {
user_answer: Base64.encode(item.formModel.user_answer)
})
result.shortAnswerList.push(formModel)
}
return result
},
{ radioList: [], checkboxList: [], shortAnswerList: [] }
)
})
return result
},
// 请求提交接口
handleSubmitRequest(params) {
api.sbumitChapterExam(params).then(response => {
if (response.status) {
this.getDetail()
} else {
this.$message.error(response.data.error)
}
})
this.submitLoading = true
params.paper_type = 0
api
.submitCourseExam(this.sid, this.cid, this.pid, params)
.then(response => {
if (params.type === 0) {
console.log('暂存成功')
return
}
if (response.code === 200) {
this.$message.success('考试答卷提交成功')
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.getExamResult()
} else {
this.$message.error(response.data.error)
}
})
.catch(error => {
this.$message.error(error.message)
})
.finally(() => {
this.submitLoading = false
})
}
},
beforeMount() {
this.getDetail()
// 获取考试结果
this.getExamResult()
},
destroyed() {
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
}
}
</script>
......@@ -271,4 +310,17 @@ export default {
margin: 40px auto;
}
}
.no-exam {
padding: 100px;
font-size: 30px;
text-align: center;
}
.exam-welcome {
padding: 40px;
line-height: 30px;
text-align: center;
::v-deep .el-button {
margin-top: 30px;
}
}
</style>
<template>
<container :title="chapter.name" v-loading="loading">
<template v-slot:header-aside v-if="isSubmited">正确率:{{detail.score}}%</template>
<div class="exam">
<div class="exam-form">
<el-form :disabled="isSubmited">
<exam-item
v-for="(item, index) in unorderedQuestions"
:index="index"
:type="item.question_type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
<div class="exam-buttons">
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" :loading="submitLoading" @click="onSubmit">{{submitText}}</el-button>
</el-tooltip>
</div>
</el-form>
</div>
</div>
</container>
</template>
<script>
// libs
import { shuffle } from 'lodash'
// components
import Container from '../common/container.vue'
import ExamItem from './examItem.vue'
// api
import * as api from '../../api'
// 章节测试题
export default {
name: 'ChapterTest',
components: { Container, ExamItem },
props: {
// 当前选中的章节
chapter: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
loading: false,
detail: null,
questions: [], // 问题列表
startTime: new Date().getTime(), // 进入时间
messageInstance: null,
submitLoading: false
}
},
watch: {
chapter: {
immediate: true,
handler(data) {
this.questions = data.homework
? this.genQuenstions(data.homework.questions)
: []
}
}
},
computed: {
// 学期ID
sid() {
return this.$route.params.sid
},
// 课程ID
cid() {
return this.$route.params.cid
},
// 当前页面的ID
pid() {
return this.$route.params.id
},
// 资源ID
resourceId() {
return this.chapter.resource_id
},
// 打乱顺序的问题列表
unorderedQuestions() {
const ids = this.questions.map(item => item.id)
const sortIds = shuffle(ids)
return sortIds.map(id => this.questions.find(item => item.id === id))
},
// 是否提交
isSubmited() {
return this.detail ? !!this.detail.work_contents : false
},
// 提交按钮文本
submitText() {
return this.isSubmited ? '已提交' : '提交'
}
},
methods: {
// 获取测试答题详情
getDetail() {
this.loading = true
api
.getChapterHomework(this.sid, this.cid, this.resourceId)
.then(response => {
this.detail = Array.isArray(response) ? null : response
if (this.detail) {
const parseAnswers = JSON.parse(this.detail.work_contents)
// 设置答案
this.questions = this.questions.map(item => {
const found = parseAnswers.find(
answer => answer.question_id === item.id
)
if (found) {
const selectedIds = found.options.reduce((result, item) => {
item.selected && result.push(item.id)
return result
}, [])
item.user_answer =
item.question_type === 2 ? selectedIds : selectedIds[0]
}
return item
})
this.questions = this.genQuenstions(this.questions)
}
})
.finally(() => {
this.loading = false
})
},
// 组装问题数据
genQuenstions(list) {
if (!list) {
return []
}
return list.map(item => {
let temp = null
if (item.question_type === 1) {
// 单选
temp = {
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
} else if (item.question_type === 2) {
// 多选
temp = {
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
} else if (item.question_type === 3) {
// 简答
temp = {
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer)
: '',
attachments: item.attachments || ''
}
}
}
return Object.assign(
{},
item,
{
content: item.question_content,
options: item.question_options
? JSON.parse(item.question_options)
: []
},
temp
)
})
},
// 提交校验
checkSubmit() {
const quenstions = this.questions
for (let i = 0; i < quenstions.length; i++) {
const value = quenstions[i].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
}
}
return true
},
// 提交
onSubmit() {
// 校验
if (!this.checkSubmit()) {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
return
}
// 计算答题时间
const duration = Math.floor(
(new Date().getTime() - this.startTime) / 1000
)
// 答案数据
const data = this.handleSubmitData()
// 计算分数
const score = data.reduce((result, item) => {
item.is_correct && result++
return result
}, 0)
const total = this.questions.length
const params = {
semester_id: this.sid,
course_id: this.cid,
chapter_id: this.pid,
work_id: this.resourceId,
work_contents: JSON.stringify(data),
duration,
score: ((score / total) * 100).toFixed(1)
}
// 请求接口
this.handleSubmitRequest(params)
},
// 提交的答案数据
handleSubmitData() {
const result = this.questions.map(item => {
// 设置提交选中状态
let isCorrect = true
const options = item.options.map(option => {
// 选择的项
const answers = item.formModel.user_answer
// 是否选中该项
const selected = Array.isArray(answers)
? answers.includes(option.id)
: option.id === answers
// 是否选择正确
if (option.checked !== selected && isCorrect) {
isCorrect = false
}
return {
id: option.id,
checked: option.checked,
option: option.option,
selected
}
})
return {
question_id: item.id,
is_correct: isCorrect ? 1 : 0,
options
}
})
return result
},
// 请求提交接口
handleSubmitRequest(params) {
this.submitLoading = true
api
.sbumitChapterHomework(params)
.then(response => {
if (response.status) {
this.getDetail()
} else {
this.$message.error(response.data.error)
}
})
.catch(error => {
this.$message.error(error.message)
})
.finally(() => {
this.submitLoading = false
})
}
},
beforeMount() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.exam-buttons {
padding: 40px 0;
text-align: center;
.el-button {
width: 240px;
margin: 40px auto;
}
}
</style>
<template>
<container :title="chapter.name" v-loading="loading">
<div class="exam-form">
<el-form :disabled="isRevised">
<el-form :disabled="disabled || !isWorkTime">
<exam-item
v-for="(item, index) in questions"
:index="index"
:type="item.question_type"
:data="item"
:value="item.formModel"
:disabled="isRevised"
:disabled="disabled || !isWorkTime"
:key="item.id"
></exam-item>
</el-form>
</div>
<div class="work-bottom" v-if="detail">
<div class="info">
<template v-if="isRevised">
<p style="color:red;" v-if="deadline">请于截止日期 {{ deadline }} 前提交</p>
<!-- 驳回状态 -->
<template v-if="detail && detail.status === 1">
<div class="work-bottom">
<div class="info">
<div class="paper-check">
<p>批改时间:{{detail.check_date}}</p>
<h4>作业被驳回,点击“重新编辑”按钮重新编辑内容再次提交</h4>
<div class="paper-check-item">
<b>评分</b>
{{detail.score}}
<b>驳回时间</b>
{{ detail.checker_time }}
</div>
<div class="paper-check-item">
<b>评语</b>
<b>驳回说明</b>
<div class="edit_html" v-html="detail.check_comments"></div>
</div>
</div>
</template>
<template v-else-if="detail.created_time">
<p class="help">已于 {{detail.created_time}} 提交,等待老师批改中。</p>
<template v-if="detail.updated_time && detail.updated_time !== detail.created_time">
<p class="help">最近提交时间: {{detail.updated_time}}</p>
</div>
</div>
<div class="buttons">
<el-button type="primary" @click="onReEdit" :disabled="!isWorkTime">重新编辑</el-button>
</div>
</template>
<!-- 正常状态 -->
<template v-else>
<div class="work-bottom" v-if="detail">
<div class="info">
<template v-if="isRevised">
<div class="paper-check">
<p>批改时间:{{ detail.checker_time }}</p>
<div class="paper-check-item">
<b>评分:</b>
{{ detail.score }}
</div>
<div class="paper-check-item">
<b>评语:</b>
<div class="edit_html" v-html="detail.check_comments"></div>
</div>
</div>
</template>
</template>
<template v-else-if="detail.created_time">
<p class="help">已于 {{ detail.created_time }} 提交,等待老师批改中。</p>
<template
v-if="
detail.updated_time &&
detail.updated_time !== detail.created_time
"
>
<p class="help">最近提交时间: {{ detail.updated_time }}</p>
</template>
</template>
</div>
</div>
</div>
<div class="buttons">
<el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right">
<el-button type="primary" @click="onSubmit" :disabled="isRevised">{{submitText}}</el-button>
</el-tooltip>
</div>
<div class="buttons">
<el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right">
<el-button
type="primary"
:disabled="disabled || !isWorkTime"
:loading="submitLoading"
@click="onSubmit"
>{{ submitText }}</el-button>
</el-tooltip>
</div>
</template>
</container>
</template>
......@@ -80,7 +114,10 @@ export default {
detail: null,
questions: [], // 问题列表
startTime: new Date().getTime(), // 进入时间
messageInstance: null
messageInstance: null,
deadline: '', // 截止时间
disabled: false,
submitLoading: false
}
},
watch: {
......@@ -112,22 +149,42 @@ export default {
},
// 是否批改
isRevised() {
return this.detail ? !!this.detail.check_date : false
return this.detail ? this.detail.status === 0 : false
},
// 提交按钮文本
submitText() {
return this.isRevised ? '已批改' : '提交'
},
// 是否是提交作业时间
isWorkTime() {
if (!this.deadline) {
return true
}
// 大于开始时间,小于结束时间
const endTime = +new Date(this.deadline)
const currentTime = new Date().getTime()
return currentTime < endTime
}
},
methods: {
// 获取作业截止时间
getDeadline() {
api
.getChapterHomeworkDeadline(this.sid, this.cid, this.pid)
.then(response => {
this.deadline = response.dead_line
})
},
// 获取详情
getDetail() {
this.loading = true
api
.getChapterExam(this.sid, this.cid, this.resourceId)
.getChapterHomework(this.sid, this.cid, this.resourceId)
.then(response => {
this.detail = Array.isArray(response) ? null : response
if (this.detail) {
// -1未处理 0已处理 1驳回
this.disabled = [0, 1].includes(this.detail.status)
const parseAnswers = JSON.parse(this.detail.work_contents)
// 设置答案
this.questions = this.questions.map(item => {
......@@ -205,7 +262,9 @@ export default {
// 校验
if (!this.checkSubmit()) {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
this.messageInstance = this.$message.error(
'答题内容不能为空,请检查并输入内容'
)
return
}
// 计算答题时间
......@@ -214,12 +273,12 @@ export default {
)
// 提交的答案数据
const answers = this.questions.map(item => {
if (item.question_type === 3) {
item.formModel.user_answer = Base64.encode(item.formModel.user_answer)
}
return {
question_id: item.id,
descreption: item.formModel.user_answer,
descreption:
item.question_type === 3
? Base64.encode(item.formModel.user_answer)
: item.formModel.user_answer,
file_url: item.formModel.attachments,
is_encoded: 1
}
......@@ -238,8 +297,9 @@ export default {
},
// 请求提交接口
handleSubmitRequest(params) {
this.submitLoading = true
api
.sbumitChapterExam(params)
.sbumitChapterHomework(params)
.then(response => {
if (response.status) {
this.$message.success('提交成功,等待批改')
......@@ -251,10 +311,49 @@ export default {
.catch(error => {
this.$message.error(error.message)
})
.finally(() => {
this.submitLoading = false
})
},
// 重新编辑
onReEdit() {
this.disabled = false
this.detail.status = -1
}
},
beforeMount() {
this.getDetail()
this.getDeadline()
}
}
</script>
<style lang="scss" scoped>
.work-bottom {
margin-top: 20px;
.info {
color: #999;
line-height: 28px;
}
}
.buttons {
padding: 20px 0;
::v-deep .el-button {
width: 120px;
}
}
.paper-check {
padding: 10px;
color: #000;
border: 1px solid #dedede;
h4 {
margin: 0 0 10px;
}
}
.paper-check-item {
display: flex;
b {
white-space: nowrap;
}
}
</style>
<template>
<container :title="detail.title" v-loading="loading">
<template v-slot:header-aside v-if="isExamComplete">分数:{{exam.score.total}}</template>
<div class="exam">
<template v-if="status.examination_status === '00'">
<div class="no-exam">暂无考试</div>
......@@ -17,42 +18,23 @@
@click="onStartExam"
>{{startExamButtonText}}</el-button>
</div>
<!-- 考试完成 -->
<div class="exam-finish" v-if="isExamComplete">
<table class="exam-table">
<tr>
<th>单选</th>
<th>多选</th>
<th>简答</th>
</tr>
<tr>
<td>{{exam.score.radio}}</td>
<td>{{exam.score.checkbox}}</td>
<td>{{exam.score.shortAnswer}}</td>
</tr>
<tr>
<td colspan="3">
<div class="exam-total">总分:{{exam.score.total}}</div>
</td>
</tr>
</table>
<el-button type="text" @click="examVisible = !examVisible">查看试卷</el-button>
</div>
<!-- 考试试题 -->
<div class="exam-form" v-if="isStartExam" v-show="examVisible">
<div class="exam-form" v-if="isStartExam">
<el-form :disabled="isSubmited">
<exam-item
v-for="(item, index) in questions"
:index="index"
:type="item.type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
<template v-for="items in questions">
<exam-item
v-for="(item, index) in items"
:index="index"
:type="item.type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
</template>
<div class="exam-buttons">
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" @click="onSubmit">{{submitText}}</el-button>
<el-button type="primary" :loading="submitLoading" @click="onSubmit">{{submitText}}</el-button>
</el-tooltip>
</div>
</el-form>
......@@ -97,18 +79,12 @@ export default {
detail: {},
status: {},
questions: [],
values: [], // 提交的答案
messageInstance: null,
exam: {},
isStartExam: false, // 是否开始考试
autoSubmitTimer: null, // 自动提交定时器
checkStatusTimer: null, // 考试状态定时器
examVisible: true
}
},
watch: {
isExamComplete(value) {
this.examVisible = !value
submitLoading: false
}
},
computed: {
......@@ -163,7 +139,7 @@ export default {
this.detail = Array.isArray(response) ? null : response
// 设置问题列表数据
this.questions = this.detail
? this.genQuenstions(this.detail.examination)
? this.genQuestions(this.detail.examination)
: []
callback && callback()
})
......@@ -172,42 +148,44 @@ export default {
})
},
// 组装问题数据
genQuenstions(data) {
if (!data) {
return
genQuestions(list) {
if (!list) {
return []
}
let { radioList, checkboxList, shortAnswerList } = data
// 单选
radioList = radioList.map(item => {
const temp = {
type: 1,
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
return Object.assign({}, item, temp)
})
// 多选
checkboxList = checkboxList.map(item => {
const temp = {
type: 2,
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
return Object.assign({}, item, temp)
})
// 问答
shortAnswerList = shortAnswerList.map(item => {
const temp = {
type: 3,
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer)
: '',
attachments: []
return list.map(data => {
let { radioList, checkboxList, shortAnswerList } = data
// 单选
radioList = radioList.map(item => {
const temp = {
type: 1,
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
}
return Object.assign({}, item, temp)
return Object.assign({}, item, temp)
})
// 多选
checkboxList = checkboxList.map(item => {
const temp = {
type: 2,
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
return Object.assign({}, item, temp)
})
// 问答
shortAnswerList = shortAnswerList.map(item => {
const temp = {
type: 3,
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer.replace(/ /gi, '+'))
: '',
attachments: item.attachments || []
}
}
return Object.assign({}, item, temp)
})
return [...radioList, ...checkboxList, ...shortAnswerList]
})
return [...radioList, ...checkboxList, ...shortAnswerList]
},
// 获取考试状态
getExamStatus() {
......@@ -234,7 +212,7 @@ export default {
if (response.code !== 8001) {
this.isStartExam = true
this.exam = response
this.questions = this.genQuenstions(response.sheet)
this.questions = this.genQuestions(response.sheet)
// 自动提交
if (this.isStartExam && !this.isSubmited && !this.isExamComplete) {
this.autoSubmit()
......@@ -244,11 +222,13 @@ export default {
},
// 提交校验
checkSubmit() {
const quenstions = this.questions
for (let i = 0; i < quenstions.length; i++) {
const value = quenstions[i].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
for (let i = 0; i < this.questions.length; i++) {
const questions = this.questions[i]
for (let k = 0; k < questions.length; k++) {
const value = questions[k].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
}
}
}
return true
......@@ -262,12 +242,7 @@ export default {
return
}
// 提交的答案数据
const answers = this.questions.map(item => {
if (item.type === 3) {
item.formModel.user_answer = Base64.encode(item.formModel.user_answer)
}
return item.formModel
})
const answers = this.handleSubmitData()
// 提交参数
const params = { answers: JSON.stringify(answers), type: 1 }
// 请求接口
......@@ -279,21 +254,41 @@ export default {
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.autoSubmitTimer = setInterval(() => {
// 提交的答案数据
const answers = this.questions.map(item => {
if (item.type === 3) {
item.formModel.user_answer = Base64.encode(
item.formModel.user_answer
)
}
return item.formModel
})
const answers = this.handleSubmitData()
const params = { answers: JSON.stringify(answers), type: 0 }
// 请求接口
this.handleSubmitRequest(params)
}, 10000)
},
// 处理请求接口答案数据
handleSubmitData() {
return this.questions.map(questions => {
return questions.reduce(
(result, item) => {
// 单选题
if (item.type === 1) {
result.radioList.push(item.formModel)
}
// 多选题
if (item.type === 2) {
result.checkboxList.push(item.formModel)
}
// 简答题
if (item.type === 3) {
const formModel = Object.assign({}, item.formModel, {
user_answer: Base64.encode(item.formModel.user_answer)
})
result.shortAnswerList.push(formModel)
}
return result
},
{ radioList: [], checkboxList: [], shortAnswerList: [] }
)
})
},
// 请求提交接口
handleSubmitRequest(params) {
this.submitLoading = true
api
.submitCourseExam(this.sid, this.cid, this.pid, params)
.then(response => {
......@@ -312,6 +307,9 @@ export default {
.catch(error => {
this.$message.error(error.message)
})
.finally(() => {
this.submitLoading = false
})
}
},
beforeMount() {
......@@ -344,27 +342,6 @@ export default {
font-size: 30px;
text-align: center;
}
.exam-finish {
margin: 40px 0;
}
.exam-table {
width: 100%;
border-collapse: collapse;
th {
background-color: #ccc;
}
td,
th {
padding: 10px;
border: 1px solid #999;
text-align: center;
}
}
.exam-total {
font-size: 18px;
text-align: right;
padding: 0 40px;
}
.exam-welcome {
padding: 40px;
line-height: 30px;
......
......@@ -19,7 +19,7 @@
ref="ruleForm"
>
<el-form-item label="主题" prop="essay_name">
<el-input v-model="ruleForm.essay_name" placeholder="主题"></el-input>
<el-input v-model="ruleForm.essay_name" placeholder="主题" maxlength="50"></el-input>
</el-form-item>
<el-form-item label="正文" prop="essay_description">
<!-- 编辑器 -->
......@@ -62,7 +62,12 @@
</div>
<div class="buttons">
<el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right">
<el-button type="primary" @click="onSubmit" :disabled="isRevised">{{submitText}}</el-button>
<el-button
type="primary"
:disabled="isRevised"
:loading="submitLoading"
@click="onSubmit"
>{{submitText}}</el-button>
</el-tooltip>
</div>
</template>
......@@ -108,17 +113,17 @@ export default {
},
rules: {
essay_name: [
{ required: true, message: '请输入主题', trigger: 'blur' },
{ max: 5, message: '最多输入 50 个字符', trigger: 'blur' }
{ required: true, message: '请输入主题', trigger: 'blur' }
],
essay_description: [
{ required: true, message: '请输入正文', trigger: 'blur' }
{ required: true, message: '请输入正文', trigger: 'change' }
],
url: [{ required: true, message: '请上传附件', trigger: 'change' }]
},
detail: null,
loading: false,
messageInstance: null
messageInstance: null,
submitLoading: false
}
},
computed: {
......@@ -159,22 +164,28 @@ export default {
},
// 提交
onSubmit() {
this.$refs.ruleForm
.validate()
.then(response => {
const params = Object.assign(this.ruleForm, {
semester_id: this.sid,
course_id: this.cid
})
this.handleSubmitRequest(params)
})
.catch(() => {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
})
this.messageInstance && this.messageInstance.close()
if (!this.ruleForm.essay_name) {
this.messageInstance = this.$message.error('请输入主题')
return
}
if (!this.ruleForm.essay_description) {
this.messageInstance = this.$message.error('请输入正文')
return
}
if (!this.ruleForm.url) {
this.messageInstance = this.$message.error('请上传附件')
return
}
const params = Object.assign(this.ruleForm, {
semester_id: this.sid,
course_id: this.cid
})
this.handleSubmitRequest(params)
},
// 请求提交接口
handleSubmitRequest(params) {
this.submitLoading = true
api
.updateCourseWork(this.sid, this.cid, params)
.then(response => {
......@@ -188,6 +199,9 @@ export default {
.catch(error => {
this.$message.error(error.message)
})
.finally(() => {
this.submitLoading = false
})
}
},
beforeMount() {
......@@ -231,5 +245,8 @@ p {
}
.paper-check-item {
display: flex;
b {
white-space: nowrap;
}
}
</style>
......@@ -2,8 +2,11 @@
<div class="q-item">
<div class="q-item-hd">
<div class="q-item-num">{{index + 1}}.</div>
<div class="q-item-title" v-html="data.content">{{data.title}}</div>
<div class="q-item-aside" v-if="typeText">({{typeText}})</div>
<div class="q-item-title" v-html="data.content"></div>
<div class="q-item-aside">
<template v-if="typeText">({{typeText}})</template>
<template v-if="data.hasOwnProperty('score')">({{data.score}}分)</template>
</div>
</div>
<div class="q-item-bd">
<!-- 单选 -->
......@@ -21,26 +24,44 @@
<!-- 简答题 -->
<template v-if="type === 3">
<v-editor v-model="currentValue.user_answer" :disabled="disabled"></v-editor>
<v-upload v-model="currentValue.attachments">请上传对应的文件附件:</v-upload>
<v-upload :disabled="disabled" v-model="currentValue.attachments">请上传对应的文件附件:</v-upload>
</template>
</div>
<div class="q-item-ft" v-if="disabled">
<template v-if="type === 3">
<p>
<span>老师评语:</span>
<p v-if="data.check_comment">
<span>评语:</span>
<span>{{data.check_comment}}</span>
</p>
</template>
<template v-else>
<p>
<span>学生答案:</span>
<span :class="isCorrect ? 'is-success' : 'is-error'">{{submitAnswerText}}</span>
</p>
<p>
<span>正确答案:</span>
<span>{{correctAnswerText}}</span>
</p>
<div class="result">
<p>
<span>学生答案:</span>
<span :class="isCorrect ? 'is-success' : 'is-error'">{{submitAnswerText}}</span>
</p>
<p>
<span>正确答案:</span>
<span>{{correctAnswerText}}</span>
</p>
</div>
</template>
<p v-if="data.hasOwnProperty('get_score')">
<span>评分:</span>
<span>{{data.get_score}}分</span>
</p>
<div class="analyze" v-if="data.analysis">
<span>解析:</span>
<div class="analyze-main">
<span style="color:blue;cursor:pointer;" @click="showAnalyze = !showAnalyze">查看解析</span>
<div
v-html="data.analysis"
v-if="data.analysis"
v-show="showAnalyze"
class="analyze-content"
></div>
</div>
</div>
</div>
</div>
</template>
......@@ -77,7 +98,8 @@ export default {
},
data() {
return {
currentValue: {}
currentValue: {},
showAnalyze: false
}
},
watch: {
......@@ -116,6 +138,14 @@ export default {
item.selected = Array.isArray(value)
? value.includes(item.id)
: value === item.id
// 处理正确的选中状态
const hasChecked = Object.prototype.hasOwnProperty.call(item, 'checked')
const rightAnswer = this.data.right_answer || ''
if (!hasChecked && rightAnswer) {
item.checked = Array.isArray(rightAnswer)
? rightAnswer.includes(item.id)
: rightAnswer === item.id
}
return item
})
},
......@@ -190,14 +220,16 @@ export default {
}
.q-item-title {
flex: 1;
padding: 0 10px;
::v-deep img {
max-width: 100%;
}
}
.q-item-aside {
padding-left: 20px;
// align-self: flex-end;
}
.q-option-item {
padding-left: 30px;
padding-left: 20px;
margin-bottom: 14px;
}
.is-success {
......@@ -229,13 +261,36 @@ export default {
}
}
.q-item-ft {
display: flex;
justify-content: flex-end;
padding: 10px 0;
p {
font-size: 14px;
margin: 0;
padding-left: 20px;
margin: 0 0 10px 0;
}
.result {
display: flex;
justify-content: flex-end;
p {
padding-left: 20px;
}
}
.analyze {
display: flex;
font-size: 14px;
}
.analyze-main {
flex: 1;
overflow: hidden;
}
.analyze-content {
margin-top: 10px;
background-color: #c9c9c97a;
border: 1px solid #c9c9c97a;
padding: 10px;
::v-deep * {
margin: 0;
padding: 0;
max-width: 100%;
}
}
}
</style>
......@@ -12,11 +12,11 @@
<script>
// componets
import ChapterWork from './chapterWork.vue'
import ChapterExam from './chapterExam.vue'
import ChapterTest from './chapterTest.vue'
export default {
name: 'ViewerWork',
components: { ChapterWork, ChapterExam },
components: { ChapterWork, ChapterTest },
props: {
// 当前选中的
chapter: {
......@@ -36,7 +36,7 @@ export default {
computed: {
currentCompoent() {
const componentNames = {
1: 'ChapterExam', // 考试
1: 'ChapterTest', // 课后测验
2: 'ChapterWork' // 作业
}
const homework = this.chapter.homework
......
......@@ -9,7 +9,7 @@
<h1 class="course-viewer-main-hd__title">{{ detail.course_name }}</h1>
<!-- 直播的时候显示帮助按钮 -->
<template v-if="isLive">
<router-link to="/app/account/feedbackCreate" target="_blank">
<router-link to="/app/feedback/feedback-create" target="_blank">
<el-tooltip effect="light" content="意见反馈">
<i class="el-icon-self-fankuiyijian"></i>
</el-tooltip>
......@@ -20,6 +20,10 @@
</el-tooltip>
</router-link>
</template>
<div class="course-menu" @click="menuVisible = !menuVisible">
<i class="el-icon-s-unfold" v-if="menuVisible"></i>
<i class="el-icon-s-fold" v-else></i>
</div>
</div>
<!-- 主体区域 -->
<div class="course-viewer-main-bd">
......@@ -27,21 +31,23 @@
:data="detail"
:chapter="activeChapter"
:pptIndex="pptIndex"
:isSeek="isSeek"
:key="pid"
@pptupdate="handlePPTupdate"
@change-ppt="handleChangePPT"
@change-ppt="handleChangePPT(...arguments, false)"
/>
</div>
</div>
<!-- 侧边栏 -->
<v-aside
:data="detail"
:chapters="chapters"
:active="activeChapter"
:ppts="ppts"
:pptIndex="pptIndex"
@change-ppt="handleChangePPT"
@change-ppt="handleChangePPT(...arguments, true)"
v-if="detail.chapters"
v-show="!isLive && !isCourseExam"
v-show="menuVisible"
></v-aside>
</div>
</template>
......@@ -59,13 +65,25 @@ export default {
return {
detail: {},
ppts: [],
pptIndex: 0
pptIndex: 0,
isSeek: false,
menuVisible: true
}
},
watch: {
activeChapter() {
this.ppts = []
this.pptIndex = 0
},
isLive(value) {
if (value) {
this.menuVisible = false
}
},
isCourseExam(value) {
if (value) {
this.menuVisible = false
}
}
},
computed: {
......@@ -92,10 +110,17 @@ export default {
children: [
{ name: '课程大作业', id: 'course_work', type: 99 },
{ name: '课程资料', id: 'course_info', type: 100 },
{ name: '教学评估', id: 'teach_evaluation', type: 102 },
{ name: '课程考试', id: 'course_exam', type: 101 }
{ name: '教学评估', id: 'teach_evaluation', type: 102 }
]
}
// 课程考试
if (this.detail.course_examination) {
customeChapter.children.push({
name: '课程考试',
id: 'course_exam',
type: 101
})
}
chapters.push(customeChapter)
return chapters
},
......@@ -107,7 +132,7 @@ export default {
},
// 直播
isLive() {
return this.activeChapter ? this.activeChapter.type === 5 : false
return this.activeChapter ? [5, 8].includes(this.activeChapter.type) : false
},
// 课程考试
isCourseExam() {
......@@ -141,8 +166,9 @@ export default {
this.ppts = list
},
// 右侧菜单选中的PPT修改
handleChangePPT(index) {
handleChangePPT(index, isSeek) {
this.pptIndex = index
this.isSeek = isSeek
}
},
beforeMount() {
......@@ -178,7 +204,7 @@ export default {
.course-viewer-main-hd__title {
flex: 1;
font-size: 1.5em;
text-align: center;
// text-align: center;
color: #a0a0a0;
}
.course-viewer-main-bd {
......@@ -230,4 +256,17 @@ export default {
font-size: 18px;
// border-bottom: 3px solid #707070;
}
.course-menu {
width: 24px;
height: 24px;
padding: 12px;
margin-right: 10px;
color: #fff;
text-align: center;
border-radius: 50%;
cursor: pointer;
&:hover {
background-color: rgba(255, 255, 255, 0.08);
}
}
</style>
import BaseAPI from '@/api/base_api'
const httpRequest = new BaseAPI(webConf)
// 获取事务类型列表
export function getAffairType() {
return httpRequest.get('/api/lms/v2/lobby/affairs')
}
// 获取事务列表
export function getAffairList() {
return httpRequest.get('/api/lms/v2/lobby/processes')
}
// 获取事务详情
export function getAffair(id) {
return httpRequest.get(`/api/lms/v2/lobby/processes/${id}`)
}
// 创建事务
export function createAffair(data) {
return httpRequest.post('/api/lms/v2/lobby/processes', data)
}
// 更新事务
export function updateAffair(id, data) {
return httpRequest.post(`/api/lms/v2/lobby/processes/${id}`, data)
}
// 删除事务
export function deleteAffair(id) {
return httpRequest({
url: `/api/lms/v2/lobby/processes/${id}`,
method: 'delete'
})
}
// 上传文件
export function upload(data) {
return httpRequest({
url: '/api/lms/v2/lobby/tools/upload',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data
})
}
// 获取学期
export function getSemesters(params) {
return httpRequest.get('/api/lms/v2/lobby/semesters')
}
// 获取课程
export function getCourses(sid) {
return httpRequest.get(`/api/lms/v2/lobby/semesters/${sid}/courses`)
}
// 获取成绩单
export function getReport() {
return httpRequest.get('/api/lms/v2/lobby/havereport')
}
// 获取学生
export function getStudent() {
return httpRequest.get('/api/lms/v2/lobby/student')
}
<template>
<div class="hall-form">
<el-radio
v-model="schoolReport"
@change="changeReport"
label="elreport"
border
size="small"
>电子成绩单</el-radio>
<el-radio
v-model="schoolReport"
@change="changeReport"
label="pareport"
border
size="small"
>纸质成绩单</el-radio>
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleForm"
:disabled="Number(ruleForm.submit) === 1"
label-width="100px"
size="small"
label-position="top"
class="hall-form-body"
>
<!-- <el-form-item label="姓名" prop="personal_name">
<el-input v-model.trim="ruleForm.personal_name"></el-input>
</el-form-item>
<el-form-item label="班级" prop="class_name">
<el-input v-model.trim="ruleForm.class_name"></el-input>
</el-form-item>-->
<el-form-item label="Sofia ID" prop="sofia_id" key="sofia_id">
<el-input v-model.trim="ruleForm.sofia_id"></el-input>
</el-form-item>
<el-form-item label="电话" prop="telephone" key="telephone" v-if="schoolReport === 'pareport'">
<el-input v-model.trim="ruleForm.telephone"></el-input>
</el-form-item>
<el-form-item :label="schoolReport === 'elreport' ? '邮箱':'邮寄地址'" prop="email">
<el-input v-model.trim="ruleForm.email"></el-input>
</el-form-item>
<el-form-item label="需打印的份数" prop="number" key="number" v-if="schoolReport === 'pareport'">
<el-input v-model.trim="ruleForm.number"></el-input>
</el-form-item>
<el-form-item class="ts-width">
<span class="red" v-if="schoolReport === 'elreport'">
毕业生可申请成绩单。
<br />请仔细核查您填写的信息后提交,提交后不可修改。电子成绩单将发送至您填写的邮箱。
</span>
<span class="red" v-else>
毕业生可申请成绩单。
<br />请仔细核查您填写的信息后提交,提交后不可修改。纸质成绩单将按您填写的地址邮寄给您。
</span>
</el-form-item>
<el-form-item class="ts-width">
<el-button
type="primary"
@click="submitForm('ruleForm')"
>{{Number(ruleForm.submit) === 1 ? '已提交' : '提交'}}</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'hall-form',
props: {
formData: {
type: Object
}
},
watch: {
formData: {
immediate: true,
handler(value) {
this.ruleForm = Object.assign({}, value)
this.$nextTick(() => {
this.$refs.ruleForm.clearValidate()
})
}
}
},
data() {
var checkPhone = (rule, value, callback) => {
var reg = /^1[345789]\d{9}$/
if (!reg.test(value)) {
callback(new Error('请输入11位手机号!'))
} else {
callback()
}
}
var blurText = async (rule, value, callback) => {
var boolean = new RegExp('^[1-9][0-9]*$').test(value)
if (!boolean) {
callback(new Error('请输入正整数!'))
} else {
callback()
}
}
var checkEmail = async (rule, value, callback) => {
if (!value) {
if (this.schoolReport === 'pareport') {
callback(new Error('请输入邮寄地址!'))
} else {
callback(new Error('请输入邮箱!'))
}
} else {
callback()
}
}
var checksofiaId = (rule, value, callback) => {
var boolean = new RegExp('^[1-9][0-9]*$').test(value)
if (!value) {
callback(new Error('请输入Sofia ID!'))
} else if (!boolean) {
callback(new Error('请输入正整数!'))
} else {
callback()
}
}
return {
ruleForm: {
sofia_id: '',
telephone: '',
email: '',
number: null,
submit: 0
},
rules: {
// personal_name: [
// { required: true, message: '请输入姓名', trigger: 'blur' }
// ],
// class_name: [
// { required: true, message: '请输入班级', trigger: 'blur' }
// ],
sofia_id: [{ required: true, validator: checksofiaId, trigger: 'blur' }],
telephone: [{ required: true, validator: checkPhone, trigger: 'blur' }],
email: [{ required: true, validator: checkEmail, trigger: 'blur' }],
number: [{ required: true, validator: blurText, trigger: 'blur' }]
},
schoolReport: 'elreport'
}
},
methods: {
submitForm(formName) {
this.$refs[formName].validate(valid => {
if (valid) {
this.$emit('submit', this.ruleForm, this.schoolReport)
} else {
return false
}
})
},
changeReport() {
this.$emit('submitType', this.schoolReport)
}
},
mounted() {
this.changeReport()
}
}
</script>
<style lang="scss" scoped>
.hall-form {
width: 100%;
.hall-form-body {
padding: 15px 10px 20px 10px;
.red {
color: red;
}
}
}
.hall-form .red:before {
content: '*';
color: #f56c6c;
margin-right: 4px;
}
::v-deep .el-form-item__label {
padding: 0 0 5px 0;
}
::v-deep .el-form-item {
margin-bottom: 12px;
}
::v-deep .el-input {
width: 25%;
}
::v-deep .el-button {
width: 25%;
margin-top: 20px;
}
</style>
<template>
<div class="pay">
<template v-for="(item, index) in datalist">
<div class="pay-box" :key="index">
<i class="el-icon-error" @click="handleRemove(index)" v-if="index && canEditable(item)"></i>
<pay-item :data="item" :index="index" :disabled="!canEditable(item)"></pay-item>
</div>
</template>
<el-button
icon="el-icon-plus"
@click="handleAdd"
style="margin-bottom:20px"
size="medium"
v-if="!disabled"
>新增缴费凭证</el-button>
</div>
</template>
<script>
import PayItem from './payItem'
export default {
props: {
datalist: { type: Array, default: () => [] },
disabled: { type: Boolean, default: false }
},
components: { PayItem },
methods: {
canEditable(data) {
if (this.disabled) {
return false
}
return !['1', '2'].includes(data.status)
},
// 添加
handleAdd() {
this.datalist.push({ pay_date: '', type: '', money: '', url: '' })
},
// 删除
handleRemove(index) {
this.datalist.splice(index, 1)
}
},
beforeMount() {
// 默认创建一条
!this.datalist.length && this.handleAdd()
}
}
</script>
<style lang="scss" scoped>
.pay-box {
position: relative;
border: 1px solid #b80037;
padding: 20px;
.el-icon-error {
position: absolute;
right: -14px;
top: -14px;
font-size: 28px;
color: #b80037;
cursor: pointer;
}
}
.pay-box {
margin-bottom: 20px;
}
</style>
<template>
<div class="pay-item">
<el-form-item
label="支付时间"
:prop="'payment_instrument.' + index + '.pay_date'"
:rules="{ required: true, message: '请选择日期' }"
>
<el-date-picker
v-model="data.pay_date"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择日期"
:disabled="disabled"
></el-date-picker>
</el-form-item>
<el-form-item
label="支付方式"
:prop="'payment_instrument.' + index + '.type'"
:rules="{ required: true, message: '请选择支付方式' }"
>
<el-popover placement="top-start" trigger="hover">
<p>银行账户名称:清控紫荆(北京)教育科技股份有限公司</p>
<p>银行帐号:694485289</p>
<p>开户行:中国民生银行股份有限公司北京魏公村支行</p>
<el-radio slot="reference" label="1" v-model="data.type" :disabled="disabled">银行卡转账</el-radio>
</el-popover>
<el-popover placement="top-start" trigger="hover">
<p>支付宝户名:清控紫荆(北京)教育科技股份有限公司</p>
<el-radio
slot="reference"
label="2"
v-model="data.type"
:disabled="disabled"
style="margin-left:40px;"
>支付宝转账</el-radio>
</el-popover>
</el-form-item>
<el-form-item
label="支付金额"
:prop="'payment_instrument.' + index + '.money'"
:rules="{ required: true, message: '请输入支付金额' }"
>
<el-input v-model="data.money" placeholder="输入支付金额" :disabled="disabled" style="width:220px">
<template slot="append">¥</template>
</el-input>
</el-form-item>
<el-form-item
label="上传缴费凭证"
:prop="'payment_instrument.' + index + '.url'"
:rules="{ required: true, message: '请上传缴费凭证' }"
>
<app-upload v-model="data.url" :disabled="disabled"></app-upload>
</el-form-item>
<el-form-item label="审核状态" v-if="statusText">
<el-tag>{{statusText}}</el-tag>
</el-form-item>
</div>
</template>
<script>
import AppUpload from '@/components/upload'
export default {
props: {
data: { type: Object, required: true },
index: { type: Number },
disabled: { type: Boolean, default: false }
},
components: { AppUpload },
computed: {
statusText() {
const status = {
'-1': '待审核',
0: '未通过',
1: '通过'
}
return status[this.data.status] || ''
}
}
}
</script>
......@@ -5,23 +5,28 @@
</div>
<Tap :tapParam="tapParam" @tapParam="tapIndexs"></Tap>
<div class="con-box">
<el-button
type="primary"
v-if="tapIndex==0"
@click="golearningAdd('/app/affairs-hall/learning-add/-1')"
>申请新的活动</el-button>
<el-button
type="primary"
v-if="tapIndex==1"
@click="golearningAdd('/app/affairs-hall/share-add/-1')"
>申请新乐分享</el-button>
<el-button
type="primary"
v-if="tapIndex==2"
@click="golearningAdd('/app/affairs-hall/again-add/-1')"
>申请重修</el-button>
<div style="width: 100%; height: 0.2rem;"></div>
<table-list :key="affairId" v-bind="tableOption" v-if="affairId" ref="tableList"></table-list>
<el-button type="primary" v-if="tapIndex == 0" @click="golearningAdd('/app/affairs-hall/learning-add/-1')"
>申请新的活动</el-button
>
<el-button type="primary" v-if="tapIndex == 1" @click="golearningAdd('/app/affairs-hall/share-add/-1')"
>申请新乐分享</el-button
>
<el-button type="primary" v-if="tapIndex == 2" @click="golearningAdd('/app/affairs-hall/again-add/-1')"
>申请重修</el-button
>
<div style="width: 100%; height: 0.2rem"></div>
<table-list
:key="affairId"
v-bind="tableOption"
v-if="affairId && resultName !== '成绩单'"
ref="tableList"
></table-list>
<hall-form
v-if="resultName === '成绩单'"
:formData="formData"
@submit="submit"
@submitType="obtainReport"
></hall-form>
</div>
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
<span>确定删除?</span>
......@@ -37,6 +42,7 @@
import Tap from '../../components/comTable/tap.vue'
import mPage from '../../components/comTable/mPage.vue'
import TableList from '../../components/comTable/tableList.vue'
import HallForm from './components/hall-form.vue'
import cAction from '@action'
const status = {
'-1': '待审核',
......@@ -49,6 +55,7 @@ const status = {
export default {
components: {
Tap,
HallForm,
mPage,
TableList
},
......@@ -223,6 +230,8 @@ export default {
}
},
affairList: [],
formData: {},
resultName: '',
affairId: null
}
},
......@@ -237,6 +246,32 @@ export default {
}
},
methods: {
submit(row, type) {
const params = Object.assign(
{
type: type,
affair_id: this.affairList.find(k => k.form_name === type).id
},
row
)
cAction.Affairs.submitLearning(params)
.then(() => {
this.$message({ type: 'success', message: '已提交成功!' })
this.obtainReport(type)
})
.catch(e => {
this.$message.error(e.message)
})
},
obtainReport(type = 'elreport') {
cAction.Affairs.obtainReport(type)
.then(data => {
this.formData = data
})
.catch(e => {
this.$message.error(e.message)
})
},
confirmDeletion(row) {
/* 删除 */
const loading = this.$loading({
......@@ -262,6 +297,7 @@ export default {
this.dialogVisible = false
},
tapIndexs(data) {
this.resultName = data.name
this.tapIndex = data.index
this.affairId = data.id
},
......@@ -269,9 +305,14 @@ export default {
getTapData() {
cAction.Affairs.getAffairsType()
.then(data => {
this.tapParam[0].arrItem = data.map(item => {
return { val: item.id, name: item.affair_name }
})
this.tapParam[0].arrItem = data
.map(item => {
if (item.form_name === 'elreport') {
item.affair_name = '成绩单'
}
return { val: item.id, name: item.affair_name, form_name: item.form_name }
})
.filter(k => k.form_name !== 'pareport')
const [first] = data
const datas = data[this.$route.query.index] || first
this.affairId = datas.id
......@@ -283,7 +324,9 @@ export default {
},
// 新增
golearningAdd(url) {
this.$router.push({ path: url, query: { id: this.affairId } })
if (this.affairId) {
this.$router.push({ path: url, query: { id: this.affairId } })
}
},
// 列表接口请求之前
tableListbeforeRequest(params) {
......@@ -291,7 +334,7 @@ export default {
return params
}
},
beforeMount() {
created() {
this.getTapData()
}
}
......
<template>
<div>
<div class="con-title">重修申请</div>
<div class="con-box">
<el-button type="text">
<router-link :to="{ path: '/app/affairs-hall/hall', query: { index: 2 } }">返回列表</router-link>
</el-button>
<el-row type="flex" justify="center">
<el-col :lg="12">
<el-form
ref="form"
:model="ruleForm"
:rules="rules"
:disabled="isView"
label-width="110px"
>
<el-form-item label="姓名" prop="personal_name" required>
<el-input
v-model="ruleForm.personal_name"
readonly
:disabled="disabledInfo"
placeholder="请输入您的姓名"
/>
</el-form-item>
<el-form-item label="班级" prop="class_name" required>
<el-input
v-model="ruleForm.class_name"
readonly
:disabled="disabledInfo"
placeholder="请输入您的班级"
/>
</el-form-item>
<el-form-item label="Sofia ID" prop="sofia_id" required>
<el-input
v-model="ruleForm.sofia_id"
readonly
:disabled="disabledInfo"
placeholder="请输入您的Sofia ID"
/>
</el-form-item>
<el-form-item label="重修学期" prop="semester_name">
<el-select
v-model="ruleForm.semester_name"
placeholder="请选择"
@change="onSemesterChange"
:disabled="disabledInfo"
>
<el-option v-for="item in semesterList" :key="item.id" :value="item.semester_name"></el-option>
</el-select>
</el-form-item>
<el-form-item label="重修课程" prop="course_id">
<el-checkbox-group v-model="ruleForm.course_id">
<el-checkbox
v-for="item in courseList"
:label="item.id"
:key="item.id"
:disabled="disabledInfo"
>{{item.course_name}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<div style="padding-left:110px" v-if="hasPay">
<div class="score" v-if="detail.form">{{detail.form.score_sum_str}}</div>
<pay :datalist="ruleForm.payment_instrument" :disabled="disabledPay || isView"></pay>
</div>
<template v-if="isView && detail.status !== -1">
<el-form-item label="审核时间">
<el-input :value="detail.approve_time1" readonly />
</el-form-item>
<el-form-item label="备注" prop="content">
<el-input
type="textarea"
:autosize="{ minRows: 5 }"
:value="detail.remark"
readonly
/>
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" @click="onSubmit" v-if="!isView">保存并提交</el-button>
<el-button type="info" @click="onAbort" v-if="hasAbort">撤回申请</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import * as api from '../api'
import Pay from '../components/pay'
export default {
props: {
isAdd: { type: Boolean, default: false },
isEdit: { type: Boolean, default: false },
isView: { type: Boolean, default: false }
},
components: { Pay },
data() {
return {
detail: {},
ruleForm: {
personal_name: '',
class_name: '',
sofia_id: '123',
semester_name: '',
course_id: [],
payment_instrument: []
},
rules: {
personal_name: [{ required: true, message: '请输入您的姓名', trigger: 'blur' }],
class_name: [{ required: true, message: '请输入您的班级', trigger: 'blur' }],
semester_name: [{ required: true, message: '请输入您的挂科学期', trigger: 'blur' }],
course_name: [{ required: true, message: '请选择您的重修课程名称', trigger: 'blur' }],
course_id: [{ required: true, message: '请选择您的重修课程名称', trigger: 'blur' }]
},
semesterList: [], // 学期列表
courseList: [] // 课程列表
}
},
watch: {
semesterId(value) {
value && this.getCourses()
}
},
computed: {
pid() {
return this.$route.params.id
},
// 事务ID
affairId() {
return this.$route.query.id
},
// 学期ID
semesterId() {
const found = this.semesterList.find(item => item.semester_name === this.ruleForm.semester_name)
return found ? found.id : ''
},
// 撤回
hasAbort() {
return this.isEdit && this.detail.status === -1
},
// 支付凭证
hasPay() {
return !this.isAdd && this.detail.file_put !== 0
},
// 禁用信息部分
disabledInfo() {
return this.isView || (this.isEdit && this.detail.status !== -1)
},
// 禁用支付凭证模块
disabledPay() {
return this.isView || (this.isEdit && this.detail.file_put === 2)
}
},
methods: {
init() {
// 获取学期
this.getSemesters()
if (this.isEdit || this.isView) {
this.getDetail()
} else {
// 获取学生信息
this.getStudent()
}
},
// 获取学生信息
getStudent() {
api.getStudent().then(response => {
const data = this.$_.pick(response, ['personal_name', 'class_name', 'sofia_id'])
Object.assign(this.ruleForm, data)
})
},
// 获取学期
getSemesters() {
api.getSemesters().then(response => {
this.semesterList = response.data
const [first] = this.semesterList
// 设置默认值
if (first && !this.isEdit) {
this.ruleForm.semester_name = first.semester_name
}
})
},
onSemesterChange() {
this.ruleForm.course_id = []
},
// 获取课程
getCourses() {
api.getCourses(this.semesterId).then(response => {
this.courseList = response.data
})
},
// 获取详情
getDetail() {
api.getAffair(this.pid).then(response => {
const form = response.form
if (form.course_name_arr) {
form.course_id = form.course_name_arr.map(item => item.course_id)
}
// 设置表单数据
const ruleForm = this.$_.pick(response.form, [
'personal_name',
'class_name',
'sofia_id',
'semester_name',
'course_id',
'payment_instrument'
])
Object.assign(this.ruleForm, ruleForm)
// 设置接口返回的数据
this.detail = response
})
},
// 添加
addRequest(data) {
api.createAffair(data).then(response => {
this.$message.success(response.message)
this.$router.push({ path: '/app/affairs-hall/hall', query: { index: 2 } })
})
},
// 修改
updateRequest(data) {
api.updateAffair(this.pid, data).then(response => {
this.$message.success(response.message)
this.$router.push({ path: '/app/affairs-hall/hall', query: { index: 2 } })
})
},
// 提交
onSubmit() {
this.$refs.form
.validate()
.then(response => {
const data = Object.assign({ affair_id: this.affairId }, this.ruleForm)
data.course_id = this.ruleForm.course_id.join(',')
data.payment_instrument = JSON.stringify(this.ruleForm.payment_instrument)
this.isEdit ? this.updateRequest(data) : this.addRequest(data)
})
.catch(() => {
this.$message.error('请完善表单信息')
})
},
// 撤回
onAbort() {
this.$confirm('你确定要撤回重修申请吗?撤回后,需要重新提交申请并由教务审批。', {
confirmButtonText: '确定',
cancelButtonText: '取消'
})
.then(() => {})
.catch(() => {})
}
},
mounted() {
this.init()
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-form--label-top .el-form-item__label {
line-height: 1;
}
.score {
margin-bottom: 20px;
color: #b80037;
}
</style>
<template>
</template>
<script>
export default {
}
</script>
......@@ -144,7 +144,8 @@ export default {
!this.ckeditor && (this.ckeditor = CKEDITOR.replace('editor', {
height: 300,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
filebrowserImageUploadUrl: '/api/ck/form/ckeditor-upload',
fileTools_requestHeaders: { tenant: 'sofia' },
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: ['Source', '-', 'Save', 'NewPage', 'Preview'] },
......
......@@ -3,7 +3,7 @@
<template v-if="newLiveMsg.live">
<div class="live-msg">
<div class="txt">直播提醒:</div>
<div class="txt">您的直播课程 《{{newLiveMsg.course_name}}》 将于 {{newLiveMsg.live.start_time}} 开始</div>
<div class="txt">您的直播课程 《{{ newLiveMsg.course_name }}》 将于 {{ newLiveMsg.live.start_time }} 开始</div>
<el-button class="in-btn" type="primary" size="small" round @click="goLive">进入直播</el-button>
</div>
</template>
......@@ -15,14 +15,16 @@
<ul>
<template v-for="(item1, index1) in item.arrItem">
<li
:class="['tab', (item.selectIndex == index1 ? 'active' : '')]"
:class="['tab', item.selectIndex == index1 ? 'active' : '']"
@click="selFindSelect"
:key="index1"
:data-index="index1"
:data-i="index"
:data-key="item.key"
:data-val="item1.val"
>{{item1.name}}</li>
>
{{ item1.name }}
</li>
</template>
</ul>
</div>
......@@ -61,20 +63,20 @@
</template>
</div>
<div class="right-bd">
<div class="title" @click="goCourseContent(item)">{{item.title}}</div>
<div class="title" @click="goCourseContent(item)">{{ item.title }}</div>
<div class="tags">
<template v-for="(item1, index) in item.arrTab">
<span v-bind:key="index">{{item1}}</span>
<span v-bind:key="index">{{ item1 }}</span>
</template>
</div>
<div class="time">
{{item.status}}&emsp;&emsp;
<em>{{item.time}}</em>
{{ item.status }}&emsp;&emsp;
<em>{{ item.time }}</em>
</div>
<!-- <div class="progress">
{{ $t('pages.learn.course.progress') }}&emsp;<el-progress :percentage="item.progress > 99.5 ? 100 : item.progress" color="#b49441"></el-progress>
</div>-->
<div class="right-sel">{{item.myStatus}}</div>
<div class="right-sel">{{ item.myStatus }}</div>
<el-button
class="in-btn"
type="primary"
......@@ -82,7 +84,8 @@
round
@click="goCourseContent(item)"
v-show="canOpenCourse(item.id)"
>{{ $t('pages.learn.course.showCourse') }}</el-button>
>{{ $t('pages.learn.course.showCourse') }}</el-button
>
</div>
</li>
</template>
......@@ -137,13 +140,10 @@ export default {
clearInterval(this.timeInterval)
this.timeInterval = null
}
this.timeInterval = setInterval(() => {
cAction.Player.getNewLiveMsg().then(json => {
if (json.status === 200) {
this.newLiveMsg = json.data
}
})
}, 3000)
// 获取最新直播
this.getLatestLive()
// 定时获取最新直播
this.timeInterval = setInterval(this.getLatestLive, 10000)
this.find.forEach(k => {
k.arrItem = k.arrItem.filter(l => Number(l.show) === 1)
})
......@@ -249,11 +249,30 @@ export default {
},
/* 直接进直播 */
goLive() {
if (this.canOpenCourse(this.newLiveMsg.course_id)) {
this.$router.push({ path: `/player/${this.newLiveMsg.course_id}/live/${this.newLiveMsg.live.id}` })
} else {
if (!this.canOpenCourse(this.newLiveMsg.course_id)) {
this.$message.error('尚未开通该课程的观看权限')
return
}
if (this.newLiveMsg.live.type === 8) {
window.open(this.newLiveMsg.live.record_url || this.newLiveMsg.live.join_url)
} else {
this.$router.push({
name: 'viewerCourseChapter',
params: { cid: this.newLiveMsg.course_id, id: this.newLiveMsg.chapter_id }
})
}
},
// 获取最新直播
getLatestLive() {
cAction.Player.getNewLiveMsg()
.then(json => {
if (json.status === 200) {
this.newLiveMsg = json.data
}
})
.catch(e => {
this.$message.error(e.message)
})
}
}
}
......
......@@ -4,11 +4,12 @@
<div class="detail-box">
<div class="box-thd">
<div class="title" @click="noWantThisCourse">{{headerInfo.title}}
<!-- <template v-if='headerInfo.isStart && tabs[1].chapterList.currentChapterId'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn' :data-cid='cid' :data-sid='sid' :data-type='tabs[1].chapterList.currentVideoProvider' :data-vid='tabs[1].chapterList.currentChapterId'>继续学习</el-button>
<!--
<template v-if='headerInfo.isStart && tabs[1].chapterList.currentChapter'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn(tabs[1].chapterList.currentChapter)'>继续学习</el-button>
</template>
<template v-else-if='headerInfo.isStart'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn' :data-cid='cid' :data-sid='sid' :data-type='firstVideo.video_provider' :data-vid='firstVideo.vid'>开始学习</el-button>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn(firstVideo)'>开始学习</el-button>
</template>
<template v-else>
<el-button class="rbtn" type="primary" size="mini" @click='wantThisCourse'>选课</el-button>
......@@ -151,7 +152,7 @@
<div class='tt'>{{item1.title}}</div>
<template v-for='(item2, index) in item1.arr'>
<div v-bind:key="index" class='rd'>
<div class='col3-td1' :data-sid='item1.sid' :data-cid='item1.cid' :data-vid='item2.vid' :data-type='item2.type' :data-duration='item2.duration' @mousedown="jumpVAOrfinishVA($event)">{{item2.name}}</div>
<div class='col3-td1' :data-sid='item1.sid' :data-cid='item1.cid' :data-vid='item2.vid' :data-id='item2.id' :data-type='item2.type' :data-duration='item2.duration' @mousedown="jumpVAOrfinishVA($event)">{{item2.name}}</div>
<div class='col3-td2'>{{item2.time}}</div>
<div class='col3-td3'>{{item2.progress}}</div>
</div>
......@@ -215,8 +216,6 @@
</el-row>
</div>
</div>
<!-- v-model="setArtContent.content" -->
<!-- <textarea id="editor"></textarea> -->
</template>
<script>
......@@ -460,7 +459,6 @@ export default {
this.tabs[0].content = json.tabs0Content
this.tabs[1].chapterList = json.tabs1ChapterList
json.tabs3richTest && (this.tabs[3].richText = json.tabs3richTest)
// 设置开始学习的视频
const courseList = json.tabs1ChapterList.course
for (let i = 0; i < courseList.length; i++) {
......@@ -504,12 +502,8 @@ export default {
window.addEventListener('resize', this.resizeRoot.bind(this), false)
/* 实时刷新数据 */
if (this.timeHeart) { clearInterval(this.timeHeart); this.timeHeart = null }
this.timeHeart = setInterval(() => {
this.updatePages()
}, 3000)
setTimeout(() => {
this.domLength = $('.lhhId').length
}, 1000)
// this.timeHeart = setInterval(this.updatePages(), 3000)
this.arrFn = this.initBindKeyfn()
},
destroyed () {
window.removeEventListener('resize', this.resizeRoot.bind(this), false)
......@@ -539,7 +533,8 @@ export default {
!this.ckeditor && (this.ckeditor = CKEDITOR.replace('editor', {
height: 300,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
filebrowserImageUploadUrl: '/api/ck/form/ckeditor-upload',
fileTools_requestHeaders: { tenant: 'sofia' },
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: ['Source', '-', 'Save', 'NewPage', 'Preview'] },
......@@ -572,43 +567,28 @@ export default {
/**
* 课程内容 - 列表展开或者跳转
*/
clickJumpOrStatus (e) {
const data = e.currentTarget.dataset
const flag = data.status
clickJumpOrStatus (index, data) {
const flag = !!data.chapters.length
if (flag) {
const index = data.index
const json = this.tabs
const temp = json[1].chapterList.course[index]
temp.isUp = !temp.isUp
} else {
/* 进入详情页,不管是哪个,都存localstorage */
window.localStorage.setItem('headerInfo', JSON.stringify(this.headerInfo))
const course = this.tabs[1].chapterList.course[data.index]
const cid = data.cid
if (course.type === 'course_info') {
this.$router.push({ path: `/player/${cid}/course-info/course_info` })
} else if (course.type === 'course_work') {
if (!this.headerInfo.survey) {
this.$message('请先填写教学评估,然后完成大作业。')
return false
}
this.$router.push({ path: `/player/${cid}/course-work/course_work` })
} else if (course.type === 'teach_evaluation') {
/* 暂时 不增加 手机端 */
// let w = document.documentElement.clientWidth
this.$router.push({ path: `/survey/${cid}` })
// if (w > 767) {
// this.$router.push({ path: `/survey/${cid}` })
// } else {
// this.$router.push({ path: `/survey-phone/${cid}` })
// }
} else if (course.type === 'course_exam') {
const _id = course.data[0].id
this.$router.push({ path: `/player/${cid}/chapter-exam/${_id}` })
} else if (course.type === 'exam') {
this.$router.push({ path: `/player/${cid}/exam/${course.examId}` })
const { sid, cid } = data
// 课程大作业
if (data.id === 'course_work' && !this.headerInfo.survey) {
this.$message('请先填写教学评估,然后完成大作业。')
return
}
// 教学评估
if (data.id === 'teach_evaluation') {
const { sid, cid } = this.$route.params
this.$router.push({ name: 'survey', params: { sid, cid } })
return
}
this.$router.push({ name: 'viewerCourseChapter', params: { sid, cid, id: data.id } })
}
},
/* 直接跳转打开新页面 */
......@@ -616,8 +596,8 @@ export default {
cAction.Player.getChapterList(cid, sid, _id).then(json => {
this.live = (json.curJson && json.curJson.live) || {}
if (this.live.id) {
if (this.live.record_id && this.live.live_status === 103) {
this.live.url = 'https://view.csslcloud.net/api/view/callback?recordid=' + this.live.record_id + '&roomid=' + this.live.room_id + '&userid=' + this.live.user_id + '&autoLogin=true&viewername=' + (this.live.viewer_name || '匿名') + '&viewertoken=' + this.live.viewer_token // + '&groupid=xxx'
if (this.live.live_status === 2 && this.live.enable_record && this.live.record_url) {
this.live.url = this.live.record_url
} else {
this.live.viewer_name = window.G.UserInfo.student_info.personal_name || window.G.UserInfo.nickname
this.live.url = 'https://view.csslcloud.net/api/view/index?roomid=' + this.live.room_id + '&userid=' + this.live.user_id + '&autoLogin=true&viewername=' + (this.live.viewer_name || '匿名') + '&viewertoken=' + this.live.viewer_token // + '&groupid=xxx'
......@@ -650,79 +630,56 @@ export default {
/**
* 跳转到对应音视频播放页
*/
jumpToOtherVA (e) {
jumpToOtherVA (data) {
/* 如果未选课,不能查看课程内容 */
if (!this.headerInfo.isStart) {
this.$message.error('先选课,才能学习')
return
}
let _data = e.target.dataset
if (!/name/gi.test(e.target.className)) {
_data = e.target.parentElement.dataset
const { sid, cid, vid, type } = data
/* 进入详情页,不管是哪个,都存localstorage */
window.localStorage.setItem('headerInfo', JSON.stringify(this.headerInfo))
if (type === 1) {
return
}
const cid = _data.cid
const _id = _data.vid
const type = _data.type
if (!_data.hasva) {
/* 进入详情页,不管是哪个,都存localstorage */
window.localStorage.setItem('headerInfo', JSON.stringify(this.headerInfo))
/* 如果存在 - 课后习题类型(chapterExam), type:3、work_type:1 */
/* 如果存在 - 课后问题类型(chapterWork), type:3、work_type:2 */
/* 如果存在 - 课后阅读类型(chapterRead), type:4 */
const i1 = _data.index
const i2 = _data.count
const _course = this.tabs[1].chapterList.course[i1]
if (_course && _course.chapters[i2]) {
if (_course.chapters[i2].type === 3) {
if (_course.chapters[i2].work_type === 1) {
this.$router.push({ path: `/player/${cid}/chapter-exam/${_id}` })
} else if (_course.chapters[i2].work_type === 2) {
this.$router.push({ path: `/player/${cid}/chapter-work/${_id}` })
}
} else if (_course.chapters[i2].type === 4) {
this.$router.push({ path: `/player/${cid}/chapter-read/${_id}` })
} else if (_course.chapters[i2].type === 5) {
const status = _course.chapters[i2].live.live_status
if (status !== 0 && status !== 1 && status !== 103) {
this.$message.error(_course.chapters[i2].live.statusStr)
return
}
const enableRecord = _course.chapters[i2].live.enable_record
if (status === 103 && enableRecord !== undefined && enableRecord !== null && !enableRecord) {
this.$message.info('该直播没有回放')
return
}
/* 判别如果为 云课堂记录 id 则直接进入 云课堂 */
if (this.cloudClassUrls[_id]) {
const viewerName = window.G.UserInfo.student_info.personal_name || window.G.UserInfo.nickname
const url = this.cloudClassUrls[_id] + '&viewername=' + viewerName + '&autoLogin=true'
window.open(url)
return
}
// cc直播
if (type === 5) {
const live = data.live
const status = live.live_status
if (status === 2 && !live.enable_record) {
this.$message.error(live.statusStr)
return
}
if (this.isOpenNewTabFlag) {
this.openNewTab(0, cid, _id)
} else {
this.$router.push({ path: `/player/${cid}/live/${_id}` })
}
}
/* 判别如果为 云课堂记录 id 则直接进入 云课堂 */
if (this.cloudClassUrls[vid]) {
const viewerName = window.G.UserInfo.student_info.personal_name || window.G.UserInfo.nickname
const url = this.cloudClassUrls[vid] + '&viewername=' + viewerName + '&autoLogin=true'
window.open(url)
return
}
// 新窗口打开
if (this.isOpenNewTabFlag) {
this.openNewTab(sid, cid, vid)
return
}
this.$message.error('点击频率过快,系统反应不过来,请稍后再试,003')
}
// zoom直播
if (type === 8) {
window.open(data.live.record_url || data.live.join_url)
return
}
this.$router.push({ path: `/player/${cid}/chapter-video/${_id}/${type}` })
this.$router.push({ name: 'viewerCourseChapter', params: { sid, cid, id: data.id } })
},
/**
* 开始学习或继续学习 - 跳转到对应音视频播放页
*/
startLearn (e) {
const _data = e.currentTarget.dataset
const cid = _data.cid
const vid = _data.vid
const type = _data.type
this.$router.push({ path: `/player/${cid}/chapter-video/${vid}/${type}` })
startLearn (data) {
if (data.id) {
this.$router.push({ name: 'viewerCourseChapter', params: { sid: this.sid, cid: this.cid, id: data.id } })
} else {
this.$message.error('当前暂无点播课程')
}
},
/**
* 退课 - 隐藏功能,点击 标题15次,进行退课
......@@ -758,11 +715,11 @@ export default {
const _cid = data.cid
const _vid = data.vid
const _duration = data.duration
const type = data.type
const _id = data.id
/* 字母 o */
if (e.keyCode === 79) {
/* 直接跳转 进入 继续学习 */
this.$router.push({ path: `/player/${_cid}/chapter-video/${_vid}/${type}` })
this.$router.push({ name: 'viewerCourseChapter', params: { cid: _cid, id: _id } })
}
/* 字母 f */
if (e.keyCode === 70) {
......
......@@ -5,37 +5,29 @@
<img class="logo" src="../../assets/images/bank-logo2.png" />
<div class="user">
<div class="nickname">{{ nickName }}</div>
<div class="logout" @click="logout">{{$t('components.learnSysLayout.sideBar.outLogin')}}</div>
<div class="logout" @click="logout">{{ $t('components.learnSysLayout.sideBar.outLogin') }}</div>
</div>
</div>
<div class="hint" v-if="latest" @click="onClick(latest.live, latest.course_id)">
<div class="left">
<div class="left-1">{{$t('live.liveReminder')}}</div>
<div
class="left-2"
>{{latest.course_name}}{{$t('live.startTime', { time: latest.live.start_time })}}</div>
<div class="left-1">{{ $t('live.liveReminder') }}</div>
<div class="left-2">
{{ latest.course_name }}{{ $t('live.startTime', { time: latest.live.start_time }) }}
</div>
</div>
<div class="right">{{$t('live.goLive')}}</div>
<div class="right">{{ $t('live.goLive') }}</div>
</div>
<div class="tips">{{$t('live.replayTips')}}</div>
<div class="tips">{{ $t('live.replayTips') }}</div>
<div class="live-list">
<template v-for="item in dataList">
<div
class="live-item"
:key="subitem.id"
v-for="subitem in item.live"
@click="onClick(subitem, item.course_id)"
>
<h4 class="live-item-name">{{item.course_name}}</h4>
<div class="live-item" :key="subitem.id" v-for="subitem in item.live" @click="onClick(subitem)">
<h4 class="live-item-name">{{ item.course_name }}</h4>
<div class="live-item-main">
<img class="live-item-pic" :src="item.curriculum.curriculum_picture" />
<div class="live-item-content">
<div class="live-item-content__title">{{subitem.chapter_name}}</div>
<div class="live-item-content__time">{{subitem.start_time}}</div>
<div
class="live-item-content__status"
v-if="!(subitem.live_status === 103 && !subitem.enable_record)"
>{{calcTimeText(subitem.start_time, subitem.live_status)}}</div>
<div class="live-item-content__title">{{ subitem.topic }}</div>
<div class="live-item-content__time">{{ subitem.start_time }}</div>
<div class="live-item-content__status">{{ calcTimeText(subitem.start_time, subitem.live_status) }}</div>
</div>
</div>
</div>
......@@ -69,7 +61,8 @@ export default {
cAction.Other.outLogin()
.then(str => {
window.G.UserInfo = {}
this.$router.push({ path: '/login/index' })
// this.$router.push({ path: '/login/index' })
window.location.href = webConf.others.loginUrl
})
.catch(e => {
this.$message.error(e.message)
......@@ -138,17 +131,21 @@ export default {
this.$message.error('尚未开通该课程的观看权限')
return
}
const { live_status: liveStatus, live_type: liveType = 'live', start_time: liveTime } = data
data.live_status = parseInt(data.live_status)
const { live_status: liveStatus, type: liveType = 5, start_time: liveTime } = data
let message = this.calcTimeText(liveTime, liveStatus)
if (liveStatus === 103 && data.enable_record !== 1) {
if (liveStatus === 2 && !data.enable_record) {
message = this.$t('live.noPlayback')
this.message && this.message.close()
this.message = this.$message({ type: 'warning', offset: 0, message })
return
}
if (liveType === 'cloud') {
// 打开云课堂
this.openCloudClass(data, message)
return
}
if (liveType === 'live') {
if (liveType === 5) {
// 打开云直播
this.openCloudLive(data, message)
return
......@@ -158,28 +155,18 @@ export default {
this.openMeeting(data, message)
return
}
// 其他直播类型直接打开
if (liveStatus === 1) {
// 进行中
this.openNewWindow(data.join_url)
} else if (liveStatus === 103) {
// 查看回放
this.openNewWindow(data.record_url)
} else {
this.message && this.message.close()
this.message = this.$message({ type: 'warning', offset: 0, message })
}
this.openNewWindow(data.record_url || data.join_url)
},
// 打开云课堂
openCloudClass(data, message) {
// https://doc.bokecc.com/class/developer/api/login.html
const liveStatus = data.live_status
data.viewer_name = data.viewer_name || window.G.UserInfo.student_info.personal_name
data.user_name = data.user_name || window.G.UserInfo.student_info.personal_name
if (liveStatus === 1) {
// 进行中
const url = `http://view.csslcloud.net/api/view/index?roomid=${data.room_id}&userid=${data.user_id}&autoLogin=true&viewername=${data.username}&viewertoken=${data.password}`
const url = `http://view.csslcloud.net/api/view/index?roomid=${data.room_id}&userid=${data.account_id}&autoLogin=true&viewername=${data.username}&viewertoken=${data.password}`
this.openNewWindow(url)
} else if (liveStatus === 103) {
} else if (liveStatus === 2) {
// 查看回放
const replayUrl = data.record_url.replayUrl
const url = replayUrl
......@@ -195,19 +182,16 @@ export default {
openCloudLive(data, message) {
// https://doc.bokecc.com/live/Appendix_1.html
const liveStatus = data.live_status
data.viewer_name = data.viewer_name || this.nickName
if (liveStatus === 1) {
// 进行中
const url = `https://view.csslcloud.net/api/view/index?roomid=${data.room_id}&userid=${data.user_id}&autoLogin=true&viewername=${data.viewer_name}&viewertoken=${data.viewer_token}`
this.openNewWindow(url)
} else if (liveStatus === 103 && data.enable_record === 1) {
data.user_name = data.user_name || this.nickName
if (liveStatus === 2 && data.enable_record === 1) {
// enable_record 0:不启用回放 1:开启回放
// 查看回放
const url = `https://view.csslcloud.net/api/view/callback?recordid=${data.record_id}&roomid=${data.room_id}&userid=${data.user_id}&autoLogin=true&viewername=${data.viewer_name}&viewertoken=${data.viewer_token}`
this.openNewWindow(url)
// const url = `https://view.csslcloud.net/api/view/callback?recordid=${data.record_id}&roomid=${data.room_id}&userid=${data.account_id}&autoLogin=true&viewername=${data.user_name}&viewertoken=${data.play_pass}`
this.openNewWindow(data.record_url)
} else {
this.message && this.message.close()
this.message = this.$message({ type: 'warning', offset: 0, message })
// 查看直播
const url = `https://view.csslcloud.net/api/view/index?roomid=${data.room_id}&userid=${data.account_id}&autoLogin=true&viewername=${data.user_name}&viewertoken=${data.play_pass}`
this.openNewWindow(url)
}
},
// 打开腾讯会议
......@@ -226,16 +210,16 @@ export default {
// 计算日期
calcTimeText(liveTime, liveStatus) {
const map = {
0: this.$t('live.notStarted'),
1: this.$t('live.liveStreaming'),
2: this.$t('live.liveEnd'),
101: this.$t('live.liveEndNotVideo'),
102: this.$t('live.liveEndNotVideo'),
103: this.$t('live.watchReplay')
1: this.$t('live.notStarted'),
2: this.$t('live.liveStreaming'),
3: this.$t('live.liveEnd'),
4: this.$t('live.start'),
5: this.$t('live.notStarted'),
11: this.$t('live.liveEnd')
}
let result = map[liveStatus] || liveTime
if (liveStatus === 0 && liveTime) {
if (liveStatus === 1 && liveTime) {
liveTime = liveTime.replace(/-/g, '/')
const time = (new Date(liveTime).getTime() - new Date().getTime()) / 1000 || 0
if (time <= 5 * 60) {
......
<template>
<div>
<div class="con-title">公告通知</div>
<div class="con-title">通知</div>
<div class="con-box">
<el-collapse accordion v-model="activeNames" @change="handleChange">
<el-collapse accordion v-model="activeName" @change="handleChange" v-if="msgList.length">
<template v-for="(item, index) in msgList">
<el-collapse-item v-bind:key="index" :name="index">
<template slot="title">
<template v-if="!item.isShow">
<el-badge is-dot class="item">{{ item.title }}</el-badge>
</template>
<template v-if="item.isShow">{{ item.title }}</template>
<el-badge :is-dot="!item.isShow" class="item">系统通知</el-badge>
</template>
<div v-html="item.text"></div>
</el-collapse-item>
</template>
</el-collapse>
<div style="height: 0.3rem;"></div>
<div class="empty" v-else>您没有收到任何通知</div>
<el-pagination
:current-page.sync="page.page"
:page-size="page.page_size"
layout="total, prev, pager, next, jumper"
background
layout="prev, pager, next"
:current-page.sync="page.currentPage"
:page-size="page.limit"
:total="page.total"
@current-change="handleCurrentChange"
:hide-on-single-page="true"
@current-change="handleCurrentChange"
style="margin:0 auto;padding:40px 0;text-align:center;"
></el-pagination>
</div>
</div>
......@@ -32,10 +31,9 @@
import cAction from '@action'
export default {
components: {},
data() {
return {
activeNames: [],
activeName: null,
msgList: [],
page: {
page: 1,
......@@ -44,35 +42,26 @@ export default {
}
}
},
mounted() {
beforeMount() {
this.getData()
},
methods: {
handleChange(val) {
if (typeof val === 'number' && this.msgList[val].isShow === 0) {
cAction.Other.setMsgWmp(this.msgList[val].id)
.then(json => {
this.getData()
})
.catch(e => {
this.$message.error(e.message)
cAction.Other.setMyMsg(this.msgList[val].id).then(json => {
this.getData()
cAction.Other.getNavMsg().then(data => {
this.$store.commit('myMsg', data.num)
})
})
}
},
getData() {
const pageParam = {
page: this.page.page,
page_size: this.page.page_size
}
cAction.Other.getMyMsg(pageParam)
.then(json => {
this.msgList = json.list
this.page.total = json.count
this.$store.commit('myMsg', json.countNum)
})
.catch(e => {
this.$message.error(e.message)
})
const params = { offset: this.page.offset, limit: this.page.limit }
cAction.Other.getMyMsg(params).then(response => {
this.msgList = response.list
this.page.total = parseInt(response.count)
})
},
handleCurrentChange() {
this.getData()
......@@ -85,36 +74,10 @@ export default {
.el-collapse-item__header {
line-height: 15px;
}
.total-core,
.compulsory-core,
.elective-core {
margin-top: 0.1rem;
font-size: 16px;
line-height: 36px;
.title {
float: left;
// margin-left: 5px;
}
.core {
float: right;
margin-right: 3px;
font-size: 12px;
}
}
.color-box {
padding: 0.1rem 0 0.2rem 0;
text-align: right;
.color {
display: inline-block;
vertical-align: text-bottom;
width: 14px;
height: 14px;
}
.txt {
display: inline-block;
padding: 0 0.2rem 0 0.1rem;
font-size: 14px;
line-height: 36px;
}
.empty {
padding: 100px;
font-size: 18px;
color: #5f6368;
text-align: center;
}
</style>
......@@ -43,7 +43,6 @@ export default {
components: {},
data() {
const Gu = window.G.UserInfo
console.log(Gu)
return {
imgUrl: Gu.avatar || '',
user: {
......@@ -58,7 +57,7 @@ export default {
type: '',
lastModifiedDate: '',
size: '',
avatar: ''
file: ''
},
filesArr: []
}
......@@ -69,30 +68,7 @@ export default {
this.file.type = file.raw.type
this.file.lastModifiedDate = file.raw.lastModifiedDate
this.file.size = file.raw.size
this.file.avatar = file.raw
// try {
// /* 这个必然是IE */
// let stream = new ActiveXObject('ADODB.Stream') // eslint-disable-line
// stream.type = 1
// stream.open()
// // console.log(this.$refs.upFile.$el.children[0].children[1].files[0])
// debugger
// // stream.loadFromFile(filename)
// // text = stream.readText(adReadAll)
// // stream.close()
// } catch (e) {
// let reader = new FileReader() // eslint-disable-line
// reader.readAsBinaryString(this.$refs.upFile.$el.children[0].children[1].files[0]) // 这个读法是异步的
// reader.onloadend = () => {
// // 这个事件在读取结束后,无论成功或者失败都会触发
// if (reader.error) {
// /* 文件读取失败 */
// this.$message.error('文件读取失败,请重试')
// } else {
// this.file.avatar = reader.result
// }
// }
// }
this.file.file = file.raw
},
uploadFile() {
if (!/\.(jpg|jpeg|gif|png|bmp)$/gi.test(this.file.name)) {
......@@ -108,9 +84,8 @@ export default {
})
action.Other.uploadFile(this.file)
.then(data => {
this.imgUrl = data.avatar
window.G.UserInfo.avatar = data.avatar
this.filesArr.pop()
this.imgUrl = data.url
this.updateUser()
})
.catch(() => {
this.filesArr.pop()
......@@ -118,6 +93,13 @@ export default {
.finally(() => {
loading.close()
})
},
updateUser() {
action.Other.updateUser({
SsoBasicUser: { info: { avatar: this.imgUrl } }
}).then(data => {
window.G.UserInfo.avatar = this.imgUrl
})
}
}
}
......@@ -137,12 +119,9 @@ export default {
border-radius: 50%;
overflow: hidden;
img {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
transform: translate(-50%, -50%);
object-fit: cover;
}
}
.info {
......
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title">
<div>
<h3>{{exam.title}}</h3>
</div>
</div>
<template v-if="status.isStart">
<div class="play-paper-content">
<template v-if="exam.id">
<div class="exam">
<!-- <div style='text-align: center;'> -->
<!-- <div class='topic'> -->
<!-- <div class='tit'>{{exam.title}}</div> -->
<template v-if="exam.type === 2">
<template v-if="exam.score">
<div style="font-size: 18px;">分数:{{exam.score.total}}</div>
</template>
</template>
<template v-else-if="exam.type === 1">
<div class="no-exam">试卷批改中,请耐心等待</div>
</template>
<!-- </div> -->
<!-- </div> -->
<template v-if="(exam.type !== 1)">
<div
style="text-align: center;"
v-if="exam.paper_deadline"
>考试截止时间为:{{exam.paper_deadline}}</div>
<template v-for="question in exam.examination">
<!-- 单选题 -->
<template v-if="question.radioList.length">
<template v-for="(item, index) in question.radioList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(单选题)</div>
<el-radio-group class="radio-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-radio
v-bind:key="item1.id"
:label="item1.id"
:disabled="isSubmited"
:class="['radio', ((item.right_answer && !!exam.type) ? (item1.id === item.right_answer ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-radio>
</template>
</el-radio-group>
<template v-if="item.right_answer && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', (item.right_answer === item.user_answer ? 'success' : 'error')]"
>{{ item.user_answer | getRadioAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getRadioAnswer(item.options) }}
</div>
</template>
<div class="analysis" v-if="item.analysis">
<p>解析:</p>
<div v-html="item.analysis"></div>
</div>
</div>
</template>
</template>
<!-- 多选题 -->
<template v-if="question.checkboxList.length">
<template v-for="(item, index) in question.checkboxList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{question.radioList.length+index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(多选题)</div>
<el-checkbox-group class="checkbox-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-checkbox
v-bind:key="item1.id"
:label="item1.id"
:disabled="isSubmited"
:class="['checkbox', ((item.right_answer.length && !!exam.type) ? (isCheckboxChecked(item1.id, item.right_answer) ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-checkbox>
</template>
</el-checkbox-group>
<template v-if="item.right_answer.length && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', ((item.right_answer.length && isCheckboxRight(item.user_answer, item.right_answer)) ? 'success' : 'error')]"
>{{ item.user_answer | getCheckboxAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getCheckboxAnswer(item.options) }}
</div>
</template>
<div class="analysis" v-if="item.analysis">
<p>解析:</p>
<div v-html="item.analysis"></div>
</div>
</div>
</template>
</template>
<!-- 简答题 -->
<template v-if="question.shortAnswerList.length">
<template v-for="(item, index) in question.shortAnswerList">
<div class="q-group" :key="index">
<div
class="q-sa-title"
>{{question.radioList.length+question.checkboxList.length+index+1}}.&nbsp;&nbsp;简答题</div>
<div class="edit_html" v-html="item.content || ''"></div>
<v-editor v-model="item.user_answer" :disabled="isSubmited"></v-editor>
<div style="height: 10px;"></div>
<!-- 利用key值自动更新组件 -->
<component
:is="item.upload.type"
v-bind:key="item.upload.id + new Date().getTime()"
:item="item.upload"
:formData="item"
:isUpload="!exam.type"
></component>
<div class="result" v-if="item.check_comment">评语:{{item.check_comment}}</div>
<div class="analysis" v-if="item.analysis">
<p>解析:</p>
<div v-html="item.analysis"></div>
</div>
</div>
</template>
</template>
</template>
<div
:class="['btn', (isSubmited && 'on')]"
@click="submitExam"
:data-submit="isSubmited"
@mousedown="_SubmitMouseLeftDown()"
>{{isSubmited ? "已提交" : "提交"}}</div>
<div class="care">(注意:考试只有一次提交机会)</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</template>
</div>
</template>
</div>
</template>
<template v-else>
<div class="exam">
<!-- <p>考试须知:</p> -->
<div style="text-align: left;" v-if="exam.paper_deadline">考试截止时间为:{{exam.paper_deadline}}</div>
<template v-if="isExamTime">
<div
style="width: 25%;"
:class="['btn']"
@click="beginExam(true)"
@mousedown="beginExam(true)"
>开始考试</div>
</template>
</div>
</template>
</div>
</div>
</template>
<script>
import cAction from '@action'
import Base64 from 'Base64'
import VEditor from '@/components/editor.vue'
const A_Z = (function() {
const result = []
for (let i = 0; i < 26; i++) {
result.push(String.fromCharCode(65 + i))
}
return result
})()
var getLetter = val => {
return A_Z[val]
}
export default {
props: {
chapters: {
type: Array,
default() {
return []
}
},
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }
},
components: { VEditor },
filters: {
getLetter: getLetter,
getRadioAnswer: (val, arr) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === val) {
return getLetter(i)
}
}
},
getCheckboxAnswer: (val, arr) => {
let str = ''
for (let i = 0; i < val.length; i++) {
const tmpId = val[i]
for (let j = 0; j < arr.length; j++) {
if (arr[j].id === tmpId) {
str += getLetter(j) + ','
break
}
}
}
return str.substr(0, str.length - 1)
}
},
data() {
return {
_time: null, // 定时器,自动化提交
exam: {},
status: {
isStart: false,
startTime: '',
terminateTime: '',
serverTime: '',
examinationStatus: '',
type: 0,
isPublished: 0
}
}
},
watch: {
id: {
handler() {
this.init()
}
}
},
computed: {
// 当前章节
activeChatper() {
let found = null
for (const item of this.chapters) {
found = item.children.find(subItem => subItem.id === this.id)
if (found) {
break
}
}
return found || {}
},
// 是否是考试时间
isExamTime() {
if (!this.exam.paper_deadline) {
return true
}
// 大于开始时间,小于结束时间
const endTime = +new Date(this.exam.paper_deadline)
const currentTime = new Date().getTime()
return currentTime < endTime
},
// 考试完成
isExamComplete() {
// 考试完成,批改完成并且公布成绩
return this.exam.is_published === 1 && this.exam.type === 2
},
// 是否提交
isSubmited() {
return this.exam.type === 1 || this.exam.type === 2
}
},
mounted() {
this.init()
this.$emit('changeSideBar', '')
setTimeout(() => {
if (window.document.getElementById('switch-btn')) {
window.document.getElementById('switch-btn').style.display = 'none'
}
}, 500)
},
destroyed() {
if (this._time) {
clearInterval(this._time)
this._time = null
}
if (window.document.getElementById('switch-btn')) {
window.document.getElementById('switch-btn').style.display = 'block'
}
},
methods: {
isCheckboxRight: (val, arr) => {
let flag = true
for (let i = 0; i < arr.length; i++) {
const tmpId = arr[i]
let j = 0
for (; j < val.length; j++) {
if (val[j] === tmpId) {
break
}
}
if (j === val.length) {
flag = false
break
}
}
return flag
},
isCheckboxChecked: (val, arr) => {
let i = 0
for (; i < arr.length; i++) {
if (arr[i].id === val || arr[i] === val) {
return true
}
}
return false
},
init() {
const data = this.activeChatper.paper
const exam = {}
exam.id = data.id
exam.title = data.paper_title
exam.score = {}
exam.type = 3
exam.examination = data.examination.map(exam => {
for (let i = 0; i < exam.radioList.length; i++) {
exam.radioList[i].user_answer = ''
exam.radioList[i].right_answer = ''
exam.radioList[i].get_score = -1
}
for (let i = 0; i < exam.checkboxList.length; i++) {
exam.checkboxList[i].user_answer = []
exam.checkboxList[i].right_answer = []
exam.checkboxList[i].get_score = -1
}
for (let i = 0; i < exam.shortAnswerList.length; i++) {
exam.shortAnswerList[i].user_answer = ''
exam.shortAnswerList[i].get_score = -1
exam.shortAnswerList[i].attachments = []
exam.shortAnswerList[i].upload = {
type: 'upload-form',
label: '附件上传:',
model: 'attachments',
action: webConf.apiBaseURL + '/util/upload-file',
data: {
special: 'exam'
},
attrs: {
multiple: true,
headers: {
tenant: 'sofia'
}
},
html: `
<div style="color: #72818c; font-size: 14px;">
<p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M。</p>
</div>
`
}
}
return exam
})
exam.paper_deadline = data.paper_deadline
this.exam = JSON.parse(JSON.stringify(exam))
// this.loadExamStatus()
// this.loadExamInfo()
this.loadAjax()
if (this._time) {
clearInterval(this._time)
this._time = null
}
this._time = setInterval(() => {
// this.loadExamStatus()
if (!this.isSubmited && this.status.isStart) {
// console.log(11, '暂存')
this.submitExam({ submitType: true }) // 暂存, submitType: true 暂存;其他或不填为提交
}
/* 到时间 自动提交 */
if (
!this.isSubmited &&
this.status.isStart &&
new Date(this.status.terminateTime).getTime() -
new Date(this.status.serverTime).getTime() <=
5000
) {
this.submitExam({ submitType: false, currentTarget: { dataset: {} } })
}
}, 10000)
},
/* 定时调用 - 考试状态 */
loadExamStatus() {
cAction.Player.getExamStatus(this.cid, this.sid, this.id)
.then(_data => {
if (_data.status && _data.status === 200) {
this.status.startTime = this.setTime(_data.start_time)
this.status.terminateTime = _data.terminate_time
this.status.serverTime = this.setTime(_data.server_time)
this.status.examinationStatus = _data.examination_status
} else {
this.$message.error('数据异常,请联系管理员')
}
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {})
},
/* 时间格式化(解决safari时间兼容问题); */
setTime(time) {
return time.replace(/-/g, '/')
},
/* 开始考试 */
beginExam(flag) {
this.status.isStart = true
// this.loadAjax(flag)
},
/* 加载题库基本数据 */
loadExamInfo() {
const loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
cAction.Player.getExamInfo(this.cid, this.sid)
.then(_data => {
this.exam = _data
this.exam.id = this.id
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
loading.close()
/* 正在考试,考试结束 */
this.beginExam()
})
},
/**
* 生命周期函数--监听页面加载
* @param flag 通过该字段 判别是点击进入考试 还是 自动调用
*/
loadAjax(flag) {
const loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
cAction.Player.getExamAnswer(this.cid, this.sid, this.id, {
paper_type: 0
})
.then(_data => {
if (_data.code === 8001) {
console.log(
'没有考试内容,认为是第一次答题,并且在答题期间,所以显示考试开始页面'
)
} else {
this.status.isStart = _data.type !== 3
this.exam.title = _data.title
this.$set(this.exam, 'type', _data.type)
this.exam.score = _data.score
this.exam.submitted_time = _data.submitted_time
this.exam.isPublished = _data.isPublished
this.status.type = _data.type
this.status.isPublished = _data.isPublished
this.exam.examination = this.exam.examination.map((exam, index) => {
const rawExam = _data.examination[index]
if (!rawExam) {
return exam
}
for (let i = 0; i < exam.radioList.length; i++) {
for (let j = 0; j < rawExam.radioList.length; j++) {
if (rawExam.radioList[j].id === exam.radioList[i].id) {
for (const k in rawExam.radioList[j]) {
exam.radioList[i][k] = rawExam.radioList[j][k]
}
}
}
}
for (let i = 0; i < exam.checkboxList.length; i++) {
for (let j = 0; j < rawExam.checkboxList.length; j++) {
if (rawExam.checkboxList[j].id === exam.checkboxList[i].id) {
for (const k in rawExam.checkboxList[j]) {
exam.checkboxList[i][k] = rawExam.checkboxList[j][k]
}
}
}
}
for (let i = 0; i < exam.shortAnswerList.length; i++) {
for (let j = 0; j < rawExam.shortAnswerList.length; j++) {
if (
rawExam.shortAnswerList[j].id === exam.shortAnswerList[i].id
) {
for (const k in rawExam.shortAnswerList[j]) {
exam.shortAnswerList[i][k] = rawExam.shortAnswerList[j][k]
}
}
}
}
return exam
})
}
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
// console.log(this.exam.type, this.exam.isPublished)
loading.close()
if (
this.status.isStart &&
this.exam.type !== 1 &&
this.exam.type !== 2
) {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
}
})
},
/**
* 提交试题
*/
submitExam(e) {
if (!e.submitType && e.currentTarget.dataset.submit) {
this.$message.error('已做过,不能再提交')
return
}
const body = { answers: [], type: !e.submitType ? 1 : 0, paper_type: 0 } // type: 0 缓存;type: 1 提交
for (let k = 0; k < this.exam.examination.length; k++) {
const exam = this.exam.examination[k]
const radioList = []
for (let i = 0; i < exam.radioList.length; i++) {
const tmp = exam.radioList[i]
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有单选题未做,不能提交')
return
}
radioList.push({ id: tmp.id, user_answer: tmp.user_answer })
}
const checkboxList = []
for (let i = 0; i < exam.checkboxList.length; i++) {
const tmp = exam.checkboxList[i]
if (!tmp.user_answer.length && !e.submitType) {
this.$message.error('还有多选题未做,不能提交')
return
}
checkboxList.push({ id: tmp.id, user_answer: tmp.user_answer })
}
const shortAnswerList = []
for (let i = 0; i < exam.shortAnswerList.length; i++) {
const tmp = exam.shortAnswerList[i]
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有简答题未做,不能提交')
return
}
shortAnswerList.push({
id: tmp.id,
user_answer: Base64.encode(tmp.user_answer),
attachments: tmp.attachments
})
}
body.answers[k] = { radioList, checkboxList, shortAnswerList }
}
body.answers = JSON.stringify(body.answers)
let loading = null
if (!e.submitType) {
loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
}
cAction.Player.submitExam(this.cid, this.sid, this.id, body)
.then(_res => {
if (e.submitType) {
// this.$message.success('暂存成功')
console.log('暂存成功')
return
}
if (_res.code === 200) {
this.$message.success('考试答卷提交成功')
// this.exam.type = 1
this.init()
console.log(111, 'this.loadAjax()')
} else {
this.$message.error(_res.data.error)
}
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
if (!e.submitType) {
loading.close()
}
})
},
_SubmitMouseLeftDown() {
const _fn1 = this.repeatExam.bind(this, false)
document.addEventListener('keydown', _fn1, false)
const _fn3 = function() {
document.removeEventListener('keydown', _fn1)
document.removeEventListener('mouseup', _fn3)
}
document.addEventListener('mouseup', _fn3, false)
},
/**
* 重做
*/
repeatExam(e, flag) {
let _flag = flag
/* 字母 f */
if (e.keyCode === 70) {
_flag = true
}
if (!_flag) {
return
}
const loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
cAction.chapterAction
.getExamDetail(this.sid, this.cid, this.id)
.then(_data => {
this.exam = {}
})
.catch(e => {
this.$message.error(e.message)
})
.finally(() => {
loading.close()
})
}
}
}
</script>
<style lang="scss" scoped>
.play {
.exam {
padding: 0;
}
.exam .topic {
display: inline-block;
margin-bottom: 0.1rem;
}
.exam .topic .tit {
margin: 0 auto;
padding: 0 0.2rem;
text-align: center;
font-size: 0.24rem;
color: #313131;
background: #fff;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.exam .topic .cur {
text-align: center;
font-size: 0.18rem;
color: #313131;
line-height: 0.4rem;
}
/* 循环 所有选择题 */
.exam .q-group {
padding: 0.1rem 0.1rem;
border-bottom: 1px solid #c9c9c97a;
overflow: hidden;
}
.exam .q-group .q-num {
float: left;
margin-right: 0.1rem;
font-size: 0.16rem;
color: #676a6c;
}
.exam .q-group .q-title {
float: left;
width: 90%;
font-size: 0.16rem;
color: #676a6c;
text-align: justify;
}
.exam .q-group .q-type {
float: right;
font-size: 0.16rem;
color: #676a6c;
}
.exam .q-group .radio-group {
float: left;
margin-top: 0.1rem;
width: 100%;
}
.exam .q-group .radio-group .radio {
display: block;
font-size: 0.18rem;
color: #3f3b3a;
line-height: 0.3rem;
margin-bottom: 0.1rem;
}
.exam .q-group .checkbox-group {
float: left;
margin-top: 0.1rem;
width: 100%;
}
.exam .q-group .checkbox-group .checkbox {
display: block;
font-size: 0.18rem;
color: #3f3b3a;
line-height: 0.3rem;
margin-bottom: 0.1rem;
}
.exam .q-group .radio-group .radio.error,
.exam .q-group .checkbox-group .checkbox.error {
color: #d80000;
}
.exam .q-group .radio-group .radio.success,
.exam .q-group .checkbox-group .checkbox.success {
color: #090;
}
.exam .q-group .result {
clear: both;
display: flex;
justify-content: flex-end;
font-size: 0.18rem;
color: #3f3b3a;
margin-right: 0;
}
.exam .q-group .result .stu {
display: inline-block;
}
.exam .q-group .result .stu.error {
color: #d80000;
}
.exam .q-group .result .stu.success {
color: #090;
}
.exam .q-group:last-child {
border-bottom: none;
}
.exam .btn {
margin: 0.2rem auto;
width: 60%;
height: 0.5rem;
line-height: 0.5rem;
font-size: 0.16rem;
text-align: center;
font-weight: 300;
color: #fff;
border-radius: 0.1rem;
background: #b49441;
cursor: pointer;
}
.exam .btn.on {
opacity: 0.5;
}
.exam .care {
font-size: 0.16rem;
color: #d80000;
text-align: center;
}
.exam .q-sa-title {
float: left;
width: 100%;
font-size: 0.16rem;
color: #676a6c;
text-align: justify;
}
}
.no-exam {
font-size: 0.24rem;
line-height: 19;
text-align: center;
}
.analysis {
clear: both;
display: flex;
margin: 20px 0;
padding: 0 20px;
border: 1px solid #c9c9c97a;
}
</style>
......@@ -7,29 +7,46 @@
</div>
</div>
<div class="play-paper-content play-chapter-work">
<ul>
<li v-for="(item, index) in questions" :key="index">
<div class="work-number">{{index + 1}}.</div>
<div class="work-title">
<div class="edit_html" v-html="item.question_content"></div>
</div>
<!-- 文本内容 -->
<v-editor v-model="item.descreption"></v-editor>
<!-- 上传附件 -->
<v-upload v-model="item.file_url">请上传对应的文件附件:</v-upload>
</li>
</ul>
<el-form ref="form" :disabled="disabled">
<ul>
<li v-for="(item, index) in questions" :key="index">
<div class="work-number">{{index + 1}}.</div>
<div class="work-title">
<div class="edit_html" v-html="item.question_content"></div>
</div>
<!-- 文本内容 -->
<v-editor v-model="item.descreption" :disabled="disabled"></v-editor>
<!-- 上传附件 -->
<v-upload v-model="item.file_url">请上传对应的文件附件:</v-upload>
</li>
</ul>
</el-form>
<template v-if="deadLine">
<p style="color: red">请于截止日期 {{deadLine}} 前提交</p>
</template>
<div class="area-btns">
<!-- 驳回状态 -->
<div class="area-btns" v-if="homeData.status === 1">
<el-button type="primary" @click="onReEdit">重新编辑</el-button>
<div class="play-paper-check">
<h4>作业被驳回,点击“重新编辑”按钮重新编辑内容再次提交</h4>
<div class="play-paper-check-item">
<b>驳回时间:</b>
{{homeData.checker_time}}
</div>
<div class="play-paper-check-item">
<b>驳回说明:</b>
<div class="edit_html" v-html="homeData.check_comments"></div>
</div>
</div>
</div>
<div class="area-btns" v-else>
<el-button
type="primary"
@click="submitWork"
:disabled="!!homeData.checker_time || deadLineFlag"
>{{homeData.checker_time ? '已批改' : '提交'}}</el-button>
:disabled="homeData.status === 0 || deadLineFlag"
>{{homeData.status === 0 ? '已批改' : '提交'}}</el-button>
<span class="help-info">&emsp;&emsp;在获老师批改之前,可以多次提交,将以最后一次提交为准</span>
<template v-if="homeData.checker_time">
<template v-if="homeData.status === 0 && homeData.checker_time">
<div class="play-paper-check">
<h4>
已获批改
......@@ -76,7 +93,8 @@ export default {
homeData: {},
deadLine: '',
deadLineFlag: false,
questions: []
questions: [],
disabled: false
}
},
watch: {
......@@ -107,8 +125,12 @@ export default {
})
cAction.Player.getHomework(this.sid, this.cid, this.id)
.then(data => {
if (Array.isArray(data)) {
return
}
this.homeData = data
// 状态处理
this.disabled = data.status === 0 || data.status === 1
const parseAnswers = JSON.parse(data.work_contents)
this.questions = this.questions.map(item => {
const found = parseAnswers.find(
......@@ -125,7 +147,6 @@ export default {
})
})
.catch(e => {
this.filesArr.pop()
this.$message.error(e.message)
})
.finally(() => {
......@@ -181,12 +202,16 @@ export default {
}
})
.catch(e => {
this.filesArr.pop()
this.$message.error(e.message)
})
.finally(() => {
loading.close()
})
},
// 重新编辑
onReEdit() {
this.disabled = false
this.homeData.status = -1
}
}
}
......
......@@ -194,7 +194,8 @@ export default {
!this.ckeditor && (this.ckeditor = CKEDITOR.replace('editor-courseWork', {
height: 600,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
filebrowserImageUploadUrl: '/api/ck/form/ckeditor-upload',
fileTools_requestHeaders: { tenant: 'sofia' },
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: ['Source', '-', 'Save', 'NewPage', 'Preview'] },
......
......@@ -13,22 +13,7 @@
<template v-if="status.type === 2 && status.isPublished === 1">
<template v-if="exam.score.total !== undefined">
<div
style="font-size: 18px;"
>得分:单选:{{exam.score.radio}}分,多选:{{exam.score.checkbox}}分,简答:{{exam.score.shortAnswer}}分,总分:{{exam.score.total}}</div>
</template>
<template v-if="exam.shortAnswerList.length">
<template v-for="(item, index) in exam.shortAnswerList">
<template v-if="item.check_comment">
<div style="font-size: 18px; margin-top: 10px;" :key="index">
<div>简答题{{index+1}}</div>
<div>
老师评语:
<div style="display: inline-block" v-html="item.check_comment || ''"></div>
</div>
</div>
</template>
</template>
<div style="font-size: 18px;">总分:{{exam.score.total}}</div>
</template>
</template>
<template v-else-if="status.type === 0">
......@@ -45,7 +30,7 @@
</div>
</div>
<template v-if="status.isStart">
<div class="play-paper-content play-chapter-exam">
<div class="play-paper-content">
<template v-if="exam.id">
<div class="exam">
<!-- <div style='text-align: center;'> -->
......@@ -53,117 +38,116 @@
<!-- <div class='tit'>{{exam.title}}</div> -->
<template v-if="exam.type === 2 && exam.isPublished === 1">
<template v-if="exam.score.total !== undefined">
<div
style="font-size: 18px;"
>得分:单选:{{exam.score.radio}}分,多选:{{exam.score.checkbox}}分,简答:{{exam.score.shortAnswer}}分,总分:{{exam.score.total}}</div>
</template>
<template v-if="exam.shortAnswerList.length">
<template v-for="(item, index) in exam.shortAnswerList">
<template v-if="item.check_comment">
<div style="font-size: 18px; margin-top: 10px;" :key="index">
<div>简答题{{index+1}}</div>
<div>
老师评语:
<div style="display: inline-block" v-html="item.check_comment || ''"></div>
</div>
</div>
</template>
</template>
<div style="font-size: 18px;">总分:{{exam.score.total}}</div>
</template>
</template>
<template v-else-if="exam.type === 1 || exam.type === 2">
<template v-else-if="exam.type === 1">
<div class="no-exam">试卷批改中,请耐心等待</div>
</template>
<!-- </div> -->
<!-- </div> -->
<template v-if="(exam.type !== 1 && exam.type !== 2)">
<template v-if="(exam.type !== 1)">
<div style="text-align: center;">考试截止时间为:{{status.terminateTime}}</div>
<!-- 单选题 -->
<template v-if="exam.radioList.length">
<template v-for="(item, index) in exam.radioList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(单选题)</div>
<el-radio-group class="radio-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-radio
v-bind:key="item1.id"
:label="item1.id"
:disabled="!!item.right_answer && !!exam.type"
:class="['radio', ((item.right_answer && !!exam.type) ? (item1.id === item.right_answer ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-radio>
<template v-for="question in exam.examination">
<!-- 单选题 -->
<template v-if="question.radioList.length">
<template v-for="(item, index) in question.radioList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(单选题)</div>
<el-radio-group class="radio-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-radio
v-bind:key="item1.id"
:label="item1.id"
:disabled="isSubmited"
:class="['radio', ((item.right_answer) ? (item1.id === item.right_answer ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-radio>
</template>
</el-radio-group>
<template v-if="item.right_answer && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', (item.right_answer === item.user_answer ? 'success' : 'error')]"
>{{ item.user_answer | getRadioAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getRadioAnswer(item.options) }}
</div>
</template>
</el-radio-group>
<template v-if="item.right_answer && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', (item.right_answer === item.user_answer ? 'success' : 'error')]"
>{{ item.user_answer | getRadioAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getRadioAnswer(item.options) }}
<div class="analysis" v-if="item.analysis">
<p>解析:</p>
<div v-html="item.analysis"></div>
</div>
</template>
</div>
</div>
</template>
</template>
</template>
<!-- 多选题 -->
<template v-if="exam.checkboxList.length">
<template v-for="(item, index) in exam.checkboxList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{exam.radioList.length+index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(多选题)</div>
<el-checkbox-group class="checkbox-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-checkbox
v-bind:key="item1.id"
:label="item1.id"
:disabled="!!item.right_answer.length && !!exam.type"
:class="['checkbox', ((item.right_answer.length && !!exam.type) ? (isCheckboxChecked(item1.id, item.right_answer) ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-checkbox>
<!-- 多选题 -->
<template v-if="question.checkboxList.length">
<template v-for="(item, index) in question.checkboxList">
<div v-bind:key="item.id" class="q-group" :data-index="index">
<div class="q-num">{{question.radioList.length+index+1}}.</div>
<div class="q-title" v-html="item.content"></div>
<div class="q-type">(多选题)</div>
<el-checkbox-group class="checkbox-group" v-model="item.user_answer">
<template v-for="(item1, index1) in item.options">
<el-checkbox
v-bind:key="item1.id"
:label="item1.id"
:disabled="isSubmited"
:class="['checkbox', ((item.right_answer.length) ? (isCheckboxChecked(item1.id, item.right_answer) ? 'success' : 'error') : '')]"
>{{ index1 | getLetter() }}. {{item1.option}}</el-checkbox>
</template>
</el-checkbox-group>
<template v-if="item.right_answer.length && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', ((item.right_answer.length && isCheckboxRight(item.user_answer, item.right_answer)) ? 'success' : 'error')]"
>{{ item.user_answer | getCheckboxAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getCheckboxAnswer(item.options) }}
</div>
</template>
</el-checkbox-group>
<template v-if="item.right_answer.length && !!exam.type">
<div class="result">
学生答案:
<div
:class="['stu', ((item.right_answer.length && isCheckboxRight(item.user_answer, item.right_answer)) ? 'success' : 'error')]"
>{{ item.user_answer | getCheckboxAnswer(item.options) }}</div>
&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getCheckboxAnswer(item.options) }}
<div class="analysis" v-if="item.analysis">
<p>解析:</p>
<div v-html="item.analysis"></div>
</div>
</template>
</div>
</div>
</template>
</template>
</template>
<!-- 简答题 -->
<template v-if="exam.shortAnswerList.length">
<template v-for="(item, index) in exam.shortAnswerList">
<div class="q-group" :key="index">
<div
class="q-sa-title"
>{{exam.radioList.length+exam.checkboxList.length+index+1}}.&nbsp;&nbsp;简答题</div>
<div class="edit_html" v-html="item.content || ''"></div>
<textarea :id="('editor-exam' + index)" v-model="item.user_answer"></textarea>
<div style="height: 10px;"></div>
<!-- 利用key值自动更新组件 -->
<component
:is="item.upload.type"
v-bind:key="item.upload.id + new Date().getTime()"
:item="item.upload"
:formData="item"
:isUpload="!exam.type"
></component>
</div>
<!-- 简答题 -->
<template v-if="question.shortAnswerList.length">
<template v-for="(item, index) in question.shortAnswerList">
<div class="q-group" :key="index">
<div
class="q-sa-title"
>{{question.radioList.length+question.checkboxList.length+index+1}}.&nbsp;&nbsp;简答题</div>
<div class="edit_html" v-html="item.content || ''"></div>
<v-editor v-model="item.user_answer" :disabled="isSubmited"></v-editor>
<div style="height: 10px;"></div>
<!-- 利用key值自动更新组件 -->
<component
:is="item.upload.type"
v-bind:key="item.upload.id + new Date().getTime()"
:item="item.upload"
:formData="item"
:isUpload="!exam.type"
></component>
<div class="result" v-if="item.check_comment">评语:{{item.check_comment}}</div>
<div class="analysis" v-if="item.analysis">
<p>解析:</p>
<div v-html="item.analysis"></div>
</div>
</div>
</template>
</template>
</template>
<div
:class="['btn', (exam.type && 'on')]"
:class="['btn', (isSubmited && 'on')]"
@click="submitExam"
:data-submit="!!exam.type"
:data-submit="isSubmited"
@mousedown="_SubmitMouseLeftDown()"
>{{exam.type ? "已提交" : "提交"}}</div>
>{{isSubmited ? "已提交" : "提交"}}</div>
<div class="care">(注意:考试只有一次提交机会)</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</template>
......@@ -197,37 +181,17 @@
import cAction from '@action'
import Base64 from 'Base64'
import CKEDITOR from 'CKEDITOR'
import VEditor from '@/components/editor.vue'
var getLetter = val => {
switch (val) {
case 0:
return 'A'
case 1:
return 'B'
case 2:
return 'C'
case 3:
return 'D'
case 4:
return 'E'
case 5:
return 'F'
case 6:
return 'G'
case 7:
return 'H'
case 8:
return 'I'
case 9:
return 'J'
case 10:
return 'K'
case 11:
return 'L'
case 12:
return 'M'
const A_Z = (function() {
const result = []
for (let i = 0; i < 26; i++) {
result.push(String.fromCharCode(65 + i))
}
return result
})()
var getLetter = val => {
return A_Z[val]
}
export default {
......@@ -236,6 +200,7 @@ export default {
cid: { type: String, require: false },
id: { type: String, require: false }
},
components: { VEditor },
filters: {
getLetter: getLetter,
getRadioAnswer: (val, arr) => {
......@@ -262,131 +227,7 @@ export default {
data() {
return {
_time: null, // 定时器,自动化提交
exam: {
// id: '1',
// title: '标题',
// type: 0, // 0: 暂存,可以继续答题;1: 提交,不能再继续答题(等待批改) 2: 已批改(这时候有分数)
// radioList: [
// {
// id: '6622309081933676544',
// content: '\u5047\u8bbe\u8d27\u5e01\u9700\u6c42\u4e3aL=ky-hr\uff0c\u8d27\u5e01\u4f9b\u7ed9\u589e\u52a010\u4ebf\u7f8e\u5143\u800c\u5176\u5b83\u6761\u4ef6\u4e0d\u53d8\uff0c\u5219\u4f1a\u4f7fLM \u66f2\u7ebf( )',
// options: [
// {
// id: '6622310260604403712',
// option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143'
// },
// {
// id: '6622310260604403713',
// option: '\u53f3\u79fb k\u4e58\u4ee510\u4ebf\u7f8e\u5143'
// },
// {
// id: '6622310260604403714',
// option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143\u9664\u4ee5k'
// },
// {
// id: '6622310260604403715',
// option: '\u53f3\u79fb k\u9664\u4ee510\u4ebf\u7f8e\u5143'
// }
// ],
// user_answer: '6622310260604403714',
// right_answer: '6622310260604403714',
// get_score: 30,
// score: 30
// }
// ],
// checkboxList: [
// {
// id: '6622310510798831616',
// content: '\u51ef\u6069\u65af\u5b8f\u89c2\u7ecf\u6d4e\u7406\u8bba\u7684\u4e3b\u8981\u524d\u63d0\u5305\u62ec\uff08\uff09',
// options: [
// {
// id: '6622310872641437696',
// option: '\u8fb9\u9645\u6d88\u8d39\u503e\u5411\u9012\u51cf'
// },
// {
// id: '6622310872641437697',
// option: '\u8d44\u672c\u8fb9\u9645\u6548\u7387\u9012\u51cf'
// },
// {
// id: '6622310872641437698',
// option: '\u4e0d\u786e\u5b9a\u6027\u4e0e\u6d41\u52a8\u6027\u504f\u597d'
// },
// {
// id: '6622310872641437699',
// option: '\u540d\u4e49\u5de5\u8d44\u521a\u6027'
// }
// ],
// user_answer: [
// '6622310872641437697',
// '6622310872641437698',
// '6622310872641437699'
// ],
// right_answer: [
// '6622310872641437697',
// '6622310872641437698'
// ],
// get_score: 30,
// score: 30
// }
// ],
// shortAnswerList: [
// {
// id: '6622311487476072448',
// content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
// user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
// check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
// get_score: 30,
// score: 40,
// attachments: [],
// upload: {
// type: 'upload-form',
// label: '附件上传:',
// model: 'attachments',
// action: webConf.apiBaseURL + '/util/upload-file',
// data: {
// special: 'exam'
// },
// attrs: {
// multiple: true,
// headers: {
// 'tenant': 'sofia'
// }
// },
// html: `
// <div style="color: #72818c; font-size: 14px;">
// <p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M</p>
// </div>
// `
// }
// },
// {
// id: '6622311487476072448',
// content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
// user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
// check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
// get_score: 30,
// score: 40,
// attachments: [],
// upload: {
// type: 'upload-form',
// label: '附件上传:',
// model: 'attachments',
// action: webConf.apiBaseURL + '/util/upload-file',
// data: {
// special: 'exam'
// },
// attrs: {
// multiple: true
// },
// html: `
// <div style="color: #72818c; font-size: 14px;">
// <p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M</p>
// </div>
// `
// }
// }
// ]
},
exam: {},
status: {
isStart: false,
startTime: '',
......@@ -398,6 +239,24 @@ export default {
}
}
},
watch: {
id: {
handler() {
this.init()
}
}
},
computed: {
// 考试完成
isExamComplete() {
// 考试完成,批改完成并且公布成绩
return this.exam.is_published === 1 && this.exam.type === 2
},
// 是否提交
isSubmited() {
return this.exam.type === 1 || this.exam.type === 2
}
},
mounted() {
this.init()
this.$emit('changeSideBar', '')
......@@ -443,76 +302,6 @@ export default {
}
return false
},
initckeditor() {
if (!this.exam.shortAnswerList) {
return
}
/* 删除所有 ckeditor 实例 */
const instances = CKEDITOR.instances
for (const name in instances) {
instances[name].destroy()
}
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
if (!instances['editor-exam' + i]) {
CKEDITOR.replace('editor-exam' + i, {
height: 300,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview' ] },
{
name: 'styles',
items: ['Styles', 'Format', 'Font', 'FontSize']
},
{ name: 'colors', items: ['TextColor', 'BGColor'] },
{ name: 'tools', items: ['Maximize', 'ShowBlocks'] },
// { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
{ name: 'editing', items: ['Find', 'Replace'] },
// { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
'/',
{
name: 'basicstyles',
items: [
'Bold',
'Italic',
'Underline',
'Strike',
'Subscript',
'Superscript',
'-',
'RemoveFormat'
]
},
{
name: 'paragraph',
items: [
'NumberedList',
'BulletedList',
'-',
'Outdent',
'Indent',
'-',
'Blockquote',
'CreateDiv',
'-',
'JustifyLeft',
'JustifyCenter',
'JustifyRight',
'JustifyBlock',
'-',
'BidiLtr',
'BidiRtl'
]
},
{ name: 'links', items: ['Link', 'Unlink', 'Anchor'] },
{ name: 'insert', items: ['Image', 'Table', 'HorizontalRule'] }
]
})
}
this.exam.shortAnswerList[i].ckeditor = instances['editor-exam' + i]
}
},
init() {
this.loadExamStatus()
this.loadExamInfo()
......@@ -522,13 +311,13 @@ export default {
}
this._time = setInterval(() => {
this.loadExamStatus()
if (!this.exam.type && this.status.isStart) {
if (!this.isSubmited && this.status.isStart) {
// console.log(11, '暂存')
this.submitExam({ submitType: true }) // 暂存, submitType: true 暂存;其他或不填为提交
}
/* 到时间 自动提交 */
if (
!this.exam.type &&
!this.isSubmited &&
this.status.isStart &&
new Date(this.status.terminateTime).getTime() -
new Date(this.status.serverTime).getTime() <=
......@@ -536,7 +325,7 @@ export default {
) {
this.submitExam({ submitType: false, currentTarget: { dataset: {} } })
}
}, 3000)
}, 10000)
},
/* 定时调用 - 考试状态 */
loadExamStatus() {
......@@ -615,44 +404,48 @@ export default {
} else {
// this.exam.id = _data.id
this.exam.title = _data.title
this.exam.type = _data.type
this.$set(this.exam, 'type', _data.type)
this.exam.score = _data.score
this.exam.submitted_time = _data.submitted_time
this.exam.isPublished = _data.isPublished
this.status.type = _data.type
this.status.isPublished = _data.isPublished
for (let i = 0; i < this.exam.radioList.length; i++) {
for (let j = 0; j < _data.radioList.length; j++) {
if (_data.radioList[j].id === this.exam.radioList[i].id) {
for (const k in _data.radioList[j]) {
this.exam.radioList[i][k] = _data.radioList[j][k]
this.exam.examination = this.exam.examination.map((exam, index) => {
const rawExam = _data.examination[index]
if (!rawExam) {
return exam
}
for (let i = 0; i < exam.radioList.length; i++) {
for (let j = 0; j < rawExam.radioList.length; j++) {
if (rawExam.radioList[j].id === exam.radioList[i].id) {
for (const k in rawExam.radioList[j]) {
exam.radioList[i][k] = rawExam.radioList[j][k]
}
}
}
}
}
for (let i = 0; i < this.exam.checkboxList.length; i++) {
for (let j = 0; j < _data.checkboxList.length; j++) {
if (_data.checkboxList[j].id === this.exam.checkboxList[i].id) {
for (const k in _data.checkboxList[j]) {
this.exam.checkboxList[i][k] = _data.checkboxList[j][k]
for (let i = 0; i < exam.checkboxList.length; i++) {
for (let j = 0; j < rawExam.checkboxList.length; j++) {
if (rawExam.checkboxList[j].id === exam.checkboxList[i].id) {
for (const k in rawExam.checkboxList[j]) {
exam.checkboxList[i][k] = rawExam.checkboxList[j][k]
}
}
}
}
}
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
for (let j = 0; j < _data.shortAnswerList.length; j++) {
if (
_data.shortAnswerList[j].id ===
this.exam.shortAnswerList[i].id
) {
for (const k in _data.shortAnswerList[j]) {
this.exam.shortAnswerList[i][k] =
_data.shortAnswerList[j][k]
for (let i = 0; i < exam.shortAnswerList.length; i++) {
for (let j = 0; j < rawExam.shortAnswerList.length; j++) {
if (
rawExam.shortAnswerList[j].id === exam.shortAnswerList[i].id
) {
for (const k in rawExam.shortAnswerList[j]) {
exam.shortAnswerList[i][k] = rawExam.shortAnswerList[j][k]
}
}
}
}
}
return exam
})
}
})
.catch(e => {
......@@ -668,7 +461,6 @@ export default {
) {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
this.initckeditor()
}
})
},
......@@ -680,44 +472,41 @@ export default {
this.$message.error('已做过,不能再提交')
return
}
const body = { answers: {}, type: !e.submitType ? 1 : 0 } // type: 0 缓存;type: 1 提交
body.answers.radioList = []
for (let i = 0; i < this.exam.radioList.length; i++) {
const tmp = this.exam.radioList[i]
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有单选题未做,不能提交')
return
const body = { answers: [], type: !e.submitType ? 1 : 0 } // type: 0 缓存;type: 1 提交
for (let k = 0; k < this.exam.examination.length; k++) {
const exam = this.exam.examination[k]
const radioList = []
for (let i = 0; i < exam.radioList.length; i++) {
const tmp = exam.radioList[i]
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有单选题未做,不能提交')
return
}
radioList.push({ id: tmp.id, user_answer: tmp.user_answer })
}
body.answers.radioList.push({
id: tmp.id,
user_answer: tmp.user_answer
})
}
body.answers.checkboxList = []
for (let i = 0; i < this.exam.checkboxList.length; i++) {
const tmp = this.exam.checkboxList[i]
if (!tmp.user_answer.length && !e.submitType) {
this.$message.error('还有多选题未做,不能提交')
return
const checkboxList = []
for (let i = 0; i < exam.checkboxList.length; i++) {
const tmp = exam.checkboxList[i]
if (!tmp.user_answer.length && !e.submitType) {
this.$message.error('还有多选题未做,不能提交')
return
}
checkboxList.push({ id: tmp.id, user_answer: tmp.user_answer })
}
body.answers.checkboxList.push({
id: tmp.id,
user_answer: tmp.user_answer
})
}
body.answers.shortAnswerList = []
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
const tmp = this.exam.shortAnswerList[i]
tmp.user_answer = tmp.ckeditor.getData()
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有简答题未做,不能提交')
return
const shortAnswerList = []
for (let i = 0; i < exam.shortAnswerList.length; i++) {
const tmp = exam.shortAnswerList[i]
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有简答题未做,不能提交')
return
}
shortAnswerList.push({
id: tmp.id,
user_answer: Base64.encode(tmp.user_answer),
attachments: tmp.attachments
})
}
body.answers.shortAnswerList.push({
id: tmp.id,
user_answer: Base64.encode(tmp.user_answer),
attachments: tmp.attachments
})
body.answers[k] = { radioList, checkboxList, shortAnswerList }
}
body.answers = JSON.stringify(body.answers)
let loading = null
......@@ -793,13 +582,6 @@ export default {
loading.close()
})
}
},
watch: {
id: {
handler() {
this.init()
}
}
}
}
</script>
......@@ -886,7 +668,9 @@ export default {
color: #090;
}
.exam .q-group .result {
float: right;
clear: both;
display: flex;
justify-content: flex-end;
font-size: 0.18rem;
color: #3f3b3a;
margin-right: 0;
......@@ -938,4 +722,11 @@ export default {
line-height: 19;
text-align: center;
}
.analysis {
clear: both;
display: flex;
margin: 20px 0;
padding: 0 20px;
border: 1px solid #c9c9c97a;
}
</style>
......@@ -18,6 +18,7 @@
<div class="play-content">
<router-view
ref="comTotalChapter"
:chapters="rawResponse.chapters"
:chapterName="curChapterName"
:chapterId="chapterId"
:courseInfo="courseInfo"
......@@ -33,6 +34,7 @@
@updateProgress="updateProgress"
@changeSideBar="changeSideBar"
:key="id"
v-if="rawResponse.chapters"
></router-view>
</div>
</div>
......@@ -151,7 +153,8 @@ export default {
chapterExam: {},
/* 章节视频 */
chapterVideo: {},
chapterPpts: []
chapterPpts: [],
rawResponse: {} // 接口返回的数据
}
},
beforeRouteUpdate (to, from, next) {
......@@ -178,6 +181,7 @@ export default {
return
}
cAction.Player.getChapterList(to.params.cid, to.params.sid, to.params.id).then(json => {
this.rawResponse = json.rawResponse
this.chapterList = json.json
this.courseInfo = json.courseInfo
this.courseWork = json.courseWork
......@@ -219,6 +223,7 @@ export default {
return
}
cAction.Player.getChapterList(this.cid, this.sid, this.id).then(json => {
this.rawResponse = json.rawResponse
this.chapterList = json.json
this.courseInfo = json.courseInfo
this.courseWork = json.courseWork
......
......@@ -59,8 +59,8 @@ export default {
}
}, 1000)
if (this.live.id) {
if (this.live.record_id && this.live.live_status === 103) {
this.live.url = 'https://view.csslcloud.net/api/view/callback?recordid=' + this.live.record_id + '&roomid=' + this.live.room_id + '&userid=' + this.live.user_id + '&autoLogin=true&viewername=' + this.live.viewer_name + '&viewertoken=' + this.live.viewer_token // + '&groupid=xxx'
if (this.live.live_status === 2 && this.live.enable_record && this.live.record_url) {
this.live.url = this.live.record_url
} else {
this.live.viewer_name = window.G.UserInfo.student_info.personal_name || window.G.UserInfo.nickname
this.live.url = 'https://view.csslcloud.net/api/view/index?roomid=' + this.live.room_id + '&userid=' + this.live.user_id + '&autoLogin=true&viewername=' + this.live.viewer_name + '&viewertoken=' + this.live.viewer_token // + '&groupid=xxx'
......
......@@ -71,16 +71,14 @@ export default {
this.$router.push({ path: `/player/${cid}/exam/${_id}` })
} else if (_course.chapters[i2].type === 5) {
const status = _course.chapters[i2].live.live_status
if (status !== 0 && status !== 1 && status !== 103) {
this.$message.error(_course.chapters[i2].live.statusStr)
return
}
const enableRecord = _course.chapters[i2].live.enable_record
if (status === 103 && enableRecord !== undefined && enableRecord !== null && !enableRecord) {
if (status === 2 && enableRecord) {
this.$message.info('该直播没有回放')
return
}
this.$router.push({ path: `/player/${cid}/live/${_id}` })
} else if (_course.chapters[i2].type === 9) {
this.$router.push({ path: `/player/${cid}/chapter-exam2/${_course.chapters[i2].chapterId}` })
}
return
}
......
// import viewerRoutes from '@/modules/viewer/routes.js'
import viewerRoutes from '@/modules/viewer/routes.js'
export default [
{ path: '/', redirect: '/login/is-login' },
......@@ -6,19 +6,19 @@ export default [
path: '/login/index',
name: 'login-normal',
component: () => import('@/pages/login/index.vue'),
props: (route) => ({ query: route.query, params: route.params })
props: route => ({ query: route.query, params: route.params })
},
{
path: '/login/code',
name: 'login-code',
component: () => import('@/pages/login/code.vue'),
props: (route) => ({ query: route.query, params: route.params })
props: route => ({ query: route.query, params: route.params })
},
{
path: '/login/forget',
name: 'login-forget',
component: () => import('@/pages/login/forget.vue'),
props: (route) => ({ query: route.query, params: route.params })
props: route => ({ query: route.query, params: route.params })
},
{
path: '/login/is-login',
......@@ -136,7 +136,59 @@ export default [
children: [
// { path: 'set-pwd', component: () => import('../pages/account/setPwd.vue') },
// { path: 'update-pic', component: () => import('../pages/account/updatePic.vue') },
{ path: 'my-examination', component: () => import('../pages/examination/myExamination.vue') }
{ path: 'my-examination', component: () => import('../pages/examination/myExamination.vue') },
{
path: 'hall',
component: () => import('../pages/affairsHall/hall.vue')
},
{
path: 'learning-add/:rid',
component: () => import('../pages/affairsHall/learningAdd.vue'),
props: true
},
{
path: 'share-add/:rid',
component: () => import('../pages/affairsHall/shareAdd.vue'),
props: true
},
{
path: 'again-add/:rid',
component: () => import('../pages/affairsHall/againAdd.vue'),
props: true
},
{
path: 'my-apply',
component: () => import('../pages/affairsHall/myApply.vue'),
props: true
},
{
path: 'upload-report/:rid/:reid',
component: () => import('../pages/affairsHall/uploadReport.vue'),
props: true
},
{
path: 'view-report/:rid',
component: () => import('../pages/affairsHall/viewReport.vue'),
props: true
},
{
name: 'ehallRetakeAdd',
path: 'retake/add',
component: () => import(/* webpackChunkName: "ehall" */ '../pages/affairsHall/retake/edit.vue'),
props: { isAdd: true }
},
{
name: 'ehallRetakeEdit',
path: 'retake/edit/:id',
component: () => import(/* webpackChunkName: "ehall" */ '../pages/affairsHall/retake/edit.vue'),
props: { isEdit: true }
},
{
name: 'ehallRetakeView',
path: 'retake/view/:id',
component: () => import(/* webpackChunkName: "ehall" */ '../pages/affairsHall/retake/edit.vue'),
props: { isView: true }
}
]
}
]
......@@ -164,6 +216,12 @@ export default [
component: () => import('@/pages/player/chapterExam/chapterExam.vue'),
props: true
},
{
path: 'chapter-exam2/:id',
name: 'chapterExam2',
component: () => import('@/pages/player/chapterExam/chapterExam2.vue'),
props: true
},
{
path: 'chapter-read/:id',
name: 'chapterRead',
......@@ -248,7 +306,7 @@ export default [
// /* survey-phone 内未找到页面时 - 指向 */
// { path: '/survey-phone/*', redirect: '/learn-error/learn-error' },
/* 如果所有页面都没找到 - 指向 */
{ path: '*', component: () => import('@/components/errorPages/404.vue') }
{ path: '*', component: () => import('@/components/errorPages/404.vue') },
// viewer module routes
// ...viewerRoutes
...viewerRoutes
]
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
myMsg: 0
}
const getters = {
myMsg (state) {
return state.myMsg
}
}
const mutations = {
myMsg(state, sum) {
state.myMsg = sum
}
}
const store = new Vuex.Store({
state,
getters,
mutations
})
export default store
export default new Vuex.Store({
state: {
myMsg: 0
},
getters: {
myMsg(state) {
return state.myMsg
}
},
mutations: {
myMsg(state, sum) {
state.myMsg = sum
}
},
actions: {}
})
......@@ -20,8 +20,9 @@
), function(global) {
'use strict';
// existing version for noConflict()
global = global || {};
var _Base64 = global.Base64;
var version = "2.5.0";
var version = "2.5.2";
// if node.js and NOT React Native, we use Buffer
var buffer;
if (typeof module !== 'undefined' && module.exports) {
......@@ -48,8 +49,8 @@
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
+ fromCharCode(0x80 | (cc & 0x3f)))
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
} else {
var cc = 0x10000
+ (c.charCodeAt(0) - 0xD800) * 0x400
......@@ -82,32 +83,21 @@
} : function(b) {
return b.replace(/[\s\S]{1,3}/g, cb_encode);
};
var _encode = buffer ?
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
? function (u) {
return (u.constructor === buffer.constructor ? u : buffer.from(u))
.toString('base64')
}
: function (u) {
return (u.constructor === buffer.constructor ? u : new buffer(u))
.toString('base64')
}
: function (u) { return btoa(utob(u)) }
;
var _encode = function(u) {
var isUint8Array = Object.prototype.toString.call(u) === '[object Uint8Array]';
return isUint8Array ? u.toString('base64')
: btoa(utob(String(u)));
}
var encode = function(u, urisafe) {
return !urisafe
? _encode(String(u))
? _encode(u)
: _encode(String(u)).replace(/[+\/]/g, function(m0) {
return m0 == '+' ? '-' : '_';
}).replace(/=/g, '');
};
var encodeURI = function(u) { return encode(u, true) };
// decoder stuff
var re_btou = new RegExp([
'[\xC0-\xDF][\x80-\xBF]',
'[\xE0-\xEF][\x80-\xBF]{2}',
'[\xF0-\xF7][\x80-\xBF]{3}'
].join('|'), 'g');
var re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g;
var cb_btou = function(cccc) {
switch(cccc.length) {
case 4:
......@@ -232,3 +222,4 @@
// that's it!
return {Base64: global.Base64}
}));
export default {
title: '紫荆教育MBA学习系统',
tenant: 'sofia'
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论