提交 f27a18fc authored 作者: GOD_ZYX's avatar GOD_ZYX

修改 完毕 登录

上级 d03b3ea9
......@@ -29,7 +29,9 @@ $GLOBAL.BaseConfig = {
},
resolve: {
alias: {
'@services': path.resolve(__dirname, '../client/components/services')
'@services': path.resolve(__dirname, '../client/components/services'),
'@tools': path.resolve(__dirname, '../client/components/tools'),
'@actions': path.resolve(__dirname, '../client/project/' + projectName + '/actions')
}
},
module: {
......
......@@ -22,6 +22,7 @@ if ($GLOBAL.isDev) {
},
devServer: {
port: $GLOBAL.serverPort,
disableHostCheck: true,
overlay: {
errors: true
......@@ -39,6 +40,7 @@ if ($GLOBAL.isDev) {
'Referer': $GLOBAL.webConf.devDomain
},
pathRewrite: {
'^/api': ''
}
}
},
......
......@@ -36,6 +36,18 @@
<div class="fontclass">.el-icon-self-album</div>
</li>
<li>
<i class="icon selfAllIcon el-icon-self-cc-book"></i>
<div class="name">cc-book</div>
<div class="fontclass">.el-icon-self-cc-book</div>
</li>
<li>
<i class="icon selfAllIcon el-icon-self-13"></i>
<div class="name">image-o</div>
<div class="fontclass">.el-icon-self-13</div>
</li>
<li>
<i class="icon selfAllIcon el-icon-self-grade"></i>
<div class="name">Grade</div>
......
......@@ -59,6 +59,22 @@
<div class="fontclass">#el-icon-self-album</div>
</li>
<li>
<svg class="icon" aria-hidden="true">
<use xlink:href="#el-icon-self-cc-book"></use>
</svg>
<div class="name">cc-book</div>
<div class="fontclass">#el-icon-self-cc-book</div>
</li>
<li>
<svg class="icon" aria-hidden="true">
<use xlink:href="#el-icon-self-13"></use>
</svg>
<div class="name">image-o</div>
<div class="fontclass">#el-icon-self-13</div>
</li>
<li>
<svg class="icon" aria-hidden="true">
<use xlink:href="#el-icon-self-grade"></use>
......
......@@ -56,6 +56,18 @@
<div class="code">&amp;#xe734;</div>
</li>
<li>
<i class="icon selfAllIcon">&#xe615;</i>
<div class="name">cc-book</div>
<div class="code">&amp;#xe615;</div>
</li>
<li>
<i class="icon selfAllIcon">&#xe6ce;</i>
<div class="name">image-o</div>
<div class="code">&amp;#xe6ce;</div>
</li>
<li>
<i class="icon selfAllIcon">&#xe66c;</i>
<div class="name">Grade</div>
......
@font-face {font-family: "selfAllIcon";
src: url('iconfont.eot?t=1545290857246'); /* IE9*/
src: url('iconfont.eot?t=1545290857246#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAwAAAsAAAAAEmQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8d0kmY21hcAAAAYAAAACjAAACLhxZIGtnbHlmAAACJAAAB40AAAsEG7S/NWhlYWQAAAm0AAAALgAAADYTog7PaGhlYQAACeQAAAAcAAAAJAfeA4xobXR4AAAKAAAAAA8AAAAsLAAAAGxvY2EAAAoQAAAAGAAAABgQahNMbWF4cAAACigAAAAfAAAAIAEdANNuYW1lAAAKSAAAAVIAAAKR7FrMk3Bvc3QAAAucAAAAZAAAAIGFfd3ZeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMT43YW7438AQw9zA0AAUZgTJAQDhKgwZeJzlkjEOwjAMRV9oaGnFwIAYe4KqU2/VjsydUU/BxNGcYxQ7LggJboCtFydfSRzZAfZAoXRKhPAgYHZXNWS9oMl65KrrMydVogSppJZeJpllSW0a1hXe6vipflnQW15+2RzNUlFzoNQ3BXaWRbeWP87/mx3zeNtWjdXZyfPKwWLtYLF3tKLI6GhtkcmxzsvsWKdlcbTypNaxn5EGh/gEu1cz6QB4nI1WfYwVVxW/5975evPmzbw3M2/mzX68t+97ge6D9zXDR1hgF9ZuKAi7CFJA2KQLiLZBKa1UxMWgXVvCH1QbiobG0tXEmDRRiaki2bLGf9rEpIk1yD/U6D9o6j/UNLHv1XNnWERprC/z5p5z7rn3nvu7v3PuEIWQj+bZPPs0UUiK9JAiKZEh8iQ5TQjUoCDZTsOvtvyGY0uFiqwD11sVuVCJbGJxGKI+GIZ2q1KtSOiUhWAYmg4Xiy0/8NdhZ7UG7WEIGq4zDOhW1EG2HRc9XCdo+EEWmtjbqmDfMB8dOK4OdAD8CR+fr5t9Jj5HC0NDG4eGCnIspsdiML4eSn4JHwiyhbiU3TMYlxOeVq5K8P3XfvLXolOilMpU7OsTblzevU3LaIq27/C1jtjfC82Ml+rJblm28tRGNJS704c/+9gPnI2nmstBuEEPLOGr+kt0vqp5HviqG4f281X12HwvX7ME79Ue6U05ktebkTMZWbYTA3967UcvKysDxhTFjtWm25dvCFUplslINbFzbdUTDdgy5SUh31epoj7y1c4CNMdfcEbPje587PINQgQ8i2vsOhsJz+JJ8tvwDCKgq9FRtCpWYTlIVcStWkFI1yFesoR4ViuIcqUVoC3wEb8c+AHaUPYdlF00uY6bBQMcF00oOxLKtow2mR8UziqjDWWpzF3wWPxoEj7HotSqVqIl+YqLkixFg/nYRcl2nWgpvpJMyX8A+vmkOm7b45KltmpKDGwjcVJNiT0GlZRpgGlFokaPmFJPJgwbYkqtpVrSPf+Y8sn+YvedtDsWt3I5Kz7mpi2qe5ZmPq0qU8gpCvqUoj5tapanU+v/9IP/YmJ3IDFN6cdHqtrqx4f6iQPuxwJ2hWEIwoPhGgPGg/E+6KiZ6Ghq/3Yk+KPIsTfYb5Bjq8mOkF9VzMQsRGkZ5ShqOhIlTGXZ58lakGQbbbKEh9lsBGGGllFax0nFG78GSCfMfgMkhy0gvTGrYPdkvMcQ42ObMf8wF/AtarYgtUqaIMathAkCNRNMkGMJWRo4UfzVB52XZNlSP7U3loKkeloWXE+ZPqCWlSlJgevRDLu/tyq0r5h5+NkrgnDl2dkrDEDVEoP20gHRMHVFNoKsasoxqm9aJv+BsdTZczrov457STH+3e8YNHk1kSESBwPrHm9ixCNLyFpyGPG4V8/KedxvCqHI425TNbBQzSNaKVTzaA7yPCObXEzxzGyjD0MfjlKNIqhuBJYfYAVsNnjuRbhhncMyRxZrF1U7t+ujAKN16oStAFmnc9vJArbUwXZz5wqmjU43o4zUg9Gbn1vJxHjCy6RLuX2bcNWEwOImXerTlB1L1arLc0zIrUyYJtwtVTdx9u6RaBV4sT7a+Uc0PbwYte/zabNO9zhfB56vj3bn2ZaSl6SCZOB+vqnraQZ6XN4jKVJck9RVPbkCmL2cUDLy6QK7zY7g3VEmDbKGjJOdZBciifdBRC7EgROMY9bkdSgQ0V5ZHvIs4LXLjxhXdXnpd7GKQERJ1NCzbknhnRD6FOvsgBkX3rz08psMtNRZ9uUtjVE43X7ua48cY52vUD/fqNUPVI+v/8zDf07C62fOvCHE3x+fmxyZGTxQrzW6H+45QemJPY/y96PS0AkNjm4Y2S8Ix2qws7R92aW3BeHtS8u2l96jleTqPeyD288lK/Qo21DU1fhccyI5KJ5awDmFhW9cPSNcv5Dc0ZyLqzr90r05cf7uaTZybGSKsic2TE6ENAtfc3SeJEkeMSLAidSOiNQPtiSnHjQUKtzQzDccbiiyE52UlQHIWPTvpueZ/0uj61EcND0AzxzEju6du8Jdc/cWVz2PK4s14V22ieXIIFmJ0ZUr/AYJwjNrhjd3MawF/La+PyUYxoVxroUoTpe53TajZW/zQxv2ZqSkXlCVwql9aydLfpr1Wjwci9Zs3g6lje5WI43VDn6KhRCuwIpnan3LgG5fm7Kgae/aB/nB0R8GfEj3W9FQeMbyuheNdNr4MGnbybtxz7CLbIZUOKoi51sY7l3W4Ttd5LnJI+TbSWepD4tUknEDL9Hnn9q7zet/5QtHXun3tu196tvAzF5LWDj/woIAZo/VOc5mDx6aZWz20MHZ/cWZt1b4Vw8fOknpyUOHr/r1t2aKPGWK5xeQEudLiSRY9PWD3B0HUTobnv1HvxAIG8fsaJN1IboFWeQfUjlALHPgNPjnUTsVpLPAQw3C+sALKd70lWrK5Q6yVKgOgd9wRfaX7mTv6t7ujoRhJG6C585l/Myc23MSjIyxRDOM7rtgzml92pwJF9Hx4s/wFujuhVexgVfp1B8TiRtypt+Vj3Z/17NUm9TwWdoD9XVHcax21uZ93URanZTlSTUN6a2JxNZ+RXnn9/hHzMXw/vg5G8SqmSMPkVVkjJByRIQ2y5fTTU6QptzEXG9W+TddqtnG/a3FTRUkpLIj3q/IfOdcSWMRZWP9XueW1w/0XEfyYU398uMTj/v4v1xfA37nx/Fk0jUMmMBA3WTyAjYZw/ib19/vUdH3O/9Er2v4w4God26BoX2R7wlfgPIdLt+JZBjjSveXmoFb+helGttxAAAAeJxjYGRgYADi2pqKs/H8Nl8ZuFkYQOCGY2omgv7fwMLA3ADkcjAwgUQBIsEJzQAAeJxjYGRgYG7438AQw8IAAkCSkQEVcAMARxECdHicY2FgYGAhEgMABEwALQAAAAAAAMYB+AJ8AyADsgQCBF4EsAUYBYJ4nGNgZGBg4GY4zsDBAAJMQMwFhAwM/8F8BgAdmgHxAHicdZDLSsNAFIb/9CYm4EKx63GjoJBeNkLBRSm0dluh+zSdtClJpiTTQje+gQufx6fwBfQp3PubHqFIzTCH73xzzpkhAM7xAQf775J7zw48Znuu4ARKuEp/I1wjd4Xr5AfhBvlR2MUdnoQ9XOCZE5zaKbNbvAo7aOJNuIIzvAtX6T+Fa+Qv4TqajifcIF8Ju5g698Ierp0Xd5DrwOq5mu1UHJosMpl1C51E/SQZM5/oxSYJ8gNzgFOdF7HJVMdvH9iRznT+O7PYLrrWRirKTaqGHK6TxKh1blY6tP7S2nWv1YrE+6FJ+cQBcmgEsIxz/tYZdowxQhhkiMpoWVfwPGHeZ0wwlvMJ7QIbmoBzjtcct1Pan464zBQ68NH+p3ZEm5X1f99ZYMv7u7SWXYo7Z0dKGsrLdTnJ0KzLsxVNSO9jWXat0UOLK/pT75d3p99T3XZbAAB4nG3BSw7CMAwFQL+mKUnLVTiUSa1iiQQpdj/HZ8GWGRroZ6b/FgwIGBEx4YaEjBkL7pRWtfI5pCcTd22bhSZn5Pdzr3HrvEpofIxVK+fy4s7FpU/XLpc+sjm7mmsh+gLNaxmo') format('woff'),
url('iconfont.ttf?t=1545290857246') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg?t=1545290857246#selfAllIcon') format('svg'); /* iOS 4.1- */
src: url('iconfont.eot?t=1545735883467'); /* IE9*/
src: url('iconfont.eot?t=1545735883467#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAA1sAAsAAAAAFIwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8d0kmY21hcAAAAYAAAAC1AAACWoN37dBnbHlmAAACOAAACNIAAAzo/l3krWhlYWQAAAsMAAAALwAAADYTr6OTaGhlYQAACzwAAAAcAAAAJAfeA45obXR4AAALWAAAAA8AAAA0NAAAAGxvY2EAAAtoAAAAHAAAABwWxBo0bWF4cAAAC4QAAAAfAAAAIAEfANNuYW1lAAALpAAAAVIAAAKR7FrMk3Bvc3QAAAz4AAAAcQAAAJBAeyMIeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMT43YW7438AQw9zA0AAUZgTJAQDhKgwZeJzlks0NwjAMhV9o6R89cKmEOHQC1BNbtTP0jDoFQ3BiiI7xMkZ5iREFCSbA1hfFduRYtgFsASTiJFLA3eEQ5Cavi/4EVfSnuMg+YC9PQcecJRt2HDhy4uxbf14W4C3Sf0a+ilPGVY8vBWqU2Km+CgVyvdsg08+hxuxHrn+SOp7X1aIz4j031EHwiXoJNkaYOjsjTJe9oU6Dg4GQYzTCJnAyNAdwNjQR+NYIW+TPBtIHBK8/mwAAAHicjVZdbBTXFb7n3tmZ3dmZnd2d3Zkd/6z3fxeM194fzwKpbewFJxaBAqYQgim4jcElJaIlJCWlFCLauAniAVpEaEVUwG3VVoqU1qpoCQJc9cVIlSI1gfJQqNoXWqV9IFWUZic9d8emtERNV7Nzzzn33L/vnPPdIV5CPrrCrrBPEy8JkRaSJhnSRZ4lRwiBIqTEiFG281W7bETEVE4KANerOSmVc22edD+4fdAPvdVcPieiUxxq/VAxuJiu2jV7ADvzRejth1rZNPoB3dIBkCKGiR6mUSvbtThUsLeaw75+PrpmmAGgCbA32Ph8PdwWxmdvqqtrZVdXSvL5Aj4fjKyAjJ3BB2rxlF+Mby34JdVSsnkRvvf6T/+SNjKUUol62tqEm+e3rFNiilfZNnm54WlvhUrMCrXE13QuPbQSDVlnYvKJp75vrDxU6QbhJt2xiK9qLwrwVcMngK+6sms7XzXgu9LK18zAu8XHW0OGaLXGpFhMkiJq4o+v//A179IaY15vxFec6D1/U8iLvlhMLHoal5c9U4Y141YQkm25POpDX23MQmXkpFE/Xt/01PmbhAgYi8vsGhtqxuJZ8ptmDFyg824oqjk91Q1iHnHL5xDSAcRLEhHPfA5RzlVraKvZiF8H2DW0oWwbKJtoMg0zDhoYJppQNkSUIxLaJB4onFVCG8pilrtgWGx3Ej7HglTN59wl+YoLkiS6g/nYBSliGu5SfCWJkv8A9AtBeSQSGRF1uVr0+iCiqQflkKdFo6J3AmDCK1KtxROSD6paBHzeYlXWxfv+Pu8n+3uct6PmsF/v6ND9w2ZUpwFLV8LPy95xzCkKgXGv/HxY0a0A1f9PP/ivTHQS6gSlH79TOSJ//FY/ccCDWMDm5jYE4eHtagnt4f0+7KiE0TGs/NuR4I9ijl1lv8YcW042NvMrj5UYB7cs3RpFLYCJ0ixlyebFmhKlCNokEYNZKdeaFZpFaYAnFW/sImA6YfVrIBpsFtMbqwq2jPpbNI9/eDXWH9YCvj1KRBCrGUXw+HU1DAINq0yQfKokJg6kf/V+41VJ0uVHx3whCMpHJMG0vBM75Kx3XPTCNXeGLd9d1rT3HH7spRlBmHlpaoYByIpaiCxOeLRwwCtptbgclnw0sKpTeoex0LHjAQi86beCHv93vq3R4CU1RkQOBvIeb3zEIotIH5lEPO7zWTaJ5w0hFEk8bagIOqpJRCuEahLNtSSvyAoXQ7wye9GHoQ9HqUgRVNMFy64hA1bKvPZc3JDnkObIAndRuXG3VAeol6jRbAWIG427RhywpQa2qxszWDYBuhplTD2o3/rsUubxq1YsmunYtgpXVQXmD9PFNg1FfKFivruDCR1L1XAY5qnqFs7u7HZXgVOleuMf7vRwym3f49PGDWc/XwdeKdWdK2xNxgpSQdTwPN8IBKIMAn5pq+gV/YooL2vpSEG4lScUAvjRDPscqxI/6SEDhGSTnLIqSY4ixywUEdNNU20eyDwCG50HtjdURaDjqHMvUQpVbfoT51Gfqvrgok9RTskqqHLjg3ihEIcfxfMA+bizKF6AoTIV4wW1cQLekVVVdp4oxNUYKLqCD32TjxmQA1D4FI4oxGkVR2DbGCjXm/pVWe3hPtfRx6Cfl1VCPHiOO+wP9O8kSJKkk1TJGCF6M7oPBD6UeOAQPDv0OHBSxvqoLFySHhNZtSQiTedSWA3323wpV8VCKXeAwQumfJLeSCwBWJJoFNwW9sDTpUGAwVKj0GwnobiqiA/krGwslrHgjTlRFDVxzp/wX4j1x1qwraMuivV57Vysn85BZ7JxI9EJ0JmghWQnNEyckhbmp77RMwQfpHHa7u5U2OIT981JAUkUr8vyOdNskeWVIha+VEfZ7z9nYowlxOY0u8t24/dBlpTJI2SEbCKbsVowgi6BYK5zEuF1UeF3Tc2D9lx3k0tq/H6yXVbJm/x6N/GmAJd2UEPPki427/2mT7rEdoT9wtzZ1+YYKKFj7MtrMG5Hel/+2uP7WOMr1E6Wi6Ud+f0rPvPYn4Jw8ejRq4L/vZHp0aHDhR2lYtn5cOsBSg9sfZK/nxS7Diiwd3BouyDsK8KmzPrOs28JwltnO9dn3qW54PKt7P27LwdzdC8bTAdk/3RlQ7DgOTSLcwqzL146Klw7HdxYmfbLAfql+3Pi/M4RNrRvaJyyZwZHNxC3DvA1Ta8086eM2PCc6XXJoh0imNsPG1I5buC1wg1pdqAR0mMAMZ3+DUMT/l8aXYFiIWwBWOECdjj35oV5s3Obq5bFlQXev8NWsQ5SIEtxd9kc/0qoNWNWaX6dpZt8z7/IHqQ9xgszlesDd58mM51eRrPW6iWDYzExGEjJ3tShbX2jGTvKWnW+HZ0WI7ztimrOWi2KNxq8gZcdzEDPC8W2TqDr+0I6VCKbt0GyUP9BjQ9xvukOhRd0yzmjRaPah8FIJDi/78PsDDtMchxVD8+35nbnsw7f0TTnX75DfpxonNqwkEoSHuBV+spzY+us9nNP7z7Xbq0be+5bwMKtujB74uSsAOEWvbGfTe3cNcXY1K6dU9vTh6/32Jcmdx2k9OCuyUt26frhNKfF9IlZTIkTGTUIOr24k7vjIEqnXA78hUDYCFZHL+dARDclefjHcgcglm7Fc7arRTlf9PFrAO8AcZ4e8iGTO0jIEV1IDKaH/dkZbV3e6mxUNU29BZY5HbNj02bLQdBi2iJF05w7EJ5W2pTpMJxBxzM/w5veGYML2MAFOv57Vb0pxdpNaa/z25bFyqiCz+IWKA3sxbHKsQjvc9SoPCpJo3IUomtVdW271/v27/BPXC68yn7OCngzdpAlZBkZRl53E6GXJbPRCk+QilTBWq/k+Xd7qNKL5+sDzoSYyobnQUVyeT4lIssbbLjdaty22oEeb4g2PFI6v2fDHhv/50uPgN34sT8YNDUNNuBGzWDwNDYxTfur1d5uUY9tN/6JXpfxhwNRb9wGTfkiPxO+AOV7XL7nyjDMFeeXioZH+hcYAixrAAB4nGNgZGBgAOJwuQNp8fw2Xxm4WRhA4IaH/mkE/b+BhYG5AcjlYGACiQIAD1QJkAB4nGNgZGBgbvjfwBDDwgACQJKRARXwAgBHEwJ2eJxjYWBgYCERAwAF5AA1AAAAAAAAxgH4AnwDIAOEBBIEpAT0BVAFogYKBnR4nGNgZGBg4GU4zsDBAAJMQMwFhAwM/8F8BgAd0AHzAHicdZDLSsNAFIb/9CYm4EKx63GjoJBeNkLBRSm0dluh+zSdtClJpiTTQje+gQufx6fwBfQp3PubHqFIzTCH73xzzpkhAM7xAQf775J7zw48Znuu4ARKuEp/I1wjd4Xr5AfhBvlR2MUdnoQ9XOCZE5zaKbNbvAo7aOJNuIIzvAtX6T+Fa+Qv4TqajifcIF8Ju5g698Ierp0Xd5DrwOq5mu1UHJosMpl1C51E/SQZM5/oxSYJ8gNzgFOdF7HJVMdvH9iRznT+O7PYLrrWRirKTaqGHK6TxKh1blY6tP7S2nWv1YrE+6FJ+cQBcmgEsIxz/tYZdowxQhhkiMpoWVfwPGHeZ0wwlvMJ7QIbmoBzjtcct1Pan464zBQ68NH+p3ZEm5X1f99ZYMv7u7SWXYo7Z0dKGsrLdTnJ0KzLsxVNSO9jWXat0UOLK/pT75d3p99T3XZbAAB4nG3BwRKCIBQFUC6IgmV/4qLpi55PxpgCZuBpfn6Ltp2jtPoZ1X8TNAw6WPQY4OAx4oIrJtyUW2PjcoTqWhCJeWsmh4+l97KngXleSnnp+8NuldZgMh1diok8P6kSS6j9uYczzr4JSWwSWakv6DQc0gAAAA==') format('woff'),
url('iconfont.ttf?t=1545735883467') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg?t=1545735883467#selfAllIcon') format('svg'); /* iOS 4.1- */
}
.selfAllIcon {
......@@ -23,6 +23,10 @@
.el-icon-self-album:before { content: "\e734"; }
.el-icon-self-cc-book:before { content: "\e615"; }
.el-icon-self-13:before { content: "\e6ce"; }
.el-icon-self-grade:before { content: "\e66c"; }
.el-icon-self-nav:before { content: "\e66b"; }
......
!function(a){var c,i='<svg><symbol id="el-icon-self-discover" viewBox="0 0 1024 1024"><path d="M544 288c0-52.928-43.072-96-96-96s-96 43.072-96 96 43.072 96 96 96S544 340.928 544 288zM416 288c0-17.632 14.368-32 32-32s32 14.368 32 32-14.368 32-32 32S416 305.632 416 288z" ></path><path d="M304 448C259.904 448 224 483.904 224 528 224 572.128 259.904 608 304 608s80-35.872 80-80C384 483.904 348.096 448 304 448zM304 544C295.168 544 288 536.832 288 528S295.168 512 304 512s16 7.168 16 16S312.832 544 304 544z" ></path><path d="M640 736m-64 0a2 2 0 1 0 128 0 2 2 0 1 0-128 0Z" ></path><path d="M887.84 268.672c39.744-60.672 64.256-113.184 64.224-148.96 0-22.976-9.312-36.928-17.12-44.576-38.08-37.376-110.016-3.2-160.768 25.664-15.328 8.736-20.704 28.288-11.968 43.648 8.736 15.36 28.32 20.704 43.648 11.968 49.408-28.16 73.248-33.088 82.496-33.696-1.504 41.376-95.328 202.432-330.208 433.504-226.368 222.72-398.656 324.672-442.24 325.376 0.96-11.36 8.768-44.448 58.528-118.688 1.312-1.888 2.4-3.968 3.232-6.144l2.304-5.92c0.256-0.64 0.288-1.344 0.512-2.016 8.8-10.24 10.816-25.088 3.392-37.248C147.328 651.616 128 582.592 128 512 128 300.256 300.256 128 512 128c59.296 0 116.096 13.12 168.864 39.04 15.936 7.776 35.04 1.184 42.816-14.656 7.776-15.84 1.248-35.008-14.624-42.816C647.424 79.328 581.12 64 512 64 264.96 64 64 264.96 64 512c0 76.064 19.488 150.496 56.064 216.64-0.192 0.576-0.64 1.056-0.8 1.632C51.168 832.608 35.2 895.68 68.928 928.768c11.008 10.784 25.632 15.584 43.072 15.584 39.328 0 92.864-24.928 150.08-60.896C336.096 933.408 421.952 960 512 960c247.04 0 448-200.96 448-448C960 424.864 934.912 341.248 887.84 268.672zM512 896c-68.288 0-133.728-17.888-191.84-51.552 128.288-92.256 258.432-218.56 282.848-242.624 89.152-87.712 178.56-187.04 244.64-275.968C879.2 382.592 896 446.112 896 512 896 723.744 723.744 896 512 896z" ></path></symbol><symbol id="el-icon-self-settings" viewBox="0 0 1024 1024"><path d="M512 608c-52.928 0-96-43.072-96-96s43.072-96 96-96 96 43.072 96 96S564.928 608 512 608zM512 480c-17.632 0-32 14.368-32 32s14.368 32 32 32 32-14.368 32-32S529.632 480 512 480z" ></path><path d="M631.04 960c-10.016 0-19.712-4.736-25.856-13.12l-73.408-100.48c-13.664 0.832-25.92 0.864-39.584 0l-73.376 100.48c-7.904 10.816-21.728 15.616-34.656 11.872-34.592-9.888-67.488-23.456-97.76-40.32-11.712-6.528-18.112-19.648-16.032-32.896l19.2-123.264c-9.824-8.704-19.136-18.016-27.84-27.808l-123.264 19.2c-13.056 1.984-26.336-4.352-32.896-16.064-16.896-30.368-30.464-63.296-40.32-97.824-3.68-12.864 1.056-26.72 11.904-34.624l100.448-73.376C177.152 524.96 176.928 518.4 176.928 512s0.224-12.96 0.672-19.776L77.12 418.816c-10.816-7.936-15.584-21.728-11.904-34.656 9.856-34.496 23.424-67.392 40.32-97.792 6.56-11.712 19.712-18.048 32.896-16.064l123.264 19.2c8.736-9.824 18.016-19.104 27.84-27.84l-19.2-123.264C268.288 125.184 274.72 112.064 286.432 105.536c30.4-16.896 63.296-30.464 97.76-40.32 12.928-3.648 26.72 1.056 34.656 11.904l73.376 100.448c13.568-0.896 25.824-0.896 39.584 0l73.408-100.48c7.904-10.848 21.664-15.52 34.656-11.904 34.464 9.856 67.328 23.424 97.76 40.32 11.712 6.528 18.112 19.648 16.064 32.896L734.432 261.76c9.824 8.736 19.104 18.016 27.808 27.776l123.328-19.2c13.152-2.016 26.336 4.384 32.864 16.032 16.896 30.4 30.496 63.296 40.352 97.824 3.648 12.896-1.088 26.688-11.904 34.624l-100.48 73.408c0.448 6.816 0.672 13.376 0.672 19.776s-0.224 12.96-0.672 19.776l100.48 73.376c10.816 7.904 15.552 21.728 11.904 34.624-9.856 34.496-23.424 67.424-40.352 97.824-6.528 11.712-19.488 18.048-32.896 16.064l-123.296-19.2c-8.672 9.76-17.984 19.072-27.808 27.776l19.232 123.296c2.048 13.248-4.352 26.368-16.064 32.896-30.24 16.832-63.136 30.4-97.76 40.32C636.928 959.584 633.984 960 631.04 960zM337.152 872.672c13.984 6.752 28.544 12.704 43.552 17.92l70.528-96.576c6.784-9.28 17.984-14.272 29.472-12.896 20.576 2.336 42.08 2.336 62.624 0 11.84-1.376 22.72 3.616 29.472 12.896l70.528 96.576c15.04-5.184 29.6-11.168 43.552-17.92l-18.432-118.368c-1.76-11.36 2.72-22.848 11.712-30.016 16.384-12.992 31.264-27.872 44.16-44.128 7.168-8.992 18.4-13.504 30.016-11.744l118.368 18.432c6.752-14.016 12.736-28.576 17.92-43.552l-96.576-70.528c-9.28-6.784-14.208-18.016-12.896-29.472 1.152-10.272 1.952-20.704 1.952-31.296s-0.768-21.024-1.952-31.296c-1.312-11.424 3.616-22.688 12.896-29.472l96.576-70.56c-5.152-15.008-11.168-29.568-17.92-43.552l-118.4 18.432c-11.328 1.696-22.816-2.688-29.984-11.744-12.896-16.256-27.776-31.104-44.16-44.128-8.992-7.168-13.472-18.592-11.712-29.984l18.432-118.4c-14.016-6.752-28.576-12.736-43.52-17.92l-70.56 96.576c-6.784 9.28-17.696 14.368-29.472 12.928-20.544-2.368-42.016-2.4-62.56 0-11.52 1.344-22.752-3.616-29.536-12.896l-70.528-96.576c-14.976 5.152-29.536 11.136-43.552 17.92l18.432 118.368c1.76 11.392-2.72 22.848-11.744 30.016-16.32 12.928-31.2 27.808-44.128 44.128-7.168 9.024-18.56 13.472-30.016 11.744l-118.368-18.432c-6.752 14.016-12.736 28.576-17.92 43.552l96.576 70.56c9.312 6.784 14.24 18.08 12.896 29.536C241.696 491.008 240.928 501.408 240.928 512s0.768 20.992 1.952 31.264c1.344 11.456-3.616 22.688-12.896 29.504l-96.576 70.528c5.184 15.008 11.168 29.568 17.92 43.552l118.368-18.432c11.392-1.76 22.848 2.752 30.016 11.744 12.896 16.32 27.744 31.168 44.128 44.128 9.024 7.168 13.504 18.624 11.744 30.016L337.152 872.672z" ></path></symbol><symbol id="el-icon-self-new" viewBox="0 0 1024 1024"><path d="M963.072 446.336c0-211.744-200.96-384-448-384s-448 172.256-448 384c0 116.48 63.008 226.048 172.896 300.672 14.656 9.984 34.528 6.144 44.448-8.512 9.952-14.624 6.112-34.528-8.512-44.448-92.032-62.496-144.832-152.768-144.832-247.712 0-176.448 172.256-320 384-320 211.744 0 384 143.552 384 320 0 176.448-172.256 320-384 320-1.984 0-3.68 0.768-5.568 1.12-15.104-2.688-30.464 5.216-35.776 20.192-6.144 17.376-46.368 46.656-94.144 73.792 17.472-58.208 9.088-70.688 3.52-78.976-6.72-9.984-17.92-15.936-29.92-15.936-17.664 0-32 14.304-32 32 0 5.824 1.536 11.264 4.256 15.936-3.232 18.24-17.216 60.864-33.088 99.872-4.928 12.096-1.984 25.984 7.36 35.072 6.112 5.888 14.112 8.992 22.272 8.992 4.384 0 8.8-0.896 12.992-2.752 36.48-16.256 147.68-69.12 187.616-125.664C766.144 826.496 963.072 655.904 963.072 446.336z" ></path><path d="M342.624 604.544c4.672 2.4 9.664 3.52 14.592 3.52 11.616 0 22.816-6.336 28.512-17.408l71.584-139.488 91.584 142.208c5.824 9.024 15.744 14.528 26.464 14.688 0.16 0 0.32 0 0.448 0 10.56 0 20.48-5.216 26.432-13.984l128.8-188.864c9.984-14.624 6.176-34.528-8.416-44.48-14.624-9.952-34.528-6.208-44.48 8.416l-101.632 148.992-95.456-148.288c-6.176-9.6-17.152-14.752-28.48-14.656-11.424 0.576-21.696 7.2-26.912 17.344l-96.896 188.896C320.672 577.184 326.88 596.48 342.624 604.544z" ></path></symbol><symbol id="el-icon-self-album" viewBox="0 0 1024 1024"><path d="M256 448m-64 0a2 2 0 1 0 128 0 2 2 0 1 0-128 0Z" ></path><path d="M714.688 256 181.312 256C116.64 256 64 308.64 64 373.312l0 405.376C64 843.36 116.64 896 181.312 896l533.376 0C779.36 896 832 843.36 832 778.688L832 373.312C832 308.64 779.36 256 714.688 256zM768 778.688C768 808.096 744.096 832 714.688 832L181.312 832C151.936 832 128 808.096 128 778.688L128 373.312C128 343.936 151.936 320 181.312 320l533.376 0C744.096 320 768 343.936 768 373.312L768 778.688z" ></path><path d="M842.688 128 256 128C238.336 128 224 142.336 224 160s14.336 32 32 32l586.688 0C872.096 192 896 215.936 896 245.312L896 640c0 17.696 14.304 32 32 32s32-14.304 32-32L960 245.312C960 180.64 907.36 128 842.688 128z" ></path><path d="M672.64 448.736c-129.056 0-143.936 72.672-152.832 116.096-7.2 35.168-10.4 39.968-28.64 42.88-34.976 5.568-44 0.192-58.912-8.8-15.456-9.312-36.672-22.08-77.952-23.168C227.616 573.12 194.112 723.84 192.704 730.24c-3.712 17.28 7.264 34.304 24.544 38.016 2.272 0.512 4.544 0.704 6.752 0.704 14.752 0 28-10.24 31.264-25.248 0.224-1.056 23.232-104.032 95.488-104.032 0.64 0 1.28 0 1.92 0.032 24.352 0.608 33.632 6.208 46.496 13.984 24.032 14.464 48.352 25.76 102.08 17.184 64.192-10.272 74.432-60.192 81.184-93.216 8.32-40.512 13.312-64.928 90.144-64.928 17.696 0 32-14.336 32-32S690.304 448.736 672.64 448.736z" ></path></symbol><symbol id="el-icon-self-grade" viewBox="0 0 1024 1024"><path d="M863.721739 554.295652c24.486957-48.973913 28.93913-102.4 28.93913-160.278261C892.66087 189.217391 734.608696 22.26087 527.582609 22.26087 320.556522 22.26087 162.504348 189.217391 162.504348 394.017391c0 57.878261 4.452174 111.304348 28.93913 160.278261L51.2 792.486957c0 0 95.721739 35.617391 193.669565 55.652174 64.556522 73.46087 117.982609 151.373913 117.982609 151.373913l129.113043-247.095652c8.904348 0 20.034783 0 26.713043 0 8.904348 0 20.034783 0 26.713043 0l138.017391 233.73913c0 0 57.878261-64.556522 124.66087-138.017391 97.947826-20.034783 193.669565-55.652174 193.669565-55.652174L863.721739 554.295652 863.721739 554.295652 863.721739 554.295652zM353.947826 903.791304c0 0-46.747826-60.104348-89.043478-102.4-62.330435-17.808696-144.695652-35.617391-144.695652-35.617391L215.930435 601.043478c51.2 66.782609 135.791304 129.113043 220.382609 149.147826L353.947826 903.791304 353.947826 903.791304zM520.904348 710.121739c-80.13913 0-309.426087-48.973913-316.104348-316.104348C198.121739 224.834783 351.721739 64.556522 520.904348 64.556522c169.182609 0 329.46087 162.504348 329.46087 329.46087C852.591304 650.017391 601.043478 710.121739 520.904348 710.121739L520.904348 710.121739zM685.634783 903.791304l-82.365217-151.373913c84.591304-20.034783 182.53913-84.591304 233.73913-151.373913l95.721739 164.730435c0 0-82.365217 17.808696-144.695652 35.617391L685.634783 903.791304 685.634783 903.791304zM527.582609 175.86087c-117.982609 0-213.704348 95.721739-213.704348 213.704348s95.721739 213.704348 213.704348 213.704348c117.982609 0 213.704348-95.721739 213.704348-213.704348S645.565217 175.86087 527.582609 175.86087L527.582609 175.86087 527.582609 175.86087zM534.26087 558.747826c-53.426087 0-169.182609-20.034783-178.086957-178.086957-4.452174-84.591304 95.721739-164.730435 178.086957-164.730435 84.591304 0 162.504348 95.721739 164.730435 178.086957C701.217391 505.321739 587.686957 558.747826 534.26087 558.747826L534.26087 558.747826zM534.26087 558.747826" ></path></symbol><symbol id="el-icon-self-nav" viewBox="0 0 1024 1024"><path d="M888.838 319.775l-751.1 0c-23.944 0-43.339-19.553-43.339-43.646l0-40.012c0-24.093 19.395-43.645 43.339-43.645l751.1 0c23.923 0 43.334 19.554 43.334 43.645l0 40.012c-0.001 24.093-19.411 43.646-43.333 43.646l0 0zM888.838 587.509l-751.1 0c-23.944 0-43.339-19.533-43.339-43.64l0-39.998c0-24.115 19.395-43.647 43.339-43.647l751.1 0c23.923 0 43.334 19.533 43.334 43.647l0 39.998c-0.001 24.107-19.411 43.64-43.333 43.64l0 0zM888.838 876.17l-751.1 0c-23.944 0-43.339-19.532-43.339-43.627l0-40.017c0-24.093 19.395-43.641 43.339-43.641l751.1 0c23.923 0 43.334 19.548 43.334 43.641l0 40.017c-0.001 24.094-19.411 43.627-43.333 43.627l0 0z" ></path></symbol><symbol id="el-icon-self-mima" viewBox="0 0 1024 1024"><path d="M791.366 459.07l-459.174-0.791c-0.186-61.936-16.847-157.926 36.027-216.192 32.142-35.52 86.326-72.068 144.127-72.068 85.395 0 149.246 64.412 180.166 144.126 9.009 22.992 16.936 35.38 36.037 36.037 47.899 1.45 44.986-49.075 36.029-72.067C719.914 163.03 636.118 97.953 512.346 97.953c-84.12 0-130.475 17.265-180.154 72.065-76.009 83.746-72.259 219.151-72.067 288.26l-26.795 0.791c-25.045 0-45.273 20.034-45.273 44.667v378.249c0 24.774 20.269 44.755 45.273 44.755h557.99c25.009 0 45.273-19.989 45.273-44.755V503.737c0.046-24.678-20.218-44.667-45.227-44.667zM765.68 818.35c0 9.966-3.476 18.514-10.58 25.52-7.045 7.051-15.56 10.58-25.517 10.58H296.362c-9.966 0-18.472-3.478-25.475-10.58-7.051-7.049-10.624-15.553-10.624-25.52V565.635c0-19.878 16.166-36.054 36.098-36.054h433.212c19.932 0 36.107 16.176 36.107 36.054V818.35z" fill="" ></path></symbol><symbol id="el-icon-self-character" viewBox="0 0 1024 1024"><path d="M921.087359 990.72v-35.2A410.24 410.24 0 0 0 661.887359 576a307.2 307.2 0 1 0-300.8 0 410.24 410.24 0 0 0-256 380.8v35.2H101.887359a32 32 0 0 0 32 32 32 32 0 0 0 32-32v-34.56a345.6 345.6 0 0 1 691.2 0v35.2a31.36 31.36 0 0 0 31.36 30.08 32 32 0 0 0 32.64-32zM270.207359 307.2A241.28 241.28 0 1 1 511.487359 548.48 241.28 241.28 0 0 1 270.207359 307.2z" ></path></symbol><symbol id="el-icon-self-xuexi-" viewBox="0 0 1024 1024"><path d="M996.5 287.1L567.6 68.8c-34.8-17.6-76.4-17.6-111.2 0L27.5 286.4C10.6 295 0 312.1 0 331.2c0 19 10.5 36.2 27.5 44.8l219.8 111.8v305c0 33.9 18.8 64.4 49 79.7l164.9 83.6c15.9 8.1 33.4 12.1 50.9 12.1s35-4 50.9-12.1l164.9-83.6c30.2-15.3 49-45.9 49-79.7V488.2L901.4 425v111.9c0 15.2 12.3 27.5 27.5 27.5s27.5-12.3 27.5-27.5V401.6c0-1.5-0.1-2.9-0.3-4.3l40.4-20.5c17-8.6 27.5-25.8 27.5-44.8 0-19.1-10.5-36.3-27.5-44.9zM721.8 792.9c0 13.1-7.2 24.8-18.9 30.8L538 907.3c-16.3 8.3-35.8 8.3-52.1 0L321 823.7c-11.6-5.9-18.9-17.7-18.9-30.8V515.8l154.2 78.5c17.4 8.8 36.5 13.2 55.6 13.2s38.2-4.4 55.6-13.2l154.2-78.2v276.8z m-179-247.6c-19.3 9.8-42.3 9.8-61.5 0L60.5 331.2l420.7-213.4c19.3-9.8 42.3-9.8 61.5 0l420.7 214.1-420.6 213.4z" fill="" ></path></symbol><symbol id="el-icon-self-statistic" viewBox="0 0 1024 1024"><path d="M838.33 659.732c0 33.422-21.592 54.388-54.39 54.388H240.059c-32.796 0-54.388-20.966-54.388-54.388 0 0-0.409-482.507-0.409-516.961h653.477c0 54.85-0.409 516.96-0.409 516.96zM76.892 88.655v54.389h54.389v516.689c0 64.45 44.707 108.776 108.776 108.776h163.166L294.447 958.87l81.909-0.353 108.45-190.006h54.388l108.45 190.006 81.909 0.353-108.777-190.36h163.166c64.069 0 108.776-44.326 108.776-108.776v-516.69h54.389v-54.39H76.893v0.001z m271.942 543.883c15.038 0 27.194-12.156 27.194-27.194V496.568c0-15.011-12.156-27.195-27.194-27.195s-27.195 12.183-27.195 27.195v108.776c0 15.038 12.156 27.194 27.195 27.194z m326.33 0c15.039 0 27.195-12.156 27.195-27.194V360.597c0-15.011-12.156-27.195-27.195-27.195s-27.194 12.183-27.194 27.195v244.747c0 15.038 12.156 27.194 27.194 27.194z m-163.165 0c15.039 0 27.194-12.156 27.194-27.194v-326.33c0-15.011-12.155-27.195-27.194-27.195s-27.194 12.183-27.194 27.195v326.33c0 15.038 12.155 27.194 27.194 27.194z" ></path></symbol></svg>',l=(c=document.getElementsByTagName("script"))[c.length-1].getAttribute("data-injectcss");if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(c){console&&console.log(c)}}!function(c){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(c,0);else{var l=function(){document.removeEventListener("DOMContentLoaded",l,!1),c()};document.addEventListener("DOMContentLoaded",l,!1)}else document.attachEvent&&(e=c,t=a.document,o=!1,s=function(){o||(o=!0,e())},(i=function(){try{t.documentElement.doScroll("left")}catch(c){return void setTimeout(i,50)}s()})(),t.onreadystatechange=function(){"complete"==t.readyState&&(t.onreadystatechange=null,s())});var e,t,o,s,i}(function(){var c,l,e,t,o,s;(c=document.createElement("div")).innerHTML=i,i=null,(l=c.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",e=l,(t=document.body).firstChild?(o=e,(s=t.firstChild).parentNode.insertBefore(o,s)):t.appendChild(e))})}(window);
\ No newline at end of file
!function(i){var c,a='<svg><symbol id="el-icon-self-discover" viewBox="0 0 1024 1024"><path d="M544 288c0-52.928-43.072-96-96-96s-96 43.072-96 96 43.072 96 96 96S544 340.928 544 288zM416 288c0-17.632 14.368-32 32-32s32 14.368 32 32-14.368 32-32 32S416 305.632 416 288z" ></path><path d="M304 448C259.904 448 224 483.904 224 528 224 572.128 259.904 608 304 608s80-35.872 80-80C384 483.904 348.096 448 304 448zM304 544C295.168 544 288 536.832 288 528S295.168 512 304 512s16 7.168 16 16S312.832 544 304 544z" ></path><path d="M640 736m-64 0a2 2 0 1 0 128 0 2 2 0 1 0-128 0Z" ></path><path d="M887.84 268.672c39.744-60.672 64.256-113.184 64.224-148.96 0-22.976-9.312-36.928-17.12-44.576-38.08-37.376-110.016-3.2-160.768 25.664-15.328 8.736-20.704 28.288-11.968 43.648 8.736 15.36 28.32 20.704 43.648 11.968 49.408-28.16 73.248-33.088 82.496-33.696-1.504 41.376-95.328 202.432-330.208 433.504-226.368 222.72-398.656 324.672-442.24 325.376 0.96-11.36 8.768-44.448 58.528-118.688 1.312-1.888 2.4-3.968 3.232-6.144l2.304-5.92c0.256-0.64 0.288-1.344 0.512-2.016 8.8-10.24 10.816-25.088 3.392-37.248C147.328 651.616 128 582.592 128 512 128 300.256 300.256 128 512 128c59.296 0 116.096 13.12 168.864 39.04 15.936 7.776 35.04 1.184 42.816-14.656 7.776-15.84 1.248-35.008-14.624-42.816C647.424 79.328 581.12 64 512 64 264.96 64 64 264.96 64 512c0 76.064 19.488 150.496 56.064 216.64-0.192 0.576-0.64 1.056-0.8 1.632C51.168 832.608 35.2 895.68 68.928 928.768c11.008 10.784 25.632 15.584 43.072 15.584 39.328 0 92.864-24.928 150.08-60.896C336.096 933.408 421.952 960 512 960c247.04 0 448-200.96 448-448C960 424.864 934.912 341.248 887.84 268.672zM512 896c-68.288 0-133.728-17.888-191.84-51.552 128.288-92.256 258.432-218.56 282.848-242.624 89.152-87.712 178.56-187.04 244.64-275.968C879.2 382.592 896 446.112 896 512 896 723.744 723.744 896 512 896z" ></path></symbol><symbol id="el-icon-self-settings" viewBox="0 0 1024 1024"><path d="M512 608c-52.928 0-96-43.072-96-96s43.072-96 96-96 96 43.072 96 96S564.928 608 512 608zM512 480c-17.632 0-32 14.368-32 32s14.368 32 32 32 32-14.368 32-32S529.632 480 512 480z" ></path><path d="M631.04 960c-10.016 0-19.712-4.736-25.856-13.12l-73.408-100.48c-13.664 0.832-25.92 0.864-39.584 0l-73.376 100.48c-7.904 10.816-21.728 15.616-34.656 11.872-34.592-9.888-67.488-23.456-97.76-40.32-11.712-6.528-18.112-19.648-16.032-32.896l19.2-123.264c-9.824-8.704-19.136-18.016-27.84-27.808l-123.264 19.2c-13.056 1.984-26.336-4.352-32.896-16.064-16.896-30.368-30.464-63.296-40.32-97.824-3.68-12.864 1.056-26.72 11.904-34.624l100.448-73.376C177.152 524.96 176.928 518.4 176.928 512s0.224-12.96 0.672-19.776L77.12 418.816c-10.816-7.936-15.584-21.728-11.904-34.656 9.856-34.496 23.424-67.392 40.32-97.792 6.56-11.712 19.712-18.048 32.896-16.064l123.264 19.2c8.736-9.824 18.016-19.104 27.84-27.84l-19.2-123.264C268.288 125.184 274.72 112.064 286.432 105.536c30.4-16.896 63.296-30.464 97.76-40.32 12.928-3.648 26.72 1.056 34.656 11.904l73.376 100.448c13.568-0.896 25.824-0.896 39.584 0l73.408-100.48c7.904-10.848 21.664-15.52 34.656-11.904 34.464 9.856 67.328 23.424 97.76 40.32 11.712 6.528 18.112 19.648 16.064 32.896L734.432 261.76c9.824 8.736 19.104 18.016 27.808 27.776l123.328-19.2c13.152-2.016 26.336 4.384 32.864 16.032 16.896 30.4 30.496 63.296 40.352 97.824 3.648 12.896-1.088 26.688-11.904 34.624l-100.48 73.408c0.448 6.816 0.672 13.376 0.672 19.776s-0.224 12.96-0.672 19.776l100.48 73.376c10.816 7.904 15.552 21.728 11.904 34.624-9.856 34.496-23.424 67.424-40.352 97.824-6.528 11.712-19.488 18.048-32.896 16.064l-123.296-19.2c-8.672 9.76-17.984 19.072-27.808 27.776l19.232 123.296c2.048 13.248-4.352 26.368-16.064 32.896-30.24 16.832-63.136 30.4-97.76 40.32C636.928 959.584 633.984 960 631.04 960zM337.152 872.672c13.984 6.752 28.544 12.704 43.552 17.92l70.528-96.576c6.784-9.28 17.984-14.272 29.472-12.896 20.576 2.336 42.08 2.336 62.624 0 11.84-1.376 22.72 3.616 29.472 12.896l70.528 96.576c15.04-5.184 29.6-11.168 43.552-17.92l-18.432-118.368c-1.76-11.36 2.72-22.848 11.712-30.016 16.384-12.992 31.264-27.872 44.16-44.128 7.168-8.992 18.4-13.504 30.016-11.744l118.368 18.432c6.752-14.016 12.736-28.576 17.92-43.552l-96.576-70.528c-9.28-6.784-14.208-18.016-12.896-29.472 1.152-10.272 1.952-20.704 1.952-31.296s-0.768-21.024-1.952-31.296c-1.312-11.424 3.616-22.688 12.896-29.472l96.576-70.56c-5.152-15.008-11.168-29.568-17.92-43.552l-118.4 18.432c-11.328 1.696-22.816-2.688-29.984-11.744-12.896-16.256-27.776-31.104-44.16-44.128-8.992-7.168-13.472-18.592-11.712-29.984l18.432-118.4c-14.016-6.752-28.576-12.736-43.52-17.92l-70.56 96.576c-6.784 9.28-17.696 14.368-29.472 12.928-20.544-2.368-42.016-2.4-62.56 0-11.52 1.344-22.752-3.616-29.536-12.896l-70.528-96.576c-14.976 5.152-29.536 11.136-43.552 17.92l18.432 118.368c1.76 11.392-2.72 22.848-11.744 30.016-16.32 12.928-31.2 27.808-44.128 44.128-7.168 9.024-18.56 13.472-30.016 11.744l-118.368-18.432c-6.752 14.016-12.736 28.576-17.92 43.552l96.576 70.56c9.312 6.784 14.24 18.08 12.896 29.536C241.696 491.008 240.928 501.408 240.928 512s0.768 20.992 1.952 31.264c1.344 11.456-3.616 22.688-12.896 29.504l-96.576 70.528c5.184 15.008 11.168 29.568 17.92 43.552l118.368-18.432c11.392-1.76 22.848 2.752 30.016 11.744 12.896 16.32 27.744 31.168 44.128 44.128 9.024 7.168 13.504 18.624 11.744 30.016L337.152 872.672z" ></path></symbol><symbol id="el-icon-self-new" viewBox="0 0 1024 1024"><path d="M963.072 446.336c0-211.744-200.96-384-448-384s-448 172.256-448 384c0 116.48 63.008 226.048 172.896 300.672 14.656 9.984 34.528 6.144 44.448-8.512 9.952-14.624 6.112-34.528-8.512-44.448-92.032-62.496-144.832-152.768-144.832-247.712 0-176.448 172.256-320 384-320 211.744 0 384 143.552 384 320 0 176.448-172.256 320-384 320-1.984 0-3.68 0.768-5.568 1.12-15.104-2.688-30.464 5.216-35.776 20.192-6.144 17.376-46.368 46.656-94.144 73.792 17.472-58.208 9.088-70.688 3.52-78.976-6.72-9.984-17.92-15.936-29.92-15.936-17.664 0-32 14.304-32 32 0 5.824 1.536 11.264 4.256 15.936-3.232 18.24-17.216 60.864-33.088 99.872-4.928 12.096-1.984 25.984 7.36 35.072 6.112 5.888 14.112 8.992 22.272 8.992 4.384 0 8.8-0.896 12.992-2.752 36.48-16.256 147.68-69.12 187.616-125.664C766.144 826.496 963.072 655.904 963.072 446.336z" ></path><path d="M342.624 604.544c4.672 2.4 9.664 3.52 14.592 3.52 11.616 0 22.816-6.336 28.512-17.408l71.584-139.488 91.584 142.208c5.824 9.024 15.744 14.528 26.464 14.688 0.16 0 0.32 0 0.448 0 10.56 0 20.48-5.216 26.432-13.984l128.8-188.864c9.984-14.624 6.176-34.528-8.416-44.48-14.624-9.952-34.528-6.208-44.48 8.416l-101.632 148.992-95.456-148.288c-6.176-9.6-17.152-14.752-28.48-14.656-11.424 0.576-21.696 7.2-26.912 17.344l-96.896 188.896C320.672 577.184 326.88 596.48 342.624 604.544z" ></path></symbol><symbol id="el-icon-self-album" viewBox="0 0 1024 1024"><path d="M256 448m-64 0a2 2 0 1 0 128 0 2 2 0 1 0-128 0Z" ></path><path d="M714.688 256 181.312 256C116.64 256 64 308.64 64 373.312l0 405.376C64 843.36 116.64 896 181.312 896l533.376 0C779.36 896 832 843.36 832 778.688L832 373.312C832 308.64 779.36 256 714.688 256zM768 778.688C768 808.096 744.096 832 714.688 832L181.312 832C151.936 832 128 808.096 128 778.688L128 373.312C128 343.936 151.936 320 181.312 320l533.376 0C744.096 320 768 343.936 768 373.312L768 778.688z" ></path><path d="M842.688 128 256 128C238.336 128 224 142.336 224 160s14.336 32 32 32l586.688 0C872.096 192 896 215.936 896 245.312L896 640c0 17.696 14.304 32 32 32s32-14.304 32-32L960 245.312C960 180.64 907.36 128 842.688 128z" ></path><path d="M672.64 448.736c-129.056 0-143.936 72.672-152.832 116.096-7.2 35.168-10.4 39.968-28.64 42.88-34.976 5.568-44 0.192-58.912-8.8-15.456-9.312-36.672-22.08-77.952-23.168C227.616 573.12 194.112 723.84 192.704 730.24c-3.712 17.28 7.264 34.304 24.544 38.016 2.272 0.512 4.544 0.704 6.752 0.704 14.752 0 28-10.24 31.264-25.248 0.224-1.056 23.232-104.032 95.488-104.032 0.64 0 1.28 0 1.92 0.032 24.352 0.608 33.632 6.208 46.496 13.984 24.032 14.464 48.352 25.76 102.08 17.184 64.192-10.272 74.432-60.192 81.184-93.216 8.32-40.512 13.312-64.928 90.144-64.928 17.696 0 32-14.336 32-32S690.304 448.736 672.64 448.736z" ></path></symbol><symbol id="el-icon-self-cc-book" viewBox="0 0 1024 1024"><path d="M682.697195 859.707167 242.160807 859.707167c-12.152784 0-22.025643-9.893324-22.025643-22.026666 0-12.130272 9.872858-22.025643 22.025643-22.025643l440.537412 0c12.130272 0 22.025643 9.895371 22.025643 22.025643C704.723861 849.813842 694.82849 859.707167 682.697195 859.707167z" ></path><path d="M841.773214 158.324912c-12.955056 0-23.51967 10.543124-23.51967 23.520693l0 705.638185c0 12.95608-10.56666 23.520693-23.521716 23.520693L277.262256 911.004483c-38.911218 0-70.564126-31.650861-70.564126-70.564126 0-38.910195 31.652907-70.563102 70.564126-70.563102l423.382707 0c38.911218 0 70.565149-31.652907 70.565149-70.563102L771.210112 134.803195c0-38.911218-31.653931-70.564126-70.565149-70.564126L230.218824 64.23907c-38.911218 0-70.563102 31.652907-70.563102 70.564126l0 705.637162c0 64.823378 52.785204 117.608581 117.606535 117.608581l517.468548 0c38.909172 0 70.563102-31.653931 70.563102-70.565149L865.293907 181.845605C865.293907 168.868035 854.727247 158.324912 841.773214 158.324912zM230.218824 111.281479l470.426139 0c12.955056 0 23.521716 10.543124 23.521716 23.521716l0 564.510958c0 12.955056-10.56666 23.520693-23.521716 23.520693l-423.382707 0c-26.439162 0-50.90232 8.820899-70.564126 23.566742L206.698131 134.803195C206.698131 121.824603 217.241254 111.281479 230.218824 111.281479z" ></path></symbol><symbol id="el-icon-self-13" viewBox="0 0 1024 1024"><path d="M147.2 180.8h728c41.6 0 76.8 35.2 76.8 76.8v552c0 41.6-35.2 76.8-76.8 76.8h-728c-41.6 0-76.8-35.2-76.8-76.8v-552c0-41.6 35.2-76.8 76.8-76.8v0zM147.2 926.4c243.2 0 486.4 0 728 0 64 0 115.2-51.2 115.2-115.2 0-184 0-368 0-552 0-64-51.2-115.2-115.2-115.2-243.2 0-486.4 0-728 0-64 0-115.2 51.2-115.2 115.2 0 184 0 368 0 552 0 62.4 51.2 115.2 115.2 115.2z" ></path><path d="M201.6 419.2c0 44.8 36.8 81.6 81.6 81.6s81.6-36.8 81.6-81.6c0-44.8-36.8-81.6-81.6-81.6-44.8 0-81.6 36.8-81.6 81.6v0zM240 419.2c0-24 19.2-43.2 43.2-43.2s43.2 19.2 43.2 43.2c0 24-19.2 43.2-43.2 43.2-22.4 0-43.2-19.2-43.2-43.2z" ></path><path d="M675.2 499.2l203.2 203.2c8 8 8 19.2 0 27.2v0c-8 8-19.2 8-27.2 0l-203.2-203.2c-14.4-14.4-40-14.4-54.4 0l-163.2 161.6c-30.4 30.4-80 30.4-110.4 0l-25.6-25.6c-14.4-14.4-40-14.4-54.4 0l-67.2 67.2c-8 8-19.2 8-27.2 0v0c-8-8-8-19.2 0-27.2l67.2-67.2c30.4-30.4 80-30.4 110.4 0l25.6 25.6c14.4 14.4 40 14.4 54.4 0l161.6-161.6c30.4-30.4 80-30.4 110.4 0z" ></path></symbol><symbol id="el-icon-self-grade" viewBox="0 0 1024 1024"><path d="M863.721739 554.295652c24.486957-48.973913 28.93913-102.4 28.93913-160.278261C892.66087 189.217391 734.608696 22.26087 527.582609 22.26087 320.556522 22.26087 162.504348 189.217391 162.504348 394.017391c0 57.878261 4.452174 111.304348 28.93913 160.278261L51.2 792.486957c0 0 95.721739 35.617391 193.669565 55.652174 64.556522 73.46087 117.982609 151.373913 117.982609 151.373913l129.113043-247.095652c8.904348 0 20.034783 0 26.713043 0 8.904348 0 20.034783 0 26.713043 0l138.017391 233.73913c0 0 57.878261-64.556522 124.66087-138.017391 97.947826-20.034783 193.669565-55.652174 193.669565-55.652174L863.721739 554.295652 863.721739 554.295652 863.721739 554.295652zM353.947826 903.791304c0 0-46.747826-60.104348-89.043478-102.4-62.330435-17.808696-144.695652-35.617391-144.695652-35.617391L215.930435 601.043478c51.2 66.782609 135.791304 129.113043 220.382609 149.147826L353.947826 903.791304 353.947826 903.791304zM520.904348 710.121739c-80.13913 0-309.426087-48.973913-316.104348-316.104348C198.121739 224.834783 351.721739 64.556522 520.904348 64.556522c169.182609 0 329.46087 162.504348 329.46087 329.46087C852.591304 650.017391 601.043478 710.121739 520.904348 710.121739L520.904348 710.121739zM685.634783 903.791304l-82.365217-151.373913c84.591304-20.034783 182.53913-84.591304 233.73913-151.373913l95.721739 164.730435c0 0-82.365217 17.808696-144.695652 35.617391L685.634783 903.791304 685.634783 903.791304zM527.582609 175.86087c-117.982609 0-213.704348 95.721739-213.704348 213.704348s95.721739 213.704348 213.704348 213.704348c117.982609 0 213.704348-95.721739 213.704348-213.704348S645.565217 175.86087 527.582609 175.86087L527.582609 175.86087 527.582609 175.86087zM534.26087 558.747826c-53.426087 0-169.182609-20.034783-178.086957-178.086957-4.452174-84.591304 95.721739-164.730435 178.086957-164.730435 84.591304 0 162.504348 95.721739 164.730435 178.086957C701.217391 505.321739 587.686957 558.747826 534.26087 558.747826L534.26087 558.747826zM534.26087 558.747826" ></path></symbol><symbol id="el-icon-self-nav" viewBox="0 0 1024 1024"><path d="M888.838 319.775l-751.1 0c-23.944 0-43.339-19.553-43.339-43.646l0-40.012c0-24.093 19.395-43.645 43.339-43.645l751.1 0c23.923 0 43.334 19.554 43.334 43.645l0 40.012c-0.001 24.093-19.411 43.646-43.333 43.646l0 0zM888.838 587.509l-751.1 0c-23.944 0-43.339-19.533-43.339-43.64l0-39.998c0-24.115 19.395-43.647 43.339-43.647l751.1 0c23.923 0 43.334 19.533 43.334 43.647l0 39.998c-0.001 24.107-19.411 43.64-43.333 43.64l0 0zM888.838 876.17l-751.1 0c-23.944 0-43.339-19.532-43.339-43.627l0-40.017c0-24.093 19.395-43.641 43.339-43.641l751.1 0c23.923 0 43.334 19.548 43.334 43.641l0 40.017c-0.001 24.094-19.411 43.627-43.333 43.627l0 0z" ></path></symbol><symbol id="el-icon-self-mima" viewBox="0 0 1024 1024"><path d="M791.366 459.07l-459.174-0.791c-0.186-61.936-16.847-157.926 36.027-216.192 32.142-35.52 86.326-72.068 144.127-72.068 85.395 0 149.246 64.412 180.166 144.126 9.009 22.992 16.936 35.38 36.037 36.037 47.899 1.45 44.986-49.075 36.029-72.067C719.914 163.03 636.118 97.953 512.346 97.953c-84.12 0-130.475 17.265-180.154 72.065-76.009 83.746-72.259 219.151-72.067 288.26l-26.795 0.791c-25.045 0-45.273 20.034-45.273 44.667v378.249c0 24.774 20.269 44.755 45.273 44.755h557.99c25.009 0 45.273-19.989 45.273-44.755V503.737c0.046-24.678-20.218-44.667-45.227-44.667zM765.68 818.35c0 9.966-3.476 18.514-10.58 25.52-7.045 7.051-15.56 10.58-25.517 10.58H296.362c-9.966 0-18.472-3.478-25.475-10.58-7.051-7.049-10.624-15.553-10.624-25.52V565.635c0-19.878 16.166-36.054 36.098-36.054h433.212c19.932 0 36.107 16.176 36.107 36.054V818.35z" fill="" ></path></symbol><symbol id="el-icon-self-character" viewBox="0 0 1024 1024"><path d="M921.087359 990.72v-35.2A410.24 410.24 0 0 0 661.887359 576a307.2 307.2 0 1 0-300.8 0 410.24 410.24 0 0 0-256 380.8v35.2H101.887359a32 32 0 0 0 32 32 32 32 0 0 0 32-32v-34.56a345.6 345.6 0 0 1 691.2 0v35.2a31.36 31.36 0 0 0 31.36 30.08 32 32 0 0 0 32.64-32zM270.207359 307.2A241.28 241.28 0 1 1 511.487359 548.48 241.28 241.28 0 0 1 270.207359 307.2z" ></path></symbol><symbol id="el-icon-self-xuexi-" viewBox="0 0 1024 1024"><path d="M996.5 287.1L567.6 68.8c-34.8-17.6-76.4-17.6-111.2 0L27.5 286.4C10.6 295 0 312.1 0 331.2c0 19 10.5 36.2 27.5 44.8l219.8 111.8v305c0 33.9 18.8 64.4 49 79.7l164.9 83.6c15.9 8.1 33.4 12.1 50.9 12.1s35-4 50.9-12.1l164.9-83.6c30.2-15.3 49-45.9 49-79.7V488.2L901.4 425v111.9c0 15.2 12.3 27.5 27.5 27.5s27.5-12.3 27.5-27.5V401.6c0-1.5-0.1-2.9-0.3-4.3l40.4-20.5c17-8.6 27.5-25.8 27.5-44.8 0-19.1-10.5-36.3-27.5-44.9zM721.8 792.9c0 13.1-7.2 24.8-18.9 30.8L538 907.3c-16.3 8.3-35.8 8.3-52.1 0L321 823.7c-11.6-5.9-18.9-17.7-18.9-30.8V515.8l154.2 78.5c17.4 8.8 36.5 13.2 55.6 13.2s38.2-4.4 55.6-13.2l154.2-78.2v276.8z m-179-247.6c-19.3 9.8-42.3 9.8-61.5 0L60.5 331.2l420.7-213.4c19.3-9.8 42.3-9.8 61.5 0l420.7 214.1-420.6 213.4z" fill="" ></path></symbol><symbol id="el-icon-self-statistic" viewBox="0 0 1024 1024"><path d="M838.33 659.732c0 33.422-21.592 54.388-54.39 54.388H240.059c-32.796 0-54.388-20.966-54.388-54.388 0 0-0.409-482.507-0.409-516.961h653.477c0 54.85-0.409 516.96-0.409 516.96zM76.892 88.655v54.389h54.389v516.689c0 64.45 44.707 108.776 108.776 108.776h163.166L294.447 958.87l81.909-0.353 108.45-190.006h54.388l108.45 190.006 81.909 0.353-108.777-190.36h163.166c64.069 0 108.776-44.326 108.776-108.776v-516.69h54.389v-54.39H76.893v0.001z m271.942 543.883c15.038 0 27.194-12.156 27.194-27.194V496.568c0-15.011-12.156-27.195-27.194-27.195s-27.195 12.183-27.195 27.195v108.776c0 15.038 12.156 27.194 27.195 27.194z m326.33 0c15.039 0 27.195-12.156 27.195-27.194V360.597c0-15.011-12.156-27.195-27.195-27.195s-27.194 12.183-27.194 27.195v244.747c0 15.038 12.156 27.194 27.194 27.194z m-163.165 0c15.039 0 27.194-12.156 27.194-27.194v-326.33c0-15.011-12.155-27.195-27.194-27.195s-27.194 12.183-27.194 27.195v326.33c0 15.038 12.155 27.194 27.194 27.194z" ></path></symbol></svg>',l=(c=document.getElementsByTagName("script"))[c.length-1].getAttribute("data-injectcss");if(l&&!i.__iconfont__svg__cssinject__){i.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(c){console&&console.log(c)}}!function(c){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(c,0);else{var l=function(){document.removeEventListener("DOMContentLoaded",l,!1),c()};document.addEventListener("DOMContentLoaded",l,!1)}else document.attachEvent&&(t=c,e=i.document,o=!1,s=function(){o||(o=!0,t())},(a=function(){try{e.documentElement.doScroll("left")}catch(c){return void setTimeout(a,50)}s()})(),e.onreadystatechange=function(){"complete"==e.readyState&&(e.onreadystatechange=null,s())});var t,e,o,s,a}(function(){var c,l,t,e,o,s;(c=document.createElement("div")).innerHTML=a,a=null,(l=c.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",t=l,(e=document.body).firstChild?(o=t,(s=e.firstChild).parentNode.insertBefore(o,s)):e.appendChild(t))})}(window);
\ No newline at end of file
......@@ -32,6 +32,12 @@ Created by iconfont
<glyph glyph-name="album" unicode="&#59188;" d="M256 448m-64 0a2 2 0 1 0 128 0 2 2 0 1 0-128 0ZM714.688 640 181.312 640C116.64 640 64 587.36 64 522.688l0-405.376C64 52.64 116.64 0 181.312 0l533.376 0C779.36 0 832 52.64 832 117.312L832 522.688C832 587.36 779.36 640 714.688 640zM768 117.312C768 87.904 744.096 64 714.688 64L181.312 64C151.936 64 128 87.904 128 117.312L128 522.688C128 552.064 151.936 576 181.312 576l533.376 0C744.096 576 768 552.064 768 522.688L768 117.312zM842.688 768 256 768C238.336 768 224 753.664 224 736s14.336-32 32-32l586.688 0C872.096 704 896 680.064 896 650.688L896 256c0-17.696 14.304-32 32-32s32 14.304 32 32L960 650.688C960 715.36 907.36 768 842.688 768zM672.64 447.264c-129.056 0-143.936-72.672-152.832-116.096-7.2-35.168-10.4-39.968-28.64-42.88-34.976-5.568-44-0.192-58.912 8.8-15.456 9.312-36.672 22.08-77.952 23.168C227.616 322.88 194.112 172.16 192.704 165.76c-3.712-17.28 7.264-34.304 24.544-38.016 2.272-0.512 4.544-0.704 6.752-0.704 14.752 0 28 10.24 31.264 25.248 0.224 1.056 23.232 104.032 95.488 104.032 0.64 0 1.28 0 1.92-0.032 24.352-0.608 33.632-6.208 46.496-13.984 24.032-14.464 48.352-25.76 102.08-17.184 64.192 10.272 74.432 60.192 81.184 93.216 8.32 40.512 13.312 64.928 90.144 64.928 17.696 0 32 14.336 32 32S690.304 447.264 672.64 447.264z" horiz-adv-x="1024" />
<glyph glyph-name="cc-book" unicode="&#58901;" d="M682.697 24.293l-440.536 0c-12.153 0-22.026 9.893-22.026 22.027 0 12.13 9.873 22.026 22.026 22.026l440.537 0c12.13 0 22.026-9.895 22.026-22.026 0-12.133-9.895-22.027-22.027-22.027zM841.773 725.675c-12.955 0-23.52-10.543-23.52-23.521l0-705.638c0-12.956-10.567-23.521-23.522-23.521l-517.47 0c-38.911 0-70.564 31.651-70.564 70.564 0 38.91 31.653 70.563 70.564 70.563l423.383 0c38.911 0 70.565 31.653 70.565 70.563l0 564.511c0 38.911-31.654 70.564-70.565 70.564l-470.426 0c-38.911 0-70.563-31.653-70.563-70.564l0-705.637c0-64.823 52.785-117.609 117.607-117.609l517.469 0c38.909 0 70.563 31.654 70.563 70.565l0 705.638c0 12.978-10.567 23.521-23.521 23.521zM230.219 772.719l470.426 0c12.955 0 23.522-10.543 23.522-23.522l0-564.511c0-12.955-10.567-23.521-23.522-23.521l-423.383 0c-26.439 0-50.902-8.821-70.564-23.567l0 611.598c0 12.979 10.543 23.522 23.521 23.522z" horiz-adv-x="1024" />
<glyph glyph-name="13" unicode="&#59086;" d="M147.2 715.2h728c41.6 0 76.8-35.2 76.8-76.8v-552c0-41.6-35.2-76.8-76.8-76.8h-728c-41.6 0-76.8 35.2-76.8 76.8v552c0 41.6 35.2 76.8 76.8 76.8v0zM147.2-30.4c243.2 0 486.4 0 728 0 64 0 115.2 51.2 115.2 115.2 0 184 0 368 0 552 0 64-51.2 115.2-115.2 115.2-243.2 0-486.4 0-728 0-64 0-115.2-51.2-115.2-115.2 0-184 0-368 0-552 0-62.4 51.2-115.2 115.2-115.2zM201.6 476.8c0-44.8 36.8-81.6 81.6-81.6s81.6 36.8 81.6 81.6c0 44.8-36.8 81.6-81.6 81.6-44.8 0-81.6-36.8-81.6-81.6v0zM240 476.8c0 24 19.2 43.2 43.2 43.2s43.2-19.2 43.2-43.2c0-24-19.2-43.2-43.2-43.2-22.4 0-43.2 19.2-43.2 43.2zM675.2 396.8l203.2-203.2c8-8 8-19.2 0-27.2v0c-8-8-19.2-8-27.2 0l-203.2 203.2c-14.4 14.4-40 14.4-54.4 0l-163.2-161.6c-30.4-30.4-80-30.4-110.4 0l-25.6 25.6c-14.4 14.4-40 14.4-54.4 0l-67.2-67.2c-8-8-19.2-8-27.2 0v0c-8 8-8 19.2 0 27.2l67.2 67.2c30.4 30.4 80 30.4 110.4 0l25.6-25.6c14.4-14.4 40-14.4 54.4 0l161.6 161.6c30.4 30.4 80 30.4 110.4 0z" horiz-adv-x="1024" />
<glyph glyph-name="grade" unicode="&#58988;" d="M863.721739 341.704348c24.486957 48.973913 28.93913 102.4 28.93913 160.278261C892.66087 706.782609 734.608696 873.73913 527.582609 873.73913 320.556522 873.73913 162.504348 706.782609 162.504348 501.982609c0-57.878261 4.452174-111.304348 28.93913-160.278261L51.2 103.513043c0 0 95.721739-35.617391 193.669565-55.652174 64.556522-73.46087 117.982609-151.373913 117.982609-151.373913l129.113043 247.095652c8.904348 0 20.034783 0 26.713043 0 8.904348 0 20.034783 0 26.713043 0l138.017391-233.73913c0 0 57.878261 64.556522 124.66087 138.017391 97.947826 20.034783 193.669565 55.652174 193.669565 55.652174L863.721739 341.704348 863.721739 341.704348 863.721739 341.704348zM353.947826-7.791304c0 0-46.747826 60.104348-89.043478 102.4-62.330435 17.808696-144.695652 35.617391-144.695652 35.617391L215.930435 294.956522c51.2-66.782609 135.791304-129.113043 220.382609-149.147826L353.947826-7.791304 353.947826-7.791304zM520.904348 185.878261c-80.13913 0-309.426087 48.973913-316.104348 316.104348C198.121739 671.165217 351.721739 831.443478 520.904348 831.443478c169.182609 0 329.46087-162.504348 329.46087-329.46087C852.591304 245.982609 601.043478 185.878261 520.904348 185.878261L520.904348 185.878261zM685.634783-7.791304l-82.365217 151.373913c84.591304 20.034783 182.53913 84.591304 233.73913 151.373913l95.721739-164.730435c0 0-82.365217-17.808696-144.695652-35.617391L685.634783-7.791304 685.634783-7.791304zM527.582609 720.13913c-117.982609 0-213.704348-95.721739-213.704348-213.704348s95.721739-213.704348 213.704348-213.704348c117.982609 0 213.704348 95.721739 213.704348 213.704348S645.565217 720.13913 527.582609 720.13913L527.582609 720.13913 527.582609 720.13913zM534.26087 337.252174c-53.426087 0-169.182609 20.034783-178.086957 178.086957-4.452174 84.591304 95.721739 164.730435 178.086957 164.730435 84.591304 0 162.504348-95.721739 164.730435-178.086957C701.217391 390.678261 587.686957 337.252174 534.26087 337.252174L534.26087 337.252174zM534.26087 337.252174" horiz-adv-x="1024" />
......
......@@ -32,6 +32,8 @@
</template>
<script>
import cAction from '@actions'
export default {
data () {
return {
......@@ -53,16 +55,25 @@
onSubmitSetAccount () {
this.$refs['setAccountform'].validate((valid) => {
if (valid) {
if (this.setAccount.user === 'admin@qq.com' && this.setAccount.pwd === '123456') {
/* 跳转 */
const loading = this.$loading({
lock: true,
text: '',
spinner: '',
background: 'rgba(255, 255, 255, 0.9)'
})
cAction.loginAction.userLogin({
account: this.setAccount.user,
password: this.$md5('uokoaduw' + this.setAccount.pwd.split('').reverse().join('') + 'auhgniq')
}).then(data => {
/* 记录 userInfo 信息 */
/* 查询上次跳转信息,并跳转回去 */
this.$router.push({ path: '/app/my-learn/course' })
} else {
this.$message.error('用户名或密码错误')
}
/* 重置密码 */
this.$refs['setAccountform'].resetFields()
/* 重置账号、密码 */
// this.$refs['setAccountform'].resetFields()
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
} else {
this.$message.error('请检查输入项,确认无误后,重新提交')
this.$message.error('请根据输入框提示,检查输入项。')
return false
}
})
......
import React from 'react'
/**
* 音频基础组件类
* props:
* src: 音频源地址
* lastTime: xx, 上次播放时间
* skipBegin: 忽略片头,true/false
* onmeta: 加载资源,可获取如视频长度等信息 父级方法实现
* onloaded: 加载完音频,可播放方法 父级方法实现
* onloading: 加载中音频 父级方法实现
* onplay: 播放音频 父级方法实现
* onpause: 暂停播放 父级方法实现
* ontime:
* 外部可用:
* duration
* seek
* play
* pause
*
*
* 问题:
* 1. 华为 P7;小米 自带浏览器,不支持 倍速
* 2. 音频 跳跃时,可能刷新音频,导致currentTime重新开始
* 3. 外观不够美化,进度条 不能拖动
* 4. 图片放大 这个插件也需要集成化
* 5.
*/
class Audio extends React.Component {
static SKIP_SECS = 7; // 片头7s
/**
* 构造函数
*/
constructor(props) {
/* 继承属性 */
super(props);
/* 缓存音频 音频列表 */
this.playList = [];
/* 音频列表中,播放第几个 */
this.audioIndex = 0;
// console.log('diaoyong', this.playList);
/* 当前所在播放时间 */
this.time = 0;
this.stepTimer = null; //time计时器
// this.lastSeekTime = 0; // 记录上一次 seek时,存储的时间值
}
/**
* 组件渲染前
*/
componentWillMount() {
/* 组件渲染前 加载Audio对象并加载音频 */
this.getAudioObject();
this.skipBeginAndPlay();
}
/**
* 组件渲染后
*/
componentDidMount() {
// 组件创建完成 执行 音频播放
// this.audioPlay(0);
}
componentWillReceiveProps(nextProps) {
// console.log(12222);
// if (nextProps.src !== this.props.src) {
// // 暂停当前sound
// let sound = this.getAudioObject();
// if (sound && sound.playing()) {
// sound.pause();
// }
// // 检查是否已存在
// let existIndex = findIndex(this.playList, {src: nextProps.src});
// if (existIndex < 0) {
// existIndex = this.playList.length;
// this.playList.push({src: nextProps.src, howl: null, time: 0});
// }
// this.audioPlay(existIndex);
// }
}
componentWillUnmount() {
this.getAudioObject().pause();
}
shouldComponentUpdate(nextProps, nextState) {
return false;
}
// 第一播放,跳过片头,同时考虑lastTime
skipBeginAndPlay() {
let lastTime = this.props.lastTime || 0;
if (this.props.skipBegin) { lastTime = Math.max(lastTime, Audio.SKIP_SECS); }
if (lastTime) {
// if (lastTime > sound.duration() - 10) { lastTime = 0; } // 最后10s不执行跳转lastTime
// if (cur < lastTime) { // 多次调用,如果当前播放时间大于lastTime时,应该终止播放
this.seek(lastTime);
// }
}
}
// 无参数时,播放当前index音频到对应位置。
// index 要播放的序号
audioPlay (index) {
this.audioIndex = index;
this.time = 0;
this.skipBeginAndPlay();
}
step() {
clearTimeout(this.stepTimer);
let sound = this.getAudioObject();
if (!sound) {return;}
let seek = Math.round(sound.seek() || 0);
let playing = sound.playing();
if (seek !== this.time) {
this.time = seek;
this.props.ontime(seek);
}
if (playing) {
this.stepTimer = setTimeout(this.step, 300);
}
}
stepPause() {
clearTimeout(this.stepTimer);
this.props.onpause();
}
/**
* 搜寻点击进度条某个位置 跳播方式
* @param {number} s 传入当前需要改变的时间
*/
seek(s) {
/**
* 设置时,需要判断是否在seekable的范围内,不在,等待缓冲
* 如果在了,就可以设置 -- 华为手机 就是不行,设置 可能会断,重新加载音频
*/
let _audio = this.getAudioObject(), i = 0;
// if (!/android/gi.test(navigator.userAgent)) {
if ('fastSeek' in _audio) {
_audio.fastSeek(s); // 改变audio.currentTime的值
return ;
}
this.pause();
_audio.currentTime = s;
this.play();
// } else {
// for (i=0; i<_audio.seekable.length; i++) {
// if (_audio.seekable.start(i) <= s && s >= _audio.seekable.end(i)) {
// break;
// }
// }
// if (i < _audio.seekable.length) {
// _audio.currentTime = s;
// } else {
// _audio.buffered.end(_audio.buffered.length - 1);
// }
// }
}
/**
* 返回视频长度
*/
getDuration() {
return this.getAudioObject().duration || 0;
}
/**
* 开始播放
*/
play() {
this.getAudioObject().play();
}
/**
* 播放暂停
*/
pause() {
this.getAudioObject().pause();
}
/**
* 播放速率改变
* @param {float} n 速率值,每次加0.5
*/
rate(n) {
this.getAudioObject().defaultPlaybackRate = n
this.getAudioObject().playbackRate = n
}
/**
* render渲染
*/
render () {
return null
}
/**
* 获取 Audio对象
* 内部创建一个 音频对象
*/
getAudioObject() {
/* 在缓存音频列表里,获取对应要播放的音频对象 */
let playItem = this.playList[this.audioIndex];
/* 如果音频列表里没有 */
if (!playItem) {
this.playList[this.audioIndex] = {src: this.props.src, obj: null, time: 0};
playItem = this.playList[this.audioIndex];
}
let _mediaAudio = playItem.obj;
/* 如果缓存中没有音频对象 */
if (!_mediaAudio) {
/* 创建一个Audio对象 */
_mediaAudio = document.createElement("audio");
_mediaAudio.src = playItem.src; // 资源src加载
_mediaAudio.preload = 'auto'; // none:不预载; metadata:仅仅缓冲文件的元数据; auto:缓冲音频文件
_mediaAudio.autoPlay = true;
_mediaAudio.load(); // 重新加载src指定的资源
_mediaAudio.loadAll = false; // 可能存在缓存,如果从缓存中获取,那么歌曲就全部加载完成了,就不再需要监听progress事件,虽然它还会继续加载
/* 开始请求 顺序 1 */
_mediaAudio.addEventListener('loadstart', () => {
}, false);
/* 正在请求 顺序 2, 这个比较特殊,有缓存时,它还会请求 */
_mediaAudio.addEventListener('progress', () => {
/* 父级事件,告诉父级音频在加载中 */
!_mediaAudio.loadAll && this.props.onloading();
// console.log('loading', !_mediaAudio.loadAll);
}, false);
/* 资源长度改 顺序 3 */
_mediaAudio.addEventListener('durationchange', () => {
/* 父级事件,告诉父级一些资源信息 华为原生浏览器上,播放前loadedmetadata事件监听,获取不到duration信息 */
this.props.onmeta();
}, false);
/* 可以播放,但中途可能因为加载而暂 顺序 4 */
_mediaAudio.addEventListener('canplay', () => {
/* 父级事件,告诉父级音频加载完成 */
this.props.onloaded();
_mediaAudio.play();
}, false);
/* 可以播放,歌曲全部加载完毕 顺序 5 */
_mediaAudio.addEventListener('canplaythrough', () => {
/* 歌曲全部加载 */
_mediaAudio.loadAll = true;
/* 父级事件,告诉父级音频加载完成 */
this.props.onloaded();
_mediaAudio.play();
}, false);
/* play()和autoplay 开始播放时,触发 但并未真正开始播放 */
_mediaAudio.addEventListener('play', () => {
/* 父级事件,告诉父级音频正在播放 */
this.props.onplay();
/* 父级事件,告诉父级音频在加载中 */
this.props.onloading();
}, false);
/* playing 开始播放时,触发,真正开始播放 */
_mediaAudio.addEventListener('playing', () => {
/**
* 0 HAVE_NOTHING 没有关于音频/视频是否就绪的信息
* 1 HAVE_METADATA 关于音频/视频就绪的元数据
* 2 HAVE_CURRENT_DATA 关于当前播放位置的数据是可用的,但没有足够的数据来播放下一帧/毫秒
* 3 HAVE_FUTURE_DATA 当前及至少下一帧的数据是可用的
* 4 HAVE_ENOUGH_DATA 可用数据足以开始播放
*/
if (_mediaAudio.readyState >= 2) {
/* 父级事件,告诉父级音频加载完成 */
this.props.onloaded();
}
}, false);
/* pause() 暂停播放时,触发 */
_mediaAudio.addEventListener('pause', () => {
/* 父级事件,告诉父级音频暂停 */
this.props.onpause();
}, false);
/* 获取一些预设信息,事件监听 */
_mediaAudio.addEventListener('loadedmetadata', () => {
/* 父级事件,告诉父级一些资源信息 */
this.props.onmeta();
}, false);
// waiting error abort
/* 播放时间改变时,实时改变进度条 */
_mediaAudio.addEventListener('timeupdate', () => {
/* 父级事件,告诉父级当前播放位置 */
this.props.ontime(_mediaAudio.currentTime);
// console.log('play');
}, false);
/* 设置playbackRate,触发事件ratechange,播放速率改变 */
_mediaAudio.addEventListener('ratechange', () => {
// alert(this.getAudioObject().playbackRate)
}, false);
/* 设置currentTime,触发事件seeking,寻找中 */
_mediaAudio.addEventListener('seeking', () => {
/* 父级事件,告诉父级音频在加载中 */
this.props.onloading();
}, false);
/* 设置currentTime,触发事件seeked,寻找完成 */
_mediaAudio.addEventListener('seeked', () => {
/* 父级事件,告诉父级音频加载完成 */
this.props.onloaded();
}, false);
playItem.obj = _mediaAudio;
}
return _mediaAudio;
}
}
module.exports = Audio;
import React from 'react'
import { Link } from 'react-router'
import sortedIndex from 'lodash/sortedIndex'
import {toTimeString} from '../../libs/utils'
import Audio from './Audio.jsx'
import ProgressCircle from '../../components/ProgressCircle.jsx'
import Loading from '../../components/Loading.jsx'
import {chapterType} from '../../libs/const'
let pagePlayMap = {};
const RATES = [1.0,1.2,1.5,2.0,2.5,3.0]
let isSkipBegin = false;
if (process.env.BROWSER) {
require('css/audioPlay.css');
// require('howler/dist/howler.min.js');
require('previewImage');
isSkipBegin = /skip=1/.test(document.cookie); // 浏览器下根据cookie判断是否跳过片头
}
class AudioPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
pptIndex: 0,
loading: true, // 是否加载中
playing: false, // 是否播放中
time: 0, // 当前播放时间
duration: 0, // 总的播放时间
rateIndex: 0, //是否加倍
skipBegin: isSkipBegin, // 跳过片头
showChapter: false, // 显示章节弹出层
};
this.pptTimes = this.props.ppts.map(ppt => ppt.ppt_point);
this.pagePlayMap = pagePlayMap; // 播放期间记录本次页面进度 {[time, progress]}
this._setState = this._setState.bind(this);
this.getAudio = this.getAudio.bind(this);
this.getProgress = this.getProgress.bind(this);
this.onAudioMeta = this.onAudioMeta.bind(this);
this.onAudioLoad = this.onAudioLoad.bind(this);
this.onAudioLoading = this.onAudioLoading.bind(this);
this.onAudioPlay = this.onAudioPlay.bind(this);
this.onAudioPause = this.onAudioPause.bind(this);
this.onAudioCurrentTime = this.onAudioCurrentTime.bind(this);
this.toggleChapter = this.toggleChapter.bind(this);
this.toggleSet = this.toggleSet.bind(this);
this.handlePlay = this.handlePlay.bind(this);
this.handleForward = this.handleForward.bind(this);
this.handleBackward = this.handleBackward.bind(this);
this.handleTouchBar = this.handleTouchBar.bind(this);
this.handleOver = this.handleOver.bind(this);
this.handleSkip = this.handleSkip.bind(this);
this.handleRate = this.handleRate.bind(this);
this.handleClickList = this.handleClickList.bind(this);
this.handlePptView = this.handlePptView.bind(this);
}
componentDidMount() {
}
componentWillReceiveProps(nextProps) {
// 切换index,重置audio相关state
//if (nextProps.index !== this.props.index) {
// this._setState({time: 0})
//}
// 更新pptTimes
if (nextProps.ppts !== this.props.ppts) {
this.pptTimes = nextProps.ppts.map(ppt => ppt.ppt_point);
}
}
componentWillUnmount() {
this.unmounted = true; // 标记unmount状态,防止音频停止事件触发setState
let preview = document.getElementById('__previewImage-container');
if (preview) {
preview.style.display = 'none';
}
}
shouldComponentUpdate(nextProps, nextState) {
return this.state !== nextState || this.props !== nextProps;
}
_setState(obj) {
this.setState((prevState, props) => Object.assign({}, prevState, obj || {}))
}
getAudio() {
return this.refs.audio;
}
// 获取进度,综合接口返回上次播放进度和本页播放进度记录
getProgress(id) {
let p = 0;
let pageProg = (this.pagePlayMap[id] || [])[1] || 0;
let dataProg = ((this.props.chapterData.progressMap || {})[id] || {}).chapter_progress || 0;
p = Math.max(pageProg, dataProg);
return p;
}
/**
* 章节层显示与否
*/
toggleChapter() {
this._setState({ showChapter: !this.state.showChapter });
}
toggleSet() {
this._setState({ showSet: !this.state.showSet });
}
// 点击章节列表外面隐藏
handleClickList(e) {
if (e.target === e.currentTarget) {
this._setState({showChapter: false});
}
}
// seek
handleTouchBar(e) {
if (this.state.loading) return;
e = e.nativeEvent;
let x = e.pageX || e.clientX;
const $ = require('jquery');
let bar = $('#audio-bar');
let toX = x - bar.offset().left;
if (toX >= 0) {
let audio = this.getAudio();
let time = toX / bar.width() * this.state.duration;
audio.seek(time);
/* 跳播时,实时改变进度条 */
this.onAudioCurrentTime(time);
}
}
// 前15s
handleBackward() {
let audio = this.getAudio();
audio.seek(Math.max(0, this.state.time - 15));
}
// 后15s
handleForward() {
let audio = this.getAudio();
audio.seek(Math.min(this.state.duration - (+this.state.playing), this.state.time + 15));
}
// 标记完成
handleOver() {
this.props.handleOver(this.state.time);
}
// 通过previewImage预览图片
handlePptView(e) {
if (typeof previewImage !== 'object') return;
let list = this.props.ppts.map(ppt => ppt.ppt_url);
if(!list.length) {
list.push(this.props.course.curriculum && this.props.course.curriculum.curriculum_picture || '')
}
previewImage.start({
urls: list,
current: list[this.state.pptIndex]
});
}
formatTime(seconds) {
return toTimeString(Math.round(seconds), seconds >= 3600 ? 'h:m:s' : 'm:s');
}
/**
* 点击播放 click方法
*/
handlePlay() {
if (this.state.loading) return;
let audio = this.getAudio();
if (this.state.playing) {
audio.pause();
} else {
audio.play();
}
}
/**
* 跳过片头 click方法
*/
handleSkip() {
let skip = +(!this.state.skipBegin);
let d = new Date();
d.setMonth(d.getMonth() + 1);
document.cookie = 'skip=' + skip + ';path=/;domain=.ezijing.com;expires=' + d.toGMTString();
this._setState({ skipBegin: skip });
}
/**
* 控制倍速 click方法
*/
handleRate() {
let audio = this.getAudio();
if (audio) {
let l = RATES.length;
let from = this.state.rateIndex;
let to = (from + 1 + l) % l;
this._setState({ rateIndex: to});
audio.rate(RATES[to]);
}
}
// 处理Audio事件, 向下组件传递内容
/**
* 获取资源信息
*/
onAudioMeta() {
this._setState({duration: this.refs.audio.getDuration()});
}
/**
* 音频加载中
*/
onAudioLoading() {
this._setState({loading: true});
}
/**
* 音频加载完成,可以播放
*/
onAudioLoad() {
this._setState({loading: false});
}
/**
* 音频播放事件
*/
onAudioPlay() {
this._setState({playing: true});
}
/**
* 音频停止事件
*/
onAudioPause() {
if (this.unmounted) return;
this._setState({playing: false});
}
/**
* 实时改变当前时间
* @param {number} time 当前时间
*/
onAudioCurrentTime(time) {
if (this.unmounted) return;
// 更新当前播放时间
this._setState({time: time});
// 更新当前进度
const cid = this.props.chapterData.leaves[this.props.index];
this.pagePlayMap[cid] = [time, Math.round(time/this.state.duration * 100)];
// 检查ppt是否需要更新
if (this.pptTimes.length) {
let pptIndex = this.pptTimes.indexOf(time);
if (pptIndex < 0) {
pptIndex = sortedIndex(this.pptTimes, time);
pptIndex = Math.max(0, pptIndex - 1);
}
if (this.state.pptIndex !== pptIndex) {
this._setState({pptIndex});
}
}
this.props.handleAudioTime(time);
}
render() {
const {pptIndex, time, duration, playing, loading} = this.state;
const {index, chapterData, ppts, course, video_curm3u8} = this.props;
const chapter = chapterData.map[chapterData.leaves[index]];
if (!course || !course.course_id || !chapter) {
return <div></div>
}
const ppt = ppts[pptIndex] || {};
const isFirst = index === 0, isLast = index === chapterData.leaves.length - 1;
const timeStr = this.formatTime(time);
const durationStr = this.formatTime(duration);
// 上次进度时间
let lastTime = 0;
if (this.pagePlayMap[chapter.id]) {
lastTime = this.pagePlayMap[chapter.id][0] || 0;
} else {
lastTime = (chapterData.progressMap[chapter.id] || {}).last_position || 0;
}
const HEADER = <header>
<h1>{course.course_name || ''}</h1>
<Link to={`/courses/${course.course_id}`} className="play-back-phone">&nbsp;</Link>
</header>
// 是否支持播放音频
chapter.video = chapter.video ? chapter.video : {}
chapter.video.video_voice = chapter.video.video_voice ? chapter.video.video_voice : (video_curm3u8.audio && video_curm3u8.audio[0].url || '')
if (!chapter.video.video_voice) {
return <div className="page-audio">
{HEADER}
<p className="text-error" style={{color:'#f00'}}>本课程尚未添加对应手机端音频文件</p>
</div>
}
return (
<div className="page-audio">
{HEADER}
<section className="play-wrap rel">
<img className="ppt-inbox" onClick={this.handlePptView} src={ppt.ppt_url || (course.curriculum && course.curriculum.curriculum_picture || '')} alt=""/>
<button type="button" onTouchEnd={this.toggleSet} className={`play-set-btn ${this.state.showSet ? 'disable' : ''}`}></button>
<div className={`play-set ${this.state.showSet ? '' : 'hide' }` } >
<div>
<div className="play-set-time"><em>{timeStr}</em>{durationStr}</div>
<div style={{display: "flex"}}>
<button onTouchEnd={this.handleSkip} className={`play-jump ${this.state.skipBegin ? 'disable' : ''}`}><em></em><br/>跳过片头</button>
<button onTouchEnd={this.handleBackward} className="play-set-button play-minus"></button>
<button onTouchEnd={this.handleForward} className="play-set-button play-add"></button>
<button onTouchEnd={this.handleRate} className="play-set-button play-double">{RATES[this.state.rateIndex].toFixed(1)}X<br/>倍速播放</button>
</div>
</div>
</div>
</section>
<footer>
<Audio
ref="audio"
src={chapter.video.video_voice}
lastTime={lastTime}
skipBegin={this.state.skipBegin}
onmeta={this.onAudioMeta}
onloading={this.onAudioLoading}
onloaded={this.onAudioLoad}
onplay={this.onAudioPlay}
onpause={this.onAudioPause}
ontime={this.onAudioCurrentTime}
/>
<div className="play-timer">
<div className="audio-bar" onClick={this.handleTouchBar} id="audio-bar"><span className="audio-progress" style={{width: ''+(duration ? time / duration * 100 : 0)+'%'}}></span></div>
<div className="show-time">
<span className="cur-time">{timeStr}</span>
<span className="total-time">{durationStr}</span>
</div>
</div>
<div className="play-ctrls">
<div className="ctrl-items play-ctrl-chapter" onTouchEnd={this.toggleChapter}><em></em><br/>章节</div>
<div className="ctrl-items btns">
<Link to={`/courses/${course.semester_id}/${course.course_id}/chapters/${isFirst ? chapter.id : chapterData.leaves[index - 1]}`} type={`button ${isFirst ? 'disable' : ''}`} className={`button play-prev ${isFirst ? 'disable' : ''}`}>
</Link>
<button type="button" onClick={this.handlePlay} className={`button play-btn ${loading ? 'play-load' : playing ? 'play-pause' : 'play-play'}`}>{loading ? <Loading/> : ''}</button>
<Link to={`/courses/${course.semester_id}/${course.course_id}/chapters/${isLast ? chapter.id : chapterData.leaves[index + 1]}`} type={`button ${isLast ? 'disable' : ''}`} className={`button play-next ${isLast ? 'disable' : ''}`}>
</Link>
</div>
{ /* <div className={`ctrl-items play-ctrl-mark ${this.getProgress(chapter.id) === 100 ? 'disable' : ''}`} onTouchEnd={this.handleOver}><em></em><br/>标记为已学完</div>*/ }
</div>
<div className={`play-chapter ${this.state.showChapter ? '' : 'hide' }` } onClick={this.handleClickList}>
<div className="play-chapter-inner">
{chapterData.tree.map((tree, i) => {
let root = chapterData.map[tree[0]];
return (
<dl key={i}>
<dt>{root.name}</dt>
{tree[1].map((leaf, j) => {
leaf = chapterData.map[leaf];
return (
<dd key={j} className={index === chapterData.leaves.indexOf(leaf.id) ? 'on' : ''}>
<div className="cl">
<ProgressCircle percent={this.getProgress(leaf.id)} className="play-prog" />
<Link to={`/courses/${course.semester_id}/${course.course_id}/chapters/${leaf.id}`}>{leaf.name}</Link>
{leaf.type === chapterType.VIDEO ?
<span className="fr">{this.formatTime(leaf.video && leaf.video.video_length || 0)}</span>
: null
}
</div>
</dd>
);
})}
</dl>
);
})}
</div>
</div>
</footer>
</div>
)
}
}
module.exports = AudioPanel;
/**
* 测验类章节
* chapter
* courseId
* action
* dispatch
*/
import React from 'react'
import Formsy from 'formsy-react';
import FormsyComponent from '../../components/formsy/Component.jsx'
import FormItem from '../../components/formsy/FormItem.jsx'
import CoursesAction from '../../actions/CoursesAction'
import OperateAction from '../../actions/OperateAction'
import { getRequestTypes } from '../../libs/utils';
class ChapterExam extends FormsyComponent {
constructor(props) {
super(props);
this.state = { error: '' };
}
componentDidMount () {
this.courseAction = new CoursesAction();
this.operateAction = new OperateAction();
this.startTime = (new Date()).getTime();
// 加载回答
this.props.dispatch(this.courseAction.loadChapterWork(this.props.semesterId, this.props.chapter.homework.id));
}
componentWillReceiveProps (nextProps) {
let submitType = getRequestTypes(CoursesAction.SUBMIT_CHAPTER_WORK);
switch (nextProps.action.type) {
case submitType.success:
this.disableSubmitButton();
// 提交成功,重新获取答案
nextProps.dispatch(this.courseAction.loadChapterWork(this.props.semesterId, nextProps.chapter.homework.id));
nextProps.handleSubmited(nextProps.chapter.id);
break;
case submitType.failure:
this.enableSubmitButton();
nextProps.dispatch(this.operateAction.showErrorMessage(nextProps.action.error.message || '提交章节测验失败'));
break;
}
}
/**
* 提交登录
*/
onSubmit = model => {
this.loadingSubmitButton();
const {courseId, chapter, semesterId} = this.props;
const questions = chapter.homework && chapter.homework.questions || [];
let data = {
course_id: courseId,
chapter_id:chapter.id,
work_id: chapter.homework.id,
semester_id: semesterId,
work_contents: '',
//url: '',
duration: Math.ceil(((new Date()).getTime() - this.startTime) / 1000),
score: 0
}
// 组织返回答案结构
let correctNumber = 0;
let answers = [];
questions.forEach(q => {
let opts = [],
isCorrect = 0, seledNum = 0, correctCount = 0,
curSels = q.question_type === 2 ? model[q.id] || [] : [model[q.id] || ''],
options;
try {
options = JSON.parse(q.question_options);
} catch (e) {
options = [];
console.log('Try parse question_options JSON string fail. in play/ChapterExam');
}
options.forEach(o => {
let isSelected = (curSels.indexOf(o.id) >= 0) - 0;
if (o.checked) { correctCount++; }
if (o.checked && isSelected) { seledNum++; }
opts.push({
id: o.id,
option: o.option,
checked: o.checked,
selected: isSelected
});
});
isCorrect = (seledNum && seledNum === correctCount) - 0;
answers.push({
question_id: q.id,
options: opts,
is_correct: isCorrect
});
if (isCorrect) { correctNumber++; }
});
data.score = Math.round(correctNumber / questions.length * 1000) / 10; // 百分制
data.work_contents = JSON.stringify(answers);
this.props.dispatch(this.courseAction.submitChapterWork(data));
}
// 表单变更时,取消掉全局错误消息
onFormChange = () => {
this._setState({ error: '' });
}
// 将答案解析出来
parseAnswers = (strAnswers) => {
if (strAnswers === this._lastParseString && this._lastParseAnswers) {
return this._lastParseAnswers;
}
let r = {};
try {
let answers = JSON.parse(strAnswers);
this._lastParseString = strAnswers; // 记录上次成功解析的串
(answers || []).forEach(q => {
let opts = {};
(q.options || []).forEach(o => {
opts[o.id] = o;
});
r[q.question_id] = opts;
});
this._lastParseAnswers = r; // 记录上次的解析结果
} catch (e) { console.log('parse answer string to json failed.', e) }
return r;
}
render () {
const {chapter, chapter_work} = this.props;
const questions = chapter.homework && chapter.homework.questions;
let work = null;
let answers = {}
if (chapter_work.data && chapter_work.data.work_contents) {
work = chapter_work.data;
answers = this.parseAnswers(work.work_contents);
}
return (
<div className="play-paper">
<div className="play-paper-body">
<div className="play-paper-title"><div><h3>{chapter.name}</h3></div></div>
<div className="play-paper-content play-chapter-exam">
{work ?
<div className="result">正确率:{work.score || 0}%</div>
: null
}
<Formsy.Form
onValid={this.enableSubmitButton}
onInvalid={this.disableSubmitButton}
onValidSubmit={this.onSubmit}
onChange={this.onFormChange}
>
{questions.length ?
<ul>
{questions.map((q, qi) => {
let options;
let formOpts = {};
let rights = [];
let isCheckbox = q.question_type === 2;
let answer = answers[q.id] || {};
try {
options = JSON.parse(q.question_options);
options.forEach((o, i) => {
let k = String.fromCharCode(65 + i);
formOpts[o.id] = k + '. ' + o.option;
if (o.checked) rights.push(k);
})
} catch (e) {}
return (
<li key={qi}>
<div className="exam-number">{qi + 1}.</div>
<div className="exam-title"><div className="edit_html" dangerouslySetInnerHTML={{__html: (isCheckbox ? '(多选题)' : '(单选题)') + q.question_content}}></div></div>
{!work ?
<FormItem
name={q.id}
itemType={isCheckbox ? FormItem.CHECKBOX : FormItem.RADIO}
options={formOpts}
required
/>
:
<div>
{options.map((o, oi) => {
let selected = answer[o.id] && answer[o.id].selected;
let rclass = o.checked ? 'correct' : selected ? 'wrong' : '';
let tclass = isCheckbox ? 'checkbox' : 'radio';
return (
<div className={`${tclass} ${rclass}`} key={oi}>
<label><input name={q.id} type={tclass} defaultChecked={selected} readOnly/>&nbsp;{String.fromCharCode(65 + oi)}. {o.option}</label>
</div>
);
})}
<div className="answer">正确答案:{rights.join(',')}</div>
</div>
}
</li>
)
})}
</ul>
:
<p className="no-data">暂无问题数据</p>
}
<p className="text-danger">{this.state.error}</p>
<div className="area-btns">
<button type="submit" disabled={!this.canSubmit() || work} className="btn btn-primary" >{this.isSubmitLoading() ? '保存中...' : work ? '已提交' : '提交'}</button>
<span className="help-info">&emsp;&emsp;注意:测试只有一次提交机会</span>
</div>
</Formsy.Form>
</div>
</div>
</div>
);
}
}
module.exports = ChapterExam;
/**
* 阅读类章节
* chapter
* handleReaded
*/
import React, { Component } from 'react'
class ChapterRead extends Component {
componentDidMount() {
const {chapter} = this.props;
if (chapter && chapter.reading) {
this.props.handleReaded();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.chapter && nextProps.chapter &&
this.props.chapter.id !== nextProps.chapter.id) {
nextProps.handleReaded();
}
}
render () {
const {chapter} = this.props;
const reads = chapter.reading ? [chapter.reading] : [];
return (
<div className="play-paper">
<div className="play-paper-body">
<div className="play-paper-title"><div><h3>{chapter.name}</h3></div></div>
<div className="play-paper-content">
{reads.length ?
<ul className="play-read-files">
{reads.map((o,k) => {
return <li key={k}><a href={o.reading_attachment} target="_blank">{o.reading_content}</a></li>
})}
</ul>
:
<p className="no-data">暂无阅读材料</p>
}
</div>
</div>
</div>
);
}
}
module.exports = ChapterRead;
/**
* 视频类章节
* props:
* username
* courseId
* chapter 当前章节
* ppts
* prevChapterId 上一节、下一节对应的章节ID
* nextChapterId
* curProgress
*
* handlePlayTime 播放时间变化
* handleOver 标记已完成
*/
import React, {Component} from 'react'
import { Link } from 'react-router';
import debounce from 'lodash/debounce'
import Video from '../../components/Video.jsx';
import Ppt from '../../components/Ppt.jsx';
let isSkipBegin = false;
if (process.env.BROWSER) {
isSkipBegin = /skip=1/.test(document.cookie); // 浏览器下根据cookie判断是否跳过片头
}
const PLAY_SPACE_WIDTH = 15;
const PLAY_SPACE_HEIGHT = 10;
const VIDEO_DEFAULT_WIDTH = 550;
const VIDEO_DEFAULT_HEIGHT = 360;
class ChapterVideo extends Component {
constructor (props) {
super(props)
this.state = {
pptIndex: 0,
pptBoxOnly: false, // 仅展示ppt框
pptBoxShow: false, // 展示ppt框
skipBegin: isSkipBegin, // 跳过片头
calculatedSize: false // 是否以计算过播放位置的尺寸
}
this.videoWidth = VIDEO_DEFAULT_WIDTH;
this.videoHeight = VIDEO_DEFAULT_HEIGHT;
this.lastTime = null
}
componentDidMount () {
let size = this.getCalculateSize();
if (size.video.w) {
this.videoWidth = size.video.w;
this.videoHeight= size.video.h;
}
// 延迟计算尺寸,因flash初始化需要时间
setTimeout(this.jdugeSize, 300);
// 窗口resize,重置大小
const $ = require('jquery');
$(window).on('resize.chaptervideo', debounce(this.jdugeSize.bind(this), 200));
}
componentWillReceiveProps (nextProps) {
// 章节变化时,重置位置,并在之后计算大小
if (nextProps.chapter.id !== this.props.chapter.id) {
this.videoWidth = VIDEO_DEFAULT_WIDTH;
this.videoHeight = VIDEO_DEFAULT_HEIGHT;
this.lastTime = null;
this._setState({calculatedSize: false})
setTimeout(this.jdugeSize, 300)
}
}
componentWillUnmount () {
const $ = require('jquery');
$(window).off('resize.chaptervideo');
}
// 根据时间设置ppt位置
setPptIndexByTime = time => {
const ppts = this.props.ppts || []
const len = ppts.length
let i = 0;
for(; i < len; i++) {
if (time < ppts[i].ppt_point) {
break;
}
}
if (this.state.pptIndex !== i - 1) {
this._setState({ pptIndex: i - 1 });
}
}
// 设置视频播放时间点
setVideoTime = time => {
if (this.refs.video) {
this.refs.video.setTimeTo(time);
}
};
// 播放时间变更
onVideoTimeChange = (e, data) => {
let time = parseFloat(data.time)
// 因视频播放完成后也会不断触发playing,因此比对上次时间
if (this.lastTime === time) { return }
this.lastTime = time
// 判断ppt位置
this.setPptIndexByTime(time)
this.props.handlePlayTime(time)
}
// toggle PPT播放框
togglePptBox = e => {
e.preventDefault();
let isShow = !this.state.pptBoxShow;
this._setState({
pptBoxShow: isShow,
pptBoxOnly: false
});
setTimeout(this.jdugeSize, 0)
};
// 仅显示ppt播放框
togglePptBoxOnly = e => {
e.preventDefault();
this._setState({ pptBoxOnly: !this.state.pptBoxOnly });
setTimeout(this.jdugeSize, 0)
};
// 跳过片头
toggleSkipBegin = e => {
e.preventDefault();
let skip = !this.state.skipBegin;
let d = new Date();
d.setMonth(d.getMonth() + 1);
document.cookie = 'skip=' + (+skip) + ';path=/;domain=.ezijing.com;expires=' + d.toGMTString();
if (skip && this.refs.video) {
this.refs.video.skipBegin();
}
this._setState({ skipBegin: skip });
};
// 标记完成状态
handleOver = e => { // 标记完成toggle
e.preventDefault();
let t = this.refs.video.getTime();
this.props.handleOver(t);
};
/**
* 判断并设置尺寸位置
*/
jdugeSize = () => {
let box = this.refs.box;
let size = this.getCalculateSize();
let uh = 0, uw = 0;
if (this.state.pptBoxOnly) { // 仅ppt
uw = size.ppt.w;
uh = size.ppt.h;
if (this.refs.ppt) {
this.refs.ppt.setSize(uw, uh);
}
} else if (this.state.pptBoxShow) { // 同时显示video、ppt
if (this.refs.ppt) {
this.refs.ppt.setSize(size.ppt.w, size.ppt.h);
}
if (this.refs.video) {
this.refs.video.setSize(size.video.w, size.video.h);
}
uw = size.ppt.w + size.video.w;
uh = size.video.h;
} else { // 只显示video
uw = size.video.w;
uh = size.video.h;
if (this.refs.video) {
this.refs.video.setSize(uw, uh);
}
}
this._setState({calculatedSize: true});
box.style.paddingLeft = '' + ((size.space.w - uw) / 2 + PLAY_SPACE_WIDTH) + 'px';
box.style.paddingTop = '' + ((size.space.h - uh) / 2 + PLAY_SPACE_HEIGHT) + 'px';
};
// 获取各个元素计算后的尺寸
getCalculateSize = () => {
let container = this.refs.container;
let w = container.offsetWidth - PLAY_SPACE_WIDTH * 2;
let h = container.offsetHeight - 53 - PLAY_SPACE_HEIGHT * 2; // 减去底部操作条高度
let videoRatio = 550 / 363;
let pptRatio = 336 / 236;
let r = {
space: {w: w, h: h}, // 可容纳play的容器总大小
video: {w: 0, h: 0}, // 视频大小
ppt: {w: 0, h: 0}, // ppt容器大小
};
if (this.state.pptBoxOnly) { // 仅ppt
r.ppt.w = w < h * pptRatio ? w : h * pptRatio;
r.ppt.h = h < w / pptRatio ? h : w / pptRatio;
} else if (this.state.pptBoxShow) { // 同时显示video、ppt
let halfW = w / 2;
let vw = halfW < h * videoRatio ? halfW : h * videoRatio;
let vh = h < halfW / videoRatio ? h : halfW / videoRatio;
let ph = vh;
let pw = ph * pptRatio;
r.video.w = vw; r.video.h = vh;
r.ppt.w = pw; r.ppt.h = ph;
} else { // 只显示video
r.video.w = w < h * videoRatio ? w : h * videoRatio;
r.video.h = h < w / videoRatio ? h : w / videoRatio;
}
return r;
}
_setState = obj => {
this.setState(Object.assign({}, this.state, obj || {}));
};
render () {
const {chapter, semesterId, courseId, username,
prevChapterId, nextChapterId,
curProgress,
ppts, lastTime} = this.props
const video = chapter.video;
const chapterId = chapter.id;
return (
<div className="play-content-video" ref="container">
<div className="play-center cl" ref="box">
{video ?
<div>
<div className={`play-video fl ${this.state.pptBoxOnly ? 'play-video-hide' : ''} ${this.state.calculatedSize ? '' : 'play-video-init-center'}`}>
<Video
ref="video"
videoId = {video.video_origionalID}
videoSrt = {video.video_subtitle || ''}
username = {username}
width = {this.videoWidth}
height = {this.videoHeight}
handlePlayTime = {this.onVideoTimeChange}
lastTime={lastTime}
/>
</div>
<div className={`play-jiangyi fl ${this.state.pptBoxShow ? '' : 'hide'}`}>
<Ppt
ref="ppt"
ppts = {ppts}
currentIndex = {this.state.pptIndex}
onVideoSyncTime = {this.setVideoTime}
onPptOnly = {this.togglePptBoxOnly}
onClose = {this.togglePptBox}
/>
</div>
</div>
:
<p>课程视频数据不存在</p>
}
</div>
<div className="play-footer">
<div className="fl">
<Link to={`/courses/${semesterId}/${courseId}/chapters/${prevChapterId || chapterId}`} className={`play-state play-state-prev ${prevChapterId?'': 'play-state-prev-disable'}`}>上一节</Link>
<Link to={`/courses/${semesterId}/${courseId}/chapters/${nextChapterId || chapterId}`} className={`play-state play-state-next ${nextChapterId?'': 'play-state-next-disable'}`}>下一节</Link>
</div>
<div className="fr">
<em className={`play-state play-state-check${curProgress == 100 ? '-active' : ''}`} onClick={this.handleOver} data-vid={video && video.id || ''} data-progress={curProgress} data-total={video && video.video_length || 1}>{curProgress == 100 ? '已学完' : '标记为已学完'}</em>
{ppts.length ?
<em className={`play-state play-state-ppt${this.state.pptBoxShow ? '-active' : ''}`} onClick={this.togglePptBox}>同步显示PPT</em>
: null
}
<em className={`play-state play-state-check${this.state.skipBegin ? '-active' : ''}`} onClick={this.toggleSkipBegin}>始终跳过片头</em>
</div>
</div>
</div>
);
}
}
module.exports = ChapterVideo
/**
* 作业类章节
*/
import React from 'react'
import Formsy from 'formsy-react';
import FormsyComponent from '../../components/formsy/Component.jsx'
import FormItem from '../../components/formsy/FormItem.jsx'
import CoursesAction from '../../actions/CoursesAction'
import OperateAction from '../../actions/OperateAction'
import { getRequestTypes } from '../../libs/utils';
class ChapterWork extends FormsyComponent {
constructor(props) {
super(props);
this.state = { error: '' };
}
componentDidMount () {
this.courseAction = new CoursesAction();
this.operateAction = new OperateAction();
this.startTime = (new Date()).getTime();
// 加载回答
this.props.dispatch(this.courseAction.loadChapterWork(this.props.semesterId, this.props.chapter.homework.id));
}
componentWillReceiveProps (nextProps) {
let submitType = getRequestTypes(CoursesAction.SUBMIT_CHAPTER_WORK);
switch (nextProps.action.type) {
case submitType.success:
this.enableSubmitButton();
// 提交成功,重新获取答案
nextProps.dispatch(this.courseAction.loadChapterWork(this.props.semesterId, nextProps.chapter.homework.id));
nextProps.handleSubmited(nextProps.chapter.id);
break;
case submitType.failure:
this.enableSubmitButton();
nextProps.dispatch(this.operateAction.showErrorMessage(nextProps.action.error.message || '提交章节作业失败'));
break;
}
}
/**
* 提交登录
*/
onSubmit = model => {
this.loadingSubmitButton();
const {courseId, chapter, semesterId} = this.props;
const questions = chapter.homework && chapter.homework.questions || [];
let data = {
course_id: courseId,
chapter_id:chapter.id,
work_id: chapter.homework.id,
semester_id: semesterId,
work_contents: '',
duration: Math.ceil(((new Date()).getTime() - this.startTime) / 1000),
}
// 组织返回答案结构
let answers = questions.map(q => {
return {
question_id: q.id,
descreption: model['desc_' + q.id],
file_url: model['file_' + q.id]
};
});
data.work_contents = JSON.stringify(answers);
this.props.dispatch(this.courseAction.submitChapterWork(data));
}
// 表单变更时,取消掉全局错误消息
onFormChange = () => {
this._setState({ error: '' });
}
handleUploadError = msg => {
this.props.dispatch(this.operateAction.showErrorMessage(msg || '上传文件失败'));
}
render () {
const {chapter, chapter_work} = this.props;
const questions = chapter.homework && chapter.homework.questions || [];
let work = null;
let answers = {}
if (chapter_work.data && chapter_work.data.work_contents) {
work = chapter_work.data;
try {
let answerObject = JSON.parse(work.work_contents);
answerObject.forEach(a => {
answers[a.question_id] = a;
})
} catch(e) {console.log('parse work_contents to json failed.')}
}
return (
<div className="play-paper">
<div className="play-paper-body">
<div className="play-paper-title"><div><h3>{chapter.name}</h3></div></div>
<div className="play-paper-content play-chapter-work">
<Formsy.Form
onValid={this.enableSubmitButton}
onInvalid={this.disableSubmitButton}
onValidSubmit={this.onSubmit}
onChange={this.onFormChange}
>
{questions.length ?
<ul>
{questions.map((q, qi) => {
let answer = answers[q.id] || {};
return (
<li key={qi}>
<div className="work-number">{qi + 1}.</div>
<div className="work-title">
<div className="edit_html" dangerouslySetInnerHTML={{__html: q.question_content}}></div>
</div>
<FormItem
name={'desc_' + q.id}
itemType={FormItem.TEXTRICH}
value={answer.descreption || ''}
required
/>
<FormItem
name={'file_' + q.id}
fileVal="file"
value={answer.file_url || ''}
btnClassName="upbtn"
label="请上传对应的文件附件:"
itemType={FormItem.FILE}
text="上传附件"
server="/api/tenant/util/upload-file"
fileSingleSizeLimit={1024*1024*10}
accept={[{extensions: 'docx'}]}
onFileError={this.handleUploadError}
/>
<p className="help help-file">只支持docx格式的文件,文件小于10M</p>
{answer.file_url && <a style={{display: 'block', marginBottom: '20px', color: 'blue'}} href={answer.file_url} >下载附件</a> }
</li>
);
})}
</ul>
:
<p className="no-data">暂无数据</p>
}
<p className="text-danger">{this.state.error}</p>
<div className="area-btns">
<button type="submit" disabled={!this.canSubmit() || (work && work.checker_id)} className="btn btn-primary" >{this.isSubmitLoading() ? '保存中...' : (work && work.checker_id) ? '已批改' : '提交'}</button>
<span className="help-info">&emsp;&emsp;在获老师批改之前,可以多次提交,将以最后一次提交为准</span>
{work ?
!work.checker_id ?
<p className="help">已于 {work.created_time} 提交,等待批改中</p>
:
<div className="play-paper-check">
<h4>已获批改 <small>批改于{work.check_date}</small></h4>
<div className="play-paper-check-item"><b>评分:</b>{work.score}</div>
<div className="play-paper-check-item">
<b>评语:</b>
<div className="edit_html" dangerouslySetInnerHTML={{__html:work.check_comments}}></div>
</div>
</div>
: null
}
</div>
</Formsy.Form>
</div>
</div>
</div>
);
}
}
module.exports = ChapterWork;
/**
* 课程资料
*
* courseId
* files
*/
import React, { Component } from 'react'
class CourseInfo extends Component {
render () {
const files = this.props.files || [];
return (
<div className="play-paper">
<div className="play-paper-body">
<div className="play-paper-title"><div><h3>课程资料</h3></div></div>
<div className="play-paper-content">
{files.length ?
<ul className="play-read-files">
{files.map((f, i) => {
return <li key={i}><a href={f.file_url} target="_blank">{f.file_name}</a></li>
})}
</ul>
:
<p className="no-data">暂无课程资料</p>
}
</div>
</div>
</div>
);
}
}
module.exports = CourseInfo;
/**
* 课程大作业
* porps:
* course
* course_work
* action
* dispatch
*/
import React, { Component } from 'react'
import { Link } from 'react-router';
import Formsy from 'formsy-react';
import FormsyComponent from '../../components/formsy/Component.jsx'
import FormItem from '../../components/formsy/FormItem.jsx'
import CoursesAction from '../../actions/CoursesAction'
import OperateAction from '../../actions/OperateAction'
import { getRequestTypes } from '../../libs/utils';
class CourseWork extends FormsyComponent {
constructor(props) {
super(props);
this.state = { error: '' };
}
componentDidMount () {
this.courseAction = new CoursesAction();
this.operateAction = new OperateAction();
this.courseId = this.props.course.course_id;
this.semesterId = this.props.semesterId;
// 加载回答
this.props.dispatch(this.courseAction.loadCourseWork(this.semesterId, this.courseId));
}
componentWillReceiveProps (nextProps) {
let submitType = getRequestTypes(CoursesAction.SUBMIT_COURSE_WORK);
switch (nextProps.action.type) {
case submitType.success:
this.enableSubmitButton();
if (nextProps.action.response.data.status == 0) {
nextProps.dispatch(this.operateAction.showErrorMessage('账号出错,请联系管理员'));
console.log(JSON.stringify(nextProps.action.response.data.error))
break;
}
// 提交成功,重新获取答案
nextProps.dispatch(this.courseAction.loadCourseWork(this.semesterId, this.courseId));
nextProps.handleSubmited('work');
break;
case submitType.failure:
this.enableSubmitButton();
nextProps.dispatch(this.operateAction.showErrorMessage(nextProps.action.error.message || '提交课程大作业失败'));
break;
}
}
/**
* 提交
*/
onSubmit = model => {
this.loadingSubmitButton();
model.course_id = this.props.course.course_id;
model.semester_id = this.props.semesterId;
this.props.dispatch(this.courseAction.submitCourseWork(model));
}
// 表单变更时,取消掉全局错误消息
onFormChange = () => {
this._setState({ error: '' });
}
handleUploadError = msg => {
this.props.dispatch(this.operateAction.showErrorMessage(msg || '上传文件失败'));
}
render () {
const {course, course_work} = this.props;
const info = course_work.data || {};
return (
<div className="play-paper">
<div className="play-paper-body">
<div className="play-paper-title"><div><h3>课程大作业</h3></div></div>
<div className="play-paper-content">
<div className="play-paper-step">&#9312; 阅读大作业要求</div>
<div className="edit_html" dangerouslySetInnerHTML={{__html: course.curriculum && course.curriculum.curriculum_essay || ''}}></div>
<p>截止日期:{course.essay_date || ''}</p>
<div className="play-paper-step">&#9313; 填写作业主题、摘要,上传附件(点击“提交”保存)</div>
<Formsy.Form
onValid={this.enableSubmitButton}
onInvalid={this.disableSubmitButton}
onValidSubmit={this.onSubmit}
onChange={this.onFormChange}
>
<input type="hidden" name="id" value={info.id || ''} />
<div>
<FormItem
name="essay_name"
value={info.essay_name || ''}
placeholder="主题"
required
/>
<FormItem
name="essay_description"
value={info.essay_description || ''}
label="摘要"
itemType={FormItem.TEXTRICH}
required
/>
<FormItem
name="url"
fileVal="file"
value ={info.file_url || ''}
btnClassName="upbtn"
label="大作业附件:"
itemType={FormItem.FILE}
text="选择附件"
server="/api/tenant/util/upload-file"
fileSingleSizeLimit={1024*1024*10}
accept={[{extensions: 'docx'}]}
onFileError={this.handleUploadError}
required
/>
<p className="help help-file">只支持docx格式的文件,文件小于10M</p>
{info.file_url && <a style={{display: 'block', marginBottom: '20px', color: 'blue'}} href={info.file_url} >下载附件</a> }
</div>
<p className="text-danger">{this.state.error}</p>
<div className="area-btns">
<div className="play-paper-step">&#9314; 截止日期前提交</div>
<button type="submit" disabled={!this.canSubmit() || info.checker_id} className="btn btn-primary" >{this.isSubmitLoading() ? '保存中...' : info.checker_id ? '已批改' : '提交'}</button>
<span className="help-info">&emsp;&emsp;在获老师批改之前,可以多次提交,将以最后一次提交为准</span>
{info.id ?
!info.checker_id ?
<p className="help">已于 {info.created_time} 提交,等待批改中</p>
:
<div className="play-paper-check">
<h4>已获批改 <small>批改于{info.check_date}</small></h4>
<div className="play-paper-check-item"><b>评分:</b>{info.score}</div>
<div className="play-paper-check-item">
<b>评语:</b>
<div className="edit_html" dangerouslySetInnerHTML={{__html:info.check_comments}}></div>
</div>
</div>
: null
}
</div>
</Formsy.Form>
</div>
</div>
</div>
);
}
}
module.exports = CourseWork
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { Link } from 'react-router';
import { chapterType } from '../../libs/const';
import { getIdt, getRequestTypes, paramify } from '../../libs/utils';
import CoursesAction from '../../actions/CoursesAction';
import OperateAction from '../../actions/OperateAction';
import Loading from '../../components/Loading.jsx';
// 页面组成部分
import SideList from './SideList.jsx'
import SidePpt from './SidePpt.jsx'
import ChapterVideo from './ChapterVideo.jsx'
import ChapterRead from './ChapterRead.jsx'
import ChapterExam from './ChapterExam.jsx'
import ChapterWork from './ChapterWork.jsx'
import CourseWork from './CourseWork.jsx'
import CourseInfo from './CourseInfo.jsx'
import AudioPanel from './AudioPanel.jsx'
import VideoPhonePanel from './VideoPhonePanel.jsx'
if (process.env.BROWSER) {
require('css/course.css');
require('css/play.css');
}
const SIDEBAR_CHAPTER = 'sidebar_chapter';
const SIDEBAR_PPT = 'sidebar_ppt';
const IS_PHONE = /Android|iPhone|iPad/i.test(navigator && navigator.userAgent || '');
class Play extends Component {
// 初始加载数据
// static fetchData({dispatch, params={}, location={}, apiClient}) {
// const courseAction = new CoursesAction({ apiClient });
// let arr = [
// dispatch( courseAction.loadCourseDetail(params.courseId) ),
// dispatch( courseAction.loadChapterProgress(params.courseId) )
// ];
// return Promise.all(arr);
// }
constructor(props) {
super(props);
this.state = {
sidebar: IS_PHONE ? false : SIDEBAR_CHAPTER, // 控制侧边栏显示与否,及显示chapter还是ppt
pptBoxOnly: false, // 仅展示ppt框
pptBoxShow: false, // 展示ppt框
curProgress: 0, // 当前播放章节的进度,仅为更新this.pageProgressMap时,触发页面刷新
};
this.pageProgressMap = {}; // 播放期间记录本次页面进度
// 需上传数据
this.pushData = {
timer: null, // 定期发送timer
currentChapterId: null,
currentVideoId: null, // 当前播放视频id
total: 0, // 播放累计总时间
max: 0, // 最大播放时间点
arr: [], // 播放数组
last: 0, // 上次播放时间点,用来与当前第一个点比较是否重复
};
this.courseAction = new CoursesAction();
this.operateAction = new OperateAction();
this.getChapterMap = this.getChapterMap.bind(this);
this.getCurrentChapter = this.getCurrentChapter.bind(this);
this.getProgress = this.getProgress.bind(this);
}
componentDidMount() {
this._chapterMap = null;
const {params, course} = this.props;
// 如果没有指定章节ID,并且又取得了章节数据,跳转到第一节
if (!params.chapterId &&
(params.courseId === (course._req && course._req.courseId) && course.data)) {
let chapters = course.data && course.data.chapters || [];
let children = chapters[0] && chapters[0].children || [];
let firstId = children[0] && children[0].id;
this.props.history.push(`/courses/${params.semesterId}/${params.courseId}/chapters/${firstId}`);
return;
}
this.loadNeededData(this.props);
// 数据采集
this.pushData.timer = setInterval(this.sendPlayerProgress.bind(this), 5000);
}
componentDidUpdate() {
//if (this.refs.chapterVideo) {
// this.refs.chapterVideo.jdugeSize()
//}
}
componentWillReceiveProps(nextProps) {
this.loadNeededData(nextProps);
// 如果章节变化,需要再次获取最新的私密信息,以便保证每次播放都检查权限
if (this.props.location.pathname !== nextProps.location.pathname) {
// this.sendPlayerProgress();
}
if (this.props.video_progress.isFetching !== nextProps.video_progress.isFetching &&
nextProps.video_progress.data) {
let cur = this.getCurrentChapter(nextProps.params.chapterId);
if (cur) {
let video_progress = nextProps.video_progress.data;
Object.assign(this.pushData, {
currentVideoId: cur.video.id,
total: (video_progress.pt || 0) - 0,
max: (video_progress.mpt || 0) - 0,
last: (video_progress.cpt || 0) - 0,
})
}
}
}
componentWillUnmount() {
let operateAction = new OperateAction();
this.props.dispatch(operateAction.cleanData(CoursesAction.LOAD_COURSE_DETAIL));
this.props.dispatch(operateAction.cleanData(CoursesAction.LOAD_CHAPTER_PROGRESS));
clearInterval(this.pushData.timer);
this.sendPlayerProgress();
}
// 获取章节map
getChapterMap() {
if (!this._chapterMap) {
let ret = null;
let info = this.props.course.data || {};
let list = info.chapters || [];
list.forEach(chapter => {
(chapter.children || []).forEach(item => {
ret = ret || {};
ret[item.id] = item;
});
});
this._chapterMap = ret;
}
return this._chapterMap;
}
// 获取当前章节
getCurrentChapter(chapterId) {
let cur = null;
let id = chapterId || this.props.params.chapterId;
let map = this.getChapterMap();
if (map && id) {
cur = map[id];
}
return cur;
}
/**
* 加载需要的数据:已有数据与props中参数不一致时,加载对应的数据
*/
loadNeededData = props => {
const {params, course, ppts, user} = props;
const courseAction = new CoursesAction();
// 课程详情
if (params.courseId != (course._req && course._req.courseId)) {
props.dispatch( courseAction.loadCourseDetail(params.semesterId, params.courseId) );
props.dispatch( courseAction.loadChapterProgress(params.semesterId, params.courseId) );
}
// 课程章节
if (params.chapterId !== this.pushData.currentChapterId && course.data) {
// 只有章节数据存在时,才加载当前视频进度
let curChapter = this.getCurrentChapter(params.chapterId);
if (curChapter && curChapter.video) {
this.pushData.currentChapterId = params.chapterId;
props.dispatch( courseAction.loadPlayerProgress({
device_id: getIdt(),
video_id: curChapter.video.id,
semester_id: params.semesterId
//_idt: getIdt(),
//_uid: user.data && user.data.uid || null,
//_cid: params.courseId,
//_vid: curChapter.video.id,
}) );
// 存在章节时,加载ppt
if (curChapter.video &&
curChapter.video.id &&
curChapter.video.id != (ppts._req && ppts._req.videoId)) {
props.dispatch( courseAction.loadVideoPpts(curChapter.video.id) );
}
/* 读取对应视频 - 手机端 m3u8源 */
props.dispatch( courseAction.loadVideoPhone({ vid: curChapter.video.id }) )
}
}
};
// 发送到数据采集
sendPlayerProgress = () => {
if (this.pushData.arr.length && this.pushData.currentVideoId) {
const courseAction = new CoursesAction();
const len = this.pushData.arr.length;
this.pushData.total += len - (this.pushData.last === this.pushData.arr[0] ? 1 : 0);
this.pushData.last = this.pushData.arr[len - 1];
this.pushData.max = Math.max.apply(null, [this.pushData.max].concat(this.pushData.arr))
// 更新pageProgressMap
let chapter = this.getCurrentChapter();
if (chapter) {
let v = Math.ceil(this.pushData.max / chapter.video.video_length * 100);
this.pageProgressMap[chapter.id] = v;
if (this.state.curProgress != v) {
this.setState({curProgress: v});
}
}
let params = {
d: getIdt(),
i: getIdt(),
c: this.props.params.courseId,
s: this.props.params.semesterId,
v: this.pushData.currentVideoId,
//_ts: Math.round(new Date().getTime() / 1000),
_p: this.pushData.total,
_m: this.pushData.max > chapter && chapter.video.video_length ? chapter.video.video_length : this.pushData.max,
_c: this.pushData.last > chapter && chapter.video.video_length ? chapter.video.video_length : this.pushData.last,
};
this.props.dispatch( courseAction.playerProgress(params) );
// 再次推送另一个系统
delete params._dt_0;
const imgId = '_tj_pt_gif_';
let img = document.getElementById(imgId);
if (!img) {
img = new Image();
img.id = imgId;
img.style.display = 'none';
document.body.appendChild(img);
}
img.src = 'https://tj.ezijing.com/pt.gif?' + paramify(params);
this.pushData.arr = [];
}
};
// 阅读材料统计进度
sendReaded = () => {
let chapter = this.getCurrentChapter();
if (chapter && chapter.reading) {
let params = {
d: getIdt(),
i: getIdt(),
c: this.props.params.courseId,
s: this.props.params.semesterId,
r: chapter.reading.id
};
const courseAction = new CoursesAction();
this.props.dispatch( courseAction.chapterReaded(params) );
this.pageProgressMap[chapter.id] = 100;
}
};
chapterSubmited = id => {
this.pageProgressMap[id] = 100;
};
// 点击返回详情时,重新加载课程的private信息,以便更新latest_play字段数据
onClickToDetail = e => {
const courseAction = new CoursesAction();
this.props.dispatch( courseAction.loadCoursePrivate(this.props.params.courseId) );
};
changeSidebar = (v) => {
this.setState({sidebar: v});
if (v != this.state.sidebar && (v === false || this.state.sidebar === false)) {
if (this.refs.chapterVideo) {
setTimeout(() => {
this.refs.chapterVideo.jdugeSize()
}, 0)
}
}
}
handleHideSidebar = () => { // 隐藏侧边栏
this.changeSidebar(false)
};
// 特殊处理,为了刷新页面
handleHideSidebar1 = () => {
this.changeSidebar(false)
window.location.reload()
};
reloadPages () { // 刷新页面
window.location.reload()
}
handleShowSidebarChapter = e => { // 显示侧边章节
e.preventDefault();
this.changeSidebar(SIDEBAR_CHAPTER);
};
handleShowSidebarPpt = e => { // 显示侧边ppt
e.preventDefault();
this.changeSidebar(SIDEBAR_PPT);
};
// 侧边点击ppt,根据ppt播放时间点同步video
// @param index 被点击的ppt序列号
handleClickSidePpt = index => {
const ppts = this.props.ppts.data || []
if (ppts.length > index && this.refs.chapterVideo) {
const ppt = ppts[index]
this.refs.chapterVideo.setVideoTime(ppt.ppt_point)
}
};
handlePlayTime = time => { // 视频播放时间变更调用此方法
// 侧边ppt对应位置
if (this.refs.sidePpt) {
this.refs.sidePpt.setIndexByPoint(time)
}
// 存储到要上传到大数据的数据集中
time = Math.round(time);
if (this.pushData.arr.indexOf(time) < 0) {
this.pushData.arr.push(time);
}
};
// 标记完成
// @param time 当前播放到的时间点
handleOver = time => {
let cur = this.getCurrentChapter();
let progress = this.getProgress(cur.id);
if (cur) {
this._signOver = progress != 100;
this.props.dispatch( this.courseAction[progress == 100 ? 'playerUnover': 'playerOver']({
video_id: cur.video.id,
chapter_id: cur.id,
current_progress: Math.round(time/cur.video.video_length * 100)
}) );
}
};
// 获取进度,综合接口返回上次播放进度和本页播放进度记录
getProgress(id) {
const progressData = this.props.chapter_progress.data || {};
if (id === 'work') { // 大作业
return Math.max(
progressData.essay && progressData.essay.progress || 0,
this.pageProgressMap.essay || 0);
}
let p = 0;
let chapterProgressMap = progressData.chapters || {};
let pobj = chapterProgressMap[id] || {};
if (id === this.props.params.chapterId) {
p = (this._signOver || (pobj.sign && this._signOver === undefined)) ? 100 : pobj.progress || 0;
} else {
p = pobj.sign ? 100 : pobj.progress || 0;
}
p = Math.max(this.pageProgressMap[id] || 0, p);
return p;
}
render() {
let {course, ppts, params, user, video_curm3u8, video_progress} = this.props;
if (course.isFetching) {
return <div className="play"><Loading /></div>
} else if (!(course.data && course.data.course_id)) {
return <div className="play"><p className="no-data">课程不存在</p></div>
}
let cpt = (video_progress.data || {}).cpt || 0;
course = course.data || {};
let chapters = course.chapters || [];
ppts = ppts.data || [];
video_curm3u8 = video_curm3u8.data || {};
// 构造chapterMap,方便查找;顺便在同一个遍历中创建层级结构
let chapterMap = this.getChapterMap() || {}; // map
// 当前章节
let chapter = params.chapterId && chapterMap[params.chapterId] || {};
// 当前章节进度
let curProgress = this.getProgress(params.chapterId);
this.pushData.currentVideoId = chapter.video && chapter.video.id || null;
const isVideo = chapter.type === chapterType.VIDEO;
// 前一节、后一节
let leafIds = [];
chapters.forEach(c => {
(c.children || []).forEach(o => {
leafIds.push(o.id);
});
});
let prevChapterId = null;
let nextChapterId = null;
let curIndex = leafIds.indexOf(params.chapterId);
prevChapterId = curIndex > 0 ? leafIds[curIndex - 1] : null;
nextChapterId = curIndex < leafIds.length - 1 ? leafIds[curIndex + 1] : null;
if (isVideo && !video_progress.data) {
return <div className="play"><Loading /></div>
}
/* 手机端 没用 device接口传的 lastTime值 */
if (IS_PHONE && !video_curm3u8.video && !video_curm3u8.audio) {
return <div><Loading/></div>
}
/* 手机播放页面单独展示 - 音频播放 */
if (isVideo && IS_PHONE && video_curm3u8.audio) {
if (!this.audioChapterData) {
let map = {}; // {chapterId: chapterObj}
let tree = []; // [[cid, [cid, cid]]]
let leaves = [];
chapters.forEach(c => {
map[c.id] = c;
tree.push([c.id, []]);
c.children.forEach(o => {
map[o.id] = o;
leaves.push(o.id)
tree[tree.length - 1][1].push(o.id);
})
})
this.audioChapterData = {map, tree, leaves,
progressMap: this.props.chapter_progress.data || {}};
}
return <div className="play">
<AudioPanel
index={curIndex}
chapterData={this.audioChapterData}
course={course}
ppts={ppts}
skipBegin={false}
handleOver={this.handleOver}
handleAudioTime = {this.handlePlayTime}
video_curm3u8={video_curm3u8}
/>
</div>
} else
/* 有视频时,先播放视频, 并且存在手机端 m3u8时 */
if (isVideo && IS_PHONE && !chapter.video.video_voice) {
if (!this.audioChapterData) {
let map = {}; // {chapterId: chapterObj}
let tree = []; // [[cid, [cid, cid]]]
let leaves = [];
chapters.forEach(c => {
map[c.id] = c;
tree.push([c.id, []]);
c.children.forEach(o => {
map[o.id] = o;
leaves.push(o.id)
tree[tree.length - 1][1].push(o.id);
})
})
this.audioChapterData = {map, tree, leaves,
progressMap: this.props.chapter_progress.data || {}
};
}
return (<div className="play">
<VideoPhonePanel
index={curIndex}
chapterData={this.audioChapterData}
course={course}
ppts={ppts}
video_curm3u8={video_curm3u8}
skipBegin={false}
handleOver={this.handleOver}
handleVideoTime = {this.handlePlayTime}
/>
</div>)
}
return (
<div className={`play ${this.state.sidebar ? '' : 'sidebar-hide'}`}>
<div className="left-content">
<div className="play-top cl" style={this.state.sidebar ? {} : {marginRight: 0}}>
<Link className="play-back" to={`/courses/${params.semesterId}/${params.courseId}/cont`} onClick={this.onClickToDetail}>&lt;</Link>
<p>{course.course_name}</p>
<p>{chapter.chapter_name}</p>
</div>
<div className="play-content">
{params.chapterId === 'work' ?
<CourseWork
course={course}
semesterId={course.semester_id}
course_work={this.props.course_work}
dispatch={this.props.dispatch}
action={this.props.action}
handleSubmited={this.chapterSubmited}
/>
: params.chapterId === 'info' ?
<CourseInfo
courseId={params.courseId}
files={course.files}
/>
: chapter.type === chapterType.VIDEO ?
<ChapterVideo
ref="chapterVideo"
courseId={params.courseId}
semesterId={course.semester_id}
ppts={ppts}
chapter={chapter}
username={user.data && user.data.username || ''}
prevChapterId={prevChapterId}
nextChapterId={nextChapterId}
curProgress={curProgress}
handlePlayTime={this.handlePlayTime}
handleOver={this.handleOver}
lastTime={cpt}
/>
: chapter.type === chapterType.WORKOREXAM ?
(chapter.homework && chapter.homework.work_type === chapterType.WORK_HOME ?
<ChapterWork
chapter={chapter}
chapter_work={this.props.chapter_work}
courseId={params.courseId}
semesterId={course.semester_id}
action={this.props.action}
dispatch={this.props.dispatch}
handleSubmited={this.chapterSubmited}
/>
:
<ChapterExam
chapter={chapter}
chapter_work={this.props.chapter_work}
courseId={params.courseId}
semesterId={course.semester_id}
action={this.props.action}
dispatch={this.props.dispatch}
handleSubmited={this.chapterSubmited}
/>
)
:
<ChapterRead
chapter={chapter}
handleReaded={this.sendReaded}
/>
}
</div>
</div>
<div className="right-content" style={{ right: this.state.sidebar ? 0 : -388 }}>
<p className="right-arrow" onClick={this.handleHideSidebar}><span>&gt;</span></p>
<div className="control-panel">
<ul className="nav-tabs play-nav cl">
<li className={`videoChapter ${this.state.sidebar === SIDEBAR_CHAPTER ? 'current' : ''}`}><a href="#sidebar_chapter" onClick={this.handleShowSidebarChapter}>章节</a></li>
{ppts.length ?
<li className={`videoJy ${this.state.sidebar === SIDEBAR_PPT ? 'current' : ''}`}><a href="#sidebar_ppt" onClick={this.handleShowSidebarPpt}>讲义</a></li>
: null
}
</ul>
<div className="tab-content">
{this.state.sidebar === SIDEBAR_CHAPTER ?
<SideList
chapters={chapters}
getProgressByChapterId={this.getProgress}
courseId={params.courseId}
semesterId={params.semesterId}
chapterId={params.chapterId}
progress={curProgress}
handleClickItem={IS_PHONE ? this.handleHideSidebar1 : this.reloadPages}
/>
:
<SidePpt
ref="sidePpt"
ppts={ppts}
handleClickPpt={this.handleClickSidePpt}
/>
}
</div>
</div>
</div>
{this.state.sidebar ? null :
<div className="switch">
<a href="#sidebar_chapter" className="switch-chapter" onClick={this.handleShowSidebarChapter}></a>
{isVideo && ppts.length ?
<a href="#sidebar_ppt" className="switch-handout" onClick={this.handleShowSidebarPpt}></a>
: null
}
</div>
}
</div>
);
}
}
module.exports = connect( state => ({
action: state.action,
user: state.user,
course : state.course,
course_work: state.course_work,
chapter_work: state.chapter_work,
ppts: state.ppts,
chapter_progress: state.chapter_progress,
video_progress: state.video_progress, // 接口device
video_curm3u8: state.video_m3u8
}) )(Play);
/**
* 侧边章节导航列表
* props:
* chapters []
* getProgressByChapterId func
* courseId
* chapterId string
* progress number
*/
import React, { Component } from 'react'
import { Link } from 'react-router';
import ProgressCircle from '../../components/ProgressCircle.jsx'
import {chapterType} from '../../libs/const'
const NAV_COURSE = {
name: '大作业及资料',
children: [{
id: 'work',
type: chapterType.WORKOREXAM,
name: '课程大作业',
homework: {work_type: chapterType.WORK_HOME}
}, {
id: 'info',
type: chapterType.READ,
name: '课程资料'
}]
};
class SideList extends Component {
reloadPages () {
window.location.reload()
}
render () {
const {chapters, getProgressByChapterId, semesterId, courseId, chapterId, progress} = this.props;
let list = (chapters || []).concat([NAV_COURSE]);
return (
<div className="tab-pane current">
<ul className="under-control chapter-list current">
{list.map((item, index) => {
return (
<li className="chapter-item" key={index}>
<span className="cpt">{item.name}</span>
<div className="knob-list-wrap">
<ul className="knob-list">
{(item.children || []).map((o, i) => {
let isCur = chapterId === o.id;
let prog = isCur ? progress : getProgressByChapterId(o.id);
let part = Math.max(Math.round(prog / 25), 0);
let type;
switch (o.type) {
case chapterType.VIDEO:
type = 'video'; break;
case chapterType.READ:
type = 'read'; break;
default:
type = 'exam';
if (o.homework && o.homework.work_type === chapterType.WORK_HOME) {
type = 'work';
}
}
return (
<li className={`knob-item ${isCur ? 'current' : ''} ${part ? ['one', 'two', 'three', 'four'][part - 1] + '-four' : ''}`} key={i}>
<ProgressCircle percent={prog} className="play-chapter-progress"/>
<i className="icon icon-pro" title={`学习进度 ${prog}%`}></i>
{(type == 'work') ?
<Link to={`/courses/${semesterId}/${courseId}/chapters/${o.id}`} className="knob-name" onClick={this.reloadPages}>{o.name}</Link>
:
<Link to={`/courses/${semesterId}/${courseId}/chapters/${o.id}`} className="knob-name" onClick={this.props.handleClickItem}>{o.name}</Link>
}
<i className={`icon-play-chapter icon-play-${type}`}></i>
</li>
);
})}
</ul>
</div>
</li>
);
})}
</ul>
</div>
);
}
}
module.exports = SideList;
/**
* 侧边显示讲义ppt
* props:
* ppts: []
* handleClickPpt: func
*/
import React, { Component } from 'react'
class SidePpt extends Component {
constructor (props) {
super(props)
this.state = { index: 0 }
}
// 根据播放时间同步展示ppt
// @param time 播放时间
setIndexByPoint = time => {
const ppts = this.props.ppts || []
const len = ppts.length
let i = 0;
for(; i < len; i++) {
if (time < ppts[i].ppt_point) {
break;
}
}
if (this.state.index !== i - 1) {
this.setState({ index: i - 1 });
}
}
//handleSyncVideoTime = e => { // 点击ppt跳转对应的播放时间
// 点击某个ppt
onClickPpt = e => {
let toIndex = e.currentTarget.getAttribute('data-index') - 0
if (this.state.index === toIndex) { return }
this.setState({ index: toIndex })
this.props.handleClickPpt(toIndex)
}
render () {
const {ppts} = this.props
return (
<div className="tab-pane current">
<div className="jiangyi-list">
<div className="jy-list">
{(ppts || []).length ?
ppts.map((item, index) => {
return (
<div key={index} onClick={this.onClickPpt} data-index={index} className={index === this.state.index ? 'current' : ''}>
<img src={item.ppt_url} alt=""/>
</div>
);
})
:
<div className="no-data">暂无讲义</div>
}
</div>
</div>
</div>
);
}
}
module.exports = SidePpt;
import React from 'react'
import { ReactDom } from 'react-dom';
/**
* 视频基础组件类
* props:
* src: 视频源地址
* lastTime: xx, 上次播放时间
* skipBegin: 忽略片头,true/false
* onmeta: 加载资源,可获取如视频长度等信息 父级方法实现
* onloaded: 加载完视频,可播放方法 父级方法实现
* onloading: 加载中视频 父级方法实现
* onplay: 播放视频 父级方法实现
* onpause: 暂停播放 父级方法实现
* ontime:
* 外部可用:
* duration
* seek
* play
* pause
*
*
* 问题:
* 1. 华为 P7;小米 自带浏览器,不支持 倍速
* 2. 视频 跳跃时,可能刷新视频,导致currentTime重新开始
* 3. 外观不够美化,进度条 不能拖动
* 4. 图片放大 这个插件也需要集成化
* 5.
*/
class VideoPhone extends React.Component {
static SKIP_SECS = 7; // 片头7s
/**
* 构造函数
*/
constructor(props) {
/* 继承属性 */
super(props);
/* 缓存视频 视频列表 */
this.playList = [];
/* 视频列表中,播放第几个 */
this.videoIndex = 0;
// console.log('diaoyong', this.playList);
/* 当前所在播放时间 */
this.time = 0;
this.stepTimer = null; //time计时器
// this.lastSeekTime = 0; // 记录上一次 seek时,存储的时间值
}
/**
* 组件渲染前
*/
componentWillMount() {
}
/**
* 组件渲染后
*/
componentDidMount() {
this.refs.video_obj.setAttribute('x5-video-arientation', 'portrait');
this.refs.video_obj.setAttribute('x-webkit-airplay', 'true');
this.refs.video_obj.setAttribute('webkit-playsinline', '');
this.refs.video_obj.setAttribute('playsinline', 'true');
this.refs.video_obj.setAttribute('x5-video-player-typ', 'h5');
/* 跟audio组件唯一不同点是 video对象是组件渲染完成后添加 加载Video对象并加载视频 */
this.getVideoObject()
}
componentWillReceiveProps(nextProps) {
// console.log(12222);
// if (nextProps.src !== this.props.src) {
// // 暂停当前sound
// let sound = this.getVideoObject();
// if (sound && sound.playing()) {
// sound.pause();
// }
// // 检查是否已存在
// let existIndex = findIndex(this.playList, {src: nextProps.src});
// if (existIndex < 0) {
// existIndex = this.playList.length;
// this.playList.push({src: nextProps.src, howl: null, time: 0});
// }
// this.audioPlay(existIndex);
// }
}
componentWillUnmount() {
this.getVideoObject().pause();
}
shouldComponentUpdate(nextProps, nextState) {
return false;
}
// 第一播放,跳过片头,同时考虑lastTime
skipBeginAndPlay() {
let sound = this.getVideoObject();
if (sound && sound.state() === 'loaded') {
let lastTime = this.props.lastTime || 0;
if (this.props.skipBegin) { lastTime = Math.max(lastTime, Video.SKIP_SECS); }
if (lastTime) {
if (lastTime > sound.duration() - 10) { lastTime = 0; } // 最后10s不执行跳转lastTime
let cur = sound.seek();
if (cur < lastTime) {
sound.seek(lastTime);
}
}
sound.play();
}
}
// 无参数时,播放当前index视频到对应位置。
// index 要播放的序号
audioPlay (index) {
this.videoIndex = index;
this.time = 0;
this.skipBeginAndPlay();
}
step() {
clearTimeout(this.stepTimer);
let sound = this.getVideoObject();
if (!sound) {return;}
let seek = Math.round(sound.seek() || 0);
let playing = sound.playing();
if (seek !== this.time) {
this.time = seek;
this.props.ontime(seek);
}
if (playing) {
this.stepTimer = setTimeout(this.step, 300);
}
}
stepPause() {
clearTimeout(this.stepTimer);
this.props.onpause();
}
/**
* 搜寻点击进度条某个位置 跳播方式
* @param {number} s 传入当前需要改变的时间
*/
seek(s) {
/**
* 设置时,需要判断是否在seekable的范围内,不在,等待缓冲
* 如果在了,就可以设置 -- 华为手机 就是不行,设置 可能会断,重新加载视频
*/
let _video = this.getVideoObject(), i = 0;
// if (!/android/gi.test(navigator.userAgent)) {
if ('fastSeek' in _video) {
_video.fastSeek(s); // 改变audio.currentTime的值
return ;
}
this.pause();
_video.currentTime = s;
this.play();
// } else {
// for (i=0; i<_video.seekable.length; i++) {
// if (_video.seekable.start(i) <= s && s >= _video.seekable.end(i)) {
// break;
// }
// }
// if (i < _video.seekable.length) {
// _video.currentTime = s;
// } else {
// _video.buffered.end(_video.buffered.length - 1);
// }
// }
}
/**
* 返回视频长度
*/
getDuration() {
return this.getVideoObject().duration || 0;
}
/**
* 开始播放
*/
play() {
this.getVideoObject().play();
}
/**
* 播放暂停
*/
pause() {
this.getVideoObject().pause();
}
/**
* 播放速率改变
* @param {float} n 速率值,每次加0.5
*/
rate(n) {
this.getVideoObject().defaultPlaybackRate = n
this.getVideoObject().playbackRate = n
}
/**
* render渲染
*/
render () {
let videoEl = document.createElement("video");
// 是否支持 MP4
let isSupportMP4 = videoEl.canPlayType('video/mp4') !== '';
// 是否支持 HLS 的 m3u8
let isSupportHLSm3u8 = /like Mac OS X/gi.test(navigator.userAgent);// 判别是否为手机
// 移动端 支持 m3u8 -
// 安卓只支持 application/vnd.apple.mpegurl,而ios对于application/x-mpegURL,application/vnd.apple.mpegURL均支持
let isSupportm3u8 = videoEl.canPlayType('application/vnd.apple.mpegURL') !== '';
// alert(isSupportMP4+'1')
// alert(isSupportHLSm3u8+'2')
// alert(isSupportm3u8+'3')
return (<video ref="video_obj" poster={this.props.poster} width="100%" height="100%" videoWidth="" videoHeight=""
preload="auto" x5-video-arientation="portrait"
x-webkit-airplay="true"
webkit-playsinline playsinline="true"
x5-video-player-typ="h5">
{
isSupportHLSm3u8 ?
<source type="video/mp4" src="https://img.zai-art.com/video1/a264203d6f683194f8bfe7d698beee36.mp4" />
: isSupportm3u8 ?
<source src={this.props.src} />
: <source type="video/mp4" src="https://img.zai-art.com/video1/a264203d6f683194f8bfe7d698beee36.mp4" />
}
</video>)
}
//
/**
* 获取 Video对象
* 内部创建一个 视频对象
*/
getVideoObject() {
/* 在缓存视频列表里,获取对应要播放的视频对象 */
let playItem = this.playList[this.videoIndex];
/* 如果视频列表里没有 */
if (!playItem) {
this.playList[this.videoIndex] = {src: this.props.src, obj: null, time: 0};
playItem = this.playList[this.videoIndex];
}
let _mediaVideo = playItem.obj;
/* 如果缓存中没有视频对象 */
if (!_mediaVideo) {
/* 创建一个Video对象 */
_mediaVideo = this.refs.video_obj;
_mediaVideo.src = this.props.src;
_mediaVideo.load();
_mediaVideo.loadAll = false; // 可能存在缓存,如果从缓存中获取,那么歌曲就全部加载完成了,就不再需要监听progress事件,虽然它还会继续加载
/* 开始请求 顺序 1 */
_mediaVideo.addEventListener('loadstart', () => {
}, false);
/* 正在请求 顺序 2, 这个比较特殊,有缓存时,它还会请求 */
_mediaVideo.addEventListener('progress', () => {
/* 父级事件,告诉父级视频在加载中 */
!_mediaVideo.loadAll && this.props.onloading();
// console.log('loading', !_mediaVideo.loadAll);
}, false);
/* 资源长度改 顺序 3 */
_mediaVideo.addEventListener('durationchange', () => {
/* 父级事件,告诉父级一些资源信息 华为原生浏览器上,播放前loadedmetadata事件监听,获取不到duration信息 */
this.props.onmeta();
}, false);
/* 可以播放,但中途可能因为加载而暂 顺序 4 */
_mediaVideo.addEventListener('canplay', () => {
/* 歌曲可播放,就代表加载完 */
_mediaVideo.loadAll = true;
/* 父级事件,告诉父级视频加载完成 */
this.props.onloaded();
}, false);
/* 可以播放,歌曲全部加载完毕 顺序 5 */
_mediaVideo.addEventListener('canplaythrough', () => {
/* 父级事件,告诉父级视频加载完成 */
this.props.onloaded();
}, false);
/* play()和autoplay 开始播放时,触发 但并未真正开始播放 */
_mediaVideo.addEventListener('play', () => {
/* 父级事件,告诉父级视频正在播放 */
this.props.onplay();
/* 父级事件,告诉父级视频在加载中 */
this.props.onloading();
}, false);
/* playing 开始播放时,触发,真正开始播放 */
_mediaVideo.addEventListener('playing', () => {
/**
* 0 HAVE_NOTHING 没有关于视频/视频是否就绪的信息
* 1 HAVE_METADATA 关于视频/视频就绪的元数据
* 2 HAVE_CURRENT_DATA 关于当前播放位置的数据是可用的,但没有足够的数据来播放下一帧/毫秒
* 3 HAVE_FUTURE_DATA 当前及至少下一帧的数据是可用的
* 4 HAVE_ENOUGH_DATA 可用数据足以开始播放
*/
if (_mediaVideo.readyState >= 2) {
/* 父级事件,告诉父级视频加载完成 */
this.props.onloaded();
}
}, false);
/* pause() 暂停播放时,触发 */
_mediaVideo.addEventListener('pause', () => {
/* 父级事件,告诉父级视频暂停 */
this.props.onpause();
}, false);
/* 获取一些预设信息,事件监听 */
_mediaVideo.addEventListener('loadedmetadata', () => {
/* 父级事件,告诉父级一些资源信息 */
this.props.onmeta();
}, false);
// waiting error abort
/* 播放时间改变时,实时改变进度条 */
_mediaVideo.addEventListener('timeupdate', () => {
/* 父级事件,告诉父级当前播放位置 */
this.props.ontime(_mediaVideo.currentTime);
// console.log('play');
}, false);
/* 设置playbackRate,触发事件ratechange,播放速率改变 */
_mediaVideo.addEventListener('ratechange', () => {
// alert(this.getVideoObject().playbackRate)
}, false);
/* 设置currentTime,触发事件seeking,寻找中 */
_mediaVideo.addEventListener('seeking', () => {
/* 父级事件,告诉父级视频在加载中 */
this.props.onloading();
}, false);
/* 设置currentTime,触发事件seeked,寻找完成 */
_mediaVideo.addEventListener('seeked', () => {
/* 父级事件,告诉父级视频加载完成 */
this.props.onloaded();
}, false);
playItem.obj = _mediaVideo;
}
return _mediaVideo;
}
}
module.exports = VideoPhone;
import React from 'react'
import { Link } from 'react-router'
import sortedIndex from 'lodash/sortedIndex'
import {toTimeString} from '../../libs/utils'
import VideoPhone from './VideoPhone.jsx'
import ProgressCircle from '../../components/ProgressCircle.jsx'
import Loading from '../../components/Loading.jsx'
import {chapterType} from '../../libs/const'
let pagePlayMap = {};
const RATES = [1.0,1.2,1.5,2.0,2.5,3.0]
let isSkipBegin = false;
if (process.env.BROWSER) {
require('css/videoPlay.css');
// require('howler/dist/howler.min.js');
// require('previewImage');
isSkipBegin = /skip=1/.test(document.cookie); // 浏览器下根据cookie判断是否跳过片头
}
class VideoPhonePanel extends React.Component {
constructor(props) {
super(props);
this.state = {
pptIndex: 0,
loading: true, // 是否加载中
playing: false, // 是否播放中
time: 0, // 当前播放时间
duration: 0, // 总的播放时间
rateIndex: 0, //是否加倍
skipBegin: isSkipBegin, // 跳过片头
showChapter: false, // 显示章节弹出层
};
this.pptTimes = this.props.ppts.map(ppt => ppt.ppt_point);
this.pagePlayMap = pagePlayMap; // 播放期间记录本次页面进度 {[time, progress]}
this._setState = this._setState.bind(this);
this.getAudio = this.getAudio.bind(this);
this.getProgress = this.getProgress.bind(this);
this.onAudioMeta = this.onAudioMeta.bind(this);
this.onAudioLoad = this.onAudioLoad.bind(this);
this.onAudioLoading = this.onAudioLoading.bind(this);
this.onAudioPlay = this.onAudioPlay.bind(this);
this.onAudioPause = this.onAudioPause.bind(this);
this.onAudioCurrentTime = this.onAudioCurrentTime.bind(this);
this.toggleChapter = this.toggleChapter.bind(this);
this.toggleSet = this.toggleSet.bind(this);
this.handlePlay = this.handlePlay.bind(this);
this.handleForward = this.handleForward.bind(this);
this.handleBackward = this.handleBackward.bind(this);
this.handleTouchBar = this.handleTouchBar.bind(this);
this.handleOver = this.handleOver.bind(this);
this.handleSkip = this.handleSkip.bind(this);
this.handleRate = this.handleRate.bind(this);
this.handleClickList = this.handleClickList.bind(this);
this.handlePptView = this.handlePptView.bind(this);
}
componentDidMount() {
}
componentWillReceiveProps(nextProps) {
// 切换index,重置audio相关state
//if (nextProps.index !== this.props.index) {
// this._setState({time: 0})
//}
// 更新pptTimes
if (nextProps.ppts !== this.props.ppts) {
this.pptTimes = nextProps.ppts.map(ppt => ppt.ppt_point);
}
}
componentWillUnmount() {
this.unmounted = true; // 标记unmount状态,防止音频停止事件触发setState
let preview = document.getElementById('__previewImage-container');
if (preview) {
preview.style.display = 'none';
}
}
shouldComponentUpdate(nextProps, nextState) {
return this.state !== nextState || this.props !== nextProps;
}
_setState(obj) {
this.setState((prevState, props) => Object.assign({}, prevState, obj || {}))
}
getAudio() {
return this.refs.video;
}
// 获取进度,综合接口返回上次播放进度和本页播放进度记录
getProgress(id) {
let p = 0;
let pageProg = (this.pagePlayMap[id] || [])[1] || 0;
let dataProg = ((this.props.chapterData.progressMap || {})[id] || {}).chapter_progress || 0;
p = Math.max(pageProg, dataProg);
return p;
}
/**
* 章节层显示与否
*/
toggleChapter() {
this._setState({ showChapter: !this.state.showChapter });
}
toggleSet() {
this._setState({ showSet: !this.state.showSet });
}
// 点击章节列表外面隐藏
handleClickList(e) {
if (e.target === e.currentTarget) {
this._setState({showChapter: false});
}
}
// seek
handleTouchBar(e) {
if (this.state.loading) return;
e = e.nativeEvent;
let x = e.pageX || e.clientX;
const $ = require('jquery');
let bar = $('#video-bar');
let toX = x - bar.offset().left;
if (toX >= 0) {
let audio = this.getAudio();
let time = toX / bar.width() * this.state.duration;
audio.seek(time);
/* 跳播时,实时改变进度条 */
this.onAudioCurrentTime(time);
}
}
// 前15s
handleBackward() {
let audio = this.getAudio();
audio.seek(Math.max(0, this.state.time - 15));
}
// 后15s
handleForward() {
let audio = this.getAudio();
audio.seek(Math.min(this.state.duration - (+this.state.playing), this.state.time + 15));
}
// 标记完成
handleOver() {
this.props.handleOver(this.state.time);
}
// 通过previewImage预览图片
handlePptView(e) {
if (typeof previewImage !== 'object') return;
let list = this.props.ppts.map(ppt => ppt.ppt_url);
if(!list.length) {
list.push(this.props.course.curriculum && this.props.course.curriculum.curriculum_picture || '')
}
previewImage.start({
urls: list,
current: list[this.state.pptIndex]
});
}
formatTime(seconds) {
return toTimeString(Math.round(seconds), seconds >= 3600 ? 'h:m:s' : 'm:s');
}
/**
* 点击播放 click方法
*/
handlePlay() {
if (this.state.loading) return;
let audio = this.getAudio();
if (this.state.playing) {
audio.pause();
} else {
audio.play();
}
}
/**
* 跳过片头 click方法
*/
handleSkip() {
let skip = +(!this.state.skipBegin);
let d = new Date();
d.setMonth(d.getMonth() + 1);
document.cookie = 'skip=' + skip + ';path=/;domain=.ezijing.com;expires=' + d.toGMTString();
this._setState({ skipBegin: skip });
}
/**
* 控制倍速 click方法
*/
handleRate() {
let audio = this.getAudio();
if (audio) {
let l = RATES.length;
let from = this.state.rateIndex;
let to = (from + 1 + l) % l;
this._setState({ rateIndex: to});
audio.rate(RATES[to]);
}
}
// 处理Audio事件, 向下组件传递内容
/**
* 获取资源信息
*/
onAudioMeta() {
this._setState({duration: this.refs.video.getDuration()});
}
/**
* 音频加载中
*/
onAudioLoading() {
this._setState({loading: true});
}
/**
* 音频加载完成,可以播放
*/
onAudioLoad() {
this._setState({loading: false});
}
/**
* 音频播放事件
*/
onAudioPlay() {
this._setState({playing: true});
}
/**
* 音频停止事件
*/
onAudioPause() {
if (this.unmounted) return;
this._setState({playing: false});
}
/**
* 实时改变当前时间
* @param {number} time 当前时间
*/
onAudioCurrentTime(time) {
if (this.unmounted) return;
// 更新当前播放时间
this._setState({time: time});
// 更新当前进度
const cid = this.props.chapterData.leaves[this.props.index];
this.pagePlayMap[cid] = [time, Math.round(time/this.state.duration * 100)];
// 检查ppt是否需要更新
if (this.pptTimes.length) {
let pptIndex = this.pptTimes.indexOf(time);
if (pptIndex < 0) {
pptIndex = sortedIndex(this.pptTimes, time);
pptIndex = Math.max(0, pptIndex - 1);
}
if (this.state.pptIndex !== pptIndex) {
this._setState({pptIndex});
}
}
this.props.handleVideoTime(time);
}
render() {
const {pptIndex, time, duration, playing, loading} = this.state;
const {index, chapterData, ppts, course, video_curm3u8} = this.props;
const chapter = chapterData.map[chapterData.leaves[index]];
if (!course || !course.course_id || !chapter) {
return <div></div>
}
const ppt = ppts[pptIndex] || {};
const isFirst = index === 0, isLast = index === chapterData.leaves.length - 1;
const timeStr = this.formatTime(time);
const durationStr = this.formatTime(duration);
// 上次进度时间
let lastTime = 0;
if (this.pagePlayMap[chapter.id]) {
lastTime = this.pagePlayMap[chapter.id][0] || 0;
} else {
lastTime = (chapterData.progressMap[chapter.id] || {}).last_position || 0;
}
const HEADER = <header>
<h1>{course.course_name || ''}</h1>
<Link to={`/courses/${course.course_id}`} className="play-back-phone">&nbsp;</Link>
</header>
/* 视频还没加载完成 */
if(!video_curm3u8.video) {
return <div><Loading/></div>
}
// 是否支持播放音频
return <div className="page-video">
{HEADER}
<p className="text-error" style={{color:'#f00'}}>暂不支持手机端播放视频文件</p>
</div>
return (
<div className="page-video">
{HEADER}
<section className="play-wrap rel">
<VideoPhone
ref="video"
src={video_curm3u8.streaming[0].playurl}
lastTime={lastTime}
skipBegin={this.state.skipBegin}
onmeta={this.onAudioMeta}
onloading={this.onAudioLoading}
onloaded={this.onAudioLoad}
onplay={this.onAudioPlay}
onpause={this.onAudioPause}
ontime={this.onAudioCurrentTime}
poster={course.curriculum && course.curriculum.curriculum_picture || ''}
/>
<button type="button" onTouchEnd={this.toggleSet} className={`play-set-btn ${this.state.showSet ? 'disable' : ''}`}></button>
<div className={`play-set ${this.state.showSet ? '' : 'hide' }` } >
<div>
<div className="play-set-time"><em>{timeStr}</em>{durationStr}</div>
<div style={{display: "flex"}}>
<button onTouchEnd={this.handleSkip} className={`play-jump ${this.state.skipBegin ? 'disable' : ''}`}><em></em><br/>跳过片头</button>
<button onTouchEnd={this.handleBackward} className="play-set-button play-minus"></button>
<button onTouchEnd={this.handleForward} className="play-set-button play-add"></button>
<button onTouchEnd={this.handleRate} className="play-set-button play-double">{RATES[this.state.rateIndex].toFixed(1)}X<br/>倍速播放</button>
</div>
</div>
</div>
</section>
<footer>
<div className="play-timer">
<div className="video-bar" onClick={this.handleTouchBar} id="video-bar"><span className="video-progress" style={{width: ''+(duration ? time / duration * 100 : 0)+'%'}}></span></div>
<div className="show-time">
<span className="cur-time">{timeStr}</span>
<span className="total-time">{durationStr}</span>
</div>
</div>
<div className="play-ctrls">
<div className="ctrl-items play-ctrl-chapter" onTouchEnd={this.toggleChapter}><em></em><br/>章节</div>
<div className="ctrl-items btns">
<Link to={`/courses/${course.course_id}/chapters/${isFirst ? chapter.id : chapterData.leaves[index - 1]}`} type={`button ${isFirst ? 'disable' : ''}`} className={`button play-prev ${isFirst ? 'disable' : ''}`}>
</Link>
<button type="button" onClick={this.handlePlay} className={`button play-btn ${loading ? 'play-load' : playing ? 'play-pause' : 'play-play'}`}>{loading ? <Loading/> : ''}</button>
<Link to={`/courses/${course.course_id}/chapters/${isLast ? chapter.id : chapterData.leaves[index + 1]}`} type={`button ${isLast ? 'disable' : ''}`} className={`button play-next ${isLast ? 'disable' : ''}`}>
</Link>
</div>
<div className={`ctrl-items play-ctrl-mark ${this.getProgress(chapter.id) === 100 ? 'disable' : ''}`} onTouchEnd={this.handleOver}><em></em><br/>标记为已学完</div>
</div>
<div className={`play-chapter ${this.state.showChapter ? '' : 'hide' }` } onClick={this.handleClickList}>
<div className="play-chapter-inner">
{chapterData.tree.map((tree, i) => {
let root = chapterData.map[tree[0]];
return (
<dl key={i}>
<dt>{root.name}</dt>
{tree[1].map((leaf, j) => {
leaf = chapterData.map[leaf];
return (
<dd key={j} className={index === chapterData.leaves.indexOf(leaf.id) ? 'on' : ''}>
<div className="cl">
<ProgressCircle percent={this.getProgress(leaf.id)} className="play-prog" />
<Link to={`/courses/${course.course_id}/chapters/${leaf.id}`}>{leaf.name}</Link>
{leaf.type === chapterType.VIDEO ?
<span className="fr">{this.formatTime(leaf.video && leaf.video.video_length || 0)}</span>
: null
}
</div>
</dd>
);
})}
</dl>
);
})}
</div>
</div>
</footer>
</div>
)
}
}
module.exports = VideoPhonePanel;
.play{overflow:hidden; position: fixed;top:0; z-index: 800; width: 100%; height: 100%; background-color:#3f3f3f;color:#a0a0a0;}
.left-content{ position: absolute; right: 350px; top: 0; left: 0; bottom: 0; min-width: 705px;height:100%;}
.sidebar-hide .left-content{right:0;}
.play-top{ line-height: 56px;}
.play-top p{font-size:1.5em;text-align:center;}
.play-content{position:absolute;top:56px;bottom:0;left:0;right:0;}
.play-content-video{height:100%;}
.play-center .text-error{ font-size:0.875em;line-height:1.5em; }
.play-video-hide{visible:hidden;overflow:hidden;width:0}
.play-video-init-center{position:absolute;top:50%;left:50%;margin:-180px 0 0 -275px;}
.play-ppt { position: relative;width: 550px; height: 363.375px;background-color:#000;}
.play-ppt-img { width: 100%; height: 100%;}
.play-controls {position: absolute;bottom:0;left:0;right:0;height: 44px; line-height: 42px; padding: 0 14px;background-color: #000;}
.play-controls .fl i{ color: #8c8c8b}
.play-page{ position: absolute; left: 50%; margin-left: -75px; color: #fff; width: 150px; text-align: center; font-size: 0.875em;}
.play-page .play-now { color: #d29f29;}
.play-amazing i{ color: #fff; margin: 0 10px;}
.play-amazing i.active,
.play-amazing i:hover{ color: #d29f29;}
.play-amazing .icon-rotate{ font-size: 1.125em;}
.play-footer{ position: absolute; bottom: 0; left: 0;right:0; z-index: 200;padding:18px 20px 15px;}
.play-footer a:hover{color:#a0a0a0;}
/* .play-state, .play-back{display:inline-block;color:#a0a0a0;padding-left: 25px;font-size:14px;line-height:18px;margin:0 20px;background:url(../img/play-icons.png) no-repeat 0 0;cursor:pointer} */
.play-back{position:absolute;top:10px;left:10px;width:40px;height:40px;overflow:hidden;text-indent:-300px;background-position:14px -329px;}
.play-state-prev{background-position:0 2px;}
.play-state-prev-disable{background-position:0 -78px;color:#666;}
.play-state-next{background-position:0 -38px;}
.play-state-next-disable{background-position:0 -118px;color:#666;}
.play-state-check{background-position:0 -160px;}
.play-state-check-active{background-position:0 -200px;color: #b19241;}
.play-state-ppt{background-position:0 -240px;}
.play-state-ppt-active{background-position:0 -280px;}
.play-state-prev-disable:hover,
.play-state-next-disable:hover {color:#666!important;}
.switch { display: block; width: 36px; height: 110px; position: absolute; right: 0px; top: 50%; text-align: center; margin: -55px 0 0; z-index: 998; cursor: pointer;}
/* .switch a{background:url(../img/play-side-icon.png) no-repeat 0 0;display:block;width:36px;height:54px;margin-bottom:20px;} */
.switch a.switch-handout{background-position:0 -71px;}
.right-content { position: absolute; right: 0px; width: 350px; z-index: 200; background: #212121; bottom: 0; top: 0px; border-left:19px solid #1b1b1b;}
.right-content .right-arrow{ position: absolute; left:-19px; top:0;bottom:0;width:19px;height:100%;cursor: pointer; color: #969696; font-size: 0.875em;}
.right-content .right-arrow>span{position:absolute;top: 50%;left:0;width:19px;margin-top:-10px;text-align:center}
.play .tab-content { width: 100%; position: absolute; top: 50px; bottom: 0px;}
.play .tab-content .tab-pane { display: none;}
.play .tab-content .tab-pane.current { display: block; height: 100%; overflow: auto;}
.control-panel { height: 100%; position: relative;}
.control-panel ul { margin: 0px; padding: 0px; line-height: 1.6; overflow: hidden;}
.control-panel .play-nav{padding:15px 0; border-bottom: 0;background-color:#232323;}
.play-nav li{ width: 50%;padding:0; font-size:16px; }
.play-nav li.current:after{display:none;}
.play-nav li a{ color: #909090; border:none;}
.play-nav li:first-child a{border: 0;}
.play-nav li.current a{ color: #b49441;}
.play-nav li.current:after{ width:75px; height:3px; margin: 0 0 0 -37px;}
.play-nav .videoJy{border-left:1px solid #3f3f3f;}
.control-panel span { padding: 8px 14px; color: #909090; background: #242424; display: block; padding: 14px 22px;}
.control-panel .knob-list li { position: relative; padding: 0 0px 0 23px;}
.control-panel .knob-list li a { color: #909090; font-size: 0.875em; padding: 16px 35px 16px 20px; text-decoration: none; display: block; cursor: pointer;}
.control-panel .knob-item.current { background: #232323;}
.control-panel .knob-item.current a {color:#b49441;}
.control-panel .knob-item:after { content: ""; position: absolute; left: 22px; top: 0; width: 1px; height: 100px; background: #616161; z-index: 5 }
.control-panel .knob-item:before {content: ""; width: 18px; height: 18px; border-radius:50%; border:2px solid #5b5b5b; background: #5b5b5b; position: absolute; left: 13px; top: 16px; content: ""; display: block; z-index: 10;}
.control-panel .knob-item .icon-pro{top:16px;left:23px;z-index:15;}
.control-panel .four-four .icon-pro{left:13px;}
.control-panel .jiangyi-list { line-height: 0;}
.control-panel .jiangyi-list div { cursor: pointer; padding: 8px 16px;}
.control-panel .jiangyi-list div.current { background: #888;}
.control-panel .jy-list img { width: 100%;}
#player p{color:#fff;text-align:center;padding:50px 0;}
#player p a{color:#b01c40;text-decoration:underline;}
.play-chapter-progress{position:absolute;top:16px;left:13px;z-index:20;display:none\0;}
.play-chapter-progress .circle-progress:after{background-color:#5b5b5b;}
.play-chapter-progress .circle-progress{background-color:#5b5b5b;}
.right-content .chapter-item:last-child{border-bottom:none;margin-top:10px;}
.right-content .chapter-item .cpt{color:#b0b0b0;padding:10px 22px;background-color:#2f2f2f;}
.right-content .chapter-item-active a{text-decoration:none;}
.right-content .chapter-item-active .cpt{color:#b49441;}
/* .icon-play-chapter{display:inline-block;vertical-align:middle;width:14px;height:14px;background:url(../img/icon-play-chapter.png) no-repeat;position:absolute;right:13px;top:19px;} */
.icon-play-video{background-position:0 0;}
.icon-play-work{background-position:0 -37px;}
.icon-play-exam{background-position:0 -73px;}
.icon-play-read{background-position:0 -110px;}
/*作业类页*/
.play-paper{position:absolute;top:0;bottom:0;left:0;right:0;overflow:auto;background-color:#e5e5e5;}
.play-paper-body{min-height:500px;margin:25px;padding:15px 45px 25px;color:#313131;box-shadow:0 0 2px rgba(0,0,0,.05);background-color:#f2f2f2;}
.play-paper-title{margin:0 10px;text-align:center;}
.play-paper-title div{padding-bottom:3px;display:inline-block;border-bottom:1px solid #707070;}
.play-paper-title h3{padding:0 0 5px;margin:0;display:inline-block;font-size:20px;border-bottom:3px solid #707070;}
.play-paper-body .help{color:#999;font-size:12px;}
.play-paper-body .help-file{margin-top:-10px;}
.play-paper-body .area-btns{margin:20px 0;}
.play-paper-body .area-btns .btn{padding:6px 25px;}
.play-paper-body .area-btns .help{margin-top:10px; font-size: 14px}
.play-paper-body .area-btns .help-info{display:inline-block;vertical-align:middle;}
.play-paper-body .webuploader-btn .upbtn{color:#b49441;text-decoration:underline;padding:3px 15px;font-size:.9em;display:inline-block;}
.play-paper-body .webuploader-btn .loading .fa-spin{font-size:1.2em!important;}
.play-paper-body label{font-weight:normal;}
.play-paper-body input{vertical-align:middle;}
.play-paper-check{margin-top:20px;padding:20px;border:1px solid #dedede;}
.play-paper-check h4{font-size:16px;margin:0 0 10px;}
.play-paper-check-item{padding-left:3em;}
.play-paper-check-item b{margin-left:-3em;margin-top:1px;display:inline-block;vertical-align:top;}
.play-paper-check-item .edit_html{display:inline-block;vertical-align:top;}
/*阅读材料*/
.play-read-files{padding:30px;}
.play-read-files li{font-size:16px;padding:20px 30px;margin-bottom:10px;background-color:#fff;}
/*章节作业*/
.play-chapter-work{padding:25px;}
.play-chapter-work .work-number{margin-left:-25px;}
.play-chapter-work .work-title{margin-top:-20px;margin-bottom:10px;}
.play-chapter-work .area-btns{margin:20px -25px 0;border-top:1px solid #eaeaea;padding-top:20px;}
.play-chapter-exam{padding:25px;}
.play-chapter-exam li{position:relative;margin-top:15px;border-bottom:1px solid #b49441;}
.play-chapter-exam .exam-title{margin: -20px 0 10px 25px;}
.play-chapter-exam .wrong{color:#d80000;}
.play-chapter-exam .correct{color:#090;}
.play-chapter-exam .answer{position:absolute;right:25px;bottom:10px;}
.play-chapter-exam .result{float:right;margin-top:-20px;margin-right:-20px;font-size:16px;font-weight:bold;}
.play-paper-step{font-weight:bold;font-size:16px;margin:30px -20px 15px;padding-bottom:10px;border-bottom:1px dashed #cecece;}
@media (max-width:768px){
.left-content {min-width:0;right:0;}
.play-back{margin:0;}
.play-chapter-work{padding:25px 0;margin-right:-20px;}
.play-read-files{padding:30px 0;margin:0 -20px;}
.switch{top:80%;}
.play-paper-body{ padding: 15px 25px 25px; }
.play-paper-step{ margin: 30px 0 15px; }
}
<template>
<div className={`play ${this.state.sidebar ? '' : 'sidebar-hide'}`}>
<div className="left-content">
<div className="play-top cl" style={this.state.sidebar ? {} : {marginRight: 0}}>
<Link className="play-back" to={`/courses/${params.semesterId}/${params.courseId}/cont`} onClick={this.onClickToDetail}>&lt;</Link>
<p>{course.course_name}</p>
<p>{chapter.chapter_name}</p>
</div>
<div className="play-content">
{params.chapterId === 'work' ?
<CourseWork
course={course}
semesterId={course.semester_id}
course_work={this.props.course_work}
dispatch={this.props.dispatch}
action={this.props.action}
handleSubmited={this.chapterSubmited}
/>
: params.chapterId === 'info' ?
<CourseInfo
courseId={params.courseId}
files={course.files}
/>
: chapter.type === chapterType.VIDEO ?
<ChapterVideo
ref="chapterVideo"
courseId={params.courseId}
semesterId={course.semester_id}
ppts={ppts}
chapter={chapter}
username={user.data && user.data.username || ''}
prevChapterId={prevChapterId}
nextChapterId={nextChapterId}
curProgress={curProgress}
handlePlayTime={this.handlePlayTime}
handleOver={this.handleOver}
lastTime={cpt}
/>
: chapter.type === chapterType.WORKOREXAM ?
(chapter.homework && chapter.homework.work_type === chapterType.WORK_HOME ?
<ChapterWork
chapter={chapter}
chapter_work={this.props.chapter_work}
courseId={params.courseId}
semesterId={course.semester_id}
action={this.props.action}
dispatch={this.props.dispatch}
handleSubmited={this.chapterSubmited}
/>
:
<ChapterExam
chapter={chapter}
chapter_work={this.props.chapter_work}
courseId={params.courseId}
semesterId={course.semester_id}
action={this.props.action}
dispatch={this.props.dispatch}
handleSubmited={this.chapterSubmited}
/>
)
:
<ChapterRead
chapter={chapter}
handleReaded={this.sendReaded}
/>
}
</div>
</div>
<div className="right-content" style={{ right: this.state.sidebar ? 0 : -388 }}>
<p className="right-arrow" onClick={this.handleHideSidebar}><span>&gt;</span></p>
<div className="control-panel">
<ul className="nav-tabs play-nav cl">
<li className={`videoChapter ${this.state.sidebar === SIDEBAR_CHAPTER ? 'current' : ''}`}><a href="#sidebar_chapter" onClick={this.handleShowSidebarChapter}>章节</a></li>
{ppts.length ?
<li className={`videoJy ${this.state.sidebar === SIDEBAR_PPT ? 'current' : ''}`}><a href="#sidebar_ppt" onClick={this.handleShowSidebarPpt}>讲义</a></li>
: null
}
</ul>
<div className="tab-content">
{this.state.sidebar === SIDEBAR_CHAPTER ?
<SideList
chapters={chapters}
getProgressByChapterId={this.getProgress}
courseId={params.courseId}
semesterId={params.semesterId}
chapterId={params.chapterId}
progress={curProgress}
handleClickItem={IS_PHONE ? this.handleHideSidebar1 : this.reloadPages}
/>
:
<SidePpt
ref="sidePpt"
ppts={ppts}
handleClickPpt={this.handleClickSidePpt}
/>
}
</div>
</div>
</div>
{this.state.sidebar ? null :
<div className="switch">
<a href="#sidebar_chapter" className="switch-chapter" onClick={this.handleShowSidebarChapter}></a>
{isVideo && ppts.length ?
<a href="#sidebar_ppt" className="switch-handout" onClick={this.handleShowSidebarPpt}></a>
: null
}
</div>
}
</div>
</template>
<style lang="scss" scoped>
@import './index.css';
</style>
import BaseAPI from '../base_api'
export default class ChapterAPI extends BaseAPI {
/**
* 获取章节列表信息
* @param {[string]} cur_course_id -> cid
* @param {[string]} cur_semester_id -> sid
* @param {[string]} cur_video_id -> vid
*/
getChapterList = (cid, sid, vid) => this.get(`/v2/education/courses/${sid}/${cid}`, {})
/**
* 获取对应某个章节的详细信息
* @param {[string]} vid
*/
getCurrentChapterDetail = (vid) => this.post('/v2/education/video-streaming', { vid })
/**
* 获取进度信息
* @param {[string]} vid
* @param {[string]} did
* @param {[string]} sid
*/
getProgress = (vid, did, sid) => this.get(`/v2/education/video/${sid}/${vid}/device`, { device_id: did })
/**
* 提交进度信息
* @param {[object]} obj
*/
updateProgress = (obj = {}) => this.post('/v2/analytics/upload-video', {
d: obj.did,
i: obj.did,
c: obj.cid,
s: obj.sid,
v: obj.vid,
_p: obj.pt, // 累计时间
_m: obj.mpt, // 当前播放最大时间
_c: obj.cpt // 当前播放位置
})
}
import BaseAPI from '../base_api'
export default class CourseAPI extends BaseAPI {
/**
* 获取学期分类信息
*/
getlearnFindList = () => this.get('/v2/education/semesters', {})
/**
* 获取所有课程列表 - 选课广场 和 我的课程 共用同一个,通过 isMy判断
* @param {[string]} isMy
* @param {[object]} param
*/
getCourseList = (isMy, param) => this.get('/v2/education/courses' + (isMy ? '/my' : ''), param)
/**
* 获取某个课程详细信息 - 课程考核 和 课程讨论单独获取
* @param {[string]} id
* @param {[string]} sid
*/
getCourseDetail = (id, sid) => this.get(`/v2/education/courses/${sid}/${id}`, {})
/**
* 获取课程考核信息
* @param {[string]} cid
* @param {[string]} sid
*/
getCourseAssess = (cid, sid) => this.get(`/v2/analytics/courses/${sid}/${cid}/evaluation`, {})
/**
* 获取试题信息
* @param {[string]} eid
* @param {[string]} sid
*/
getExamDetail = (eid, sid) => this.get(`/v2/education/homeworks/${sid}/${eid}`, {})
/**
* 提交考试信息
* @param {[object]} param
*/
submitExamDetail = (param) => this.post(`/v2/education/homeworks`, param)
/**
* 选课
* @param {[string]} cid
* @param {[string]} sid
*/
selectCourse = (cid, sid) => this.post(`/v2/education/courses/major`, { course_id: cid, semester_id: sid })
/**
* 退课
* @param {[string]} cid
* @param {[string]} sid
*/
outSelectCourse = (cid, sid) => this.post(`/v2/education/courses/drop`, { course_id: cid, semester_id: sid })
}
import BaseAPI from '../base_api'
export default class DiscussAPI extends BaseAPI {
/**
* 获取讨论题目列表,“我提出的问题”和“我参与的问题”信息
* dataJson.limit - 获取数量
* dataJson.offset - 偏移量
* @param {[string]} path
* @param {[object]} dataJson
*/
getDiscussList = (path, dataJson) => this.get('/v2/qa/questions' + path, dataJson)
/**
* 获取讨论题目列表,“课程的问题”信息
* dataJson.limit - 获取数量
* dataJson.offset - 偏移量
* dataJson.sort - 排序类型
* @param {[string]} cid
* @param {[string]} sid
* @param {[object]} dataJson
*/
getCourseDiscussList = (cid, sid, dataJson) => this.get(`/v2/qa/questions/course/${sid}/${cid}`, dataJson)
/**
* 获取问题详情
* @param {[string]} qid
*/
getDiscussDetail = (qid) => this.get(`/v2/qa/questions/${qid}`, {})
/**
* 删除提问
* @param {[string]} qid
*/
deleteDiscuss = (qid) => this.delete(`/v2/qa/questions/${qid}`, {})
/**
* 提出问题
* @param {[object]} param
*/
publishQues = (param) => this.get(`/v2/qa/questions`, param)
/**
* 回答问题
* @param {[object]} param
*/
answerQues = (param) => this.post(`/v2/qa/answers`, param)
/**
* 删除回答
* @param {[string]} aid
*/
deleteAnswer = (aid) => this.delete(`/v2/qa/answers/${aid}`, {})
/**
* 回复评论
* @param {[object]} param
*/
callbackComment = (param) => this.get(`/v2/qa/comments`, param)
/**
* 删除评论
* @param {[string]} cid
*/
deleteComment = (cid) => this.delete(`/v2/qa/comments/${cid}`, {})
/**
* 点赞
* @param {[object]} param
*/
like = (param) => this.post(`/v2/qa/tags`, param)
/**
* 取消点赞
* @param {[string]} tagid
*/
unlike = (tagid) => this.delete(`/v2/qa/tags/${tagid}`, {})
}
import BaseAPI from '../base_api'
export default class LoginAPI extends BaseAPI {
/**
* 调用登录接口
* @param {[string]} obj.account 用户名
* @param {[string]} obj.password 密码 md5加密
*/
userLogin = obj => this.post('/tenant/user/login', obj)
/**
* 获取政策文件列表
* @param {[string]} obj.page 传入页码
* @param {[string]} obj.per_page 每页多少条
*/
getDocuments = obj => this.get('/v1/cms/documents', obj)
/**
* 当前登录用户,检测是否该系统有权限
*/
getInfo = () => this.get('/tenant/user/getinfo', {})
}
import BaseAPI from '../base_api'
export default class MsgAPI extends BaseAPI {
/**
* 获取我的消息信息
*/
getMyMsg = () => this.get('/v2/education/message/my', {})
}
import BaseAPI from '../base_api'
export default class ScoreAPI extends BaseAPI {
/**
* 获取学术报告列表
*/
getReportList = () => this.get('/tenant/user/getinfo', {})
}
import BaseAPI from '../base_api'
export default class ScoreAPI extends BaseAPI {
/**
* 获取我的学分信息
*/
getMyScore = () => this.get('/v2/education/credits', {})
}
import BaseAPI from './base_api'
export default class BackendAPI extends BaseAPI {
/**
* 获取文章列表
*/
getArticleList = (obj = {}) => this.get('/v1/cms/projects', obj)
/**
* 获取政策文件列表
* @param {[string]} obj.page 传入页码
* @param {[string]} obj.per_page 每页多少条
*/
getDocuments = obj => this.get('/v1/cms/documents', obj)
/**
* 获取政策文件详情
* @param {[string]} id 传入政策文件id
*/
getDocumentsId = (id, obj = {}) => this.get('/v1/cms/documents/' + id, obj)
/**
* 查询证书接口
* @param {[string]} obj.id_card_type 证件类型;身份证:10 军官证:20 默认:10;不必填
* @param {[string]} obj.id_card_number 证件号码;必填
* @param {[string]} obj.name 姓名;必填
* @param {[string]} obj.cert_id 证件编号;必填
*/
getCertify = obj => this.post('/v1/ea/certificates/query', obj)
}
import axios from 'axios'
import _ from 'lodash'
import tools from '@tools'
export default class API {
constructor (config) {
/* 创建一个 自定义配置axios实例 */
axios.defaults.withCredentials = true // 让ajax携带cookie
this._axios = axios.create({
timeout: config.timeout || 5 * 1000,
/* 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream' */
......@@ -24,6 +26,30 @@ export default class API {
/* 具体执行请求失败后业务逻辑前,先执行该方法 */
let beforeFail = _config.beforeFail ? _config.beforeFail : this._reqFail
/* 这里可以统一处理 登录 */
/**
* 增加 Cookie 和 请求头传值,如果是 服务端渲染,就放Node端
* 所有请求 全部增加 token 和 tenant 请求头并 自带 cookie
*/
let s = decodeURIComponent(tools.cookies.getCookie('_SUP') || '')
let ticket = ''
s = s.match(/"([\d\w=]+)";\}$/)
if (s && s.length === 2) {
s = new Buffer(s[1], 'base64').toString() // eslint-disable-line
s = s.slice(12, s.search('-TGT') + 4)
ticket = s.split('').reverse().join('')
}
let headers = {
// 'User-Agent': req.get('user-agent'),
// 'X-Forwarded-For': req.get('x-forwarded-for'),
// 'X-Real-Ip': req.get('x-real-ip'),
// 'Referer': req.get('referer'),
// 'Cookie': tools.cookies.getAllCookies(),
'token': ticket,
'tenant': 'sofia'
}
_config.headers = _.assignIn(_config.headers, headers)
/* 创建并根据参数发起请求 */
return this._axios(_config)
.then(beforeSuccess.bind(this), beforeFail.bind(this))
......@@ -36,8 +62,18 @@ export default class API {
*/
_reqSuccess (res) {
let { status, data } = res
let err = null
/* 针对 MBA学习系统 做特殊 请求成功处理 */
if (status === 200) {
return data
if (data.status !== 200 && data.message) {
err = new Error(data.message)
throw err
} else {
return data
}
} else {
err = new Error(JSON.stringify(res.data))
throw err
}
}
......@@ -49,12 +85,17 @@ export default class API {
_reqFail (res) {
let err = null
if (res.response) {
err = new Error(res.response.message)
// err.code = res.response.result_code
if (res.response.data) {
err = new Error(res.response.data.message)
} else {
/* 错误处理,服务端非正常返回 */
err = new Error(JSON.stringify(res.response))
}
} else {
err = new Error(res.message)
err = new Error('msg:' + res.message + 'stack:' + res.stack)
err.code = 500
}
/* 如果出错,创建错误对象,并抛出一个错误。 */
throw err
}
......
import BackendAPI from './backend_api'
import LoginAPI from './api/login_api'
import ChapterAPI from './api/chapter_api'
import CourseAPI from './api/course_api'
import DiscussAPI from './api/discuss_api'
import MsgAPI from './api/msg_api'
import ScoreAPI from './api/score_api'
import ReportAPI from './api/report_api'
let backendApi = new BackendAPI(webConf)
let loginApi = new LoginAPI(webConf)
let chapterApi = new ChapterAPI(webConf)
let courseApi = new CourseAPI(webConf)
let discussApi = new DiscussAPI(webConf)
let msgApi = new MsgAPI(webConf)
let scoreApi = new ScoreAPI(webConf)
let reportApi = new ReportAPI(webConf)
export {
backendApi
loginApi,
chapterApi,
courseApi,
discussApi,
msgApi,
scoreApi,
reportApi
}
export default class ConvertTime {
/**
* 工具方法 - 播放时间 转化 h:m:s
* @param {[string]} duration 时间戳
*/
durationToTimeString (duration) {
let h = Math.floor(duration / 3600)
let m = Math.floor((duration - h * 3600) / 60)
let s = (duration - h * 3600 - m * 60) % 60
function tenify (a) {
return a >= 10 ? a : '0' + a
}
let to = { h: tenify(h), m: tenify(m), s: tenify(s) }
let format = 'h:m:s'
return format.replace(/h|m|s/g, k => to[k]).replace(/^00:/, '')
}
}
export default class Cookies {
/**
* 设置 Cookies,用于客户端
* @param {[string]} cName 要设置cookie的key值
* @param {[string]} cValue 要设置cookie的value值
* @param {[object]} obj 要设置cookie的其他值
* @param {[object]} obj.path 路径
* @param {[object]} obj.domain 域名
* @param {[object]} obj.expires 过期时间
*/
setCookie (cName, cValue, obj) {
let d = new Date()
if (obj.expires) {
d.setTime(d.getTime() + ((obj.expires || 0) * 24 * 60 * 60 * 1000))
obj.expires = d.toUTCString()
}
let str = ''
for (let key in obj) {
str += '; ' + key + '=' + obj[key]
}
document.cookie = cName + '=' + cValue + str
}
/**
* 获取指定cookie,用于客户端
* @param {[string]} cName 要获取cookie的key值
*/
getCookie (cName) {
let key = cName + '='
let ca = document.cookie.split(';')
for (let i = 0; i < ca.length; i++) {
let c = ca[i]
while (c.charAt(0) === ' ') c = c.substring(1)
if (c.indexOf(key) !== -1) {
return c.substring(key.length, c.length)
}
}
return ''
}
/**
* 获取全cookie,用于客户端
*/
getAllCookies () {
return document.cookie
}
/**
* 清除cookie,用于客户端
* @param {[string]} cName 要清除cookie的key值
*/
clearCookie (cName) {
document.cookie = cName + '=; expires=-1'
}
}
import Cookies from './cookies'
import ConvertTime from './convert_time'
let cookies = new Cookies()
let convertTime = new ConvertTime()
export default {
cookies,
convertTime
}
import { chapterApi } from '@services'
import tools from '@tools'
export default class ChapterActon {
/* 获取章节列表信息 */
getChapterList (cid, sid, vid, callback) {
return chapterApi.getChapterList(cid, sid, vid).then(res => {
let i = 0
let j = 0
let nextVideo = {}
let prevVideo = {}
/* 计算上一章 和 下一章 */
for (; i < res.data.chapters.length; i++) {
let _ = res.data.chapters[i]
for (j = 0; j < _.children.length; j++) {
if (_.children[j].resource_id === vid) {
// wx.setNavigationBarTitle({ title: _.children[j].name || '音视频' });
if (j - 1 >= 0) {
prevVideo = {
id: _.children[j - 1].resource_id,
time: (_.children[j - 1].video && tools.convertTime.durationToTimeString(_.children[j - 1].video.video_length)) || '',
name: _.children[j - 1].name
}
} else if (i - 1 >= 0) {
let _temp = res.data.chapters[i - 1]
prevVideo = {
id: _temp.children[_temp.children.length - 1].resource_id,
time: (_temp.children[_temp.children.length - 1].video && tools.convertTime.durationToTimeString(_temp.children[_temp.children.length - 1].video.video_length)) || '',
name: _temp.children[_temp.children.length - 1].name
}
}
if (j + 1 < _.children.length) {
nextVideo = {
id: _.children[j + 1].resource_id,
time: (_.children[j + 1].video && tools.convertTime.durationToTimeString(_.children[j + 1].video.video_length)) || '',
name: _.children[j + 1].name
}
} else if (i + 1 < res.data.chapters.length) {
let _temp = res.data.chapters[i + 1]
nextVideo = {
id: _temp.children[0].resource_id,
time: (_temp.children[0].video && tools.convertTime.durationToTimeString(_temp.children[0].video.video_length)) || '',
name: _temp.children[0].name
}
}
}
}
}
let json = {
isShow: false,
currentChapterId: vid,
course: res.data.chapters.map((_, i) => {
return {
title: _.name,
chapters: _.children.map((__, j) => {
let _homework = (__.type === 3 && __.homework && __.homework.work_type === 1 && __.homework) || ''
if (_homework) {
_homework.course_id = res.data.course_id
_homework.chapter_id = __.id
_homework.work_id = __.resource_id
_homework.semester_id = res.data.semester_id
}
return {
id: __.resource_id,
time: (__.video && tools.convertTime.durationToTimeString(__.video.video_length)) || '',
name: __.name,
homework: _homework
}
})
}
}),
nextVideo: nextVideo,
prevVideo: prevVideo
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取对应某个章节的详细信息 */
getCurrentChapterDetail (vid, callback) {
return chapterApi.getCurrentChapterDetail(vid).then(res => {
let json = {
video: {
src: res.data.video[0].playurl,
spareSrc: 'http://pd4t7ae3m.bkt.clouddn.com/test.mp4' // 正式环境时,需要将 contentVideo 中 spareSrc 改成 src
},
audio: {
src: res.data.audio[0].url,
poster: (res.data.ppts && res.data.ppts[0] && res.data.ppts[0].ppt_url) || ''
},
image: {
imgUrls: res.data.ppts && res.data.ppts.map(function (_, i) { return _.ppt_url }),
current: 0,
selectIndex: 0,
timeArr: res.data.ppts && res.data.ppts.map(function (_, i) { return _.ppt_point })
}
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取进度信息 */
getProgress (vid, did, sid, callback) {
return chapterApi.getProgress(vid, did, sid).then(res => {
// callback(res.data) // 可以不使用callback 因为使用then
return res.data
})
}
/* 提交进度信息 */
updateProgress (obj) {
return chapterApi.updateProgress(obj).then(res => res)
}
}
import { courseApi } from '@services'
import tools from '@tools'
export default class CourseAction {
/* 获取学期分类信息 */
getlearnFindList (callback) {
return courseApi.getlearnFindList().then(res => {
let json = [{ val: '-1', name: '全部' }]
for (let i = 0; i < res.data.length; i++) {
let _list = res.data
json.push({
val: _list[i].id,
name: _list[i].semester_name
})
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取所有课程列表 - 选课广场 和 我的课程 共用同一个,通过 isMy判断 */
getCourseList (isMy, param, callback) {
return courseApi.getCourseList(isMy, param).then(res => {
let json = []
for (let i = 0; i < res.data.length; i++) {
let cur = res.data[i]
let _cur = res.data[i].curriculum
/* 课程类型 */
let _type = _cur.curriculum_elective_type
let str1 = _type === 1 ? '必修课' : (_type === 2 ? '选修课' : (_type === 3 ? '重修课' : ''))
json.push({
id: cur.course_id,
sid: cur.semester_id,
src: (_cur && _cur.curriculum_picture) || '',
title: cur.course_name,
arrTab: [(((_cur && _cur.curriculum_credit) || 0) + '学分'), str1, cur.semester_name],
status: _cur.is_enabled ? '已发布' : '未发布',
time: cur.begin_date.split(' ')[0] + ' 至 ' + cur.end_date.split(' ')[0],
myStatus: cur.selected !== 0 ? (cur.score ? ('总成绩:' + cur.score) : '已选修') : '未选修',
progress: cur.course_progress + '%'
})
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取某个课程详细信息 - 课程考核 和 课程讨论单独获取 */
getCourseDetail (id, sid, callback) {
return courseApi.getCourseDetail(id, sid).then(res => {
let cur = res.data
let _cur = cur.curriculum
/* 课程类型 */
let _type = _cur.curriculum_elective_type
let str1 = _type === 1 ? '必修课' : (_type === 2 ? '选修课' : (_type === 3 ? '重修课' : ''))
let json = {
headerInfo: {
isStart: !!cur.selected, // 是否为开始学习按钮 或者 选课按钮
id: cur.course_id,
sid: cur.semester_id,
bgSrc: (_cur && _cur.curriculum_picture) || '../icons/home/default.jpg',
title: cur.course_name,
arrTab: [(((_cur && _cur.curriculum_credit) || 0) + '学分'), str1, cur.semester_name],
status: _cur.is_enabled ? '已发布' : '未发布',
time: cur.begin_date.split(' ')[0] + ' 至 ' + cur.end_date.split(' ')[0],
progress: cur.course_progress + '%'
},
tabs0Content: {},
tabs1ChapterList: {}
}
/* 课程简介 */
json.tabs0Content = {
text: _cur && _cur.curriculum_represent,
teachers: []
}
for (let i = 0; i < cur.lecturers.length; i++) {
let item = cur.lecturers[i]
json.tabs0Content.teachers.push({
src: item.lecturer_avatar,
name: item.lecturer_name,
edu: item.lecturer_education || '',
job: item.lecturer_office || '',
unit: item.lecturer_title || ''
})
}
/* 课程内容 */
json.tabs1ChapterList = {
currentChapterId: cur.latest_play || '',
course: cur.chapters.map((_, i) => {
return {
title: _.name,
isUp: true,
chapters: _.children.map((__, j) => {
let _homework = (__.type === 3 && __.homework && __.homework.work_type === 1 && __.homework) || ''
if (_homework) {
_homework.course_id = res.data.course_id
_homework.chapter_id = __.id
_homework.work_id = __.resource_id
_homework.semester_id = res.data.semester_id
}
return {
cid: cur.course_id,
sid: cur.semester_id,
vid: __.resource_id,
time: (__.video && tools.convertTime.durationToTimeString(__.video.video_length)) || '',
name: __.name,
homework: _homework
}
})
}
})
}
json.tabs1ChapterList.course.push({
title: '课程大作业',
isUp: true,
chapters: []
})
json.tabs1ChapterList.course.push({
title: '课程资料',
isUp: true,
chapters: []
})
/* 课程考核 考核标准文案读取 */
json.tabs3richTest = cur.course_evaluation
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取课程考核信息 */
getCourseAssess (cid, sid, callback) {
return courseApi.getCourseAssess(cid, sid).then(res => {
let cur = res.data
let video = []
for (let i in cur.video_evaluation) {
let _ = cur.video_evaluation[i]
let tempArr = []
for (let j in _.sections) {
let __ = _.sections[j]
tempArr.push({
name: __.title,
time: (__.duration && tools.convertTime.durationToTimeString(__.duration)) || '00:00',
progress: (__.progress && (__.progress + '%')) || '0%'
})
}
video.push({
title: _.title,
arr: tempArr
})
}
let homewrok = []
for (let i in cur.homework_evaluation) {
let _ = cur.homework_evaluation[i]
let tempArr = []
for (let j in _.sections) {
let __ = _.sections[j]
tempArr.push({
name: __.title,
score: __.score || '0'
})
}
homewrok.push({
title: _.title,
arr: tempArr
})
}
let json = {
score: cur.course_score,
duration: tools.convertTime.durationToTimeString(cur.course_duration || 0),
progress: cur.course_progress,
video: video,
homewrok: homewrok,
essay: {
status: cur.essay_evaluation.status || '暂无',
score: cur.essay_evaluation.score || '暂无'
}
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取试题信息 */
getExamDetail (eid, sid, callback) {
return courseApi.getExamDetail(eid, sid).then(res => {
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
/* 提交考试信息 */
submitExamDetail (param, callback) {
return courseApi.submitExamDetail(param).then(res => {
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
/* 选课 */
selectCourse (cid, sid, callback) {
return courseApi.selectCourse(cid, sid).then(res => {
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
/* 退课 */
outSelectCourse (cid, sid, callback) {
return courseApi.outSelectCourse(cid, sid).then(res => {
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
}
import { discussApi } from '@services'
export default class DiscussAction {
/* 获取讨论题目列表,“我提出的问题”和“我参与的问题”信息 */
/**
* dataJson.limit - 获取数量
* dataJson.offset - 偏移量
*/
getDiscussList (path, dataJson, callback) {
return discussApi.getDiscussList(path, dataJson).then(res => {
let _data = res.data
let json = _data.map(function (_, i) {
return {
id: _.id,
sid: _.semester_id,
user: {
url: _.questioner.avatar || '',
name: _.questioner.nickname,
time: _.created_time
},
title: _.title,
text: _.contents,
askCnt: _.answer_count,
TouCnt: _.tag_total_count,
courseName: '在线学习课程',
comments: _.comments,
mine: _.mine
}
})
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取讨论题目列表,“课程的问题”信息 */
/**
* dataJson.limit - 获取数量
* dataJson.offset - 偏移量
* dataJson.sort - 排序类型
*/
getCourseDiscussList (cid, sid, dataJson, callback) {
return discussApi.getCourseDiscussList(cid, sid, dataJson).then(res => {
let _data = res.data
let json = _data.map(function (_, i) {
return {
id: _.id,
sid: _.semester_id,
user: {
url: _.questioner.avatar || '',
name: _.questioner.nickname,
time: _.created_time
},
title: _.title,
text: _.contents,
askCnt: _.answer_count,
TouCnt: _.tag_total_count,
courseName: '在线学习课程',
comments: _.comments,
mine: _.mine
}
})
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取问题详情 */
getDiscussDetail (qid, callback) {
return discussApi.getDiscussDetail(qid).then(res => {
let _data = res.data
let json = {
ques: {
qid: _data.id,
sid: _data.semester_id,
user: {
url: _data.questioner.avatar || '',
name: _data.questioner.nickname,
time: _data.created_time
},
title: _data.title,
text: _data.contents,
askCnt: _data.answer_count || 0,
TouCnt: _data.tag_total_count || 0,
likeCnt: _data.tag_count || 0,
comCnt: _data.comments.length,
mine: _data.mine,
isShowComment: false,
has_tag: _data.has_tag,
tag_id: (_data.tag && _data.tag.id) || '',
comments: _data.comments.map(function (_, i) {
return {
cid: _.id,
user: {
url: _.observer.avatar || '',
name: _.observer.nickname,
time: _.created_time
},
text: _.comments,
mine: _.mine
}
})
},
answer: (_data.answers && _data.answers.map(function (_, i) {
return {
aid: _.id,
user: {
url: _.replier.avatar || '',
name: _.replier.nickname,
time: _.created_time
},
text: _.contents,
likeCnt: _.tag_count,
comCnt: _.comments.length,
mine: _.mine,
isShowComment: false,
has_tag: _.has_tag,
tag_id: (_.tag && _.tag.id) || '',
comments: _.comments.map(function (__, i) {
return {
cid: __.id,
user: {
url: __.observer.avatar || '',
name: __.observer.nickname,
time: __.created_time
},
text: __.comments,
mine: __.mine
}
})
}
})) || []
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 删除提问 */
deleteDiscuss (qid, callback) {
return discussApi.deleteDiscuss(qid).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
/* 提出问题 */
publishQues (param, callback) {
return discussApi.publishQues(param).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
/* 回答问题 */
answerQues (param, callback) {
return discussApi.answerQues(param).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
/* 删除回答 */
deleteAnswer (aid, callback) {
return discussApi.deleteAnswer(aid).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
/* 回复评论 */
callbackComment (param, callback) {
return discussApi.callbackComment(param).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
/* 删除评论 */
deleteComment (cid, callback) {
return discussApi.deleteComment(cid).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
/* 点赞 */
like (param, callback) {
return discussApi.like(param).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
/* 取消点赞 */
unlike (tagid, callback) {
return discussApi.unlike(tagid).then(res => {
let _data = res.data
if (_data.success) {
// callback(res) // 可以不使用callback 因为使用then
return res
}
})
}
}
import { loginApi } from '@services'
import tools from '@tools'
import hmacSHA256 from 'crypto-js/hmac-sha256'
import Base64 from 'crypto-js/enc-base64'
export default class LoginAction {
/**
* 当前登录用户,检测是否该系统有权限
*/
getInfo (callback) {
return loginApi.getInfo().then(res => {
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
/**
* 调用登录接口
*/
userLogin (obj, callback) {
return loginApi.userLogin(obj).then(res => {
/* 设置cookie */
let expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
// 翻转、加盐、base64
const salt = '0ZSGxuBkSJS5'
let sup = res.ticket.split('').reverse().join('')
let rad = Math.random()
let token = salt + sup + rad
sup = new Buffer(token).toString('base64') // eslint-disable-line
// yii格式加密 hmac sha256
let serialize = `a:2:{i:0;s:4:"_SUP";i:1;s:${sup.length}:"${sup}";}`
let hamc = Base64.stringify(hmacSHA256(serialize, 'VzpR5JMDNqUsOZ0IFQARNLU9_0KLr9UC'))
sup = encodeURIComponent(hamc + serialize)
// 设置到cookie
let opts = { path: '/', domain: '.ezijing.com', expires: expires }
tools.cookies.setCookie('_SUP', sup, opts)
// result.data.uid = result.data.sso_id
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
}
import { msgApi } from '@services'
export default class MsgAction {
/* 获取我的消息信息 */
getMyMsg (callback) {
return msgApi.getMyMsg().then(res => {
let json = res.data.map(function (_, i) {
return {
isRead: false,
id: _.id,
text: _.message,
time: _.created_time,
isShow: false // 该字段用来做 每条信息的打开、关闭
}
})
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
}
import { reportApi } from '@services'
export default class ReportAction {
/* 获取学术报告列表 */
getReportList (callback) {
return reportApi.getReportList().then(res => {
debugger
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
}
import { scoreApi } from '@services'
export default class ScoreAction {
/* 获取我的学分信息 */
getMyScore (callback) {
return scoreApi.getMyScore().then(res => {
let _data = res.data
let json = {
total: _data.total_credits,
myTotal: _data.my_total_credits,
myTotalStr: (_data.total_credits && ((_data.my_total_credits * 1.0 / _data.total_credits * 100).toFixed(1) + '%')) || '0%',
must: _data.required_credits,
myMust: _data.my_required_credits,
myMustStr: (_data.required_credits && ((_data.my_required_credits * 1.0 / _data.required_credits * 100).toFixed(1) + '%')) || '0%',
unmust: _data.optional_credits,
myUnmust: _data.my_optional_credits,
myUnmustStr: (_data.optional_credits && ((_data.my_optional_credits * 1.0 / _data.optional_credits * 100).toFixed(1) + '%')) || '0%'
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
}
import LoginAction from './LoginAction'
import ChapterAction from './ChapterAction'
import CourseAction from './CourseAction'
import DiscussAction from './DiscussAction'
import MsgAction from './MsgAction'
import ScoreAction from './ScoreAction'
import ReportAction from './ReportAction'
let loginAction = new LoginAction()
let chapterAction = new ChapterAction()
let courseAction = new CourseAction()
let discussAction = new DiscussAction()
let msgAction = new MsgAction()
let scoreAction = new ScoreAction()
let reportAction = new ReportAction()
export default {
loginAction,
chapterAction,
courseAction,
discussAction,
msgAction,
scoreAction,
reportAction
}
......@@ -59,4 +59,7 @@ export default {
background: #252625;
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1);
}
.el-main {
padding: 0;
}
</style>
......@@ -14,7 +14,7 @@ export default class GlobalVariable {
*/
setInitSiderbarVar (Vue, obj) {
let _$ = Vue.prototype.$GlobalVariable
if ((!_$['siderbar']) && /^\/app\//g.test(obj.to.path)) {
if (/^\/app\//g.test(obj.to.path)) {
_$['siderbar'] = {
defaultPath: (obj && obj.to && obj.to.path) || ''
}
......
<template>
<div class="con-tainer">
<router-view></router-view>
</div>
<router-view></router-view>
</template>
<style lang="scss" scoped>
.con-tainer {
background: #ffffff;
padding: 20px;
}
</style>
......@@ -46,4 +46,17 @@ export default {
}
}
}
@media (max-width: 767px) {
.menu {
.nav-left {
img.logo {
display: none;
}
.text-title {
margin-left: 0;
}
}
}
}
</style>
<template>
<el-aside :style="[obj.sidebarStyle]">
<el-menu :default-active="defaultActive" :collapse="obj.status">
<el-menu-item index="0" class="my-account" @click="goUpdatePic">
<div class="pic">
<el-menu :default-active="defaultActive" :collapse="obj.status" :default-openeds="['1']" @select="curSelect">
<el-menu-item index="0" class="my-account">
<div class="pic" @click="goUpdatePic">
<div class="set-pic">修改头像</div>
<template v-if="pic">
<img :src="pic" alt="人物头像">
</template>
......@@ -52,6 +53,8 @@ export default {
let defaultActive = ''
switch (_path) {
case '/app/my-learn/course': defaultActive = '1-1'; break
case '/app/my-learn/course-detail': defaultActive = '1-1'; break
case '/app/my-learn/course-all': defaultActive = '0'; break
case '/app/my-learn/discussion': defaultActive = '1-2'; break
case '/app/my-learn/report': defaultActive = '1-3'; break
case '/app/my-grade/credit': defaultActive = '2-1'; break
......@@ -81,6 +84,9 @@ export default {
]
}
},
mounted () {
this.defineEvent()
},
methods: {
goPages (str) {
switch (str) {
......@@ -90,16 +96,29 @@ export default {
case '2-1': this.$router.push({ path: '/app/my-grade/credit' }); break
}
},
/* 修改头像 - 跳转方法 */
goUpdatePic () {
this.$router.push({ path: '/app/account/update-pic' })
},
/* 修改密码 - 跳转方法 */
goSetPwd () {
this.$router.push({ path: '/app/account/set-pwd' })
},
/* 退出登录 - 跳转方法 */
goOutLogin () {
/* 清空一下记录,然后调整到登录页 */
this.$router.push({ path: '/login/index' })
},
/* 定义监听事件 */
defineEvent () {
this.VueEvent.$off('can-change-sidebar').$on('can-change-sidebar', (data) => {
this.defaultActive = data.defaultActive
})
},
/* 当前菜单选中 */
curSelect (i, p) {
this.defaultActive = i
},
/* 侧边栏 拖拽条按下并移动前 */
beforeMove (e) {
this.obj.isMouseDown = true
......@@ -123,6 +142,10 @@ export default {
.el-menu {
user-select: none;
height: 100%;
/* 直接消失 */
&.el-menu--collapse {
display: none;
}
.el-submenu {
.el-menu-item {
overflow: hidden;
......@@ -138,6 +161,7 @@ export default {
overflow: hidden;
text-align: center;
.pic {
position: relative;
margin-bottom: 15px;
img {
display: block;
......@@ -146,6 +170,23 @@ export default {
height: 80px;
border-radius: 50%;
}
&:hover {
.set-pic {
display: block;
color: #000000;
}
}
.set-pic {
display: none;
position: absolute;
left: 50%;
top: 70%;
width: 50%;
line-height: 2;
color: #000000;
background: #eeeeee4f;
transform: translate(-50%, -50%);
}
}
}
}
......@@ -158,5 +199,12 @@ export default {
height: 100%;
cursor: ew-resize;
}
@media (max-width: 767px) {
.el-aside {
// position: fixed;
// z-index: 99;
}
}
</style>
......@@ -12,7 +12,7 @@
<script>
import mTable from './mTable.vue'
import { backendApi } from '@services'
import cAction from '@actions'
export default {
name: 'mPage',
......@@ -46,6 +46,7 @@ export default {
* 这个参数很重要,他定义了 回调执行方式,传参方式,
* obj.paramsFn 在调用接口前,对接口参数进行处理, 返回要传的参数对象
* obj.nameAPI 对应的接口名
* obj.actionClass 对应的action类
* obj.nameExcel 对应的导出Excel接口名
*/
objFn: { type: Object, require: true }
......@@ -54,10 +55,13 @@ export default {
methods: {
getList () {
const loading = this.$loading({ lock: true, text: '数据加载中,请稍后。。。', background: 'rgba(0, 0, 0, 0.7)' })
let obj = this.objFn.paramsFn(this)
backendApi[this.objFn.nameAPI](obj)
while (this.tableData.length) {
this.tableData.pop()
}
cAction[this.objFn.actionClass][this.objFn.nameAPI](obj)
.then(data => {
// this.tableData = []
this.objFn.callback(this, data)
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
}
......
......@@ -48,3 +48,41 @@ body {
"Wenquanyi Micro Hei",
sans-serif;
}
/* 独立 特殊修改 */
/* 进度条 原始背景色 加深 */
.el-progress-bar__outer {
background-color: #e5e5e5 !important;
}
/* 富文本 */
.course-assess .detail .b1 { display: block; max-width: 5rem; margin: 0.2rem auto; width: 100%; height: auto; }
.course-assess .detail .h1 { font-size: 0.16rem; font-weight: 700; color: #313131; line-height: 0.38rem; }
.course-assess .detail .h2 { font-size: 0.14rem; font-weight: 700; color: #313131; line-height: 0.3rem; }
.course-assess .detail .p { font-size: 0.14rem; color: #313131; line-height: 1.5; text-align: justify; }
.course-assess .detail .em { font-size: 0.12rem; color: #b49441; line-height: 1.5; text-align: justify; }
/* Extra small devices (portrait phones, less than 576px) */
@media (max-width: 575px) {
html { font-size: 80px; }
}
/* Small devices (landscape phones, 576px and up) */
@media (min-width: 576px) and (max-width: 767px) {
html { font-size: 80px; }
.step3 { width: 80%; }
}
/* Medium devices (tablets, 768px and up) */
@media (min-width: 768px) and (max-width: 991px) {
html { font-size: 80px; }
}
/* Large devices (desktops, 992px and up) */
@media (min-width: 992px) and (max-width: 1199px) {
html { font-size: 90px; }
}
/* Extra large devices (large desktops, 1200px and up) */
@media (min-width: 1200px) {
html { font-size: 100px; }
}
......@@ -5,6 +5,9 @@ import VueRouter from 'vue-router' // 使用 vue-router
import createRouter from './router' // router定义
import Main from './main.vue' // 初始化 vue页面
/* 引入 md5 */
import md5 from 'js-md5'
/* 定义全局变量 */
import GlobalVariable from './components/beforeEnterPages/setGlobalVariable'
/* 处理低版本浏览器支持axiosfinally问题 */
......@@ -14,6 +17,7 @@ Vue.use(VueRouter)
const router = createRouter()
/* 设置全局变量 */
Vue.prototype.$GlobalVariable = {}
Vue.prototype.$md5 = md5
/* 导航守卫 */
router.beforeEach((to, from, next) => {
/* 设置 全局变量 */
......
<template>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="修改密码" name="1">
<div>
<div class="con-title">修改密码</div>
<div class="con-box">
<el-row>
<!-- <el-col class="hidden-sm-and-down" :md="6" :lg="7" :xl="8" style="height: 1px;"></el-col> -->
<el-col :xs="24" :sm="24" :md="12" :lg="10" :xl="8">
<el-col :xs="24" :sm="16" :md="12" :lg="10" :xl="8">
<el-form ref="setpwdform" label-width="100px" :model="accountSetPwd" :rules="rules">
<el-form-item label="旧密码" prop="oldPwd">
<el-input v-model="accountSetPwd.oldPwd" type="password" placeholder="请输入密码"></el-input>
......@@ -20,8 +21,8 @@
</el-form>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
......@@ -72,3 +73,15 @@
}
}
</script>
<style lang="scss" scoped>
.con-title {
padding: 0 30px;
font-size: 16px;
line-height: 45px;
border-bottom: 1px solid #c9c9c9;
}
.con-box {
padding: 0.5rem 0.3rem;
}
</style>
<template>
<div>页面开发中</div>
<div>
<div class="con-title">修改头像</div>
<div class="con-box">
<el-row justify="center">
<el-col :xs="24" :sm="9" :md="8" :lg="7" :xl="6">
<div class="pic-show">
<img src="" alt="">
</div>
</el-col>
<el-col :xs="24" :sm="15" :md="16" :lg="17" :xl="18">
<div class="info">支持jpg、gif、png或bmp格式的图片,建议文件小于5M</div>
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
multiple
:limit="3">
<el-button type="primary">点击上传</el-button>
</el-upload>
</el-col>
</el-row>
</div>
</div>
</template>
<style lang="scss" scoped>
.con-title {
padding: 0 30px;
font-size: 16px;
line-height: 45px;
border-bottom: 1px solid #c9c9c9;
}
.con-box {
padding: 0.5rem 0.3rem;
.pic-show {
display: block;
margin: 0 auto;
width: 2rem;
height: 2rem;
background: #ffffff url('../../assets/img/person-default.jpg') center center no-repeat;
background-size: cover;
border: 1px solid #ffffff;
border-radius: 50%;
}
.info {
margin-top: 0.2rem;
}
.upload-demo {
display: block;
margin: 0.8rem 0 0 0;
}
}
@media (max-width: 767px) {
.con-box {
.info {
margin-top: 0.2rem;
text-align: center;
}
.upload-demo {
text-align: center;
}
}
}
</style>
<template>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="统计代码" name="1">
<el-row>
<!-- <el-col class="hidden-sm-and-down" :md="4" :lg="5" :xl="8" style="height: 1px;"></el-col> -->
<el-col :xs="24" :sm="24" :md="16" :lg="14" :xl="8">
<el-form ref="setbaidustatisticsform" label-width="120px" :model="setBaiduStatistics" :rules="baiduStatisticsRules">
<el-form-item label="百度统计">
点击进入百度统计查看网站访问数据&nbsp;&nbsp;&nbsp;&nbsp;<el-button type="primary" size="small" @click="goBaiduCount">访问百度统计</el-button>
</el-form-item>
<el-form-item label="代码统计" prop="desc">
<el-input type="textarea" :autosize="{ minRows: 6 }" v-model="setBaiduStatistics.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmitSetBaiduStatistics">保存</el-button>
</el-form-item>
</el-form>
<div>
<div class="con-title">我的学分</div>
<div class="con-box">
<div class="total-core">
<div class="title">总计学分</div>
<div class="core">{{score.total}}</div>
<el-progress :text-inside="true" :stroke-width="30" :percentage="Math.floor(score.myTotal/score.total) || 0" color="#df9d75"></el-progress>
</div>
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="compulsory-core">
<div class="title">必修学分</div>
<div class="core">{{score.must}}</div>
<el-progress :text-inside="true" :stroke-width="30" :percentage="Math.floor(score.myMust/score.must) || 0" color="#8ca4cf"></el-progress>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="站长工具" name="2">
<el-row>
<!-- <el-col class="hidden-sm-and-down" :md="4" :lg="5" :xl="8" style="height: 1px;"></el-col> -->
<el-col :xs="24" :sm="24" :md="16" :lg="14" :xl="8">
<el-form ref="setbaiduwebmasterform" label-width="120px" :model="setBaiduWebmaster" :rules="baiduWebmasterRules">
<el-form-item label="百度站长">
点击进入百度站长工具管理网站&nbsp;&nbsp;&nbsp;&nbsp;<el-button type="primary" size="small" @click="goBaiduWebmaster">访问百度站长</el-button>
</el-form-item>
<el-form-item label="验证文件" prop="files">
<el-upload action="" ref="uploadwebmasterfile" :auto-upload="false">
<el-button slot="trigger" size="medium">上传文件</el-button>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmitSetBaiduWebmaster">保存</el-button>
</el-form-item>
</el-form>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="elective-core">
<div class="title">选修学分</div>
<div class="core">{{score.unmust}}</div>
<el-progress :text-inside="true" :stroke-width="30" :percentage="Math.floor(score.myUnmust/score.unmust) || 0" color="#66c6bd"></el-progress>
</div>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
<div class="color-box">
<div class="color" style="background: #8ca4cf;"></div><div class="txt">必修学分</div>
<div class="color" style="background: #66c6bd;"></div><div class="txt">选修学分</div>
<div class="color" style="background: #df9d75;"></div><div class="txt">总学分</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
activeName: '1',
setBaiduStatistics: {},
baiduStatisticsRules: {
'desc': [ { required: true, message: '请粘贴统计代码', trigger: 'blur' } ]
},
setBaiduWebmaster: {},
baiduWebmasterRules: {
'files': [ { required: true, message: '请选择文件', trigger: 'blur' } ]
}
}
},
methods: {
handleClick (tab, event) { /* console.log(tab, event) */ },
/* 百度代码统计提交 */
onSubmitSetBaiduStatistics () {
this.$refs['setbaidustatisticsform'].validate((valid) => {
if (valid) {
console.log('submit!')
} else {
this.$message.error('请检查输入项,确认无误后,重新提交')
return false
}
})
},
/* 跳转百度统计 - 官方页面 */
goBaiduCount () {
window.open('https://tongji.baidu.com/web/welcome/login?castk=LTE%3D')
},
/* 百度站长文件提交 */
onSubmitSetBaiduWebmaster () {
console.log(this.$refs.uploadwebmasterfile)
this.$refs['setbaiduwebmasterform'].validate((valid) => {
if (valid) {
console.log('submit!')
} else {
this.$message.error('请检查输入项,确认无误后,重新提交')
return false
}
})
},
goBaiduWebmaster () {
window.open('https://ziyuan.baidu.com/dashboard/index')
export default {
data () {
return {
score: {
total: 0,
myTotal: 0,
myTotalStr: '0%',
must: 0,
myMust: 0,
myMustStr: '0%',
unmust: 0,
myUnmust: 0,
myUnmustStr: '0%'
}
}
}
}
</script>
<style lang="scss" scoped>
.con-title {
padding: 0 30px;
font-size: 16px;
line-height: 45px;
border-bottom: 1px solid #c9c9c9;
}
.con-box {
margin: 0.5rem 0.3rem;
padding: 0.1rem 0.3rem;
background: #ffffff;
.total-core, .compulsory-core, .elective-core {
margin-top: 0.1rem;
font-size: 16px;
line-height: 36px;
.title {
float: left;
}
.core {
float: right;
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;
}
}
}
@media (max-width: 767px) {
.con-box {
margin: 0.2rem 0;
padding: 0.1rem 0.2rem;
}
}
</style>
<template>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="文章内容" name="1">
<el-form class="add-article-content" ref="setArtContentform" label-width="100px" :model="setArtContent" :rules="artContentRules">
<el-form-item label="文章标题" prop="title">
<el-input v-model="setArtContent.title" type="text" placeholder="请输入文章标题"></el-input>
</el-form-item>
<el-form-item label="发布时间" prop="time_date" style="max-width: 600px;">
<el-col :span="11">
<el-date-picker type="date" placeholder="请选择日期" v-model="setArtContent.time_date"></el-date-picker>
</el-col>
<el-col class="line" :span="2" style="text-align: center;">-</el-col>
<el-col :span="11">
<el-time-picker type="fixed-time" placeholder="请选择时间" v-model="setArtContent.time_time"></el-time-picker>
</el-col>
</el-form-item>
<!-- v-model="setArtContent.content" -->
<textarea id="editor"></textarea>
<el-form-item>
<el-button type="primary" @click="onSubmitSetArtContent">保存</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="高级设置" name="2">
<el-row>
<!-- <el-col class="hidden-sm-and-down" :md="2" :lg="5" :xl="6" style="height: 1px;"></el-col> -->
<el-col :xs="24" :sm="24" :md="18" :lg="14" :xl="12">
<el-form ref="setAdanceform" label-width="100px" :model="setAdance" :rules="adanceRules">
<el-form-item label="来源" prop="source">
<el-input v-model="setAdance.source" type="text" placeholder="输入文章来源"></el-input>
</el-form-item>
<el-form-item label="作者" prop="author">
<el-input v-model="setAdance.author" type="text" placeholder="输入文章作者"></el-input>
</el-form-item>
<el-form-item label="摘要" prop="detail">
<el-input type="textarea" :autosize="{ minRows: 6 }" v-model="setAdance.detail" placeholder="输入文章摘要"></el-input>
</el-form-item>
<el-form-item label="列表缩略图" prop="desc">
<el-upload action="" ref="uploadwebmasterfile" :auto-upload="false">
<el-button slot="trigger" size="medium">上传图片</el-button>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmitSetAdance">保存</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="百度优化" name="3">
<el-row>
<!-- <el-col class="hidden-sm-and-down" :md="2" :lg="5" :xl="6" style="height: 1px;"></el-col> -->
<el-col :xs="24" :sm="24" :md="18" :lg="14" :xl="12">
<el-form ref="setBaiduOptimizationform" label-width="120px" :model="setBaiduOptimization" :rules="baiduOptimizationRules">
<el-form-item label="文章页面标题" prop="title">
<el-input v-model="setBaiduOptimization.title" type="text" placeholder="请输入文章浏览器标题"></el-input>
</el-form-item>
<el-form-item label="文章页面关键词" prop="keyword">
<el-input v-model="setBaiduOptimization.keyword" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入文章页面关键词,多个关键词使用英文“,”隔开"></el-input>
</el-form-item>
<el-form-item label="文章页面描述" prop="description">
<el-input v-model="setBaiduOptimization.description" type="textarea" :autosize="{ minRows: 4 }" placeholder="请输入文章页面描述"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmitSetBaiduOptimization">保存</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
<div>
<div class="con-title">我的课程</div>
<div class="con-box">
<template v-for="(item, index) in find">
<ul v-bind:key="index" class="tabs-list">
<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-key='item.key' :data-val='item1.val'>{{item1.name}}</div>
</li>
</template>
</ul>
</template>
</div>
<div class="switch-box">
<el-button type="info" size="medium" plain>最近学习<i class="el-icon-caret-bottom icon"></i></el-button>
<el-button type="info" size="medium" plain>选修时间<i class="el-icon-caret-top icon"></i></el-button>
<el-button class="rbtn" type="primary" size="medium" icon="el-icon-self-cc-book icon" @click="goCourseAll">选课广场</el-button>
</div>
<div class="con-box">
<ul class="course-list">
<template v-for="(item, index) in homeList">
<li v-bind:key="index" class="item">
<div class="left-pic">
<template v-if="item.src">
<img :src="item.src" alt="">
</template>
<template v-else>
<div class="no-img"><i class="el-icon-self-13"></i></div>
</template>
</div>
<div class="right-bd">
<div class="title">{{item.title}}</div>
<div class="tags">
<template v-for="(item1, index) in item.arrTab">
<span v-bind:key="index">{{item1}}</span>
</template>
</div>
<div class="time">
{{item.status}}&emsp;&emsp;<em>{{item.time}}</em>
</div>
<div class="progress">
学习进度&emsp;<el-progress :percentage="item.progress" color="#b49441"></el-progress>
</div>
<div class="right-sel">{{item.myStatus}}</div>
<el-button class="in-btn" type="primary" size="small" round @click="goCourseContent" :data-id='item.id' :data-sid='item.sid'>查看课程</el-button>
</div>
</li>
</template>
</ul>
</div>
</div>
<!-- v-model="setArtContent.content" -->
<!-- <textarea id="editor"></textarea> -->
</template>
<script>
......@@ -78,29 +66,58 @@
data () {
return {
ckeditor: null,
activeName: '1',
setArtContent: {},
artContentRules: {
'title': [
{ required: true, message: '请输入文章标题', trigger: 'blur' }
],
'time_date': [
{ required: true, message: '请选择日期', trigger: 'blur' }
find: [{
name: '学期',
isShow: false,
selectIndex: 0,
key: 'semester_id',
arrItem: [ // 从后台请求
{ val: '-1', name: '全部' },
{ val: '', name: '第一学期' },
{ val: '', name: '第二学期' },
{ val: '', name: '第三学期' },
{ val: '', name: '第四学期' }
]
}, {
name: '课程类型',
isShow: false,
selectIndex: 0,
key: 'course_type',
arrItem: [
{ val: '-1', name: '全部' },
{ val: '1', name: '必修课' },
{ val: '2', name: '选修课' },
{ val: '3', name: '重修课' }
]
}, {
name: '选修状态',
isShow: false,
selectIndex: 0,
key: 'select_status',
arrItem: [
{ val: '-1', name: '全部' },
{ val: '0', name: '未选修' },
{ val: '1', name: '已选修' }
]
},
setAdance: {},
adanceRules: { 'source': [ ], 'author': [ ], 'detail': [ ]
/* 少一个缩略图 */
},
setBaiduOptimization: {},
baiduOptimizationRules: { 'title': [ ], 'keyword': [ ], 'description': [ ] }
}],
homeList: [
{
id: '',
src: '',
title: '这里是一条长的标题不要超过25个汉字标题不要超过25个汉字',
arrTab: ['3学分', '必修课', '第一学期'],
status: '已发布',
time: '2016-09-10至2017-01-10',
myStatus: '已选修',
progress: 50
}
]
}
},
mounted () {
this.initckeditor()
// this.initckeditor()
},
methods: {
handleClick (tab, event) { /* console.log(tab, event) */ },
/* 初始化 ckeditor */
initckeditor () {
this.ckeditor = CKEDITOR.replace('editor', {
......@@ -124,30 +141,183 @@
]
})
},
onSubmitSetArtContent () {
this.$refs['setArtContentform'].validate((valid) => {
if (valid) {
console.log('submit!')
} else {
this.$message.error('请检查输入项,确认无误后,重新提交')
return false
}
})
goCourseAll () {
this.$router.push({ path: '/app/my-learn/course-all' })
},
onSubmitSetAdance () {
this.$refs['setAdanceform'].validate((valid) => {
if (valid) {
console.log('submit!')
}
})
goCourseContent () {
this.$router.push({ path: '/app/my-learn/course-detail' })
},
onSubmitSetBaiduOptimization () {
this.$refs['setBaiduOptimizationform'].validate((valid) => {
if (valid) {
console.log('submit!')
}
})
selFindSelect () {
}
}
}
</script>
<style lang="scss" scoped>
.con-title {
padding: 0 30px;
font-size: 16px;
line-height: 45px;
border-bottom: 1px solid #c9c9c9;
}
.con-box {
margin: 0.3rem;
padding: 0.3rem 0.3rem 0.15rem 0.3rem;
background: #ffffff;
overflow: hidden;
}
/* 列表 筛选 */
ul.tabs-list {
float: left;
width: 100%;
margin: 0 0 0.15rem 0;
padding: 0;
font-size: 0.16rem;
line-height: 1.5;
border-top: 1px solid #e8e8e8;
&:last-child {
margin-bottom: 0;
}
.tabs-hd {
display: inline-block;
color: #fff;
padding: 5px 0 9px;
margin-top: -5px;
width: 94px;
text-align: center;
background: url(https://zws-imgs-pub.ezijing.com/754005be709bf2295bc55923c2b91fd8.png) no-repeat 0 0;
}
li {
float: left;
list-style: none;
padding: 0.1rem 0;
margin-right: 0.2rem;
.tab {
padding: 0 0.1rem;
cursor: pointer;
&.on {
background: #b49441;
color: #ffffff;
}
&:focus, &:hover {
color: #b49441;
background: #eeeeee;
}
}
}
}
/* 筛选按钮 */
.switch-box {
margin: 0 0.3rem;
.icon {
margin-left: 0.1rem;
}
.rbtn {
float: right;
}
}
/* 课程列表 */
ul.course-list {
margin: 0;
padding: 0;
font-size: 0.18rem;
color: #313131;
li.item {
padding: 0.1rem 0;
list-style: none;
border-bottom: 1px solid #dcdcdc;
overflow: hidden;
&:first-child {
padding-top: 0;
}
.left-pic {
float: left;
width: 1.8rem;
overflow: hidden;
.no-img {
width: 100%;
height: 1rem;
text-align: center;
line-height: 1.1rem;
border: 1px solid #e2e2e2;
i {
font-size: 0.4rem;
color: #e2e2e2;
}
}
img {
display: block;
width: 100%;
}
}
.right-bd {
position: relative;
display: block;
margin-left: 2rem;
.title {
width: 80%;
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tags {
margin: 0.1rem 0 0.05rem 0;
font-size: 0.14rem;
span {
padding: 0 0.1rem;
margin: 0;
border-right: 1px solid #313131;
&:first-child {
padding-left: 0;
}
&:last-child {
border: none;
}
}
}
.time {
font-size: 0.12rem;
em {
font-style: normal;
color: #898989;
}
}
.progress {
margin-top: 0.1rem;
font-size: 14px;
.el-progress {
width: 50%;
display: inline-block;
vertical-align: text-bottom;
}
}
.right-sel {
position: absolute;
right: 0;
top: 0;
font-size: 14px;
padding: 0 0.1rem;
}
.in-btn {
position: absolute;
right: 0;
bottom: 0;
}
}
}
}
@media (max-width: 767px) {
.con-box {
margin: 0.2rem 0;
padding: 0.2rem;
}
.switch-box {
margin: 0;
}
}
</style>
<template>
<div>
<div class="con-title">选课广场</div>
<div class="con-box">
<template v-for="(item, index) in find">
<ul v-bind:key="index" class="tabs-list">
<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-key='item.key' :data-val='item1.val'>{{item1.name}}</div>
</li>
</template>
</ul>
</template>
</div>
<div class="con-box">
<ul class="course-list">
<template v-for="(item, index) in homeList">
<li v-bind:key="index" class="item">
<div class="left-pic">
<template v-if="item.src">
<img :src="item.src" alt="">
</template>
<template v-else>
<div class="no-img"><i class="el-icon-self-13"></i></div>
</template>
</div>
<div class="right-bd">
<div class="title">{{item.title}}</div>
<div class="tags">
<template v-for="(item1, index) in item.arrTab">
<span v-bind:key="index">{{item1}}</span>
</template>
</div>
<div class="time">
{{item.status}}&emsp;&emsp;<em>{{item.time}}</em>
</div>
<div class="progress">
学习进度&emsp;<el-progress :percentage="item.progress" color="#b49441"></el-progress>
</div>
<div class="right-sel">{{item.myStatus}}</div>
<el-button class="in-btn" type="primary" size="small" round @click="goCourseContent" :data-id='item.id' :data-sid='item.sid'>查看课程</el-button>
</div>
</li>
</template>
</ul>
</div>
</div>
</template>
<script>
export default {
data () {
return {
find: [{
name: '学期',
isShow: false,
selectIndex: 0,
key: 'semester_id',
arrItem: [ // 从后台请求
{ val: '-1', name: '全部' },
{ val: '', name: '第一学期' },
{ val: '', name: '第二学期' },
{ val: '', name: '第三学期' },
{ val: '', name: '第四学期' }
]
}, {
name: '课程类型',
isShow: false,
selectIndex: 0,
key: 'course_type',
arrItem: [
{ val: '-1', name: '全部' },
{ val: '1', name: '必修课' },
{ val: '2', name: '选修课' },
{ val: '3', name: '重修课' }
]
}, {
name: '选修状态',
isShow: false,
selectIndex: 0,
key: 'select_status',
arrItem: [
{ val: '-1', name: '全部' },
{ val: '0', name: '未选修' },
{ val: '1', name: '已选修' }
]
}],
homeList: [
{
id: '',
src: '',
title: '这里是一条长的标题不要超过25个汉字标题不要超过25个汉字',
arrTab: ['3学分', '必修课', '第一学期'],
status: '已发布',
time: '2016-09-10至2017-01-10',
myStatus: '已选修',
progress: 50
}
]
}
},
mounted () {
/* sidebar取消 当前选中项 */
this.VueEvent.$emit('can-change-sidebar', { defaultActive: '0' })
},
methods: {
goCourseContent () {
this.$router.push({ path: '/app/my-learn/course-detail' })
},
selFindSelect () {
}
}
}
</script>
<style lang="scss" scoped>
.con-title {
padding: 0 30px;
font-size: 16px;
line-height: 45px;
border-bottom: 1px solid #c9c9c9;
}
.con-box {
margin: 0.3rem;
padding: 0.3rem 0.3rem 0.15rem 0.3rem;
background: #ffffff;
overflow: hidden;
}
/* 列表 筛选 */
ul.tabs-list {
float: left;
width: 100%;
margin: 0 0 0.15rem 0;
padding: 0;
font-size: 0.16rem;
line-height: 1.5;
border-top: 1px solid #e8e8e8;
&:last-child {
margin-bottom: 0;
}
.tabs-hd {
display: inline-block;
color: #fff;
padding: 5px 0 9px;
margin-top: -5px;
width: 94px;
text-align: center;
background: url(https://zws-imgs-pub.ezijing.com/754005be709bf2295bc55923c2b91fd8.png) no-repeat 0 0;
}
li {
float: left;
list-style: none;
padding: 0.1rem 0;
margin-right: 0.2rem;
.tab {
padding: 0 0.1rem;
cursor: pointer;
&.on {
background: #b49441;
color: #ffffff;
}
&:focus, &:hover {
color: #b49441;
background: #eeeeee;
}
}
}
}
/* 课程列表 */
ul.course-list {
margin: 0;
padding: 0;
font-size: 0.18rem;
color: #313131;
li.item {
padding: 0.1rem 0;
list-style: none;
border-bottom: 1px solid #dcdcdc;
overflow: hidden;
&:first-child {
padding-top: 0;
}
.left-pic {
float: left;
width: 1.8rem;
overflow: hidden;
.no-img {
width: 100%;
height: 1rem;
text-align: center;
line-height: 1.1rem;
border: 1px solid #e2e2e2;
i {
font-size: 0.4rem;
color: #e2e2e2;
}
}
img {
display: block;
width: 100%;
}
}
.right-bd {
position: relative;
display: block;
margin-left: 2rem;
.title {
width: 80%;
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tags {
margin: 0.1rem 0 0.05rem 0;
font-size: 0.14rem;
span {
padding: 0 0.1rem;
margin: 0;
border-right: 1px solid #313131;
&:first-child {
padding-left: 0;
}
&:last-child {
border: none;
}
}
}
.time {
font-size: 0.12rem;
em {
font-style: normal;
color: #898989;
}
}
.progress {
margin-top: 0.1rem;
font-size: 14px;
.el-progress {
width: 50%;
display: inline-block;
vertical-align: text-bottom;
}
}
.right-sel {
position: absolute;
right: 0;
top: 0;
font-size: 14px;
padding: 0 0.1rem;
}
.in-btn {
position: absolute;
right: 0;
bottom: 0;
}
}
}
}
@media (max-width: 767px) {
.con-box {
margin: 0.2rem 0;
padding: 0.2rem;
}
.switch-box {
margin: 0;
}
}
</style>
<template>
<div>
<div class="con-title">课程详情</div>
<div class="detail-box">
<div class="box-thd">
<div class="title">{{headerInfo.title}}
<template v-if='headerInfo.isStart && tabs[1].chapterList.currentChapterId'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn' :data-cid='headerInfo.id' :data-sid='headerInfo.sid' :data-vid='tabs[1].chapterList.currentChapterId'>继续学习</el-button>
</template>
<template v-else-if='headerInfo.isStart'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn' :data-cid='headerInfo.id' :data-sid='headerInfo.sid' :data-vid='tabs[1].chapterList.course[0].chapters[0].vid'>开始学习</el-button>
</template>
<template v-else>
<el-button class="rbtn" type="primary" size="mini" @click='wantThisCourse' :data-cid='headerInfo.id' :data-sid='headerInfo.sid'>选课</el-button>
</template>
</div>
<div class="tags">
<template v-for="(item1, index) in headerInfo.arrTab">
<span v-bind:key="index" class='tabs-item'>{{item1}}</span>
</template>
</div>
<div class="time">{{headerInfo.time}} {{headerInfo.status}}</div>
<div style="float: left; width: 100%; height: 1px;"></div>
<div class="progress">
学习进度&emsp;<el-progress :percentage="headerInfo.progress" color="#b49441"></el-progress>
</div>
</div>
<div class="box-tbd">
<div class="left-pic">
<template v-if="headerInfo.bgSrc">
<img :src="headerInfo.bgSrc" alt="">
</template>
<template v-else>
<div class="no-img"><i class="el-icon-self-13"></i></div>
</template>
</div>
<div class="right-content" v-html="tabs[0].content.text"></div>
</div>
<el-row :gutter="20">
<el-col :xs="24" :sm="15" :md="17" :lg="18" :xl="20">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="课程内容" name="1">
<div class='course-list'>
<template v-for="(_item, index) in tabs[1].chapterList.course">
<div v-bind:key="index" :class='["content-group", (!_item.chapters.length ? "no-child" : ""), (_item.isUp ? "up" : "")]'>
<div class='title' @click='clickJumpOrStatus($event)' :data-index='index' :data-status='!!_item.chapters.length'>{{_item.title}}
<i :class="['side', (_item.chapters.length ? '' : 'none'), (_item.isUp ? 'el-icon-arrow-down' : 'el-icon-arrow-up')]"></i>
</div>
<template v-for="(item1, index1) in _item.chapters">
<div v-bind:key="index1" :class='["body", (item1.id === tabs[1].chapterList.currentChapterId && "on")]'>
<div class='name' :data-vid='item1.vid' :data-cid='item1.cid' :data-sid='item1.sid' :data-hasVA='item1.time' :data-name='item1.name' @click='jumpToOtherVA' :data-index='index' :data-index1='index1'>
{{item1.name}}
<div class='time'>{{item1.time}}</div>
</div>
</div>
</template>
</div>
</template>
</div>
</el-tab-pane>
<el-tab-pane label="课程讨论" name="2">
<div>开发中</div>
</el-tab-pane>
<el-tab-pane label="课程考核" name="3">
<div class='course-assess'>
<div class='title'>最终成绩:{{tabs[3].assess.score && (tabs[3].assess.score + '分') || '暂无'}}</div>
<div class='topic'>
<div class='line'></div>
<div class='tit'>课程考核标准</div>
</div>
<div class='detail'>
<div class='detail-rich' v-html='tabs[3].richText'></div>
</div>
<div class='topic'>
<div class='line'></div>
<div class='tit'>学习进度及成绩</div>
</div>
<div class='table-title'>课程“音视频”观看统计( 累计学习时长:{{tabs[3].assess.duration}},完成率:{{tabs[3].assess.progress}} )</div>
<div class='table'>
<div class='th'>
<div class='col3-td1'>章节</div>
<div class='col3-td2'>学习时长</div>
<div class='col3-td3'>百分比</div>
</div>
<template v-for='(item1, index) in tabs[3].assess.video'>
<div v-bind:key="index" class='tb'>
<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'>{{item2.name}}</div>
<div class='col3-td2'>{{item2.time}}</div>
<div class='col3-td3'>{{item2.progress}}</div>
</div>
</template>
</div>
</template>
<template v-if='!tabs[3].assess.video.length'>
<div style='width: 100%; font-size: 30rpx; margin: 40rpx auto 10rpx auto; text-align: center;'>暂无数据</div>
</template>
</div>
<div class='table-title'>试题及主观题</div>
<div class='table'>
<div class='th'>
<div class='col2-td1'>章节</div>
<div class='col2-td2'>得分</div>
</div>
<template v-for='(item1, index) in tabs[3].assess.homewrok'>
<div v-bind:key="index" class='tb'>
<div class='tt'>{{item1.title}}</div>
<template v-for='(item2, index) in item1.arr'>
<div v-bind:key="index" class='rd'>
<div class='col2-td1'>{{item2.name}}</div>
<div class='col2-td2'>{{item2.score}}</div>
</div>
</template>
</div>
</template>
<template v-if='!tabs[3].assess.homewrok.length'>
<div style='width: 100%; font-size: 30rpx; margin: 40rpx auto 10rpx auto; text-align: center;'>暂无数据</div>
</template>
</div>
<div class='table-title'>大作业</div>
<div class='status-text'>状 态:{{tabs[3].assess.essay.status}}</div>
<div class='status-text'>得 分:{{tabs[3].assess.essay.score}}</div>
</div>
</el-tab-pane>
</el-tabs>
</el-col>
<el-col :xs="24" :sm="9" :md="7" :lg="6" :xl="4">
<el-tabs v-model="activeName1" @tab-click="handleClick">
<el-tab-pane label="课程讲师" name="1">
<template v-for='(item1, index) in tabs[0].content.teachers'>
<div v-bind:key='index' class='list-teacher'>
<img class="img" :src="item1.src" alt="">
<div class='ctx'>
<div class='top-ctx'>
<div class='name'>{{item1.name}}</div>
</div>
<div class='bottom-ctx'>
<div class='t1'>{{item1.edu}}</div>
<div class='t2'>{{item1.job}}</div>
<div class='t3'>{{item1.unit}}</div>
</div>
</div>
</div>
</template>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
export default {
data () {
return {
activeName: '1',
activeName1: '1',
headerInfo: {
id: '',
sid: '',
bgSrc: '',
title: '这里是一条长的标题不要超过25个汉字标题不要超过个汉字25个汉字',
time: '2016-09-10至2017-01-10',
status: '已发布',
arrTab: ['3学分', '必修课', '线上课程', '第一学期'],
isStart: true, // 是否为开始学习按钮 或者 选课按钮
progress: 50 // 改成整数
},
tabs: [{
title: '课程简介',
isShow: true,
content: {
text: '<p><b>组织心理学</b>是研究组织中人的心理与行为规律的一门科学,是心理学的一个重要分支,也是心理学在管理学方面的一个交叉应用学科。<b>组织心理学</b>通过探讨个体、群体以及结构对组织,<b>组织心理学</b>是研究组织中人的心理与行为规律的一门科学,是心理学的一个重要分支,也是心理学在管理学方面的一个交叉应用学科。<b>组织心理学</b>通过探讨个体、群体以及结构对组织。<p><p><b>组织心理学</b>是研究组织中人的心理与行为规律的一门科学,是心理学的一个重要分支,也是心理学在管理学方面的一个交叉应用学科。<b>组织心理学</b>通过探讨个体、群体以及结构对组织。</p>',
teachers: [{
src: '',
name: '讲师姓名',
edu: '学历',
job: '职称',
unit: '讲师所在单位'
}, {
src: '',
name: '讲师姓名',
edu: '学历',
job: '职称',
unit: '讲师所在单位'
}]
}
}, {
title: '课程内容',
isShow: false,
chapterList: {
currentChapterId: '11', // 当前章节id
course: [{
title: '第一章:重要概念',
isUp: true,
chapters: [
{ id: '11', time: '28:18', name: '1.1 现值(PV)和终值(FV)' },
{ id: '12', time: '19:09', name: '1.2 净现值(NPV)及实际收益率' }
]
}, {
title: '第二章:证券估值',
isUp: true,
chapters: [
{ id: '21', time: '27:49', name: '2.1 债券和股票的现金流贴现估值方法' },
{ id: '22', time: '16:04', name: '2.2 债券评级及债券协议' },
{ id: '23', time: '', name: '公司金融第二周测验' },
{ id: '24', time: '', name: '公司金融第二周作业' }
]
}, {
title: '课程大作业',
isUp: true,
chapters: []
}, {
title: '课程资料',
isUp: true,
chapters: []
}]
}
}, {
title: '课程讨论',
isShow: false
}, {
title: '课程考核',
isShow: false,
richText: "<div class='h1'>一、最终成绩计算</div> <div class='p'>课程表现得分*30%+每章试题得分*30%+结业大作业得分*40%=该门课程总得分,满分100分,低于60分为不及格,需重修此门课程。</div> <img class='b1' src='https://e-learning.ezijing.com/static/assets/img/course-check.png' mode='aspectFill' /> <div class='h1'>二、具体细则</div> <div class='h2'>课程表现:总分100分(占科目总成绩的30%)</div> <div class='p'>1、每个视频观看完成度50分:以后台数据统计为准,全部看完视频满分50分,其它酌情给分。</div> <div class='em'>注:视频观看考核的是实际播放时长,不是进度条的显示状态,进度条满格并不一定表示观看完这个视频。以下方每日学习时长为准。</div> <div class='p'>2、课程讨论及反馈建设性问题50分:课程讨论以后台数据为准,反馈问题以教务老师问题统计为准,满分50分,酌情给分。</div> <div class='h2'>每章试题:总分100分(占科目总成绩的30%)</div> <div class='p'>本课程所有试题的平均正确率*100分*占总成绩30%=此项得分。(比如正确度为80%,则此项得分:80%*100*30%=24分)</div> <div class='h2'>结业大作业:总分100分(占科目总成绩的40%)</div> <div class='p'>结业大作业满分为100分,以助教老师给分为准。</div>",
assess: {
score: '20',
duration: '00:01:20',
progress: '0/10',
video: [{
title: '第一章 市场营销原理导论',
arr: [{
name: '1.1 什么是市场营销什么是市场营销市场营销什么是市场营销什么是市场营销',
time: '12:08',
progress: '100%'
}]
}],
homewrok: [{
title: '第一章 市场营销原理导论',
arr: [{
name: '1.1 什么是市场营销什么是市场营销市场营销什么是市场营销什么是市场营销',
score: 20
}]
}],
essay: {
status: '未提交',
score: '暂无'
}
}
}]
}
},
methods: {
handleClick (tab, event) { /* console.log(tab, event) */ },
clickJumpOrStatus (e) {
let data = e.currentTarget.dataset
let flag = data.status
if (flag) {
let index = data.index
let json = this.tabs
let temp = json[1].chapterList.course[index]
temp.isUp = !temp.isUp
} else {
/* 跳转 */
// wx.showToast({ title: '请上PC使用该功能', icon: 'none' })
}
},
jumpToOtherVA () {},
startLearn () {},
wantThisCourse () {}
}
}
</script>
<style lang="scss" scoped>
.con-title {
padding: 0 30px;
font-size: 16px;
line-height: 45px;
border-bottom: 1px solid #c9c9c9;
}
body .el-tab-pane { padding-top: 0; }
.detail-box {
margin: 0.3rem;
color: #313131;
overflow: hidden;
.box-thd {
margin-bottom: 0.1rem;
overflow: hidden;
.title {
font-size: 0.22rem;
line-height: 1.5;
font-weight: 700;
.rbtn {
float: right;
}
}
.tags {
float: left;
margin: 0.1rem 0.2rem 0.05rem 0;
font-size: 0.14rem;
span {
padding: 0 0.1rem;
margin: 0;
border-right: 1px solid #313131;
&:first-child {
padding-left: 0;
}
&:last-child {
border: none;
}
}
}
.time {
float: left;
margin: 0.1rem 0 0.05rem 0;
font-size: 0.14rem;
}
.progress {
float: left;
width: 100%;
font-size: 14px;
.el-progress {
width: 30%;
display: inline-block;
vertical-align: text-bottom;
}
}
}
.box-tbd {
margin-bottom: 0.2rem;
overflow: hidden;
.left-pic {
float: left;
width: 1.8rem;
overflow: hidden;
.no-img {
width: 100%;
height: 1rem;
text-align: center;
line-height: 1.1rem;
border: 1px solid #c9c9c9;
i {
font-size: 0.4rem;
color: #c9c9c9;
}
}
img {
display: block;
width: 100%;
}
}
.right-content {
margin-left: 2rem;
line-height: 1.5;
font-size: 14px;
}
}
/* 课程列表内容 直接用 wx统一样式 修改单位 并 整体 缩小了 */
.course-list { padding: 0.2rem 0 0.1rem 0; background: #ffffff; }
.course-list .none { display: none; }
.course-list .content-group { padding: 0 0.2rem; }
.course-list .content-group.no-child { margin-bottom: 0.1rem; }
.course-list .content-group.up { margin-bottom: 0.1rem; }
.course-list .content-group.up .body { display: none; }
.course-list .content-group .title { position: relative; padding: 0.1rem 0.54rem 0.1rem 0.2rem; color: #313131; font-size: 0.16rem; line-height: 0.24rem; background: #e5e5e5; user-select: none; cursor: pointer; }
.course-list .content-group .title .side { position: absolute; top: 50%; right: 0.24rem; transform: translateY(-50%); }
.course-list .content-group .body { position: relative; padding: 0 0.2rem; font-size: 0.16rem; line-height: 0.24rem; color: #505050; }
.course-list .content-group .body.on .name { color: #b2183e; }
.course-list .content-group .body .name { padding: 0.1rem 0.8rem 0.1rem 0.2rem; border-left: 0.02rem solid #c9c9c9; }
.course-list .content-group .body .name:before { width: 0.2rem; height: 0.2rem; border-radius: 0.32rem; border: 0.02rem solid #c9c9c9; background: #e5e5e5; position: absolute; left: 0.11rem; top: 0.12rem; content: ""; display: block; z-index: 10; }
.course-list .content-group .body .name .time { position: absolute; right: 0.16rem; top: 0.1rem; }
/* 老师列表内容 直接用 wx统一样式 修改单位 并 整体 缩小了 */
.list-teacher { margin-bottom: 0.2rem; }
.list-teacher .img { float: left; width: 30%; min-height: 0.8rem; background: #c9c9c9; }
.list-teacher .ctx { position: relative; margin-left: 32%; min-height: 0.5rem; }
.list-teacher .ctx .top-ctx { position: absolute; top: 0; width: 100%; overflow: hidden; }
.list-teacher .ctx .top-ctx .tit { font-size: 14px; color: #313131; line-height: 1.5; overflow: hidden; word-break: break-all; }
.list-teacher .ctx .bottom-ctx { display: block; padding-top: 0.2rem; width: 100%; overflow: hidden; }
.list-teacher .ctx .bottom-ctx .t1 { font-size: 12px; line-height: 1.5; color: #707070; overflow: hidden; word-break: break-all; }
.list-teacher .ctx .bottom-ctx .t2 { font-size: 12px; line-height: 1.5; color: #707070; overflow: hidden; word-break: break-all; }
.list-teacher .ctx .bottom-ctx .t3 { font-size: 12px; color: #707070; text-overflow: ellipsis; overflow: hidden; word-break: break-all; }
/* 课程考核内容 直接用 wx统一样式 修改单位 并 整体 缩小了 */
.course-assess { padding: 0.2rem; margin-bottom: 1rem; background: #ffffff; overflow: hidden; }
.course-assess .title { color: #313131; font-size: 0.2rem; margin: 0.4rem auto 0.2rem auto; text-align: center; }
.course-assess .topic { position: relative; width: 100%; height: 0.50rem; overflow: hidden; }
.course-assess .topic .line { width: 3rem; height: 1px; margin: 0.25rem auto 0 auto; background: #313131; }
.course-assess .topic .tit { position: absolute; top: 50%; left: 50%; padding: 0.1rem 0.22rem; font-size: 0.14rem; font-weight: 700; color: #313131; background: #fff; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); }
.course-assess .detail { margin-bottom: 0.4rem; padding: 0 0.2rem; }
/* 富文本不定义在这 */
/* 统计表格 */
.course-assess .table-title { font-size: 0.16rem; font-weight: 700; margin: 0.2rem 0.26rem 0.20rem 0.26rem; text-align: justify; color: #b49441; }
.course-assess .table { padding: 0 0.2rem; color: #313131; padding-bottom: 0.3rem; border-bottom: 0.02rem solid #c9c9c9; }
.course-assess .table .col3-td1 { float: left; padding-left: 0.15rem; width: 65%; text-align: left; box-sizing: border-box; -webkit-box-sizing: border-box; }
.course-assess .table .col3-td2 { float: left; width: 21%; text-align: center; }
.course-assess .table .col3-td3 { float: left; width: 14%; text-align: right; box-sizing: border-box; -webkit-box-sizing: border-box; }
.course-assess .table .col2-td1 { float: left; padding-left: 0.15rem; width: 86%; text-align: left; box-sizing: border-box; -webkit-box-sizing: border-box; }
.course-assess .table .col2-td2 { float: left; width: 14%; text-align: right; box-sizing: border-box; -webkit-box-sizing: border-box; }
.course-assess .table .th { padding: 0 0.2rem; font-size: 0.16rem; overflow: hidden; border-bottom: 0.02rem solid #e5e5e5; line-height: 1.5; font-weight: 700; }
.course-assess .table .tb { padding: 0 0.2rem; font-size: 0.14rem; overflow: hidden; }
.course-assess .table .tb .tt { padding: 0.1rem 0 0.02rem 0; line-height: 0.24rem; font-weight: 700; }
.course-assess .table .tb .rd { overflow: hidden; /* border-bottom: 1rpx solid #e5e5e5; */ line-height: 0.26rem; /* padding: 10rpx 0; */ }
/* .course-assess .table .tb .rd:last-child { border-bottom: none; } */
.course-assess .status-text { padding-left: 0.3rem; font-size: 0.14rem; color: #000000; line-height: 1.5; }
}
@media (max-width: 767px) {
.detail-box { margin: 0.2rem; }
.detail-box .box-thd .progress .el-progress {
width: 80%;
}
.detail-box .box-tbd .left-pic {
width: 100%;
margin-bottom: 0.2rem;
}
.detail-box .box-tbd .right-content {
margin-left: 0;
display: block;
}
/* 课程考核样式 */
.detail-box .course-assess .detail { padding: 0; }
.detail-box .course-assess .table { padding: 0 0 0.3rem 0; }
}
</style>
<template>
<div>
<m-page
:tableHead="tableHead"
:tableData="tableData"
:params="params"
:objFn="objFn"
:tableConfig="tableConfig"
>
<template slot="self-select-btn">
<div class="self-selects">
<div class="btns">
<el-button type="primary">添加文章</el-button>
<el-button @click="batchRemove" :disabled="this.tableConfig.selection.sels.length===0">删除选中</el-button>
</div>
<div class="search-input">
<m-search @enter="searchList"></m-search>
</div>
</div>
</template>
</m-page>
</div>
<div>讨论开发中</div>
</template>
<script>
import mSearch from '../../components/module/mSearch.vue'
import mPage from '../../components/module/mPage.vue'
export default {
name: '',
components: { mSearch, mPage },
data () {
return {
tableConfig: { border: '', size: '', selection: { has: true, sels: [], width: '50px', fix: 'left', align: '' } }, // 增加选择框,has设置为true
tableHead: [
{ prop: 'title', label: '文章标题', minWidth: '170', fix: 'left', align: 'left' },
{ prop: 'type', label: '分类', minWidth: '70', fix: false },
{ prop: 'publish_time', label: '发布时间', minWidth: '90', fix: false },
{ prop: 'publish_author', label: '发布人', minWidth: '80', fix: false },
{ prop: 'operate', label: '操作', width: '60px', fix: false, commandArr: [ { command: 'newslist-edit', name: '编辑' }, { command: 'newslist-del', name: '删除' } ] }
// { prop: '', label: '', minWidth: '', fix: false, goObj: { routerName: '', params: {} } }, // 点击跳转页面并传参数
// { prop: 'img_url', label: '图片', width: '60px', fix: false } // 显示图片缩略小图,浮上去可查看大图
// { prop: 'operate', label: '操作', width: '60px', fix: false, commandArr: [ { command: 'newslist-edit', name: '编辑' }, { command: 'newslist-del', name: '删除' } ] } // 操作控制列
],
tableData: [],
params: { keywords: '', curPage: 1, pageSize: 10, total: '' },
objFn: {
paramsFn: (_this) => {
let strType = _this.params.type
return {
'org_type': /2-/gi.test(strType) ? '2' : strType,
'org_sub_type': /2-/gi.test(strType) ? strType.replace(/2-/gi, '') : '',
'org_class': '0',
'keywords': _this.params.keywords,
'page_num': _this.params.curPage,
'page_size': _this.params.pageSize,
'quota_type': 'org_bigscience_stat'
}
},
nameAPI: 'getArticleList',
// nameExcel: 'getStatSummaryExcel',
nameExcel: 'getExportExcel3_4_14',
callback: (_this, data) => {
_this.params.total = 100
/* 伪造数据 */
var fakeData = [
{ title: '测试文章,有长有短测试文章,有长有短测试文章,有长有短测试文章,有长有短', type: '校园类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '校园类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '校园类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' }
]
fakeData.forEach((elem, i) => {
elem.index = i + 1 + (_this.params.curPage - 1) * _this.params.pageSize
_this.tableData.push(elem)
})
}
}
}
},
mounted () {
this.VueEvent.$on('command-tablelist', (_obj) => {
if (_obj.command === 'newslist-edit') {
}
if (_obj.command === 'newslist-del') {
}
})
},
methods: {
searchList (val) {
this.params.keywords = val
},
/* 删除选中 */
batchRemove () {
console.log(this.tableConfig.selection.sels)
}
}
}
</script>
<style lang="scss" scoped>
.self-selects {
margin: 10px auto 30px auto;
overflow: hidden;
.btns {
float: left;
}
.search-input {
float: right;
width: 40%;
}
}
</style>
<template>
<div>学术报告在开发中</div>
<div>
<div class="con-title">学习报告</div>
<div class="con-box">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="学术报告申请" name="1">
<el-row type="flex" justify="center">
<el-col :xs="24" :sm="24" :md="16" :lg="12" :xl="8">
<el-form ref="setApplyForm" :label-width="labelWidth" :model="setApply" :rules="applyRules">
<el-form-item label="学术活动名称" prop="name">
<el-input v-model="setApply.name" placeholder="请输入活动名称" type="text" />
</el-form-item>
<el-form-item label="学术活动时间" prop="time">
<el-date-picker v-model="setApply.time" placeholder="选择日期时间" type="datetime" />
</el-form-item>
<el-form-item label="主 讲 人" prop="author">
<el-input v-model="setApply.author" placeholder="请输入主讲人" type="text" />
</el-form-item>
<el-form-item label="学术活动内容" prop="content">
<el-input v-model="setApply.content" placeholder="请输入活动内容" type="textarea" :autosize="{ minRows: 5 }" />
</el-form-item>
<el-form-item label="附 件" prop="file">
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
multiple
:limit="3">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">(可以上传 word、ppt、png、jpg 等资源)</div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onApplyFrom">保存并提交</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="学术报告提交" name="2">
<el-row type="flex" justify="center">
<el-col :xs="24" :sm="24" :md="16" :lg="12" :xl="8">
<el-form ref="setSubmitForm" :label-width="labelWidth" :model="setSubmit" :rules="submitRules">
<el-form-item label="学术报告主题" prop="name">
<el-input v-model="setSubmit.name" placeholder="请输入报告主题" type="text" />
</el-form-item>
<el-form-item label="学术报告摘要" prop="title">
<el-input v-model="setSubmit.title" placeholder="请输入报告摘要" type="text" />
</el-form-item>
<el-form-item label="附 件" prop="file">
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
multiple
:limit="3">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">(可以上传word、ppt等资源。系统提供模板下载)</div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmitFrom">保存并提交</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="学术报告统计" name="3">
<m-page
:tableHead="tableHead"
:tableData="tableData"
:params="params"
:objFn="objFn"
:tableConfig="tableConfig"
/>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
import mSearch from '../../components/module/mSearch.vue'
import mPage from '../../components/module/mPage.vue'
export default {
components: { mSearch, mPage },
data () {
return {
activeName: '1',
labelWidth: '110px',
setApply: {},
applyRules: {
'name': [
{ required: true, message: '活动名称不能为空', trigger: 'blur' }
],
'time': [
{ required: true, message: '活动时间不能为空', trigger: 'blur' }
],
'author': [
{ required: true, message: '主讲人不能为空', trigger: 'blur' }
],
'content': [
{ required: true, message: '活动内容不能为空', trigger: 'blur' }
]
},
setSubmit: {},
submitRules: {
'name': [
{ required: true, message: '报告主题不能为空', trigger: 'blur' }
],
'title': [
{ required: true, message: '报告摘要不能为空', trigger: 'blur' }
]
},
tableConfig: { border: '', size: '', selection: { has: false, sels: [], width: '50px', fix: 'left', align: '' } }, // 增加选择框,has设置为true
tableHead: [
{ prop: 'index', label: '序号', minWidth: '80', fix: false },
{ prop: 'title', label: '学号', minWidth: '150', fix: false },
{ prop: 'type', label: '姓名', minWidth: '80', fix: false },
{ prop: 'publish_time', label: '学术报告主题', minWidth: '170', fix: false },
{ prop: 'publish_author', label: '审核状态', minWidth: '100', fix: false },
{ prop: 'publish_author', label: '审核时间', minWidth: '100', fix: false }
// { prop: '', label: '', minWidth: '', fix: false, goObj: { routerName: '', params: {} } }, // 点击跳转页面并传参数
// { prop: 'img_url', label: '图片', width: '60px', fix: false, align: 'left' } // 显示图片缩略小图,浮上去可查看大图
// { prop: 'operate', label: '操作', width: '60px', fix: false, commandArr: [ { command: 'newslist-edit', name: '编辑' }, { command: 'newslist-del', name: '删除' } ] } // 操作控制列
],
tableData: [],
params: { keywords: '', curPage: 1, pageSize: 10, total: '' },
objFn: {
paramsFn: (_this) => {
let strType = _this.params.type
return {
'org_type': /2-/gi.test(strType) ? '2' : strType,
'org_sub_type': /2-/gi.test(strType) ? strType.replace(/2-/gi, '') : '',
'org_class': '0',
'keywords': _this.params.keywords,
'page_num': _this.params.curPage,
'page_size': _this.params.pageSize,
'quota_type': 'org_bigscience_stat'
}
},
nameAPI: 'getReportList',
actionClass: 'reportAction',
// nameExcel: 'getStatSummaryExcel',
nameExcel: 'getExportExcel3_4_14',
callback: (_this, data) => {
_this.params.total = 100
/* 伪造数据 */
var fakeData = [
{ title: '测试文章,有长有短测试文章,有长有短测试文章,有长有短测试文章,有长有短', type: '校园类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '校园类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '校园类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' },
{ title: '测试文章,有长有短', type: '科技类', publish_time: '2018-10-10 12:30:25', publish_author: '王帅' }
]
fakeData.forEach((elem, i) => {
elem.index = i + 1 + (_this.params.curPage - 1) * _this.params.pageSize
_this.tableData.push(elem)
})
}
}
}
},
mounted () {
window.addEventListener('resize', () => {
let width = document.documentElement.clientWidth
if (width < 790) {
this.labelWidth = 'auto'
} else {
this.labelWidth = '110px'
}
})
},
methods: {
handleClick (tab, event) { /* console.log(tab, event) */ },
onApplyFrom () {
this.$refs['setApplyForm'].validate((valid) => {
if (valid) {
console.log('submit!')
} else {
this.$message.error('请检查输入项,确认无误后,重新提交')
return false
}
})
},
onSubmitFrom () {
this.$refs['setSubmitForm'].validate((valid) => {
if (valid) {
console.log('submit!')
} else {
this.$message.error('请检查输入项,确认无误后,重新提交')
return false
}
})
}
}
}
</script>
<style lang="scss" scoped>
.con-title {
padding: 0 30px;
font-size: 16px;
line-height: 45px;
border-bottom: 1px solid #c9c9c9;
}
.con-box {
margin: 0.3rem;
padding: 0.3rem;
background: #ffffff;
}
/* 申请 */
.el-date-editor.el-input {
width: 100%;
}
.el-upload__tip {
line-height: 1.5;
}
@media (max-width: 767px) {
.con-box {
margin: 0.2rem 0;
padding: 0.1rem 0.2rem;
}
}
</style>
......@@ -29,20 +29,25 @@ export default [
{ path: '/login/*', redirect: '/login/index' },
{
path: '/app',
redirect: '/app/error/404',
component: App,
children: [
{ path: 'error/404', component: () => import('../components/error/404.vue') },
{
path: 'my-learn',
redirect: 'error/404',
component: container,
children: [
{ path: 'course', component: () => import('../pages/myLearn/course.vue') },
{ path: 'course-detail', component: () => import('../pages/myLearn/courseDetail.vue') },
{ path: 'course-all', component: () => import('../pages/myLearn/courseAll.vue') },
{ path: 'discussion', component: () => import('../pages/myLearn/discussion.vue') },
{ path: 'report', component: () => import('../pages/myLearn/report.vue') }
]
},
{
path: 'my-grade',
redirect: 'error/404',
component: container,
children: [
{ path: 'credit', component: () => import('../pages/myGrade/credit.vue') }
......@@ -50,6 +55,7 @@ export default [
},
{
path: 'account',
redirect: 'error/404',
component: container,
children: [
{ path: 'set-pwd', component: () => import('../pages/account/setPwd.vue') },
......@@ -60,6 +66,18 @@ export default [
},
/* App 内未找到页面时 - 指向 */
{ path: '/app/*', redirect: '/app/error/404' },
// {
// path: '/player',
// // redirect: '/login/index',
// component: () => import('../../../components/player/index.vue')
// // children: [
// // {
// // path: 'index',
// // name: 'normalLogin',
// // component: () => import('../../../components/login/normal/normal.vue')
// // }
// // ]
// },
/* 如果所有页面都没找到 - 指向 */
{ path: '*', component: () => import('../components/error/404.vue') }
]
......@@ -2827,6 +2827,11 @@
"randomfill": "^1.0.3"
}
},
"crypto-js": {
"version": "3.1.9-1",
"resolved": "http://registry.npm.taobao.org/crypto-js/download/crypto-js-3.1.9-1.tgz",
"integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
},
"css-color-names": {
"version": "0.0.4",
"resolved": "http://registry.npm.taobao.org/css-color-names/download/css-color-names-0.0.4.tgz",
......@@ -6820,6 +6825,12 @@
"integrity": "sha1-dIkR+wT0imDEdxs3XKxFqA3xHAM=",
"dev": true
},
"js-md5": {
"version": "0.7.3",
"resolved": "http://registry.npm.taobao.org/js-md5/download/js-md5-0.7.3.tgz",
"integrity": "sha1-tPL7sLMnRV9ZjWcn447Ccs0Jw/I=",
"dev": true
},
"js-tokens": {
"version": "3.0.2",
"resolved": "http://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.2.tgz",
......
......@@ -60,6 +60,7 @@
"gulp-util": "^3.0.8",
"gulp-zip": "^4.2.0",
"html-webpack-plugin": "^3.2.0",
"js-md5": "^0.7.3",
"mini-css-extract-plugin": "^0.4.5",
"node-sass": "^4.10.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
......@@ -82,6 +83,7 @@
"body-parser": "^1.18.3",
"connect-history-api-fallback": "^1.5.0",
"cross-env": "^5.2.0",
"crypto-js": "^3.1.9-1",
"express": "^4.16.4",
"lodash": "^4.17.11",
"multer": "^1.4.1"
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论