├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── favicon.ico ├── index.html ├── mock ├── get_classrooms.js ├── get_region_teachers.js ├── get_regions.js ├── get_roles.js ├── get_schools.js └── login.js ├── package.json ├── src ├── App.vue ├── assets │ ├── common.scss │ └── logo.png ├── components │ ├── Breadcrumb.vue │ ├── Header.vue │ ├── PageProgress.vue │ ├── Sidebar.vue │ └── UIComponents.js ├── filters │ └── index.js ├── main.js ├── routes │ └── index.js ├── services │ ├── API.js │ ├── account.js │ ├── campus.js │ ├── classroom.js │ ├── common.js │ ├── model │ │ ├── AccountVO.js │ │ ├── CampusVO.js │ │ ├── ClassroomVO.js │ │ ├── CommonVO.js │ │ ├── RoleVO.js │ │ ├── SchoolVO.js │ │ ├── SubjectVO.js │ │ └── TeacherVO.js │ ├── role.js │ ├── subject.js │ └── teacher.js ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── modules │ │ └── account.js │ ├── mutation-types.js │ └── mutations.js ├── utils │ ├── http.js │ ├── md5.js │ ├── proxy.js │ └── util.js └── views │ ├── Home.vue │ ├── Index.vue │ ├── Login.vue │ ├── NotFound.vue │ ├── Regist.vue │ └── main │ ├── CampusManage.vue │ ├── ClassroomManage.vue │ ├── Index.vue │ ├── RoleManage.vue │ └── TeacherManage.vue ├── static ├── .gitkeep └── img │ └── screenshot.png └── test ├── e2e ├── custom-assertions │ └── elementCount.js ├── nightwatch.conf.js ├── runner.js └── specs │ └── test.js └── unit ├── .eslintrc ├── index.js ├── karma.conf.js └── specs └── Hello.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": [ 12 | "transform-runtime", 13 | "syntax-dynamic-import", 14 | ["component", [{ 15 | "libraryName": "element-ui", 16 | "styleLibraryName": "theme-default" 17 | }]] 18 | ], 19 | "env": { 20 | "test": { 21 | "presets": ["env", "stage-2"], 22 | "plugins": ["istanbul"] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-spa-project 2 | > 基于vue.js技术栈,采用MVC等分层结构设计、数据解耦设计、组件化和自动化构建的单页应用项目。 3 | 4 | 技术栈 5 | 6 | - vue.js(2.4) 7 | - vuex(状态管理) 8 | - vue-router(前端路由,异步加载) 9 | - fetch(替代ajax,更高效地进行网络请求) 10 | - element-ui(基于vue的PC端UI组件库) 11 | 12 | 13 | Webpack(自动构建) 14 | 15 | - Babel预编译(ES6、SCSS等),和异步组件实现路由懒加载 16 | - ESlint代码质量控制,所有代码编写必须按照标准规范才能通过编译,并避免一些低级错误 17 | - 本地开发服务器会监听文件修改,自动编译和刷新浏览器 18 | - 前端mock数据,无需等待后端接口实现 19 | - 测试服务器自动部署deploy(需要在服务器上启动一个node写的接收脚本: [file-receiver](https://github.com/xiaoping6688/file-receiver)) 20 | 21 | 如果需要服务端渲染(SSR),推荐使用:[NUXT](https://zh.nuxtjs.org/) 22 | 23 | ## Build Setup 24 | 25 | ``` bash 26 | # install dependencies (use cnpm: npm install -g cnpm --registry=https://registry.npm.taobao.org) 27 | cnpm install 28 | 29 | # serve with hot reload at localhost:8080 30 | npm run dev 31 | 32 | # build for production with minification 33 | npm run build 34 | 35 | # run all tests for unit test and e2e test 36 | npm test 37 | 38 | # check update for npm packages, please install 'npm install npm-check-updates -g' at first 39 | npm run update 40 | ``` 41 | 42 | ![image](https://raw.githubusercontent.com/xiaoping6688/vue-spa-project/master/static/img/screenshot.png) 43 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var fs = require('fs') 11 | var express = require('express') 12 | var webpack = require('webpack') 13 | var proxyMiddleware = require('http-proxy-middleware') 14 | var webpackConfig = process.env.NODE_ENV === 'testing' 15 | ? require('./webpack.prod.conf') 16 | : require('./webpack.dev.conf') 17 | 18 | // default port where dev server listens for incoming traffic 19 | var port = process.env.PORT || config.dev.port 20 | // automatically open browser, if not set will be false 21 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 22 | // Define HTTP proxies to your custom API backend 23 | // https://github.com/chimurai/http-proxy-middleware 24 | var proxyTable = config.dev.proxyTable 25 | 26 | var app = express() 27 | var compiler = webpack(webpackConfig) 28 | 29 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 30 | publicPath: webpackConfig.output.publicPath, 31 | quiet: true 32 | }) 33 | 34 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 35 | log: () => {}, 36 | heartbeat: 2000 37 | }) 38 | // force page reload when html-webpack-plugin template changes 39 | compiler.plugin('compilation', function (compilation) { 40 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 41 | hotMiddleware.publish({ action: 'reload' }) 42 | cb() 43 | }) 44 | }) 45 | 46 | // proxy api requests 47 | Object.keys(proxyTable).forEach(function (context) { 48 | var options = proxyTable[context] 49 | if (typeof options === 'string') { 50 | options = { target: options } 51 | } 52 | app.use(proxyMiddleware(options.filter || context, options)) 53 | }) 54 | 55 | // mock data 56 | var mockDir = path.resolve(__dirname, '../mock') 57 | fs.readdirSync(mockDir).forEach(function (file) { 58 | var mock = require(path.resolve(mockDir, file)) 59 | app.use(mock.api, mock.response) 60 | }) 61 | 62 | // handle fallback for HTML5 history API 63 | app.use(require('connect-history-api-fallback')()) 64 | 65 | // serve webpack bundle output 66 | app.use(devMiddleware) 67 | 68 | // enable hot-reload and state-preserving 69 | // compilation error display 70 | app.use(hotMiddleware) 71 | 72 | // serve pure static assets 73 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 74 | app.use(staticPath, express.static('./static')) 75 | 76 | var uri = 'http://localhost:' + port 77 | 78 | var _resolve 79 | var readyPromise = new Promise(resolve => { 80 | _resolve = resolve 81 | }) 82 | 83 | console.log('> Starting dev server...') 84 | devMiddleware.waitUntilValid(() => { 85 | console.log('> Listening at ' + uri + '\n') 86 | // when env is testing, don't need open it 87 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 88 | opn(uri) 89 | } 90 | _resolve() 91 | }) 92 | 93 | var server = app.listen(port) 94 | 95 | module.exports = { 96 | ready: readyPromise, 97 | close: () => { 98 | server.close() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }), 12 | transformToRequire: { 13 | video: 'src', 14 | source: 'src', 15 | img: 'src', 16 | image: 'xlink:href' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | chunkFilename: '[name].chunk.js', // 可选,webpack v2.4 的新功能,可以设置 chunk 的名字 18 | publicPath: process.env.NODE_ENV === 'production' 19 | ? config.build.assetsPublicPath 20 | : config.dev.assetsPublicPath 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.vue', '.json'], 24 | alias: { 25 | 'vue$': 'vue/dist/vue.esm.js', 26 | '@': resolve('src') 27 | } 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.(js|vue)$/, 33 | loader: 'eslint-loader', 34 | enforce: 'pre', 35 | include: [resolve('src'), resolve('test')], 36 | options: { 37 | formatter: require('eslint-friendly-formatter') 38 | } 39 | }, 40 | { 41 | test: /\.vue$/, 42 | loader: 'vue-loader', 43 | options: vueLoaderConfig 44 | }, 45 | { 46 | test: /\.js$/, 47 | loader: 'babel-loader', 48 | include: [resolve('src'), resolve('test')] 49 | }, 50 | { 51 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 52 | loader: 'url-loader', 53 | options: { 54 | limit: 10000, 55 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 56 | } 57 | }, 58 | { 59 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 60 | loader: 'url-loader', 61 | options: { 62 | limit: 10000, 63 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 64 | } 65 | }, 66 | { 67 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 68 | loader: 'url-loader', 69 | options: { 70 | limit: 10000, 71 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 72 | } 73 | } 74 | ] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | favicon: 'favicon.ico', 30 | filename: 'index.html', 31 | template: 'index.html', 32 | inject: true 33 | }), 34 | new FriendlyErrorsPlugin() 35 | ] 36 | }) 37 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | favicon: 'favicon.ico', 56 | filename: process.env.NODE_ENV === 'testing' 57 | ? 'index.html' 58 | : config.build.index, 59 | template: 'index.html', 60 | inject: true, 61 | minify: { 62 | removeComments: true, 63 | collapseWhitespace: true, 64 | removeAttributeQuotes: true 65 | // more options: 66 | // https://github.com/kangax/html-minifier#options-quick-reference 67 | }, 68 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 69 | chunksSortMode: 'dependency' 70 | }), 71 | // split vendor js into its own file 72 | new webpack.optimize.CommonsChunkPlugin({ 73 | name: 'vendor', 74 | minChunks: function (module, count) { 75 | // any required modules inside node_modules are extracted to vendor 76 | return ( 77 | module.resource && 78 | /\.js$/.test(module.resource) && 79 | module.resource.indexOf( 80 | path.join(__dirname, '../node_modules') 81 | ) === 0 82 | ) 83 | } 84 | }), 85 | // extract webpack runtime and module manifest to its own file in order to 86 | // prevent vendor hash from being updated whenever app bundle is updated 87 | new webpack.optimize.CommonsChunkPlugin({ 88 | name: 'manifest', 89 | chunks: ['vendor'] 90 | }), 91 | // copy custom static assets 92 | new CopyWebpackPlugin([ 93 | { 94 | from: path.resolve(__dirname, '../static'), 95 | to: config.build.assetsSubDirectory, 96 | ignore: ['.*'] 97 | } 98 | ]) 99 | ] 100 | }) 101 | 102 | if (config.build.productionGzip) { 103 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 104 | 105 | webpackConfig.plugins.push( 106 | new CompressionWebpackPlugin({ 107 | asset: '[path].gz[query]', 108 | algorithm: 'gzip', 109 | test: new RegExp( 110 | '\\.(' + 111 | config.build.productionGzipExtensions.join('|') + 112 | ')$' 113 | ), 114 | threshold: 10240, 115 | minRatio: 0.8 116 | }) 117 | ) 118 | } 119 | 120 | if (config.build.bundleAnalyzerReport) { 121 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 122 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 123 | } 124 | 125 | // deploy to server 126 | if (process.env.COMMAND_ENV === 'deploy') { 127 | var DeployPlugin = require('deploy-webpack-plugin') 128 | webpackConfig.plugins.push(new DeployPlugin({ 129 | "receiver": "http://ip:8999/receiver", // optional, deploy to local directory when omitted 130 | "staticDir": "/data/webapps", // .js,.css,images will end up here 131 | "tplDir": "/data/webapps" // .html ends up here 132 | })) 133 | } 134 | 135 | module.exports = webpackConfig 136 | -------------------------------------------------------------------------------- /build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: { 31 | '/edu_rest/**': { 32 | target: 'http://other.domain/api', 33 | changeOrigin: true 34 | } 35 | }, 36 | // CSS Sourcemaps off by default because relative paths are "buggy" 37 | // with this option, according to the CSS-Loader README 38 | // (https://github.com/webpack/css-loader#sourcemaps) 39 | // In our experience, they generally work as expected, 40 | // just be aware of this issue when enabling this option. 41 | cssSourceMap: false 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping6688/vue-spa-project/2e4a884259a953b64a1a491bba7f3b73d76fbfa8/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-project 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /mock/get_classrooms.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | faker.locale = 'zh_CN' 3 | 4 | module.exports = { 5 | api: '/mock/edu_rest/acquire_school_room/:id', 6 | response: function (req, res) { 7 | res.json({ 8 | rlt: 'true', 9 | msg: 'ok', 10 | data: [ 11 | { classroom_id: faker.random.uuid(), classroom_name: faker.random.words() }, 12 | { classroom_id: faker.random.uuid(), classroom_name: faker.random.words() }, 13 | { classroom_id: faker.random.uuid(), classroom_name: faker.random.words() } 14 | ] 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mock/get_region_teachers.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | faker.locale = 'zh_CN' 3 | 4 | module.exports = { 5 | api: '/mock/edu_rest/acquire_teacher_by_org/:id', 6 | response: function (req, res) { 7 | res.json({ 8 | rlt: 'true', 9 | msg: 'ok', 10 | data: [ 11 | { teacher_id: faker.random.uuid(), teacher_name: faker.name.findName(), mobile:faker.phone.phoneNumber(), role_name:faker.name.jobType(), subject_name:faker.name.jobTitle(), campus_name:faker.random.words() }, 12 | { teacher_id: faker.random.uuid(), teacher_name: faker.name.findName(), mobile:faker.phone.phoneNumber(), role_name:faker.name.jobType(), subject_name:faker.name.jobTitle(), campus_name:faker.random.words() }, 13 | { teacher_id: faker.random.uuid(), teacher_name: faker.name.findName(), mobile:faker.phone.phoneNumber(), role_name:faker.name.jobType(), subject_name:faker.name.jobTitle(), campus_name:faker.random.words() } 14 | ] 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mock/get_regions.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | faker.locale = 'zh_CN' 3 | 4 | module.exports = { 5 | api: '/mock/edu_rest/acquire_campus/:id', 6 | response: function (req, res) { 7 | res.json({ 8 | rlt: 'true', 9 | msg: 'ok', 10 | data: [ 11 | { campus_id: faker.random.uuid(), campus_name: faker.address.city() }, 12 | { campus_id: faker.random.uuid(), campus_name: faker.address.city() }, 13 | { campus_id: faker.random.uuid(), campus_name: faker.address.city() } 14 | ] 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mock/get_roles.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | faker.locale = 'zh_CN' 3 | 4 | module.exports = { 5 | api: '/mock/edu_rest/acquire_role/:id', 6 | response: function (req, res) { 7 | res.json({ 8 | rlt: 'true', 9 | msg: 'ok', 10 | data: [ 11 | { role_id: faker.random.uuid(), role_name: faker.random.words() }, 12 | { role_id: faker.random.uuid(), role_name: faker.random.words() }, 13 | { role_id: faker.random.uuid(), role_name: faker.random.words() } 14 | ] 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mock/get_schools.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | faker.locale = 'zh_CN' 3 | 4 | module.exports = { 5 | api: '/mock/edu_rest/acquire_school/:id', 6 | response: function (req, res) { 7 | res.json({ 8 | rlt: 'true', 9 | msg: 'ok', 10 | data: [ 11 | { school_id: faker.random.uuid(), school_name: faker.address.streetName() }, 12 | { school_id: faker.random.uuid(), school_name: faker.address.streetName() }, 13 | { school_id: faker.random.uuid(), school_name: faker.address.streetName() } 14 | ] 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mock/login.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | 3 | module.exports = { 4 | api: '/mock/edu_rest/org_login', 5 | response: function (req, res) { 6 | res.json({ 7 | rlt: 'true', 8 | msg: 'ok', 9 | data: faker.random.uuid() 10 | }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-spa-project", 3 | "version": "1.2.1", 4 | "description": "基于Vue.js技术栈的管理系统Demo", 5 | "author": "super_xp@126.com", 6 | "keywords": [ 7 | "vue.js", 8 | "spa", 9 | "vue-demo" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/xiaoping6688/vue-spa-project" 14 | }, 15 | "license": "MIT", 16 | "private": false, 17 | "scripts": { 18 | "dev": "node build/dev-server.js", 19 | "start": "node build/dev-server.js", 20 | "build": "node build/build.js", 21 | "deploy": "cross-env COMMAND_ENV=deploy node build/build.js", 22 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 23 | "e2e": "node test/e2e/runner.js", 24 | "test": "npm run unit && npm run e2e", 25 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs", 26 | "update": "ncu -u -a" 27 | }, 28 | "dependencies": { 29 | "blueimp-md5": "^2.7.0", 30 | "element-ui": "^1.3.7", 31 | "normalize.css": "^7.0.0", 32 | "querystring": "^0.2.0", 33 | "vue": "^2.4.1", 34 | "vue-router": "^2.7.0", 35 | "vuex": "^2.3.1", 36 | "vuex-router-sync": "^4.2.0", 37 | "whatwg-fetch": "^2.0.3" 38 | }, 39 | "devDependencies": { 40 | "autoprefixer": "^7.1.2", 41 | "babel-core": "^6.25.0", 42 | "babel-eslint": "^7.2.3", 43 | "babel-loader": "^7.1.1", 44 | "babel-plugin-component": "^0.10.0", 45 | "babel-plugin-istanbul": "^4.1.4", 46 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 47 | "babel-plugin-transform-runtime": "^6.23.0", 48 | "babel-preset-env": "^1.6.0", 49 | "babel-preset-stage-2": "^6.24.1", 50 | "babel-register": "^6.24.1", 51 | "chai": "^4.1.0", 52 | "chalk": "^2.0.1", 53 | "chromedriver": "^2.30.1", 54 | "connect-history-api-fallback": "^1.3.0", 55 | "copy-webpack-plugin": "^4.0.1", 56 | "cross-env": "^5.0.1", 57 | "cross-spawn": "^5.1.0", 58 | "css-loader": "^0.28.4", 59 | "cssnano": "^3.10.0", 60 | "deploy-webpack-plugin": "0.0.1", 61 | "eslint": "^4.2.0", 62 | "eslint-config-standard": "^10.2.1", 63 | "eslint-friendly-formatter": "^3.0.0", 64 | "eslint-loader": "^1.9.0", 65 | "eslint-plugin-html": "^3.1.1", 66 | "eslint-plugin-import": "^2.7.0", 67 | "eslint-plugin-node": "^5.2.0", 68 | "eslint-plugin-promise": "^3.5.0", 69 | "eslint-plugin-standard": "^3.0.1", 70 | "eventsource-polyfill": "^0.9.6", 71 | "express": "^4.15.3", 72 | "extract-text-webpack-plugin": "^3.0.0", 73 | "faker": "^4.1.0", 74 | "file-loader": "^0.11.2", 75 | "friendly-errors-webpack-plugin": "^1.6.1", 76 | "html-webpack-plugin": "^2.29.0", 77 | "http-proxy-middleware": "^0.17.4", 78 | "inject-loader": "^3.0.0", 79 | "karma": "^1.7.0", 80 | "karma-coverage": "^1.1.1", 81 | "karma-mocha": "^1.3.0", 82 | "karma-phantomjs-launcher": "^1.0.4", 83 | "karma-phantomjs-shim": "^1.4.0", 84 | "karma-sinon-chai": "^1.3.1", 85 | "karma-sourcemap-loader": "^0.3.7", 86 | "karma-spec-reporter": "0.0.31", 87 | "karma-webpack": "^2.0.4", 88 | "lolex": "^2.1.1", 89 | "mocha": "^3.4.2", 90 | "nightwatch": "^0.9.16", 91 | "node-sass": "^4.5.3", 92 | "opn": "^5.1.0", 93 | "optimize-css-assets-webpack-plugin": "^2.0.0", 94 | "ora": "^1.3.0", 95 | "phantomjs-prebuilt": "^2.1.14", 96 | "prerender-spa-plugin": "^2.0.1", 97 | "rimraf": "^2.6.1", 98 | "sass-loader": "^6.0.6", 99 | "selenium-server": "3.4.0", 100 | "semver": "^5.3.0", 101 | "shelljs": "^0.7.8", 102 | "sinon": "^2.3.8", 103 | "sinon-chai": "^2.12.0", 104 | "url-loader": "^0.5.9", 105 | "vue-loader": "^12.2.2", 106 | "vue-style-loader": "^3.0.1", 107 | "vue-template-compiler": "^2.4.1", 108 | "webpack": "^3.3.0", 109 | "webpack-bundle-analyzer": "^2.8.3", 110 | "webpack-dev-middleware": "^1.11.0", 111 | "webpack-hot-middleware": "^2.18.2", 112 | "webpack-merge": "^4.1.0" 113 | }, 114 | "engines": { 115 | "node": ">= 6.0.0", 116 | "npm": ">= 3.0.0" 117 | }, 118 | "browserslist": [ 119 | "> 1%", 120 | "last 2 versions", 121 | "not ie <= 8" 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /src/assets/common.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | background-color: #d2d6de;; 6 | height: 100%; 7 | } 8 | 9 | .textspan { 10 | color: #99a9bf; 11 | padding: 10px 15px; 12 | float: right; 13 | font-size: 14px; 14 | } 15 | 16 | .queryinput { 17 | width: 90%; 18 | } -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping6688/vue-spa-project/2e4a884259a953b64a1a491bba7f3b73d76fbfa8/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * 面包屑组件 3 | */ 4 | 5 | 10 | 11 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * 顶栏导航组件 3 | */ 4 | 5 | 21 | 22 | 36 | 37 | 57 | -------------------------------------------------------------------------------- /src/components/PageProgress.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * 页面进度组件 3 | */ 4 | 5 | 8 | 9 | 28 | 29 | 41 | -------------------------------------------------------------------------------- /src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * 侧栏导航组件 3 | */ 4 | 5 | 21 | 22 | 38 | 39 | 47 | -------------------------------------------------------------------------------- /src/components/UIComponents.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue' 3 | 4 | // 使用element组件库(完整引入) 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-default/index.css' 7 | Vue.use(ElementUI) 8 | 9 | // 按需引入 10 | // import { 11 | // Row, 12 | // Col, 13 | // Icon, 14 | // Button, 15 | // Radio, 16 | // RadioGroup, 17 | // Checkbox, 18 | // CheckboxGroup, 19 | // Input, 20 | // Select, 21 | // Option, 22 | // TimePicker, 23 | // DatePicker, 24 | // Form, 25 | // FormItem, 26 | // Table, 27 | // TableColumn, 28 | // Progress, 29 | // Pagination, 30 | // Alert, 31 | // Loading, 32 | // Message, 33 | // MessageBox, 34 | // Notification, 35 | // Menu, 36 | // Submenu, 37 | // MenuItem, 38 | // Breadcrumb, 39 | // BreadcrumbItem, 40 | // Dialog 41 | // } from 'element-ui' 42 | 43 | // const components = [ 44 | // Row, 45 | // Col, 46 | // Icon, 47 | // Button, 48 | // Radio, 49 | // RadioGroup, 50 | // Checkbox, 51 | // CheckboxGroup, 52 | // Input, 53 | // Select, 54 | // Option, 55 | // TimePicker, 56 | // DatePicker, 57 | // Form, 58 | // FormItem, 59 | // Table, 60 | // TableColumn, 61 | // Progress, 62 | // Pagination, 63 | // Alert, 64 | // Loading, 65 | // Message, 66 | // MessageBox, 67 | // Notification, 68 | // Menu, 69 | // Submenu, 70 | // MenuItem, 71 | // Breadcrumb, 72 | // BreadcrumbItem, 73 | // Dialog 74 | // ] 75 | 76 | // components.forEach((item) => { 77 | // Vue.component(item.name, item) 78 | // }) 79 | 80 | // 解决组件自动弹出问题 81 | // Vue.prototype.$message = Message 82 | // Vue.prototype.$notify = Notification 83 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 过滤器 3 | */ 4 | 5 | /** 6 | * 对日期进行格式化, 7 | * @param date 要格式化的日期 8 | * @param format 进行格式化的模式字符串 9 | * 支持的模式字母有: 10 | * y:年, 11 | * M:年中的月份(1-12), 12 | * d:月份中的天(1-31), 13 | * h:小时(0-23), 14 | * m:分(0-59), 15 | * s:秒(0-59), 16 | * S:毫秒(0-999), 17 | * q:季度(1-4) 18 | * @return String 19 | */ 20 | export function dateFormat (date, format) { 21 | date = new Date(date) 22 | let map = { 23 | 'M': date.getMonth() + 1, // 月份 24 | 'd': date.getDate(), // 日 25 | 'h': date.getHours(), // 小时 26 | 'm': date.getMinutes(), // 分 27 | 's': date.getSeconds(), // 秒 28 | 'q': Math.floor((date.getMonth() + 3) / 3), // 季度 29 | 'S': date.getMilliseconds() // 毫秒 30 | } 31 | 32 | format = format.replace(/([yMdhmsqS])+/g, function (all, t) { 33 | let v = map[t] 34 | if (v !== undefined) { 35 | if (all.length > 1) { 36 | v = '0' + v 37 | v = v.substr(v.length - 2) 38 | } 39 | return v 40 | } else if (t === 'y') { 41 | return (date.getFullYear() + '').substr(4 - all.length) 42 | } 43 | return all 44 | }) 45 | 46 | return format 47 | } 48 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { sync } from 'vuex-router-sync' 3 | 4 | import App from './App' 5 | import store from './store' 6 | import router from './routes' 7 | import * as filters from './filters' 8 | 9 | // sync the router with the vuex store. 10 | // this registers `store.state.route` 11 | sync(store, router) 12 | 13 | // register global utility filters. 14 | Object.keys(filters).forEach(key => { 15 | Vue.filter(key, filters[key]) 16 | }) 17 | 18 | // bootstrap vue app 19 | const app = new Vue({ 20 | router, 21 | store, 22 | el: '#app', 23 | template: '', 24 | components: { App } 25 | }) 26 | 27 | // expose the app, the router and the store. 28 | // note we are not mounting the app here, since bootstrapping will be 29 | // different depending on whether we are in a browser or on the server. 30 | export { app, router, store } 31 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 路由配置 3 | */ 4 | 5 | import Vue from 'vue' 6 | import Router from 'vue-router' 7 | import store from '../store' 8 | 9 | // 异步组件实现路由懒加载 10 | // const Login = resolve => require(['../views/Login'], resolve) 11 | // const Regist = resolve => require(['../views/Regist'], resolve) 12 | // const NotFound = resolve => require(['../views/NotFound'], resolve) 13 | 14 | // 组件按组分块 15 | // const Home = r => require.ensure([], () => r(require('../views/Home')), 'group-main') 16 | // const CampusManage = r => require.ensure([], () => r(require('../views/main/CampusManage')), 'group-main') 17 | // const ClassroomManage = r => require.ensure([], () => r(require('../views/main/ClassroomManage')), 'group-main') 18 | // const RoleManage = r => require.ensure([], () => r(require('../views/main/RoleManage')), 'group-main') 19 | // const TeacherManage = r => require.ensure([], () => r(require('../views/main/TeacherManage')), 'group-main') 20 | 21 | // Webpack 2 + ES2015 写法,可设置 Chunk 名 22 | const Login = () => import(/* webpackChunkName: 'Login' */ '../views/Login') 23 | const Regist = () => import(/* webpackChunkName: 'Regist' */ '../views/Regist') 24 | const NotFound = () => import('../views/NotFound') 25 | 26 | const Home = () => import(/* webpackChunkName: 'group-main' */ '../views/Home') 27 | const CampusManage = () => import(/* webpackChunkName: 'group-main' */ '../views/main/CampusManage') 28 | const ClassroomManage = () => import(/* webpackChunkName: 'group-main' */ '../views/main/ClassroomManage') 29 | const RoleManage = () => import(/* webpackChunkName: 'group-main' */ '../views/main/RoleManage') 30 | const TeacherManage = () => import(/* webpackChunkName: 'group-main' */ '../views/main/TeacherManage') 31 | 32 | Vue.use(Router) 33 | 34 | const router = new Router({ 35 | mode: 'hash', // history 36 | routes: [ 37 | { path: '/login', name: 'login', component: Login }, 38 | { path: '/regist', name: 'regist', component: Regist }, 39 | { 40 | path: '/main', 41 | component: Home, 42 | children: [ 43 | { 44 | path: '', 45 | redirect: '/campus-manage' 46 | }, 47 | { 48 | path: 'campus-manage', 49 | name: 'campusmanage', 50 | alias: '/campus-manage', 51 | component: CampusManage, 52 | meta: { requiresAuth: true } 53 | }, 54 | { 55 | path: 'classroom-manage', 56 | name: 'classroommanage', 57 | alias: '/classroom-manage', 58 | component: ClassroomManage, 59 | meta: { requiresAuth: true } 60 | }, 61 | { 62 | path: 'role-manage', 63 | name: 'rolemanage', 64 | alias: '/role-manage', 65 | component: RoleManage, 66 | meta: { requiresAuth: true } 67 | }, 68 | { 69 | path: 'teacher-manage', 70 | name: 'teachermanage', 71 | alias: '/teacher-manage', 72 | component: TeacherManage, 73 | meta: { requiresAuth: true } 74 | } 75 | ] 76 | }, 77 | { path: '/', redirect: '/campus-manage' }, 78 | { path: '*', component: NotFound } 79 | ], 80 | scrollBehavior (to, from, savedPosition) { 81 | if (savedPosition) { 82 | return savedPosition 83 | } else { 84 | return { x: 0, y: 0 } 85 | } 86 | } 87 | }) 88 | 89 | // 路由钩子:每个路由开始前处理 90 | router.beforeEach((to, from, next) => { 91 | // 登录中间验证,页面需要登录而没有登录的情况直接跳转登录 92 | if (to.matched.some(record => record.meta.requiresAuth)) { 93 | if (store.getters.hasLoggedIn) { 94 | store.dispatch('setPageProgress', 60) 95 | next() 96 | } else { 97 | next({ 98 | path: '/login', 99 | query: { redirect: to.fullPath } 100 | }) 101 | } 102 | } else { 103 | next() 104 | } 105 | }) 106 | 107 | // 路由钩子:每个路由结束后处理 108 | router.afterEach(route => { 109 | // window.scrollTo(0, 0) 110 | store.dispatch('setPageProgress', 100) 111 | }) 112 | 113 | export default router 114 | -------------------------------------------------------------------------------- /src/services/API.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 接口配置 3 | */ 4 | 5 | // const BaseUrl = 'http://www/api' // for production 6 | // const BaseUrl = '' // for local poxy 7 | const BaseUrl = '/mock' // for local mock test 8 | 9 | export const LOGIN = BaseUrl + '/edu_rest/org_login' // 登录 10 | export const REGIST = BaseUrl + '/edu_rest/org_register' // 注册 11 | 12 | export const GET_REGIONS = BaseUrl + '/edu_rest/acquire_campus' // 获取校区 13 | export const ADD_REGION = BaseUrl + '/edu_rest/establish_campus' // 添加校区 14 | export const ADD_SCHOOL = BaseUrl + '/edu_rest/establish_school' // 添加学校 15 | export const GET_SCHOOLS = BaseUrl + '/edu_rest/acquire_school' // 获取学校(服务中心) 16 | 17 | export const GET_CLASSROOMS = BaseUrl + '/edu_rest/acquire_school_room' // 获取教室 18 | export const ADD_CLASSROOM = BaseUrl + '/edu_rest/establish_school_room' // 添加教室 19 | 20 | export const GET_ROLES = BaseUrl + '/edu_rest/acquire_role' // 获取角色 21 | export const ADD_ROLE = BaseUrl + '/edu_rest/establish_role' // 添加角色 22 | 23 | export const GET_REGION_TEACHERS = BaseUrl + '/edu_rest/acquire_teacher_by_org' // 获取机构教师 24 | export const GET_TEACHERS = BaseUrl + '/edu_rest/acquire_teacher' // 获取教师 辅导老师? 25 | export const ADD_TEACHER = BaseUrl + '/edu_rest/establish_teacher' // 添加教师 26 | 27 | export const GET_SUBJECTS = BaseUrl + '/edu_rest/acquire_subject' // 获取科目 28 | export const ADD_SUBJECT = BaseUrl + '/edu_rest/establish_subject' // 添加科目 29 | 30 | export const GET_YEARS = BaseUrl + '/edu_rest/acquire_year' // 获取年份 31 | export const GET_TERMS = BaseUrl + '/edu_rest/acquire_term' // 获取学期 32 | export const GET_GRADES = BaseUrl + '/edu_rest/acquire_grade' // 获取年级 33 | -------------------------------------------------------------------------------- /src/services/account.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户Service 3 | */ 4 | 5 | import * as API from './API' 6 | import { post } from '../utils/http' 7 | import { httpResultProxy } from '../utils/proxy' 8 | import AccountVO from './model/AccountVO' 9 | 10 | let headers = { 11 | 'plat': 'principal-plat' 12 | } 13 | 14 | /** 15 | * 用户登录 16 | * @return {Object} 返回结果Promise 17 | */ 18 | export function login (payload) { 19 | let it = payload[Symbol.iterator]() 20 | let args = { 21 | username: it.next().value, 22 | password: it.next().value 23 | } 24 | 25 | let promise = post(API.LOGIN, args, { headers: headers }) 26 | return httpResultProxy(promise, AccountVO) 27 | } 28 | 29 | /** 30 | * 注册 31 | */ 32 | export function regist (payload) { 33 | let it = payload[Symbol.iterator]() 34 | let args = { 35 | username: it.next().value, 36 | password: it.next().value 37 | } 38 | 39 | let promise = post(API.REGIST, args, { headers: headers }) 40 | return httpResultProxy(promise) 41 | } 42 | 43 | /** 44 | * 退出 45 | */ 46 | export function logout () { 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/services/campus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 校区管理Service 3 | */ 4 | 5 | import * as API from './API' 6 | import { get, post } from '../utils/http' 7 | import { httpResultProxy } from '../utils/proxy' 8 | import CampusVO from './model/CampusVO' 9 | import SchoolVO from './model/SchoolVO' 10 | 11 | let headers = { 12 | 'plat': 'principal-plat' 13 | } 14 | 15 | /** 16 | * 获取校区 17 | * @param {String} orgId 机构ID 18 | */ 19 | export function getRegions (orgId) { 20 | let promise = get(API.GET_REGIONS + `/${orgId}`, null, { headers: headers }) 21 | return httpResultProxy(promise, CampusVO) 22 | } 23 | 24 | /** 25 | * 添加校区 26 | */ 27 | export function addRegion (payload) { 28 | let it = payload[Symbol.iterator]() 29 | let args = { 30 | org_id: it.next().value, 31 | campus_name: it.next().value 32 | } 33 | 34 | let promise = post(API.ADD_REGION, args, { headers: headers }) 35 | return httpResultProxy(promise) 36 | } 37 | 38 | /** 39 | * 获取服务中心 40 | * @param {String} campusId 校区ID 41 | */ 42 | export function getSchools (campusId) { 43 | let promise = get(API.GET_SCHOOLS + `/${campusId}`, null, { headers: headers }) 44 | return httpResultProxy(promise, SchoolVO) 45 | } 46 | 47 | /** 48 | * 添加服务中心 49 | */ 50 | export function addSchool (payload) { 51 | let it = payload[Symbol.iterator]() 52 | let args = { 53 | org_id: it.next().value, 54 | campus_id: it.next().value, 55 | school_name: it.next().value 56 | } 57 | 58 | let promise = post(API.ADD_SCHOOL, args, { headers: headers }) 59 | return httpResultProxy(promise) 60 | } 61 | -------------------------------------------------------------------------------- /src/services/classroom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 教室管理Service 3 | */ 4 | 5 | import * as API from './API' 6 | import { get, post } from '../utils/http' 7 | import { httpResultProxy } from '../utils/proxy' 8 | import ClassroomVO from './model/ClassroomVO' 9 | 10 | let headers = { 11 | 'plat': 'principal-plat' 12 | } 13 | 14 | /** 15 | * 获取教室 16 | */ 17 | export function getClassrooms (schoolId) { 18 | let promise = get(API.GET_CLASSROOMS + `/${schoolId}`, null, { headers: headers }) 19 | return httpResultProxy(promise, ClassroomVO) 20 | } 21 | 22 | /** 23 | * 添加教室 24 | */ 25 | export function addClassroom (payload) { 26 | let it = payload[Symbol.iterator]() 27 | let args = { 28 | org_id: it.next().value, 29 | campus_id: it.next().value, 30 | school_id: it.next().value, 31 | school_room_name: it.next().value 32 | } 33 | 34 | let promise = post(API.ADD_CLASSROOM, args, { headers: headers }) 35 | return httpResultProxy(promise) 36 | } 37 | -------------------------------------------------------------------------------- /src/services/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 公共Service 3 | */ 4 | 5 | import * as API from './API' 6 | import { get } from '../utils/http' 7 | import { httpResultProxy } from '../utils/proxy' 8 | import CommonVO from './model/CommonVO' 9 | 10 | let headers = { 11 | 'plat': 'principal-plat' 12 | } 13 | 14 | /** 15 | * 获取年份 16 | */ 17 | export function getYears (orgId) { 18 | let promise = get(API.GET_YEARS + `/${orgId}`, null, { headers: headers }) 19 | return httpResultProxy(promise, CommonVO) 20 | } 21 | 22 | /** 23 | * 获取学期 24 | */ 25 | export function getTerms (orgId) { 26 | let promise = get(API.GET_TERMS + `/${orgId}`, null, { headers: headers }) 27 | return httpResultProxy(promise, CommonVO) 28 | } 29 | 30 | /** 31 | * 获取年级 32 | */ 33 | export function getGrades (orgId) { 34 | let promise = get(API.GET_GRADES + `/${orgId}`, null, { headers: headers }) 35 | return httpResultProxy(promise, CommonVO) 36 | } 37 | -------------------------------------------------------------------------------- /src/services/model/AccountVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 账号信息VO 3 | */ 4 | 5 | export default class AccountVO { 6 | constructor (data) { 7 | this.userId = data || '' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/services/model/CampusVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 校区信息VO 3 | */ 4 | 5 | export default class CampusVO { 6 | constructor (data) { 7 | data = data || {} 8 | 9 | this.id = data.campus_id || '' 10 | this.name = data.campus_name || '' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/model/ClassroomVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 教室信息VO 3 | */ 4 | 5 | export default class ClassroomVO { 6 | constructor (data) { 7 | data = data || {} 8 | 9 | this.id = data.classroom_id || '' 10 | this.name = data.classroom_name || '' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/model/CommonVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 通用信息VO 3 | */ 4 | 5 | export default class CommonVO { 6 | constructor (data) { 7 | data = data || {} 8 | 9 | this.id = data.id || '' 10 | this.name = data.name || '' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/model/RoleVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 角色信息VO 3 | */ 4 | 5 | export default class RoleVO { 6 | constructor (data) { 7 | data = data || {} 8 | 9 | this.id = data.role_id || '' 10 | this.name = data.role_name || '' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/model/SchoolVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务中心VO 3 | */ 4 | 5 | export default class SchoolVO { 6 | constructor (data) { 7 | data = data || {} 8 | 9 | this.id = data.school_id || '' 10 | this.name = data.school_name || '' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/model/SubjectVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 学科信息VO 3 | */ 4 | 5 | export default class SubjectVO { 6 | constructor (data) { 7 | data = data || {} 8 | 9 | this.id = data.subject_id || '' 10 | this.name = data.subject_name || '' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/model/TeacherVO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 教师信息VO 3 | */ 4 | 5 | export default class TeacherVO { 6 | constructor (data) { 7 | data = data || {} 8 | 9 | this.teacherId = data.teacher_id || '' 10 | this.teacherName = data.teacher_name || '' 11 | this.mobile = data.mobile || '' 12 | this.roleName = data.role_name || '' 13 | this.subjectName = data.subject_name || '' 14 | this.campusName = data.campus_name || '' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/services/role.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 角色管理Service 3 | */ 4 | 5 | import * as API from './API' 6 | import { get, post } from '../utils/http' 7 | import { httpResultProxy } from '../utils/proxy' 8 | import RoleVO from './model/RoleVO' 9 | 10 | let headers = { 11 | 'plat': 'principal-plat' 12 | } 13 | 14 | /** 15 | * 获取角色 16 | */ 17 | export function getRoles (orgId) { 18 | let promise = get(API.GET_ROLES + `/${orgId}`, null, { headers: headers }) 19 | return httpResultProxy(promise, RoleVO) 20 | } 21 | 22 | /** 23 | * 添加角色 24 | */ 25 | export function addRole (payload) { 26 | let it = payload[Symbol.iterator]() 27 | let args = { 28 | org_id: it.next().value, 29 | role_name: it.next().value 30 | } 31 | 32 | let promise = post(API.ADD_ROLE, args, { headers: headers }) 33 | return httpResultProxy(promise) 34 | } 35 | -------------------------------------------------------------------------------- /src/services/subject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 学科管理Service 3 | */ 4 | 5 | import * as API from './API' 6 | import { get, post } from '../utils/http' 7 | import { httpResultProxy } from '../utils/proxy' 8 | import SubjectVO from './model/SubjectVO' 9 | 10 | let headers = { 11 | 'plat': 'principal-plat' 12 | } 13 | 14 | /** 15 | * 获取学科 16 | */ 17 | export function getSubjects (orgId) { 18 | let promise = get(API.GET_SUBJECTS + `/${orgId}`, null, { headers: headers }) 19 | return httpResultProxy(promise, SubjectVO) 20 | } 21 | 22 | /** 23 | * 添加学科 24 | */ 25 | export function addSubject (payload) { 26 | let it = payload[Symbol.iterator]() 27 | let args = { 28 | org_id: it.next().value, 29 | subject_name: it.next().value 30 | } 31 | 32 | let promise = post(API.ADD_SUBJECT, args, { headers: headers }) 33 | return httpResultProxy(promise) 34 | } 35 | -------------------------------------------------------------------------------- /src/services/teacher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 教师管理Service 3 | */ 4 | 5 | import * as API from './API' 6 | import { get, post } from '../utils/http' 7 | import { httpResultProxy } from '../utils/proxy' 8 | import TeacherVO from './model/TeacherVO' 9 | 10 | let headers = { 11 | 'plat': 'principal-plat' 12 | } 13 | 14 | /** 15 | * 获取教师 16 | */ 17 | export function getTeachers (orgId) { 18 | let promise = get(API.GET_REGION_TEACHERS + `/${orgId}`, null, { headers: headers }) 19 | return httpResultProxy(promise, TeacherVO) 20 | } 21 | 22 | /** 23 | * 添加教师 24 | */ 25 | export function addTeacher (payload) { 26 | let it = payload[Symbol.iterator]() 27 | let args = { 28 | campus_id: it.next().value, 29 | desc: it.next().value, 30 | grade_id: it.next().value, 31 | nature_work: it.next().value, 32 | org_id: it.next().value, 33 | role_id: it.next().value, 34 | subject_id: it.next().value, 35 | teacher_email: it.next().value, 36 | teacher_mobil: it.next().value, 37 | teacher_name: it.next().value, 38 | teacher_sex: it.next().value 39 | } 40 | 41 | let promise = post(API.ADD_TEACHER, args, { headers: headers }) 42 | return httpResultProxy(promise) 43 | } 44 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局action处理 3 | */ 4 | 5 | import * as types from './mutation-types' 6 | 7 | /** 8 | * 设置页面进度 9 | */ 10 | export const setPageProgress = ({ commit }, progress) => { 11 | commit(types.SET_PAGE_PROGRESS, progress) 12 | if (progress === 100) { 13 | setTimeout(() => { 14 | commit(types.SET_PAGE_PROGRESS, 0) 15 | }, 500) 16 | } 17 | } 18 | 19 | /** 20 | * 重置STORE 21 | */ 22 | export const resetStore = ({ commit }) => { 23 | commit(types.RESET_STATE) 24 | } 25 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局getters函数 3 | */ 4 | 5 | export { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import * as actions from './actions' 4 | import * as getters from './getters' 5 | import { state, mutations } from './mutations' 6 | import account from './modules/account' 7 | 8 | Vue.use(Vuex) 9 | 10 | let isDebugMode = process.env.NODE_ENV !== 'production' 11 | 12 | const store = new Vuex.Store({ 13 | state, 14 | getters, 15 | actions, 16 | mutations, 17 | modules: { 18 | account 19 | }, 20 | strict: isDebugMode 21 | }) 22 | 23 | // 热重载 24 | if (module.hot) { 25 | module.hot.accept([ 26 | './getters', 27 | './actions', 28 | './mutations', 29 | './modules/account' 30 | ], () => { 31 | // 因为 babel 6 的模块编译格式问题,这里需要加上 .default 32 | store.hotUpdate({ 33 | getters: require('./getters').default, 34 | actions: require('./actions').default, 35 | mutations: require('./mutations').default, 36 | modules: { 37 | account: require('./modules/account').default 38 | } 39 | }) 40 | }) 41 | } 42 | 43 | export default store 44 | -------------------------------------------------------------------------------- /src/store/modules/account.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户模块 3 | */ 4 | 5 | import * as service from '../../services/account' 6 | import * as types from '../mutation-types' 7 | import AccountVO from '../../services/model/AccountVO' 8 | 9 | let userId = window.sessionStorage.userId || '' 10 | 11 | // initial state 12 | const state = new AccountVO(userId) 13 | 14 | // getters 15 | const getters = { 16 | hasLoggedIn: state => state.userId 17 | } 18 | 19 | // actions 20 | const actions = { 21 | login ({ commit, state }, payload) { 22 | return service.login(payload).then(account => { 23 | window.sessionStorage.userId = account.userId 24 | commit(types.SET_ACCOUNT, account) 25 | }) 26 | }, 27 | regist ({ commit, state }, payload) { 28 | return service.regist(payload).then(data => { 29 | window.sessionStorage.userId = data 30 | commit(types.SET_ACCOUNT, { userId: data }) 31 | }) 32 | }, 33 | logout ({ commit, state }) { 34 | window.sessionStorage.clear() 35 | commit(types.RESET_STATE) 36 | } 37 | } 38 | 39 | // mutations 40 | const mutations = { 41 | [types.SET_ACCOUNT] (state, data) { 42 | Object.assign(state, data) 43 | }, 44 | [types.RESET_STATE] (state) { 45 | state.userId = '' // TODO 46 | } 47 | } 48 | 49 | export default { 50 | state, 51 | getters, 52 | actions, 53 | mutations 54 | } 55 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mutation类型配置 3 | */ 4 | 5 | export const SET_PAGE_PROGRESS = 'SET_PAGE_PROGRESS' 6 | export const RESET_STATE = 'RESET_STATE' 7 | 8 | export const SET_ACCOUNT = 'SET_ACCOUNT' 9 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局mutation处理 3 | */ 4 | 5 | import * as types from './mutation-types' 6 | 7 | export const state = { 8 | pageProgress: 0 9 | } 10 | 11 | export const mutations = { 12 | // 设置页面进度 13 | [types.SET_PAGE_PROGRESS] (state, progress) { 14 | state.pageProgress = progress 15 | }, 16 | // 重置state 17 | [types.RESET_STATE] (state) { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP数据通信模块 3 | */ 4 | 5 | import 'whatwg-fetch' 6 | import qs from 'querystring' 7 | 8 | // 缺省请求头 9 | const defaultHeaders = { 10 | 'Accept': 'application/json', 11 | 'Content-Type': 'application/json' // application/x-www-form-urlencoded 12 | } 13 | 14 | // 请求参数加工 15 | function mutate (url, { headers, body, query, ...options }) { 16 | headers = { ...defaultHeaders, ...headers } 17 | options.headers = headers 18 | 19 | if (body) { 20 | if (typeof body === 'object') { 21 | body = JSON.stringify(body) 22 | } 23 | options.body = body 24 | } 25 | 26 | if (query) { 27 | if (typeof query === 'object') { 28 | query = qs.stringify(query) 29 | } 30 | url += (url.indexOf('?') !== -1) ? '&' : '?' 31 | url += query 32 | } 33 | 34 | options.credentials = 'omit' // omit,same-origin,include 35 | options.mode = 'same-origin' // "same-origin", "cors", "no-cors", "navigate", or "websocket" 36 | 37 | return [url, options] 38 | } 39 | 40 | function checkStatus (response) { 41 | if (response.status >= 200 && response.status < 300) { 42 | return response 43 | } else { 44 | var error = new Error(response.statusText) 45 | error.response = response 46 | throw error 47 | } 48 | } 49 | 50 | function parseJSON (response) { 51 | return response.json() 52 | } 53 | 54 | /** 55 | * 接口调用服务 56 | * @param {String} url 接口API(必填) 57 | * @param {Object} options 请求参数(选填){headers, body, query, ...options} 58 | * @return {Object} Promise 59 | * @throws {Error} 60 | */ 61 | const callService = (url, options = {}) => { 62 | return window.fetch(...mutate(url, options)) 63 | .then(checkStatus) 64 | .then(parseJSON) 65 | .catch(error => { 66 | console.log(error) 67 | throw error 68 | }) 69 | } 70 | 71 | export default callService 72 | 73 | export const get = (url, args, options = {}) => { 74 | options.method = 'GET' 75 | options.query = args 76 | return callService(url, options) 77 | } 78 | 79 | export const post = (url, args, options = {}) => { 80 | options.method = 'POST' 81 | options.body = args 82 | return callService(url, options) 83 | } 84 | 85 | export const put = (url, args, options = {}) => { 86 | options.method = 'PUT' 87 | options.body = args 88 | return callService(url, options) 89 | } 90 | 91 | export const patch = (url, args, options = {}) => { 92 | options.method = 'PATCH' 93 | options.body = args 94 | return callService(url, options) 95 | } 96 | 97 | export const del = (url, args, options = {}) => { 98 | options.method = 'DELETE' 99 | options.query = args 100 | return callService(url, options) 101 | } 102 | -------------------------------------------------------------------------------- /src/utils/md5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * md5加密(with secretkey) 3 | */ 4 | 5 | import md5 from 'blueimp-md5' 6 | 7 | export default value => md5(value, 'tal') 8 | -------------------------------------------------------------------------------- /src/utils/proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 对http请求参数代理 3 | */ 4 | export function argumentsProxy (vo) { 5 | if (vo) { 6 | if (typeof vo.revert === 'function') { 7 | return vo.revert() 8 | } else { 9 | return vo 10 | } 11 | } else { 12 | return null 13 | } 14 | } 15 | 16 | /** 17 | * 对http结果代理 18 | */ 19 | export function httpResultProxy (resultPromise, VO = null) { 20 | return new Promise((resolve, reject) => { 21 | resultPromise.then(res => { 22 | if (res) { 23 | if (res.rlt === 'true') { 24 | resolve(mapVO(VO, res.data)) 25 | } else { 26 | reject(new Error(res.msg)) 27 | } 28 | } else { 29 | reject(new Error('服务器返回数据异常')) 30 | } 31 | }, (error) => { 32 | throw error 33 | }).catch(error => { 34 | if (error.response) { 35 | // throw new Error('服务器异常!') 36 | reject(new Error('服务器异常!')) 37 | } else { 38 | // throw new Error('系统异常!') 39 | reject(new Error('服务器异常!')) 40 | } 41 | }) 42 | }) 43 | } 44 | 45 | function mapVO (VO, data) { 46 | if (VO) { 47 | if (Array.isArray(data)) { 48 | let arr = [] 49 | for (let item of data) { 50 | arr.push(new VO(item)) 51 | } 52 | return arr 53 | } else { 54 | return new VO(data) 55 | } 56 | } else { 57 | return data 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具类、实用函数 3 | * @see lodash 4 | */ 5 | 6 | /** 7 | * 找出第一个符合条件的数组成员 8 | */ 9 | export function findItem (arr, key, value) { 10 | return arr.find(item => item[key] === value) 11 | } 12 | 13 | export function findNameById (arr, id) { 14 | let item = findItem(arr, 'id', id) 15 | return item ? item.name : '' 16 | } 17 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 31 | 32 | 42 | -------------------------------------------------------------------------------- /src/views/Index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | 29 | 42 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 85 | 86 | 105 | -------------------------------------------------------------------------------- /src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /src/views/Regist.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 102 | 103 | 124 | -------------------------------------------------------------------------------- /src/views/main/CampusManage.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 189 | 190 | 193 | -------------------------------------------------------------------------------- /src/views/main/ClassroomManage.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 218 | 219 | 222 | -------------------------------------------------------------------------------- /src/views/main/Index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /src/views/main/RoleManage.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 138 | 139 | 142 | -------------------------------------------------------------------------------- /src/views/main/TeacherManage.vue: -------------------------------------------------------------------------------- 1 | 129 | 130 | 277 | 278 | 281 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping6688/vue-spa-project/2e4a884259a953b64a1a491bba7f3b73d76fbfa8/static/.gitkeep -------------------------------------------------------------------------------- /static/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoping6688/vue-spa-project/2e4a884259a953b64a1a491bba7f3b73d76fbfa8/static/img/screenshot.png -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' } 30 | ] 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from '@/components/Hello' 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------