├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .postcssrc.js ├── LICENSE ├── README.md ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── favicon.ico ├── gifs ├── 2login.gif ├── dynamictable.gif ├── echarts.gif ├── editor.gif ├── errorlog.gif ├── excel.png ├── leftmenu.gif ├── login.png ├── order.gif ├── table.gif ├── tabs.gif ├── theme.gif ├── upload1.gif └── uploadAvatar.gif ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── api │ ├── article.js │ ├── login.js │ ├── qiniu.js │ ├── remoteSearch.js │ └── transaction.js ├── assets │ ├── 401_images │ │ └── 401.gif │ ├── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png │ ├── custom-theme │ │ ├── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ └── index.css │ └── echarts-macarons.js ├── components │ ├── BackToTop │ │ └── index.vue │ ├── Breadcrumb │ │ └── index.vue │ ├── Charts │ │ ├── keyboard.vue │ │ ├── lineMarker.vue │ │ └── mixChart.vue │ ├── DndList │ │ └── index.vue │ ├── Dropzone │ │ └── index.vue │ ├── Echarts │ │ └── Echarts.vue │ ├── ErrorLog │ │ └── index.vue │ ├── GithubCorner │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── ImageCropper │ │ ├── index.vue │ │ ├── lang.js │ │ ├── upload.css │ │ └── utils.js │ ├── JsonEditor │ │ └── index.vue │ ├── LangSelect │ │ └── index.vue │ ├── MDinput │ │ └── index.vue │ ├── MarkdownEditor │ │ └── index.vue │ ├── PanThumb │ │ └── index.vue │ ├── Screenfull │ │ └── index.vue │ ├── ScrollBar │ │ └── index.vue │ ├── ScrollPane │ │ └── index.vue │ ├── Share │ │ └── dropdownMenu.vue │ ├── Sticky │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── TextHoverEffect │ │ └── Mallki.vue │ ├── ThemePicker │ │ └── index.vue │ ├── Tinymce │ │ ├── components │ │ │ └── editorImage.vue │ │ └── index.vue │ ├── Upload │ │ ├── singleImage.vue │ │ ├── singleImage2.vue │ │ └── singleImage3.vue │ └── UploadExcel │ │ └── index.vue ├── directive │ ├── clipboard │ │ ├── clipboard.js │ │ └── index.js │ ├── sticky.js │ └── waves │ │ ├── index.js │ │ ├── waves.css │ │ └── waves.js ├── errorLog.js ├── filters │ └── index.js ├── icons │ ├── index.js │ └── svg │ │ ├── 404.svg │ │ ├── bug.svg │ │ ├── chart.svg │ │ ├── clipboard.svg │ │ ├── component.svg │ │ ├── dashboard.svg │ │ ├── documentation.svg │ │ ├── drag.svg │ │ ├── email.svg │ │ ├── example.svg │ │ ├── excel.svg │ │ ├── eye.svg │ │ ├── form.svg │ │ ├── icon.svg │ │ ├── international.svg │ │ ├── language.svg │ │ ├── lock.svg │ │ ├── message.svg │ │ ├── money.svg │ │ ├── password.svg │ │ ├── people.svg │ │ ├── peoples.svg │ │ ├── qq.svg │ │ ├── shoppingCard.svg │ │ ├── star.svg │ │ ├── tab.svg │ │ ├── table.svg │ │ ├── theme.svg │ │ ├── user.svg │ │ ├── wechat.svg │ │ └── zip.svg ├── lang │ ├── en.js │ ├── index.js │ └── zh.js ├── main.js ├── mock │ ├── article.js │ ├── index.js │ ├── login.js │ ├── remoteSearch.js │ └── transaction.js ├── permission.js ├── router │ ├── _import_development.js │ ├── _import_production.js │ ├── index.js │ └── router.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── errorLog.js │ │ ├── permission.js │ │ ├── tagsView.js │ │ └── user.js ├── stores │ └── index.js ├── styles │ ├── btn.scss │ ├── color-definition.scss │ ├── common.scss │ ├── element-ui.scss │ ├── index.scss │ ├── mixin.scss │ ├── paddingMargin.scss │ ├── sidebar.scss │ ├── transition.scss │ └── variables.scss ├── utils │ ├── auth.js │ ├── clipboard.js │ ├── createUniqueString.js │ ├── i18n.js │ ├── index.js │ ├── openWindow.js │ ├── request.js │ └── validate.js ├── vendor │ ├── Blob.js │ ├── Export2Excel.js │ └── Export2Zip.js └── views │ ├── charts │ ├── keyboard.vue │ ├── line.vue │ └── mixChart.vue │ ├── clipboard │ └── index.vue │ ├── components-demo │ ├── avatarUpload.vue │ ├── backToTop.vue │ ├── countTo.vue │ ├── dndList.vue │ ├── dropzone.vue │ ├── jsonEditor.vue │ ├── markdown.vue │ ├── mixin.vue │ ├── splitpane.vue │ ├── sticky.vue │ └── tinymce.vue │ ├── dashboard │ ├── admin │ │ ├── components │ │ │ ├── BarChart.vue │ │ │ ├── BoxCard.vue │ │ │ ├── LineChart.vue │ │ │ ├── PanelGroup.vue │ │ │ ├── PieChart.vue │ │ │ ├── RaddarChart.vue │ │ │ ├── TodoList │ │ │ │ ├── Todo.vue │ │ │ │ ├── index.scss │ │ │ │ └── index.vue │ │ │ └── TransactionTable.vue │ │ └── index.vue │ ├── editor │ │ └── index.vue │ └── index.vue │ ├── documentation │ └── index.vue │ ├── errorLog │ ├── errorTestA.vue │ ├── errorTestB.vue │ └── index.vue │ ├── errorPage │ ├── 401.vue │ └── 404.vue │ ├── example │ ├── tab │ │ ├── components │ │ │ └── tabPane.vue │ │ └── index.vue │ └── table │ │ ├── complexTable.vue │ │ ├── dragTable.vue │ │ ├── dynamicTable │ │ ├── fixedThead.vue │ │ ├── index.vue │ │ └── unfixedThead.vue │ │ ├── index.vue │ │ └── inlineEditTable.vue │ ├── excel │ ├── exportExcel.vue │ ├── selectExcel.vue │ └── uploadExcel.vue │ ├── form │ ├── components │ │ └── ArticleDetail.vue │ ├── create.vue │ └── edit.vue │ ├── i18n-demo │ ├── index.vue │ └── local.js │ ├── layout │ ├── Layout.vue │ └── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Sidebar │ │ ├── SidebarItem.vue │ │ └── index.vue │ │ ├── TagsView.vue │ │ └── index.js │ ├── login │ ├── Login.vue │ ├── authredirect.vue │ ├── index.vue │ └── socialsignin.vue │ ├── main │ ├── Main.vue │ ├── charts │ │ └── Charts.vue │ └── upload │ │ └── UpLoad.vue │ ├── permission │ └── index.vue │ ├── qiniu │ └── upload.vue │ ├── register │ └── register.vue │ ├── svg-icons │ ├── generateIconsView.js │ └── index.vue │ ├── theme │ └── index.vue │ └── zip │ └── index.vue ├── static └── .gitkeep └── test ├── e2e ├── custom-assertions │ └── elementCount.js ├── nightwatch.conf.js ├── runner.js └── specs │ └── test.js └── unit ├── .eslintrc ├── jest.conf.js ├── setup.js └── specs └── HelloWorld.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": ["transform-vue-jsx", "transform-runtime",["component", [ 12 | { 13 | "libraryName": "element-ui", 14 | "styleLibraryName": "theme-chalk" 15 | } 16 | ]]], 17 | "env": { 18 | "test": { 19 | "presets": ["env", "stage-2"], 20 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.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/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://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/standard/standard/blob/master/docs/RULES-en.md 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | rules: { 20 | // allow async-await 21 | 'generator-star-spacing': 'off', 22 | // allow debugger during development 23 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.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 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver 2 | phantomjs_cdnurl=http://npm.taobao.org/mirrors/phantomjs 3 | operadriver_cdnurl=http://npm.taobao.org/mirrors/operadriver 4 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # manage-platform 2 | > 基于vue、element-ui、vue-router、axios、echarts的管理系统 3 | 4 | [线上地址] https://github.com/liwudi/manage-platform.git 5 | 6 | ## 相关demo 7 | 8 | > https://github.com/liwudi/Vue.git 9 | 10 | ## Build Setup 11 | 12 | ### 1、Clone project 13 | git clone https://github.com/liwudi/manage-platform.git 14 | 15 | ### 2、Install dependencies 16 | npm install 17 | 18 | ### 3、建议不要用cnpm 安装有各种诡异的bug 可以通过如下操作解决npm速度慢的问题 19 | npm install --registry=https://registry.npm.taobao.org 20 | 21 | ### 4、serve with hot reload at localhost:9528 22 | npm run dev 23 | 24 | ### 5、build for production with minification 25 | npm run build 26 | 27 | ### 6、build for production and view the bundle analyzer report 28 | npm run build --report 29 | 30 | ## 相关文档 31 | 32 | ### vue-router文档 33 | https://router.vuejs.org/zh-cn/ 34 | 35 | ### ElementUi 36 | http://element-cn.eleme.io/#/zh-CN 37 | 38 | ### axios中文使用文档 39 | https://www.kancloud.cn/yunye/axios/234845 40 | 41 | ### moment中文文档 42 | http://momentjs.cn/docs/ 43 | 44 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 45 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/build/logo.png -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | } 78 | ] 79 | }, 80 | node: { 81 | // prevent webpack from injecting useless setImmediate polyfill because Vue 82 | // source contains it (although only uses it if it's native). 83 | setImmediate: false, 84 | // prevent webpack from injecting mocks to Node native modules 85 | // that does not make sense for the client 86 | dgram: 'empty', 87 | fs: 'empty', 88 | net: 'empty', 89 | tls: 'empty', 90 | child_process: 'empty' 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.8 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: false, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true, 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/favicon.ico -------------------------------------------------------------------------------- /gifs/2login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/2login.gif -------------------------------------------------------------------------------- /gifs/dynamictable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/dynamictable.gif -------------------------------------------------------------------------------- /gifs/echarts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/echarts.gif -------------------------------------------------------------------------------- /gifs/editor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/editor.gif -------------------------------------------------------------------------------- /gifs/errorlog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/errorlog.gif -------------------------------------------------------------------------------- /gifs/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/excel.png -------------------------------------------------------------------------------- /gifs/leftmenu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/leftmenu.gif -------------------------------------------------------------------------------- /gifs/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/login.png -------------------------------------------------------------------------------- /gifs/order.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/order.gif -------------------------------------------------------------------------------- /gifs/table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/table.gif -------------------------------------------------------------------------------- /gifs/tabs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/tabs.gif -------------------------------------------------------------------------------- /gifs/theme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/theme.gif -------------------------------------------------------------------------------- /gifs/upload1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/upload1.gif -------------------------------------------------------------------------------- /gifs/uploadAvatar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/gifs/uploadAvatar.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | manage-platform 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | -------------------------------------------------------------------------------- /src/api/article.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function fetchList(query) { 4 | return request({ 5 | url: '/article/list', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function fetchArticle() { 12 | return request({ 13 | url: '/article/detail', 14 | method: 'get' 15 | }) 16 | } 17 | 18 | export function fetchPv(pv) { 19 | return request({ 20 | url: '/article/pv', 21 | method: 'get', 22 | params: { pv } 23 | }) 24 | } 25 | 26 | export function createArticle(data) { 27 | return request({ 28 | url: '/article/create', 29 | method: 'post', 30 | data 31 | }) 32 | } 33 | 34 | export function updateArticle(data) { 35 | return request({ 36 | url: '/article/update', 37 | method: 'post', 38 | data 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function loginByUsername(username, password) { 4 | const data = { 5 | username, 6 | password 7 | } 8 | return request({ 9 | url: '/login/login', 10 | method: 'post', 11 | data 12 | }) 13 | } 14 | 15 | export function logout() { 16 | return request({ 17 | url: '/login/logout', 18 | method: 'post' 19 | }) 20 | } 21 | 22 | export function getUserInfo(token) { 23 | return request({ 24 | url: '/user/info', 25 | method: 'get', 26 | params: { token } 27 | }) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/api/qiniu.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getToken() { 4 | return request({ 5 | url: '/qiniu/upload/token', // 假地址 自行替换 6 | method: 'get' 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/api/remoteSearch.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function userSearch(name) { 4 | return request({ 5 | url: '/search/user', 6 | method: 'get', 7 | params: { name } 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/api/transaction.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function fetchList(query) { 4 | return request({ 5 | url: '/transaction/list', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/401_images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/src/assets/401_images/401.gif -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/src/assets/custom-theme/fonts/element-icons.ttf -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/src/assets/custom-theme/fonts/element-icons.woff -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 42 | 43 | 55 | -------------------------------------------------------------------------------- /src/components/Echarts/Echarts.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /src/components/GithubCorner/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 46 | -------------------------------------------------------------------------------- /src/components/ImageCropper/lang.js: -------------------------------------------------------------------------------- 1 | const langBag = { 2 | zh: { 3 | hint: '点击,或拖动图片至此处', 4 | loading: '正在上传……', 5 | noSupported: '浏览器不支持该功能,请使用IE10以上或其他现在浏览器!', 6 | success: '上传成功', 7 | fail: '图片上传失败', 8 | preview: '头像预览', 9 | btn: { 10 | off: '取消', 11 | close: '关闭', 12 | back: '上一步', 13 | save: '保存' 14 | }, 15 | error: { 16 | onlyImg: '仅限图片格式', 17 | outOfSize: '单文件大小不能超过 ', 18 | lowestPx: '图片最低像素为(宽*高):' 19 | } 20 | }, 21 | en: { 22 | hint: 'Click, or drag the file here', 23 | loading: 'Uploading……', 24 | noSupported: 'Browser does not support, please use IE10+ or other browsers', 25 | success: 'Upload success', 26 | fail: 'Upload failed', 27 | preview: 'Preview', 28 | btn: { 29 | off: 'Cancel', 30 | close: 'Close', 31 | back: 'Back', 32 | save: 'Save' 33 | }, 34 | error: { 35 | onlyImg: 'Image only', 36 | outOfSize: 'Image exceeds size limit: ', 37 | lowestPx: 'The lowest pixel in the image: ' 38 | } 39 | } 40 | } 41 | export default langBag 42 | -------------------------------------------------------------------------------- /src/components/ImageCropper/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /** 4 | * 5 | * @param e 6 | * @param arg_opts 7 | * @returns {boolean} 8 | */ 9 | export function effectRipple(e, arg_opts) { 10 | let opts = Object.assign({ 11 | ele: e.target, // 波纹作用元素 12 | type: 'hit', // hit点击位置扩散 center中心点扩展 13 | bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 14 | }, arg_opts), 15 | target = opts.ele; 16 | if (target) { 17 | let rect = target.getBoundingClientRect(), 18 | ripple = target.querySelector('.e-ripple'); 19 | if (!ripple) { 20 | ripple = document.createElement('span'); 21 | ripple.className = 'e-ripple'; 22 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'; 23 | target.appendChild(ripple); 24 | } else { 25 | ripple.className = 'e-ripple'; 26 | } 27 | switch (opts.type) { 28 | case 'center': 29 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'; 30 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'; 31 | break; 32 | default: 33 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'; 34 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'; 35 | } 36 | ripple.style.backgroundColor = opts.bgc; 37 | ripple.className = 'e-ripple z-active'; 38 | return false; 39 | } 40 | } 41 | // database64文件格式转换为2进制 42 | /** 43 | * 44 | * @param data 45 | * @param mime 46 | * @returns {*} 47 | */ 48 | export function data2blob(data, mime) { 49 | // dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了 50 | data = data.split(',')[1]; 51 | data = window.atob(data); 52 | var ia = new Uint8Array(data.length); 53 | for (var i = 0; i < data.length; i++) { 54 | ia[i] = data.charCodeAt(i); 55 | } 56 | // canvas.toDataURL 返回的默认格式就是 image/png 57 | return new Blob([ia], {type: mime}); 58 | }; 59 | -------------------------------------------------------------------------------- /src/components/JsonEditor/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 55 | 56 | 64 | -------------------------------------------------------------------------------- /src/components/LangSelect/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 32 | 33 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/MarkdownEditor/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 80 | 81 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/components/Screenfull/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 55 | 56 | 66 | -------------------------------------------------------------------------------- /src/components/ScrollBar/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | 44 | 58 | -------------------------------------------------------------------------------- /src/components/ScrollPane/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 61 | 62 | 73 | -------------------------------------------------------------------------------- /src/components/Share/dropdownMenu.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | 37 | 98 | -------------------------------------------------------------------------------- /src/components/Sticky/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 77 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /src/components/TextHoverEffect/Mallki.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 24 | 114 | -------------------------------------------------------------------------------- /src/directive/clipboard/clipboard.js: -------------------------------------------------------------------------------- 1 | // Inspired by https://github.com/Inndy/vue-clipboard2 2 | const Clipboard = require('clipboard') 3 | if (!Clipboard) { 4 | throw new Error('you shold npm install `clipboard` --save at first ') 5 | } 6 | 7 | export default { 8 | bind(el, binding) { 9 | if (binding.arg === 'success') { 10 | el._v_clipboard_success = binding.value 11 | } else if (binding.arg === 'error') { 12 | el._v_clipboard_error = binding.value 13 | } else { 14 | const clipboard = new Clipboard(el, { 15 | text() { return binding.value }, 16 | action() { return binding.arg === 'cut' ? 'cut' : 'copy' } 17 | }) 18 | clipboard.on('success', e => { 19 | const callback = el._v_clipboard_success 20 | callback && callback(e) // eslint-disable-line 21 | }) 22 | clipboard.on('error', e => { 23 | const callback = el._v_clipboard_error 24 | callback && callback(e) // eslint-disable-line 25 | }) 26 | el._v_clipboard = clipboard 27 | } 28 | }, 29 | update(el, binding) { 30 | if (binding.arg === 'success') { 31 | el._v_clipboard_success = binding.value 32 | } else if (binding.arg === 'error') { 33 | el._v_clipboard_error = binding.value 34 | } else { 35 | el._v_clipboard.text = function() { return binding.value } 36 | el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' } 37 | } 38 | }, 39 | unbind(el, binding) { 40 | if (binding.arg === 'success') { 41 | delete el._v_clipboard_success 42 | } else if (binding.arg === 'error') { 43 | delete el._v_clipboard_error 44 | } else { 45 | el._v_clipboard.destroy() 46 | delete el._v_clipboard 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/directive/clipboard/index.js: -------------------------------------------------------------------------------- 1 | import Clipboard from './clipboard' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('Clipboard', Clipboard) 5 | } 6 | 7 | if (window.Vue) { 8 | window.clipboard = Clipboard 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | Clipboard.install = install 13 | export default Clipboard 14 | -------------------------------------------------------------------------------- /src/directive/sticky.js: -------------------------------------------------------------------------------- 1 | const vueSticky = {} 2 | let listenAction 3 | vueSticky.install = Vue => { 4 | Vue.directive('sticky', { 5 | inserted(el, binding) { 6 | const params = binding.value || {} 7 | const stickyTop = params.stickyTop || 0 8 | const zIndex = params.zIndex || 1000 9 | const elStyle = el.style 10 | 11 | elStyle.position = '-webkit-sticky' 12 | elStyle.position = 'sticky' 13 | // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary) 14 | // if (~elStyle.position.indexOf('sticky')) { 15 | // elStyle.top = `${stickyTop}px`; 16 | // elStyle.zIndex = zIndex; 17 | // return 18 | // } 19 | const elHeight = el.getBoundingClientRect().height 20 | const elWidth = el.getBoundingClientRect().width 21 | elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}` 22 | 23 | const parentElm = el.parentNode || document.documentElement 24 | const placeholder = document.createElement('div') 25 | placeholder.style.display = 'none' 26 | placeholder.style.width = `${elWidth}px` 27 | placeholder.style.height = `${elHeight}px` 28 | parentElm.insertBefore(placeholder, el) 29 | 30 | let active = false 31 | 32 | const getScroll = (target, top) => { 33 | const prop = top ? 'pageYOffset' : 'pageXOffset' 34 | const method = top ? 'scrollTop' : 'scrollLeft' 35 | let ret = target[prop] 36 | if (typeof ret !== 'number') { 37 | ret = window.document.documentElement[method] 38 | } 39 | return ret 40 | } 41 | 42 | const sticky = () => { 43 | if (active) { 44 | return 45 | } 46 | if (!elStyle.height) { 47 | elStyle.height = `${el.offsetHeight}px` 48 | } 49 | 50 | elStyle.position = 'fixed' 51 | elStyle.width = `${elWidth}px` 52 | placeholder.style.display = 'inline-block' 53 | active = true 54 | } 55 | 56 | const reset = () => { 57 | if (!active) { 58 | return 59 | } 60 | 61 | elStyle.position = '' 62 | placeholder.style.display = 'none' 63 | active = false 64 | } 65 | 66 | const check = () => { 67 | const scrollTop = getScroll(window, true) 68 | const offsetTop = el.getBoundingClientRect().top 69 | if (offsetTop < stickyTop) { 70 | sticky() 71 | } else { 72 | if (scrollTop < elHeight + stickyTop) { 73 | reset() 74 | } 75 | } 76 | } 77 | listenAction = () => { 78 | check() 79 | } 80 | 81 | window.addEventListener('scroll', listenAction) 82 | }, 83 | 84 | unbind() { 85 | window.removeEventListener('scroll', listenAction) 86 | } 87 | }) 88 | } 89 | 90 | export default vueSticky 91 | 92 | -------------------------------------------------------------------------------- /src/directive/waves/index.js: -------------------------------------------------------------------------------- 1 | import waves from './waves' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('waves', waves) 5 | } 6 | 7 | if (window.Vue) { 8 | window.waves = waves 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | waves.install = install 13 | export default waves 14 | -------------------------------------------------------------------------------- /src/directive/waves/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | background-color: rgba(0, 0, 0, 0.15); 5 | background-clip: padding-box; 6 | pointer-events: none; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | -webkit-transform: scale(0); 12 | -ms-transform: scale(0); 13 | transform: scale(0); 14 | opacity: 1; 15 | } 16 | 17 | .waves-ripple.z-active { 18 | opacity: 0; 19 | -webkit-transform: scale(2); 20 | -ms-transform: scale(2); 21 | transform: scale(2); 22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; 26 | } -------------------------------------------------------------------------------- /src/directive/waves/waves.js: -------------------------------------------------------------------------------- 1 | import './waves.css' 2 | 3 | export default{ 4 | bind(el, binding) { 5 | el.addEventListener('click', e => { 6 | const customOpts = Object.assign({}, binding.value) 7 | const opts = Object.assign({ 8 | ele: el, // 波纹作用元素 9 | type: 'hit', // hit点击位置扩散center中心点扩展 10 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 11 | }, customOpts) 12 | const target = opts.ele 13 | if (target) { 14 | target.style.position = 'relative' 15 | target.style.overflow = 'hidden' 16 | const rect = target.getBoundingClientRect() 17 | let ripple = target.querySelector('.waves-ripple') 18 | if (!ripple) { 19 | ripple = document.createElement('span') 20 | ripple.className = 'waves-ripple' 21 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' 22 | target.appendChild(ripple) 23 | } else { 24 | ripple.className = 'waves-ripple' 25 | } 26 | switch (opts.type) { 27 | case 'center': 28 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px' 29 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px' 30 | break 31 | default: 32 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px' 33 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px' 34 | } 35 | ripple.style.backgroundColor = opts.color 36 | ripple.className = 'waves-ripple z-active' 37 | return false 38 | } 39 | }, false) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/errorLog.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from './store' 3 | 4 | // you can set only in production env show the error-log 5 | // if (process.env.NODE_ENV === 'production') { 6 | 7 | Vue.config.errorHandler = function(err, vm, info, a) { 8 | // Don't ask me why I use Vue.nextTick, it just a hack. 9 | // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500 10 | Vue.nextTick(() => { 11 | store.dispatch('addErrorLog', { 12 | err, 13 | vm, 14 | info, 15 | url: window.location.href 16 | }) 17 | console.error(err, info) 18 | }) 19 | } 20 | 21 | // } 22 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg组件 3 | import generateIconsView from '@/views/svg-icons/generateIconsView.js'// just for @/views/icons , you can delete it 4 | 5 | // register globally 6 | Vue.component('svg-icon', SvgIcon) 7 | 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | const req = require.context('./svg', false, /\.svg$/) 10 | const iconMap = requireAll(req) 11 | 12 | generateIconsView.generate(iconMap) // just for @/views/icons , you can delete it 13 | -------------------------------------------------------------------------------- /src/icons/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/clipboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/svg/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/excel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/international.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/language.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/svg/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/money.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/people.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/peoples.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/shoppingCard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/zip.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import Cookies from 'js-cookie' 4 | import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang 5 | import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui lang 6 | import enLocale from './en' 7 | import zhLocale from './zh' 8 | 9 | Vue.use(VueI18n) 10 | 11 | const messages = { 12 | en: { 13 | ...enLocale, 14 | ...elementEnLocale 15 | }, 16 | zh: { 17 | ...zhLocale, 18 | ...elementZhLocale 19 | } 20 | } 21 | 22 | const i18n = new VueI18n({ 23 | locale: Cookies.get('language') || 'en', // set locale 24 | messages // set locale messages 25 | }) 26 | 27 | export default i18n 28 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import 'normalize.css/normalize.css'// A modern alternative to CSS resets 4 | 5 | 6 | 7 | import ElementUi from 'element-ui'; 8 | import 'element-ui/lib/theme-chalk/index.css'; 9 | 10 | import '@/styles/common.scss'; // global css 11 | 12 | import App from './App'; 13 | import router from './router/router' 14 | import store from './stores' 15 | 16 | 17 | Vue.use(ElementUi, { 18 | size: 'medium' 19 | }); 20 | 21 | Vue.config.productionTip = false; 22 | 23 | new Vue({ 24 | el: '#app', 25 | router, 26 | store, 27 | template: '', 28 | components: { App } 29 | }); 30 | -------------------------------------------------------------------------------- /src/mock/article.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { param2Obj } from '@/utils' 3 | 4 | const List = [] 5 | const count = 100 6 | 7 | for (let i = 0; i < count; i++) { 8 | List.push(Mock.mock({ 9 | id: '@increment', 10 | timestamp: +Mock.Random.date('T'), 11 | author: '@first', 12 | reviewer: '@first', 13 | title: '@title(5, 10)', 14 | forecast: '@float(0, 100, 2, 2)', 15 | importance: '@integer(1, 3)', 16 | 'type|1': ['CN', 'US', 'JP', 'EU'], 17 | 'status|1': ['published', 'draft', 'deleted'], 18 | display_time: '@datetime', 19 | pageviews: '@integer(300, 5000)' 20 | })) 21 | } 22 | 23 | export default { 24 | getList: config => { 25 | const { importance, type, title, page = 1, limit = 20, sort } = param2Obj(config.url) 26 | 27 | let mockList = List.filter(item => { 28 | if (importance && item.importance !== +importance) return false 29 | if (type && item.type !== type) return false 30 | if (title && item.title.indexOf(title) < 0) return false 31 | return true 32 | }) 33 | 34 | if (sort === '-id') { 35 | mockList = mockList.reverse() 36 | } 37 | 38 | const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) 39 | 40 | return { 41 | total: mockList.length, 42 | items: pageList 43 | } 44 | }, 45 | getPv: () => ({ 46 | pvData: [{ key: 'PC', pv: 1024 }, { key: 'mobile', pv: 1024 }, { key: 'ios', pv: 1024 }, { key: 'android', pv: 1024 }] 47 | }), 48 | getArticle: () => ({ 49 | id: 120000000001, 50 | author: { key: 'mockPan' }, 51 | source_name: '原创作者', 52 | category_item: [{ key: 'global', name: '全球' }], 53 | comment_disabled: true, 54 | content: '

我是测试数据我是测试数据

"', 55 | content_short: '我是测试数据', 56 | display_time: +new Date(), 57 | image_uri: 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3', 58 | platforms: ['a-platform'], 59 | source_uri: 'https://github.com/PanJiaChen/vue-element-admin', 60 | status: 'published', 61 | tags: [], 62 | title: 'vue-element-admin' 63 | }), 64 | createArticle: () => ({ 65 | data: 'success' 66 | }), 67 | updateArticle: () => ({ 68 | data: 'success' 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import loginAPI from './login' 3 | import articleAPI from './article' 4 | import remoteSearchAPI from './remoteSearch' 5 | import transactionAPI from './transaction' 6 | 7 | // Mock.setup({ 8 | // timeout: '350-600' 9 | // }) 10 | 11 | // 登录相关 12 | Mock.mock(/\/login\/login/, 'post', loginAPI.loginByUsername) 13 | Mock.mock(/\/login\/logout/, 'post', loginAPI.logout) 14 | Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo) 15 | 16 | // 文章相关 17 | Mock.mock(/\/article\/list/, 'get', articleAPI.getList) 18 | Mock.mock(/\/article\/detail/, 'get', articleAPI.getArticle) 19 | Mock.mock(/\/article\/pv/, 'get', articleAPI.getPv) 20 | Mock.mock(/\/article\/create/, 'post', articleAPI.createArticle) 21 | Mock.mock(/\/article\/update/, 'post', articleAPI.updateArticle) 22 | 23 | // 搜索相关 24 | Mock.mock(/\/search\/user/, 'get', remoteSearchAPI.searchUser) 25 | 26 | // 账单相关 27 | Mock.mock(/\/transaction\/list/, 'get', transactionAPI.getList) 28 | 29 | export default Mock 30 | -------------------------------------------------------------------------------- /src/mock/login.js: -------------------------------------------------------------------------------- 1 | import { param2Obj } from '@/utils' 2 | 3 | const userMap = { 4 | admin: { 5 | role: ['admin'], 6 | token: 'admin', 7 | introduction: '我是超级管理员', 8 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 9 | name: 'Super Admin' 10 | }, 11 | editor: { 12 | role: ['editor'], 13 | token: 'editor', 14 | introduction: '我是编辑', 15 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 16 | name: 'Normal Editor' 17 | } 18 | } 19 | 20 | export default { 21 | loginByUsername: config => { 22 | const { username } = JSON.parse(config.body) 23 | return userMap[username] 24 | }, 25 | getUserInfo: config => { 26 | const { token } = param2Obj(config.url) 27 | if (userMap[token]) { 28 | return userMap[token] 29 | } else { 30 | return false 31 | } 32 | }, 33 | logout: () => 'success' 34 | } 35 | -------------------------------------------------------------------------------- /src/mock/remoteSearch.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { param2Obj } from '@/utils' 3 | 4 | const NameList = [] 5 | const count = 100 6 | 7 | for (let i = 0; i < count; i++) { 8 | NameList.push(Mock.mock({ 9 | name: '@first' 10 | })) 11 | } 12 | NameList.push({ name: 'mockPan' }) 13 | 14 | export default { 15 | searchUser: config => { 16 | const { name } = param2Obj(config.url) 17 | const mockNameList = NameList.filter(item => { 18 | const lowerCaseName = item.name.toLowerCase() 19 | if (name && lowerCaseName.indexOf(name.toLowerCase()) < 0) return false 20 | return true 21 | }) 22 | return { items: mockNameList } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/mock/transaction.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | const List = [] 4 | const count = 20 5 | 6 | for (let i = 0; i < count; i++) { 7 | List.push(Mock.mock({ 8 | order_no: '@guid()', 9 | timestamp: +Mock.Random.date('T'), 10 | username: '@name()', 11 | price: '@float(1000, 15000, 0, 2)', 12 | 'status|1': ['success', 'pending'] 13 | })) 14 | } 15 | 16 | export default { 17 | getList: () => { 18 | return { 19 | total: List.length, 20 | items: List 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import NProgress from 'nprogress' // progress bar 4 | import 'nprogress/nprogress.css'// progress bar style 5 | import { getToken } from '@/utils/auth' // getToken from cookie 6 | import { Message } from 'element-ui' 7 | 8 | // permissiom judge 9 | function hasPermission(roles, permissionRoles) { 10 | if (roles.indexOf('admin') >= 0) return true // admin permission passed directly 11 | if (!permissionRoles) return true 12 | return roles.some(role => permissionRoles.indexOf(role) >= 0) 13 | } 14 | 15 | const whiteList = ['/login', '/authredirect']// no redirect whitelist 16 | 17 | router.beforeEach((to, from, next) => { 18 | NProgress.start() // start progress bar 19 | if (getToken()) { // 判断是否有token 20 | if (to.path === '/login') { 21 | next({ path: '/' }) 22 | NProgress.done() // router在hash模式下 手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行! 23 | } else { 24 | if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 25 | store.dispatch('GetUserInfo').then(res => { // 拉取user_info 26 | const roles = res.data.role 27 | store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表 28 | router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 29 | next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record 30 | }) 31 | }).catch(() => { 32 | store.dispatch('FedLogOut').then(() => { 33 | Message.error('Verification failed, please login again') 34 | next({ path: '/login' }) 35 | }) 36 | }) 37 | } else { 38 | // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ 39 | if (hasPermission(store.getters.roles, to.meta.role)) { 40 | next()// 41 | } else { 42 | next({ path: '/401', query: { noGoBack: true }}) 43 | NProgress.done() // router在hash模式下 手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行! 44 | } 45 | // 可删 ↑ 46 | } 47 | } 48 | } else { 49 | if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 50 | next() 51 | } else { 52 | next('/login') // 否则全部重定向到登录页 53 | NProgress.done() // router在hash模式下 手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行! 54 | } 55 | } 56 | }) 57 | 58 | router.afterEach(() => { 59 | NProgress.done() // finish progress bar 60 | }) 61 | -------------------------------------------------------------------------------- /src/router/_import_development.js: -------------------------------------------------------------------------------- 1 | module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+ 2 | -------------------------------------------------------------------------------- /src/router/_import_production.js: -------------------------------------------------------------------------------- 1 | module.exports = file => () => import('@/views/' + file + '.vue') 2 | -------------------------------------------------------------------------------- /src/router/router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mapbar_front on 2018/1/19. 3 | */ 4 | import Vue from 'vue'; 5 | import Router from 'vue-router'; 6 | 7 | import Login from '@/views/login/Login.vue'; 8 | import Register from '@/views/register/register.vue'; 9 | import Main from '@/views/main/Main.vue'; 10 | 11 | //Main下面展示的主要界面。 12 | import Charts from '@/views/main/charts/Charts.vue'; 13 | import UpLoad from '@/views/main/upload/UpLoad.vue'; 14 | 15 | 16 | Vue.use(Router); 17 | 18 | 19 | export const constantRouterMap = [ 20 | { path:'/login', component:Login }, 21 | { path:'/register', component:Register }, 22 | { 23 | path:'/', 24 | component:Main, 25 | children: [{ 26 | path: '', 27 | component: Charts 28 | },{ 29 | path: 'upload', 30 | component: UpLoad 31 | },{ 32 | path: 'charts1', 33 | component: Charts 34 | },{ 35 | path: 'charts2', 36 | component: Charts 37 | },{ 38 | path: 'charts3', 39 | component: Charts 40 | }] 41 | }, 42 | { path: '*', redirect: '/login' } 43 | ]; 44 | 45 | export default new Router({ 46 | // mode: 'history', //后端支持可开 47 | routes: constantRouterMap 48 | }); 49 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | language: state => state.app.language, 4 | visitedViews: state => state.tagsView.visitedViews, 5 | cachedViews: state => state.tagsView.cachedViews, 6 | token: state => state.user.token, 7 | avatar: state => state.user.avatar, 8 | name: state => state.user.name, 9 | introduction: state => state.user.introduction, 10 | status: state => state.user.status, 11 | roles: state => state.user.roles, 12 | setting: state => state.user.setting, 13 | permission_routers: state => state.permission.routers, 14 | addRouters: state => state.permission.addRouters, 15 | errorLogs: state => state.errorLog.logs 16 | } 17 | export default getters 18 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import errorLog from './modules/errorLog' 5 | import permission from './modules/permission' 6 | import tagsView from './modules/tagsView' 7 | import user from './modules/user' 8 | import getters from './getters' 9 | 10 | Vue.use(Vuex) 11 | 12 | const store = new Vuex.Store({ 13 | modules: { 14 | app, 15 | errorLog, 16 | permission, 17 | tagsView, 18 | user 19 | }, 20 | getters 21 | }) 22 | 23 | export default store 24 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const app = { 4 | state: { 5 | sidebar: { 6 | opened: !+Cookies.get('sidebarStatus') 7 | }, 8 | language: Cookies.get('language') || 'zh' 9 | }, 10 | mutations: { 11 | TOGGLE_SIDEBAR: state => { 12 | if (state.sidebar.opened) { 13 | Cookies.set('sidebarStatus', 1) 14 | } else { 15 | Cookies.set('sidebarStatus', 0) 16 | } 17 | state.sidebar.opened = !state.sidebar.opened 18 | }, 19 | SET_LANGUAGE: (state, language) => { 20 | state.language = language 21 | Cookies.set('language', language) 22 | } 23 | }, 24 | actions: { 25 | toggleSideBar({ commit }) { 26 | commit('TOGGLE_SIDEBAR') 27 | }, 28 | setLanguage({ commit }, language) { 29 | commit('SET_LANGUAGE', language) 30 | } 31 | } 32 | } 33 | 34 | export default app 35 | -------------------------------------------------------------------------------- /src/store/modules/errorLog.js: -------------------------------------------------------------------------------- 1 | const errorLog = { 2 | state: { 3 | logs: [] 4 | }, 5 | mutations: { 6 | ADD_ERROR_LOG: (state, log) => { 7 | state.logs.push(log) 8 | } 9 | }, 10 | actions: { 11 | addErrorLog({ commit }, log) { 12 | commit('ADD_ERROR_LOG', log) 13 | } 14 | } 15 | } 16 | 17 | export default errorLog 18 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '@/router' 2 | 3 | /** 4 | * 通过meta.role判断是否与当前用户权限匹配 5 | * @param roles 6 | * @param route 7 | */ 8 | function hasPermission(roles, route) { 9 | if (route.meta && route.meta.role) { 10 | return roles.some(role => route.meta.role.indexOf(role) >= 0) 11 | } else { 12 | return true 13 | } 14 | } 15 | 16 | /** 17 | * 递归过滤异步路由表,返回符合用户角色权限的路由表 18 | * @param asyncRouterMap 19 | * @param roles 20 | */ 21 | function filterAsyncRouter(asyncRouterMap, roles) { 22 | const accessedRouters = asyncRouterMap.filter(route => { 23 | if (hasPermission(roles, route)) { 24 | if (route.children && route.children.length) { 25 | route.children = filterAsyncRouter(route.children, roles) 26 | } 27 | return true 28 | } 29 | return false 30 | }) 31 | return accessedRouters 32 | } 33 | 34 | const permission = { 35 | state: { 36 | routers: constantRouterMap, 37 | addRouters: [] 38 | }, 39 | mutations: { 40 | SET_ROUTERS: (state, routers) => { 41 | state.addRouters = routers 42 | state.routers = constantRouterMap.concat(routers) 43 | } 44 | }, 45 | actions: { 46 | GenerateRoutes({ commit }, data) { 47 | return new Promise(resolve => { 48 | const { roles } = data 49 | let accessedRouters 50 | if (roles.indexOf('admin') >= 0) { 51 | accessedRouters = asyncRouterMap 52 | } else { 53 | accessedRouters = filterAsyncRouter(asyncRouterMap, roles) 54 | } 55 | commit('SET_ROUTERS', accessedRouters) 56 | resolve() 57 | }) 58 | } 59 | } 60 | } 61 | 62 | export default permission 63 | -------------------------------------------------------------------------------- /src/store/modules/tagsView.js: -------------------------------------------------------------------------------- 1 | const tagsView = { 2 | state: { 3 | visitedViews: [], 4 | cachedViews: [] 5 | }, 6 | mutations: { 7 | ADD_VISITED_VIEWS: (state, view) => { 8 | if (state.visitedViews.some(v => v.path === view.path)) return 9 | state.visitedViews.push({ 10 | name: view.name, 11 | path: view.path, 12 | title: view.meta.title || 'no-name' 13 | }) 14 | if (!view.meta.noCache) { 15 | state.cachedViews.push(view.name) 16 | } 17 | }, 18 | DEL_VISITED_VIEWS: (state, view) => { 19 | for (const [i, v] of state.visitedViews.entries()) { 20 | if (v.path === view.path) { 21 | state.visitedViews.splice(i, 1) 22 | break 23 | } 24 | } 25 | for (const i of state.cachedViews) { 26 | if (i === view.name) { 27 | const index = state.cachedViews.indexOf(i) 28 | state.cachedViews.splice(index, index + 1) 29 | break 30 | } 31 | } 32 | }, 33 | DEL_OTHERS_VIEWS: (state, view) => { 34 | for (const [i, v] of state.visitedViews.entries()) { 35 | if (v.path === view.path) { 36 | state.visitedViews = state.visitedViews.slice(i, i + 1) 37 | break 38 | } 39 | } 40 | for (const i of state.cachedViews) { 41 | if (i === view.name) { 42 | const index = state.cachedViews.indexOf(i) 43 | state.cachedViews = state.cachedViews.slice(index, i + 1) 44 | break 45 | } 46 | } 47 | }, 48 | DEL_ALL_VIEWS: (state) => { 49 | state.visitedViews = [] 50 | state.cachedViews = [] 51 | } 52 | }, 53 | actions: { 54 | addVisitedViews({ commit }, view) { 55 | commit('ADD_VISITED_VIEWS', view) 56 | }, 57 | delVisitedViews({ commit, state }, view) { 58 | return new Promise((resolve) => { 59 | commit('DEL_VISITED_VIEWS', view) 60 | resolve([...state.visitedViews]) 61 | }) 62 | }, 63 | delOthersViews({ commit, state }, view) { 64 | return new Promise((resolve) => { 65 | commit('DEL_OTHERS_VIEWS', view) 66 | resolve([...state.visitedViews]) 67 | }) 68 | }, 69 | delAllViews({ commit, state }) { 70 | return new Promise((resolve) => { 71 | commit('DEL_ALL_VIEWS') 72 | resolve([...state.visitedViews]) 73 | }) 74 | } 75 | } 76 | } 77 | 78 | export default tagsView 79 | -------------------------------------------------------------------------------- /src/stores/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mapbar_front on 2018/1/26. 3 | */ 4 | import Vue from 'vue' 5 | 6 | import Vuex from 'vuex' 7 | 8 | Vue.use(Vuex); 9 | 10 | const store = new Vuex.Store({ 11 | state:{ 12 | userInfo: { 13 | userName: '李武帝', 14 | password: 123456 15 | }, 16 | num: 0 17 | }, 18 | mutations: { 19 | add: function (state) { 20 | state.num++ 21 | }, 22 | dec: function (state) { 23 | state.num--; 24 | } 25 | } 26 | }); 27 | 28 | export default store 29 | -------------------------------------------------------------------------------- /src/styles/btn.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | 3 | @mixin colorBtn($color) { 4 | background: $color; 5 | &:hover { 6 | color: $color; 7 | &:before, 8 | &:after { 9 | background: $color; 10 | } 11 | } 12 | } 13 | 14 | .blue-btn { 15 | @include colorBtn($blue) 16 | } 17 | 18 | .light-blue-btn { 19 | @include colorBtn($light-blue) 20 | } 21 | 22 | .red-btn { 23 | @include colorBtn($red) 24 | } 25 | 26 | .pink-btn { 27 | @include colorBtn($pink) 28 | } 29 | 30 | .green-btn { 31 | @include colorBtn($green) 32 | } 33 | 34 | .tiffany-btn { 35 | @include colorBtn($tiffany) 36 | } 37 | 38 | .yellow-btn { 39 | @include colorBtn($yellow) 40 | } 41 | 42 | .pan-btn { 43 | font-size: 14px; 44 | color: #fff; 45 | padding: 14px 36px; 46 | border-radius: 8px; 47 | border: none; 48 | outline: none; 49 | margin-right: 25px; 50 | transition: 600ms ease all; 51 | position: relative; 52 | display: inline-block; 53 | &:hover { 54 | background: #fff; 55 | &:before, 56 | &:after { 57 | width: 100%; 58 | transition: 600ms ease all; 59 | } 60 | } 61 | &:before, 62 | &:after { 63 | content: ''; 64 | position: absolute; 65 | top: 0; 66 | right: 0; 67 | height: 2px; 68 | width: 0; 69 | transition: 400ms ease all; 70 | } 71 | &::after { 72 | right: inherit; 73 | top: inherit; 74 | left: 0; 75 | bottom: 0; 76 | } 77 | } 78 | 79 | .custom-button { 80 | display: inline-block; 81 | line-height: 1; 82 | white-space: nowrap; 83 | cursor: pointer; 84 | background: #fff; 85 | color: #fff; 86 | -webkit-appearance: none; 87 | text-align: center; 88 | box-sizing: border-box; 89 | outline: 0; 90 | margin: 0; 91 | padding: 10px 15px; 92 | font-size: 14px; 93 | border-radius: 4px; 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/styles/color-definition.scss: -------------------------------------------------------------------------------- 1 | $mainColor:#409EFF; 2 | 3 | $successColor:#67C23A; 4 | 5 | $warningColor:#E6A23C; 6 | 7 | $dangerColor:#F56C6C; 8 | 9 | $infoColor:#909399; 10 | 11 | 12 | 13 | 14 | 15 | $fontColor:#303133; 16 | 17 | $mainFontColor:#606266; 18 | 19 | $assistFontColor:#909399; 20 | 21 | $seizeFontColor:#C0C4CC; 22 | -------------------------------------------------------------------------------- /src/styles/common.scss: -------------------------------------------------------------------------------- 1 | @import "./paddingMargin.scss"; 2 | 3 | @import "./color-definition.scss"; 4 | .wrapper,#app,html,body{ 5 | width: 100%; 6 | height: 100%; 7 | } 8 | a{ 9 | text-decoration: none; 10 | } 11 | body { 12 | height: 100%; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-font-smoothing: antialiased; 15 | text-rendering: optimizeLegibility; 16 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 17 | } 18 | 19 | //flex 20 | .flex-box{ 21 | display: flex; 22 | } 23 | .flex-col-box{ 24 | flex-direction: column; 25 | } 26 | .flex1{ 27 | flex: 1; 28 | } 29 | 30 | .center{ 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | } 35 | 36 | 37 | .border{ 38 | border: 1px solid #cccccc; 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | //覆盖一些element-ui样式 2 | 3 | .el-breadcrumb__inner, .el-breadcrumb__inner a{ 4 | font-weight: 400!important; 5 | } 6 | 7 | .el-upload { 8 | input[type="file"] { 9 | display: none !important; 10 | } 11 | } 12 | 13 | .el-upload__input { 14 | display: none; 15 | } 16 | 17 | .cell { 18 | .el-tag { 19 | margin-right: 0px; 20 | } 21 | } 22 | 23 | .small-padding { 24 | .cell { 25 | padding-left: 5px; 26 | padding-right: 5px; 27 | } 28 | } 29 | 30 | .fixed-width{ 31 | .el-button--mini{ 32 | padding: 7px 10px; 33 | width: 60px; 34 | } 35 | } 36 | 37 | .status-col { 38 | .cell { 39 | padding: 0 10px; 40 | text-align: center; 41 | .el-tag { 42 | margin-right: 0px; 43 | } 44 | } 45 | } 46 | 47 | //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461 48 | .el-dialog { 49 | transform: none; 50 | left: 0; 51 | position: relative; 52 | margin: 0 auto; 53 | } 54 | 55 | //文章页textarea修改样式 56 | .article-textarea { 57 | textarea { 58 | padding-right: 40px; 59 | resize: none; 60 | border: none; 61 | border-radius: 0px; 62 | border-bottom: 1px solid #bfcbd9; 63 | } 64 | } 65 | 66 | //element ui upload 67 | .upload-container { 68 | .el-upload { 69 | width: 100%; 70 | .el-upload-dragger { 71 | width: 100%; 72 | height: 200px; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | &::-webkit-scrollbar { 14 | width: 6px; 15 | } 16 | &::-webkit-scrollbar-thumb { 17 | background: #99a9bf; 18 | border-radius: 20px; 19 | } 20 | } 21 | 22 | @mixin relative { 23 | position: relative; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | @mixin pct($pct) { 29 | width: #{$pct}; 30 | position: relative; 31 | margin: 0 auto; 32 | } 33 | 34 | @mixin triangle($width, $height, $color, $direction) { 35 | $width: $width/2; 36 | $color-border-style: $height solid $color; 37 | $transparent-border-style: $width solid transparent; 38 | height: 0; 39 | width: 0; 40 | @if $direction==up { 41 | border-bottom: $color-border-style; 42 | border-left: $transparent-border-style; 43 | border-right: $transparent-border-style; 44 | } 45 | @else if $direction==right { 46 | border-left: $color-border-style; 47 | border-top: $transparent-border-style; 48 | border-bottom: $transparent-border-style; 49 | } 50 | @else if $direction==down { 51 | border-top: $color-border-style; 52 | border-left: $transparent-border-style; 53 | border-right: $transparent-border-style; 54 | } 55 | @else if $direction==left { 56 | border-right: $color-border-style; 57 | border-top: $transparent-border-style; 58 | border-bottom: $transparent-border-style; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/styles/paddingMargin.scss: -------------------------------------------------------------------------------- 1 | //margin 2 | .margin0{ 3 | margin: 0; 4 | } 5 | .margin20{ 6 | margin: 20px !important; 7 | } 8 | .margin-left40{ 9 | margin-left: 40px !important; 10 | } 11 | .margin-left20{ 12 | margin-left: 20px !important; 13 | } 14 | .margin-top40{ 15 | margin-top: 40px !important; 16 | } 17 | .margin-top20{ 18 | margin-top: 20px !important; 19 | } 20 | .margin-right20{ 21 | margin-right: 20px !important; 22 | } 23 | .margin-right40{ 24 | margin-right: 40px !important; 25 | } 26 | .margin-bottom20{ 27 | margin-bottom: 20px !important; 28 | } 29 | .margin-bottom40{ 30 | margin-bottom: 40px !important; 31 | } 32 | 33 | 34 | 35 | //padding 36 | .padding0{ 37 | padding: 0; 38 | } 39 | .padding20{ 40 | padding: 20px !important; 41 | } 42 | .padding-left40{ 43 | padding-left: 40px !important; 44 | } 45 | .padding-left20{ 46 | padding-left: 20px !important; 47 | } 48 | .padding-top40{ 49 | padding-top: 40px !important; 50 | } 51 | .padding-top20{ 52 | padding-top: 20px !important; 53 | } 54 | .padding-right20{ 55 | padding-right: 20px !important; 56 | } 57 | .padding-right40{ 58 | padding-right: 40px !important; 59 | } 60 | .padding-bottom20{ 61 | padding-bottom: 20px !important; 62 | } 63 | .padding-bottom40{ 64 | padding-bottom: 40px !important; 65 | } 66 | 67 | 68 | //border 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | // 主体区域 3 | .main-container { 4 | min-height: 100%; 5 | transition: margin-left 0.28s; 6 | margin-left: 180px; 7 | } // 侧边栏 8 | .sidebar-container { 9 | transition: width 0.28s; 10 | width: 180px!important; 11 | height: 100%; 12 | position: fixed; 13 | top: 0; 14 | bottom: 0; 15 | left: 0; 16 | z-index: 1001; 17 | a { 18 | display: inline-block; 19 | width: 100%; 20 | } 21 | .svg-icon { 22 | margin-right: 16px; 23 | } 24 | .el-menu { 25 | border: none; 26 | width: 100%; 27 | } 28 | } 29 | .hideSidebar { 30 | .sidebar-container,.sidebar-container .el-menu { 31 | width: 36px!important; 32 | // overflow: inherit; 33 | } 34 | .main-container { 35 | margin-left: 36px; 36 | } 37 | } 38 | .hideSidebar { 39 | .submenu-title-noDropdown { 40 | padding-left: 10px!important; 41 | position: relative; 42 | span { 43 | height: 0; 44 | width: 0; 45 | overflow: hidden; 46 | visibility: hidden; 47 | transition: opacity .3s cubic-bezier(.55, 0, .1, 1); 48 | opacity: 0; 49 | display: inline-block; 50 | } 51 | &:hover { 52 | span { 53 | display: block; 54 | border-radius: 3px; 55 | z-index: 1002; 56 | width: 140px; 57 | height: 56px; 58 | visibility: visible; 59 | position: absolute; 60 | right: -145px; 61 | text-align: left; 62 | text-indent: 20px; 63 | top: 0px; 64 | background-color: $subMenuBg!important; 65 | opacity: 1; 66 | } 67 | } 68 | } 69 | .el-submenu { 70 | &>.el-submenu__title { 71 | padding-left: 10px!important; 72 | &>span { 73 | display: none; 74 | } 75 | .el-submenu__icon-arrow { 76 | display: none; 77 | } 78 | } 79 | .nest-menu { 80 | .el-submenu__icon-arrow { 81 | display: block!important; 82 | } 83 | span { 84 | display: inline-block!important; 85 | } 86 | } 87 | } 88 | } 89 | .nest-menu .el-submenu>.el-submenu__title, 90 | .el-submenu .el-menu-item { 91 | min-width: 180px!important; 92 | background-color: $subMenuBg!important; 93 | &:hover { 94 | background-color: $menuHover!important; 95 | } 96 | } 97 | .el-menu--collapse .el-menu .el-submenu{ 98 | min-width: 180px!important; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | //globl transition css 2 | 3 | /*fade*/ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /*fade*/ 15 | .breadcrumb-enter-active, 16 | .breadcrumb-leave-active { 17 | transition: all .5s; 18 | } 19 | 20 | .breadcrumb-enter, 21 | .breadcrumb-leave-active { 22 | opacity: 0; 23 | transform: translateX(20px); 24 | } 25 | 26 | .breadcrumb-move { 27 | transition: all .5s; 28 | } 29 | 30 | .breadcrumb-leave-active { 31 | position: absolute; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $blue:#324157; 2 | $light-blue:#3A71A8; 3 | $red:#C03639; 4 | $pink: #E65D6E; 5 | $green: #30B08F; 6 | $tiffany: #4AB7BD; 7 | $yellow:#FEC171; 8 | $panGreen: #30B08F; 9 | 10 | //sidebar 11 | $menuBg:#304156; 12 | $subMenuBg:#1f2d3d; 13 | $menuHover:#001528; 14 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Admin-Token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/clipboard.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Clipboard from 'clipboard' 3 | 4 | function clipboardSuccess() { 5 | Vue.prototype.$message({ 6 | message: 'Copy successfully', 7 | type: 'success', 8 | duration: 1500 9 | }) 10 | } 11 | 12 | function clipboardError() { 13 | Vue.prototype.$message({ 14 | message: 'Copy failed', 15 | type: 'error' 16 | }) 17 | } 18 | 19 | export default function handleClipboard(text, event) { 20 | const clipboard = new Clipboard(event.target, { 21 | text: () => text 22 | }) 23 | clipboard.on('success', () => { 24 | clipboardSuccess() 25 | clipboard.off('error') 26 | clipboard.off('success') 27 | clipboard.destroy() 28 | }) 29 | clipboard.on('error', () => { 30 | clipboardError() 31 | clipboard.off('error') 32 | clipboard.off('success') 33 | clipboard.destroy() 34 | }) 35 | clipboard.onClick(event) 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/createUniqueString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 17/3/8. 3 | */ 4 | export default function createUniqueString() { 5 | const timestamp = +new Date() + '' 6 | const randomNum = parseInt((1 + Math.random()) * 65536) + '' 7 | return (+(randomNum + timestamp)).toString(32) 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/i18n.js: -------------------------------------------------------------------------------- 1 | // translate router.meta.title, be used in breadcrumb sidebar tagsview 2 | export function generateTitle(title) { 3 | return this.$t('route.' + title) // $t :this method from vue-i18n, inject in @/lang/index.js 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/openWindow.js: -------------------------------------------------------------------------------- 1 | /** 2 | *Created by jiachenpan on 16/11/29. 3 | * @param {Sting} url 4 | * @param {Sting} title 5 | * @param {Number} w 6 | * @param {Number} h 7 | */ 8 | 9 | export default function openWindow(url, title, w, h) { 10 | // Fixes dual-screen position Most browsers Firefox 11 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 12 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 13 | 14 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width 15 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height 16 | 17 | const left = ((width / 2) - (w / 2)) + dualScreenLeft 18 | const top = ((height / 2) - (h / 2)) + dualScreenTop 19 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left) 20 | 21 | // Puts focus on the newWindow 22 | if (window.focus) { 23 | newWindow.focus() 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message } from 'element-ui' 3 | import store from '@/store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // create an axios instance 7 | const service = axios.create({ 8 | baseURL: process.env.BASE_API, // api的base_url 9 | timeout: 5000 // request timeout 10 | }) 11 | 12 | // request interceptor 13 | service.interceptors.request.use(config => { 14 | // Do something before request is sent 15 | if (store.getters.token) { 16 | config.headers['X-Token'] = getToken() // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改 17 | } 18 | return config 19 | }, error => { 20 | // Do something with request error 21 | console.log(error) // for debug 22 | Promise.reject(error) 23 | }) 24 | 25 | // respone interceptor 26 | service.interceptors.response.use( 27 | response => response, 28 | /** 29 | * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页 30 | * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中 31 | */ 32 | // const res = response.data; 33 | // if (res.code !== 20000) { 34 | // Message({ 35 | // message: res.message, 36 | // type: 'error', 37 | // duration: 5 * 1000 38 | // }); 39 | // // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; 40 | // if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 41 | // MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { 42 | // confirmButtonText: '重新登录', 43 | // cancelButtonText: '取消', 44 | // type: 'warning' 45 | // }).then(() => { 46 | // store.dispatch('FedLogOut').then(() => { 47 | // location.reload();// 为了重新实例化vue-router对象 避免bug 48 | // }); 49 | // }) 50 | // } 51 | // return Promise.reject('error'); 52 | // } else { 53 | // return response.data; 54 | // } 55 | error => { 56 | console.log('err' + error)// for debug 57 | Message({ 58 | message: error.message, 59 | type: 'error', 60 | duration: 5 * 1000 61 | }) 62 | return Promise.reject(error) 63 | }) 64 | 65 | export default service 66 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 16/11/18. 3 | */ 4 | 5 | export function isvalidUsername(str) { 6 | const valid_map = ['admin', 'editor'] 7 | return valid_map.indexOf(str.trim()) >= 0 8 | } 9 | 10 | /* 合法uri*/ 11 | export function validateURL(textval) { 12 | const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 13 | return urlregex.test(textval) 14 | } 15 | 16 | /* 小写字母*/ 17 | export function validateLowerCase(str) { 18 | const reg = /^[a-z]+$/ 19 | return reg.test(str) 20 | } 21 | 22 | /* 大写字母*/ 23 | export function validateUpperCase(str) { 24 | const reg = /^[A-Z]+$/ 25 | return reg.test(str) 26 | } 27 | 28 | /* 大小写字母*/ 29 | export function validatAlphabets(str) { 30 | const reg = /^[A-Za-z]+$/ 31 | return reg.test(str) 32 | } 33 | 34 | /** 35 | * validate email 36 | * @param email 37 | * @returns {boolean} 38 | */ 39 | export function validateEmail(email) { 40 | const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 41 | return re.test(email) 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/vendor/Export2Zip.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('script-loader!file-saver'); 3 | import JSZip from 'jszip' 4 | 5 | export function export_txt_to_zip(th, jsonData, txtName, zipName) { 6 | const zip = new JSZip() 7 | const txt_name = txtName || 'file' 8 | const zip_name = zipName || 'file' 9 | const data = jsonData 10 | let txtData = `${th}\r\n` 11 | data.forEach((row) => { 12 | let tempStr = '' 13 | tempStr = row.toString() 14 | txtData += `${tempStr}\r\n` 15 | }) 16 | zip.file(`${txt_name}.txt`, txtData) 17 | zip.generateAsync({type:"blob"}).then((blob) => { 18 | saveAs(blob, `${zip_name}.zip`) 19 | }, (err) => { 20 | alert('导出失败') 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /src/views/charts/keyboard.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/charts/line.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/charts/mixChart.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/clipboard/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 45 | 46 | -------------------------------------------------------------------------------- /src/views/components-demo/avatarUpload.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 44 | 45 | 52 | 53 | -------------------------------------------------------------------------------- /src/views/components-demo/dndList.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/components-demo/dropzone.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/components-demo/jsonEditor.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | 26 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/components-demo/markdown.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/views/components-demo/splitpane.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 39 | 40 | 68 | -------------------------------------------------------------------------------- /src/views/components-demo/tinymce.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/components/BarChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 107 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/components/PieChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 85 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/components/TodoList/Todo.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 71 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/components/TransactionTable.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 51 | -------------------------------------------------------------------------------- /src/views/dashboard/editor/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 41 | 42 | 75 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 32 | -------------------------------------------------------------------------------- /src/views/documentation/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 28 | 29 | 48 | -------------------------------------------------------------------------------- /src/views/errorLog/errorTestA.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /src/views/errorLog/errorTestB.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/views/errorLog/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /src/views/errorPage/401.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 52 | 53 | 89 | -------------------------------------------------------------------------------- /src/views/example/tab/components/tabPane.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 98 | 99 | -------------------------------------------------------------------------------- /src/views/example/tab/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /src/views/example/table/dynamicTable/fixedThead.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | 59 | -------------------------------------------------------------------------------- /src/views/example/table/dynamicTable/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | -------------------------------------------------------------------------------- /src/views/example/table/dynamicTable/unfixedThead.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | -------------------------------------------------------------------------------- /src/views/example/table/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /src/views/excel/exportExcel.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 85 | -------------------------------------------------------------------------------- /src/views/excel/uploadExcel.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/views/form/create.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/form/edit.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/i18n-demo/local.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | zh: { 4 | i18nView: { 5 | title: '切换语言', 6 | note: '目前只翻译了当前页面和侧边栏和导航,未完待续,敬请期待...', 7 | datePlaceholder: '请选择日期', 8 | tableDate: '日期', 9 | tableName: '姓名', 10 | tableAddress: '地址', 11 | default: '默认按钮', 12 | primary: '主要按钮', 13 | success: '成功按钮', 14 | info: '信息按钮', 15 | warning: '警告按钮', 16 | danger: '危险按钮' 17 | } 18 | 19 | }, 20 | en: { 21 | i18nView: { 22 | title: 'Switch Language', 23 | note: 'Currently only translated the i18n page and the sidebar and levelbar, please look forword to...', 24 | datePlaceholder: 'Pick a day', 25 | tableDate: 'tableDate', 26 | tableName: 'tableName', 27 | tableAddress: 'tableAddress', 28 | default: 'default:', 29 | primary: 'primary', 30 | success: 'success', 31 | info: 'info', 32 | warning: 'warning', 33 | danger: 'danger' 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 40 | -------------------------------------------------------------------------------- /src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | -------------------------------------------------------------------------------- /src/views/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar/index.vue' 3 | export { default as TagsView } from './TagsView' 4 | export { default as AppMain } from './AppMain' 5 | -------------------------------------------------------------------------------- /src/views/login/authredirect.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/views/login/socialsignin.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 69 | -------------------------------------------------------------------------------- /src/views/main/charts/Charts.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | 43 | -------------------------------------------------------------------------------- /src/views/main/upload/UpLoad.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 55 | 56 | 59 | -------------------------------------------------------------------------------- /src/views/permission/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | -------------------------------------------------------------------------------- /src/views/qiniu/upload.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 41 | -------------------------------------------------------------------------------- /src/views/svg-icons/generateIconsView.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | state: { 3 | iconsMap: [] 4 | }, 5 | generate(iconsMap) { 6 | this.state.iconsMap = iconsMap 7 | } 8 | } 9 | 10 | export default data 11 | -------------------------------------------------------------------------------- /src/views/svg-icons/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 50 | 51 | 75 | -------------------------------------------------------------------------------- /src/views/theme/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 83 | 84 | 101 | -------------------------------------------------------------------------------- /src/views/zip/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 78 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwudi/manage-platform/ff270bcd741afdd13280de2098f055409dc1faa7/static/.gitkeep -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /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 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | testPathIgnorePatterns: [ 18 | '/test/e2e' 19 | ], 20 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 21 | setupFiles: ['/test/unit/setup'], 22 | mapCoverage: true, 23 | coverageDirectory: '/test/unit/coverage', 24 | collectCoverageFrom: [ 25 | 'src/**/*.{js,vue}', 26 | '!src/main.js', 27 | '!src/router/index.js', 28 | '!**/node_modules/**' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------