├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .travis.yml ├── Dockerfile ├── 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 └── sit.env.js ├── default.conf ├── favicon.ico ├── index.html ├── package.json ├── src ├── App.vue ├── api │ ├── adminRelationDialog.js │ ├── index.js │ ├── login.js │ ├── menu.js │ ├── permission.js │ ├── permissionMenu.js │ ├── resource.js │ ├── roleUser.js │ ├── user.js │ ├── userGroup.js │ └── userRole.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 │ ├── Clickoutside │ │ └── index.js │ ├── ErrorLog │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── MLoading │ │ ├── index.js │ │ └── src │ │ │ ├── MLoading.vue │ │ │ └── index.js │ ├── OperationLog │ │ ├── index.js │ │ └── src │ │ │ └── index.vue │ ├── Particles │ │ ├── index.vue │ │ └── particles-data.js │ ├── PermissionButton │ │ ├── index.js │ │ └── src │ │ │ └── index.vue │ ├── ScrollBar │ │ └── index.vue │ ├── ScrollPane │ │ └── index.vue │ ├── SearchBar │ │ └── index.vue │ ├── SearchBox │ │ ├── index.js │ │ └── src │ │ │ ├── index.vue │ │ │ └── js │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ ├── props.js │ │ │ └── watch.js │ ├── SearchPage │ │ ├── index.js │ │ └── src │ │ │ └── index.vue │ ├── SelectBox │ │ ├── index.js │ │ └── src │ │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── TableContain │ │ ├── index.js │ │ └── src │ │ │ └── index.vue │ ├── components.js │ ├── index.js │ └── indexComponents.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 │ ├── index.js │ ├── login.js │ └── transaction.js ├── permission.js ├── public │ ├── addModel.js │ ├── index.js │ ├── indexModel.js │ ├── parent.js │ └── rules.js ├── router │ ├── _import_development.js │ ├── _import_production.js │ └── index.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── errorLog.js │ │ ├── permission.js │ │ ├── tagsView.js │ │ └── user.js ├── styles │ ├── btn.scss │ ├── element-ui.scss │ ├── index.scss │ ├── list-page.scss │ ├── mixin.scss │ ├── navbar.scss │ ├── sidebar.scss │ ├── theme-black.scss │ ├── transition.scss │ └── variables.scss ├── utils │ ├── auth.js │ ├── clipboard.js │ ├── createUniqueString.js │ ├── i18n.js │ ├── index.js │ ├── openWindow.js │ ├── request.js │ ├── util.js │ └── validate.js └── views │ ├── admin │ ├── atest │ │ ├── index.vue │ │ ├── test1 │ │ │ └── index.vue │ │ ├── test2 │ │ │ └── index.vue │ │ └── test3 │ │ │ ├── index.vue │ │ │ ├── test3-1 │ │ │ └── index.vue │ │ │ └── test3-2 │ │ │ └── index.vue │ ├── component │ │ ├── MenuRelation │ │ │ ├── index.js │ │ │ └── index.vue │ │ ├── RelationDialog │ │ │ ├── index.js │ │ │ ├── index.vue │ │ │ └── model.js │ │ └── ResourceDialog │ │ │ ├── index.js │ │ │ └── index.vue │ ├── menu │ │ ├── component │ │ │ ├── MenuTree.vue │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── tree.js │ │ └── index.vue │ ├── permission │ │ ├── add.vue │ │ └── index.vue │ ├── resource │ │ ├── add.vue │ │ └── index.vue │ ├── role │ │ ├── add.vue │ │ └── index.vue │ ├── user │ │ ├── add.vue │ │ ├── index.vue │ │ └── js │ │ │ └── add.js │ └── userGroup │ │ ├── add.vue │ │ └── index.vue │ ├── dashboard │ └── index.vue │ ├── errorPage │ ├── 401.vue │ └── 404.vue │ ├── layout │ ├── Layout.vue │ └── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Sidebar │ │ ├── SidebarItem.vue │ │ └── index.vue │ │ ├── TagsView.vue │ │ └── index.js │ ├── login │ ├── authredirect.vue │ └── index.vue │ └── svgIcons │ ├── generateIconsView.js │ └── index.vue └── static └── tinymce ├── langs └── zh_CN.js ├── skins └── lightgray │ ├── content.inline.min.css │ ├── content.min.css │ ├── fonts │ ├── tinymce-small.eot │ ├── tinymce-small.json │ ├── tinymce-small.svg │ ├── tinymce-small.ttf │ ├── tinymce-small.woff │ ├── tinymce.eot │ ├── tinymce.json │ ├── tinymce.svg │ ├── tinymce.ttf │ └── tinymce.woff │ ├── img │ ├── anchor.gif │ ├── loader.gif │ ├── object.gif │ └── trans.gif │ ├── skin.ie7.min.css │ └── skin.min.css └── tinymce.min.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"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/assets 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | gifs/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | test/unit/coverage 10 | test/e2e/reports 11 | selenium-debug.log 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | 21 | package-lock.json 22 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hub.c.163.com/library/nginx 2 | MAINTAINER neveryielding 3 | RUN rm /etc/nginx/conf.d/default.conf 4 | ADD default.conf /etc/nginx/conf.d/ 5 | COPY dist/ /usr/share/nginx/html/ -------------------------------------------------------------------------------- /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 | 2 | ## 简介 3 | 基于[vue-template-admin](https://github.com/neveryielding/vue-admin-template)脚手架开发的[ny-cloud](https://github.com/neveryielding/ny-cloud)前端权限管理系统UI,此项目依赖于ny-cloud提供的接口及数据,必须先启动ny-cloud项目才能正常使用
4 | - [在线访问](http://www.stars21.cn/)

5 | - 技术介绍请移步[vue-template-admin](https://github.com/neveryielding/vue-admin-template)
6 | - 账号密码: guest 123456
7 | - 技术交流群: 807233785
8 | #### 效果截图: 9 | ![enter image description here](http://chuantu.biz/t6/339/1530801681x-1404817431.png) 10 | ![enter image description here](http://chuantu.biz/t6/339/1530801756x-1404817706.png) 11 | ![enter image description here](http://chuantu.biz/t6/339/1530801785x-1404817706.png) 12 | ![enter image description here](http://chuantu.biz/t6/339/1530801823x-1404817706.png) 13 | ![enter image description here](http://chuantu.biz/t6/339/1530801855x-1404817706.png) 14 | ![enter image description here](http://chuantu.biz/t6/339/1530801889x-1404817706.png) 15 | ![enter image description here](http://chuantu.biz/t6/339/1530801922x-1404817706.png) 16 | ![enter image description here](http://chuantu.biz/t6/339/1530801946x-1404817706.png) 17 | ![enter image description here](http://chuantu.biz/t6/339/1530801975x-1404817706.png) 18 | ![enter image description here](http://chuantu.biz/t6/339/1530801995x-1404817706.png) 19 | ![enter image description here](http://chuantu.biz/t6/339/1530802016x-1404817706.png) 20 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | const ora = require('ora') 5 | const rm = require('rimraf') 6 | const path = require('path') 7 | const chalk = require('chalk') 8 | const webpack = require('webpack') 9 | const config = require('../config') 10 | const webpackConfig = require('./webpack.prod.conf') 11 | const server = require('pushstate-server') 12 | 13 | var spinner = ora('building for '+ process.env.env_config+ ' environment...' ) 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, (err, stats) => { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | if (stats.hasErrors()) { 30 | console.log(chalk.red(' Build failed with errors.\n')) 31 | process.exit(1) 32 | } 33 | 34 | console.log(chalk.cyan(' Build complete.\n')) 35 | console.log(chalk.yellow( 36 | ' Tip: built files are meant to be served over an HTTP server.\n' + 37 | ' Opening index.html over file:// won\'t work.\n' 38 | )) 39 | if(process.env.npm_config_preview){ 40 | server.start({ 41 | port: 1000, 42 | directory: './dist', 43 | file: '/index.html' 44 | }); 45 | console.log('> Listening at ' + 'http://localhost:1000' + '\n') 46 | } 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /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/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/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?cacheDirectory', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.svg$/, 56 | loader: 'svg-sprite-loader', 57 | include: [resolve('src/icons')], 58 | options: { 59 | symbolId: 'icon-[name]' 60 | } 61 | }, 62 | { 63 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 64 | loader: 'url-loader', 65 | exclude: [resolve('src/icons')], 66 | options: { 67 | limit: 10000, 68 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 69 | } 70 | }, 71 | { 72 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 77 | } 78 | }, 79 | { 80 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 81 | loader: 'url-loader', 82 | options: { 83 | limit: 10000, 84 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 85 | } 86 | } 87 | ] 88 | }, 89 | node: { 90 | // prevent webpack from injecting useless setImmediate polyfill because Vue 91 | // source contains it (although only uses it if it's native). 92 | setImmediate: false, 93 | // prevent webpack from injecting mocks to Node native modules 94 | // that does not make sense for the client 95 | dgram: 'empty', 96 | fs: 'empty', 97 | net: 'empty', 98 | tls: 'empty', 99 | child_process: 'empty' 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const HtmlWebpackPlugin = require('html-webpack-plugin') 9 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 10 | const portfinder = require('portfinder') 11 | 12 | function resolve (dir) { 13 | return path.join(__dirname, '..', dir) 14 | } 15 | 16 | const HOST = process.env.HOST 17 | const PORT = process.env.PORT && Number(process.env.PORT) 18 | 19 | const devWebpackConfig = merge(baseWebpackConfig, { 20 | module: { 21 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 22 | }, 23 | // cheap-module-eval-source-map is faster for development 24 | devtool: config.dev.devtool, 25 | 26 | // these devServer options should be customized in /config/index.js 27 | devServer: { 28 | clientLogLevel: 'warning', 29 | historyApiFallback: true, 30 | hot: true, 31 | compress: true, 32 | host: HOST || config.dev.host, 33 | port: PORT || config.dev.port, 34 | open: config.dev.autoOpenBrowser, 35 | overlay: config.dev.errorOverlay 36 | ? { warnings: false, errors: true } 37 | : false, 38 | publicPath: config.dev.assetsPublicPath, 39 | proxy: config.dev.proxyTable, 40 | quiet: true, // necessary for FriendlyErrorsPlugin 41 | watchOptions: { 42 | poll: config.dev.poll, 43 | } 44 | }, 45 | plugins: [ 46 | new webpack.DefinePlugin({ 47 | 'process.env': require('../config/dev.env') 48 | }), 49 | new webpack.HotModuleReplacementPlugin(), 50 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 51 | new webpack.NoEmitOnErrorsPlugin(), 52 | // https://github.com/ampedandwired/html-webpack-plugin 53 | new HtmlWebpackPlugin({ 54 | filename: 'index.html', 55 | template: 'index.html', 56 | inject: true, 57 | favicon: resolve('favicon.ico'), 58 | title: 'vue-element-admin', 59 | path: config.dev.assetsPublicPath + config.dev.assetsSubDirectory 60 | }), 61 | ] 62 | }) 63 | 64 | module.exports = new Promise((resolve, reject) => { 65 | portfinder.basePort = process.env.PORT || config.dev.port 66 | portfinder.getPort((err, port) => { 67 | if (err) { 68 | reject(err) 69 | } else { 70 | // publish the new Port, necessary for e2e tests 71 | process.env.PORT = port 72 | // add port to devServer config 73 | devWebpackConfig.devServer.port = port 74 | 75 | // Add FriendlyErrorsPlugin 76 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 77 | compilationSuccessInfo: { 78 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 79 | }, 80 | onErrors: config.dev.notifyOnErrors 81 | ? utils.createNotifierCallback() 82 | : undefined 83 | })) 84 | 85 | resolve(devWebpackConfig) 86 | } 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"development"', 3 | ENV_CONFIG: '"dev"', 4 | BASE_API: '"https://api-dev"' 5 | } 6 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.6 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 | '/login/': { 15 | target: 'http://localhost:7000', 16 | changeOrigin: true, 17 | pathRewrite: { 18 | '^/login': '/login' 19 | } 20 | }, 21 | '/admin/': { 22 | target: 'http://localhost:7000', 23 | changeOrigin: true, 24 | pathRewrite: { 25 | '^/admin': '/admin' 26 | } 27 | }, 28 | '/api/': { 29 | target: 'http://localhost:7000', 30 | changeOrigin: true, 31 | pathRewrite: { 32 | '^/api': '/api' 33 | } 34 | } 35 | }, 36 | 37 | // Various Dev Server settings 38 | host: 'localhost', // can be overwritten by process.env.HOST 39 | port: 1000, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 40 | autoOpenBrowser: true, 41 | errorOverlay: true, 42 | notifyOnErrors: false, 43 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 44 | 45 | // Use Eslint Loader? 46 | // If true, your code will be linted during bundling and 47 | // linting errors and warnings will be shown in the console. 48 | useEslint: true, 49 | // If true, eslint errors and warnings will also be shown in the error overlay 50 | // in the browser. 51 | showEslintErrorsInOverlay: false, 52 | 53 | /** 54 | * Source Maps 55 | */ 56 | 57 | // https://webpack.js.org/configuration/devtool/#development 58 | devtool: 'eval-source-map', 59 | 60 | // If you have problems debugging vue-files in devtools, 61 | // set this to false - it *may* help 62 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 63 | cacheBusting: true, 64 | 65 | // CSS Sourcemaps off by default because relative paths are "buggy" 66 | // with this option, according to the CSS-Loader README 67 | // (https://github.com/webpack/css-loader#sourcemaps) 68 | // In our experience, they generally work as expected, 69 | // just be aware of this issue when enabling this option. 70 | cssSourceMap: false, 71 | }, 72 | 73 | build: { 74 | // Template for index.html 75 | index: path.resolve(__dirname, '../dist/index.html'), 76 | 77 | // Paths 78 | assetsRoot: path.resolve(__dirname, '../dist'), 79 | assetsSubDirectory: 'static', 80 | 81 | // you can set by youself according to actual condition 82 | assetsPublicPath: './', 83 | 84 | /** 85 | * Source Maps 86 | */ 87 | 88 | productionSourceMap: false, 89 | // https://webpack.js.org/configuration/devtool/#production 90 | devtool: '#source-map', 91 | 92 | // Gzip off by default as many popular static hosts such as 93 | // Surge or Netlify already gzip all static assets for you. 94 | // Before setting to `true`, make sure to: 95 | // npm install --save-dev compression-webpack-plugin 96 | productionGzip: false, 97 | productionGzipExtensions: ['js', 'css'], 98 | 99 | // Run the build command with an extra argument to 100 | // View the bundle analyzer report after build finishes: 101 | // `npm run build --report` 102 | // Set to `true` or `false` to always turn it on or off 103 | bundleAnalyzerReport: process.env.npm_config_report 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | ENV_CONFIG: '"prod"', 4 | BASE_API: '"https://api-prod"' 5 | } 6 | -------------------------------------------------------------------------------- /config/sit.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | ENV_CONFIG: '"sit"', 4 | BASE_API: '"https://api-sit"' 5 | } 6 | -------------------------------------------------------------------------------- /default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | #charset koi8-r; 6 | #access_log /var/log/nginx/log/host.access.log main; 7 | 8 | location / { 9 | root /usr/share/nginx/html; 10 | index index.html index.htm; 11 | } 12 | 13 | location /api/ { 14 | rewrite ^(.*)$ $1 break; 15 | include uwsgi_params; 16 | proxy_pass http://172.16.164.20:7000; 17 | } 18 | 19 | location /admin/ { 20 | rewrite ^(.*)$ $1 break; 21 | include uwsgi_params; 22 | proxy_pass http://172.16.164.20:7000; 23 | } 24 | 25 | location /login/ { 26 | rewrite ^(.*)$ $1 break; 27 | include uwsgi_params; 28 | proxy_pass http://172.16.164.20:7000; 29 | } 30 | 31 | #error_page 404 /404.html; 32 | 33 | # redirect server error pages to the static page /50x.html 34 | # 35 | error_page 500 502 503 504 /50x.html; 36 | location = /50x.html { 37 | root html; 38 | } 39 | 40 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 41 | # 42 | #location ~ \.php$ { 43 | # proxy_pass http://127.0.0.1; 44 | #} 45 | 46 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 47 | # 48 | #location ~ \.php$ { 49 | # root html; 50 | # fastcgi_pass 127.0.0.1:9000; 51 | # fastcgi_index index.php; 52 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 53 | # include fastcgi_params; 54 | #} 55 | 56 | # deny access to .htaccess files, if Apache's document root 57 | # concurs with nginx's one 58 | # 59 | #location ~ /\.ht { 60 | # deny all; 61 | #} 62 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue Template 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-element-admin", 3 | "version": "3.5.1", 4 | "description": "A magical vue admin. Typical templates for enterprise applications. Newest development stack of vue. Lots of awesome features", 5 | "author": "Pan ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 10 | "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js", 11 | "build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js", 12 | "lint": "eslint --ext .js,.vue src", 13 | "test": "npm run lint" 14 | }, 15 | "dependencies": { 16 | "axios": "0.17.1", 17 | "clipboard": "1.7.1", 18 | "codemirror": "5.32.0", 19 | "dropzone": "5.2.0", 20 | "element-ui": "2.4.1", 21 | "font-awesome": "4.7.0", 22 | "js-cookie": "2.2.0", 23 | "jsonlint": "1.6.2", 24 | "jszip": "3.1.5", 25 | "mockjs": "1.0.1-beta3", 26 | "normalize.css": "7.0.0", 27 | "nprogress": "0.2.0", 28 | "vue": "2.5.10", 29 | "vue-i18n": "7.3.2", 30 | "vue-router": "3.0.1", 31 | "vuex": "3.0.1" 32 | }, 33 | "devDependencies": { 34 | "autoprefixer": "7.2.3", 35 | "babel-core": "6.26.0", 36 | "babel-eslint": "8.0.3", 37 | "babel-helper-vue-jsx-merge-props": "2.0.3", 38 | "babel-loader": "7.1.2", 39 | "babel-plugin-add-module-exports": "^0.2.1", 40 | "babel-plugin-module-resolver": "^2.2.0", 41 | "babel-plugin-syntax-jsx": "6.18.0", 42 | "babel-plugin-transform-runtime": "6.23.0", 43 | "babel-plugin-transform-vue-jsx": "3.5.0", 44 | "babel-preset-env": "1.6.1", 45 | "babel-preset-stage-2": "6.24.1", 46 | "chalk": "2.3.0", 47 | "copy-webpack-plugin": "4.3.0", 48 | "cross-env": "5.1.1", 49 | "css-loader": "0.28.7", 50 | "eslint": "4.13.1", 51 | "eslint-friendly-formatter": "3.0.0", 52 | "eslint-loader": "1.9.0", 53 | "eslint-plugin-html": "4.0.1", 54 | "extract-text-webpack-plugin": "3.0.2", 55 | "file-loader": "1.1.5", 56 | "friendly-errors-webpack-plugin": "1.6.1", 57 | "html-webpack-plugin": "2.30.1", 58 | "node-notifier": "5.1.2", 59 | "node-sass": "^4.7.2", 60 | "optimize-css-assets-webpack-plugin": "3.2.0", 61 | "ora": "1.3.0", 62 | "portfinder": "1.0.13", 63 | "postcss-import": "11.0.0", 64 | "postcss-loader": "2.0.9", 65 | "postcss-url": "7.3.0", 66 | "pushstate-server": "3.0.1", 67 | "rimraf": "2.6.2", 68 | "sass-loader": "6.0.6", 69 | "script-loader": "0.7.2", 70 | "semver": "5.4.1", 71 | "shelljs": "0.7.8", 72 | "svg-sprite-loader": "3.5.2", 73 | "uglifyjs-webpack-plugin": "1.1.3", 74 | "url-loader": "0.6.2", 75 | "vue-loader": "13.5.0", 76 | "vue-style-loader": "3.0.3", 77 | "vue-template-compiler": "2.5.10", 78 | "webpack": "3.10.0", 79 | "webpack-bundle-analyzer": "2.9.1", 80 | "webpack-dev-server": "2.9.7", 81 | "webpack-merge": "4.1.1", 82 | "particles.js": "^2.0.0" 83 | }, 84 | "engines": { 85 | "node": ">= 4.0.0", 86 | "npm": ">= 3.0.0" 87 | }, 88 | "browserslist": [ 89 | "> 1%", 90 | "last 2 versions", 91 | "not ie <= 8" 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/api/adminRelationDialog.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * 查询未关联数据 5 | * @param {*} url 链接 6 | * @param {*} query 查询参数 7 | */ 8 | export function noRelation(url, query) { 9 | return request({ 10 | url: url, 11 | method: 'get', 12 | params: query 13 | }) 14 | } 15 | 16 | /** 17 | * 查询已关联数据 18 | * @param {*} url 链接 19 | * @param {*} query 查询参数 20 | */ 21 | export function relation(url, query) { 22 | return request({ 23 | url: url, 24 | method: 'get', 25 | params: query 26 | }) 27 | } 28 | 29 | /** 30 | *批量保存关联关系 31 | * @param {*} url 链接 32 | * @param {*} query 保存关联参数 33 | */ 34 | export function batchSave(url, params) { 35 | return request({ 36 | url: url, 37 | method: 'post', 38 | data: params 39 | }) 40 | } 41 | 42 | /** 43 | *批量删除关联关系 44 | * @param {*} url 链接 45 | * @param {*} query 删除关联参数 46 | */ 47 | export function batchDelete(url, params) { 48 | return request({ 49 | url: url, 50 | method: 'post', 51 | data: params 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function fetch(url, query) { 4 | return request({ 5 | url: url, 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function fetchList(url, query) { 12 | return request({ 13 | url: url, 14 | method: 'get', 15 | params: query 16 | }) 17 | } 18 | 19 | export function curd(url, method, params) { 20 | const methods = ['get', 'delete'] 21 | const key = methods.includes(method) ? 'params' : 'data' 22 | return request({ 23 | url: url, 24 | method: method, 25 | [key]: params 26 | }) 27 | } 28 | 29 | export function fetchInfo(url, id) { 30 | return request({ 31 | url: url + '/' + id, 32 | method: 'get' 33 | }) 34 | } 35 | 36 | export function deleteId(url, id) { 37 | return request({ 38 | url: url + '/' + id, 39 | method: 'delete' 40 | }) 41 | } 42 | 43 | export function checkIsExist(url, params) { 44 | return request({ 45 | url: url, 46 | method: 'get', 47 | params: params 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function loginByUsername(username, password) { 4 | const data = { 5 | username: username, 6 | password: password, 7 | grant_type: 'password', 8 | client: 'frontend' 9 | } 10 | return request({ 11 | url: '/login/oauth/token', 12 | method: 'post', 13 | params: data, 14 | header: { 15 | 'Content-Type': 'application/x-www-form-urlencoded' 16 | }, 17 | auth: { 18 | username: 'frontend', 19 | password: 'frontend' 20 | } 21 | }) 22 | } 23 | 24 | export function logout() { 25 | return request({ 26 | url: '/login/logout', 27 | method: 'post' 28 | }) 29 | } 30 | 31 | export function getUserInfo() { 32 | return request({ 33 | url: '/admin/api/sysUser/noPermission/userInfo', 34 | method: 'get' 35 | }) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/api/menu.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function fetchMenuTree(query) { 4 | return request({ 5 | url: '/admin/api/sysMenu/tree', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function curdMenu(method, params) { 12 | const methods = ['get', 'delete'] 13 | const key = methods.includes(method) ? 'params' : 'data' 14 | if (methods.indexOf(method) >= 0) { 15 | return request({ 16 | url: '/admin/api/sysMenu', 17 | method: method 18 | }) 19 | } else { 20 | return request({ 21 | url: '/admin/api/sysMenu', 22 | method: method, 23 | [key]: params 24 | }) 25 | } 26 | } 27 | 28 | export function fetchPermissionNoExistMenus(query) { 29 | return request({ 30 | url: '/admin/api/sysMenu/permissionNoExistMenus', 31 | method: 'GET', 32 | params: query 33 | }) 34 | } 35 | 36 | export function fetchPermissionMenus(query) { 37 | return request({ 38 | url: '/admin/api/sysMenu/permissionMenus', 39 | method: 'GET', 40 | params: query 41 | }) 42 | } 43 | 44 | export function checkIsExist(params) { 45 | return request({ 46 | url: '/admin/api/sysMenu/noPermission/exist', 47 | method: 'GET', 48 | params: params 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /src/api/permission.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const url = '/admin/api/sysPermission' 4 | 5 | export function fetchList(query) { 6 | return request({ 7 | url: url, 8 | method: 'get', 9 | params: query 10 | }) 11 | } 12 | 13 | export function crud(method, params) { 14 | const methods = ['get', 'delete'] 15 | const key = methods.includes(method) ? 'params' : 'data' 16 | return request({ 17 | url: url, 18 | method: method, 19 | [key]: params 20 | }) 21 | } 22 | 23 | export function checkExist(query) { 24 | return request({ 25 | url: '/admin/api/sysPermission/exist', 26 | method: 'GET', 27 | params: query 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /src/api/permissionMenu.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function noRelation(query) { 4 | return request({ 5 | url: '/admin/api/sysPermission/permissionNoRelationMenuTree', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function relation(query) { 12 | return request({ 13 | url: '/admin/api/sysPermission/permissionMenuTree', 14 | method: 'get', 15 | params: query 16 | }) 17 | } 18 | 19 | export function batchSave(params) { 20 | return request({ 21 | url: '/admin/api/sysPermission/batchMenuAdd', 22 | method: 'post', 23 | data: params 24 | }) 25 | } 26 | 27 | export function batchDelete(params) { 28 | return request({ 29 | url: '/admin/api/sysPermission/batchMenuDelete', 30 | method: 'post', 31 | data: params 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/api/resource.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const url = '/admin/api/sysResource' 4 | 5 | export function fetchList(query) { 6 | return request({ 7 | url: url, 8 | method: 'get', 9 | params: query 10 | }) 11 | } 12 | 13 | export function fetchInfo(query) { 14 | return request({ 15 | url: url + '/info', 16 | method: 'get', 17 | params: query 18 | }) 19 | } 20 | 21 | export function crud(method, params) { 22 | const methods = ['get', 'delete'] 23 | const key = methods.includes(method) ? 'params' : 'data' 24 | return request({ 25 | url: url, 26 | method: method, 27 | [key]: params 28 | }) 29 | } 30 | 31 | export function checkExist(query) { 32 | return request({ 33 | url: '/admin/api/sysResource/exist', 34 | method: 'GET', 35 | params: query 36 | }) 37 | } 38 | 39 | export function checkNameExist(query) { 40 | return request({ 41 | url: '/admin/api/sysResource/nameExist', 42 | method: 'GET', 43 | params: query 44 | }) 45 | } 46 | 47 | export function checkUrlExist(query) { 48 | return request({ 49 | url: '/admin/api/sysResource/urlExist', 50 | method: 'GET', 51 | params: query 52 | }) 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/api/roleUser.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function crudRoleUser(method, url, params) { 4 | const methods = ['get', 'delete'] 5 | const key = methods.includes(method) ? 'params' : 'data' 6 | return request({ 7 | url: url, 8 | method: method, 9 | [key]: params 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function fetchUserMenuTree(query) { 4 | return request({ 5 | url: '/admin/api/sysUser/noPermission/userMenuTree', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function checkUserNameIsExist(query) { 12 | return request({ 13 | url: '/admin/api/sysUser/noPermission/checkUserNameIsExist', 14 | method: 'GET', 15 | params: query 16 | }) 17 | } 18 | 19 | export function fetchRoleNoExistUsers(query) { 20 | return request({ 21 | url: '/admin/api/sysUser/roleNoExistUsers', 22 | method: 'GET', 23 | params: query 24 | }) 25 | } 26 | 27 | export function fetchRoleUsers(query) { 28 | return request({ 29 | url: '/admin/api/sysUser/roleUsers', 30 | method: 'GET', 31 | params: query 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/api/userGroup.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const url = '/admin/api/sysUserGroup' 4 | 5 | export function fetchList(query) { 6 | return request({ 7 | url: url, 8 | method: 'get', 9 | params: query 10 | }) 11 | } -------------------------------------------------------------------------------- /src/api/userRole.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function noRelation(query) { 4 | return request({ 5 | url: '/admin/api/sysUserRole/userNoRelationRoleList', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function relation(query) { 12 | return request({ 13 | url: '/admin/api/sysUserRole/userRoleList', 14 | method: 'get', 15 | params: query 16 | }) 17 | } 18 | 19 | export function batchSave(params) { 20 | return request({ 21 | url: '/admin/api/sysUserRole/batchSave', 22 | method: 'post', 23 | data: params 24 | }) 25 | } 26 | 27 | export function batchDelete(params) { 28 | return request({ 29 | url: '/admin/api/sysUserRole/batchDelete', 30 | method: 'post', 31 | data: params 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/assets/401_images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/src/assets/401_images/401.gif -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/src/assets/custom-theme/fonts/element-icons.ttf -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/src/assets/custom-theme/fonts/element-icons.woff -------------------------------------------------------------------------------- /src/components/BackToTop/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 83 | 84 | 111 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 42 | 43 | 56 | -------------------------------------------------------------------------------- /src/components/Clickoutside/index.js: -------------------------------------------------------------------------------- 1 | import Clickoutside from 'element-ui/src/utils/clickoutside' 2 | export default Clickoutside 3 | -------------------------------------------------------------------------------- /src/components/ErrorLog/index.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 59 | 60 | 78 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 46 | -------------------------------------------------------------------------------- /src/components/MLoading/index.js: -------------------------------------------------------------------------------- 1 | import service from './src/index' 2 | export default { 3 | install(Vue) { 4 | Vue.prototype.$mloading = service 5 | }, 6 | service 7 | } 8 | -------------------------------------------------------------------------------- /src/components/MLoading/src/MLoading.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 54 | 55 | 71 | -------------------------------------------------------------------------------- /src/components/OperationLog/index.js: -------------------------------------------------------------------------------- 1 | 2 | import component from './src/index' 3 | component.install = function(Vue) { 4 | Vue.component(component.name.component) 5 | } 6 | export default component 7 | -------------------------------------------------------------------------------- /src/components/OperationLog/src/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 109 | -------------------------------------------------------------------------------- /src/components/Particles/particles-data.js: -------------------------------------------------------------------------------- 1 | const particlesData = { 2 | 'particles': { 3 | 'number': { 4 | 'value': 80, 5 | 'density': { 6 | 'enable': true, 7 | 'value_area': 800 8 | } 9 | }, 10 | 'color': { 11 | 'value': '#9E9E9E' 12 | }, 13 | 'shape': { 14 | 'type': 'circle', 15 | 'stroke': { 16 | 'width': 0, 17 | 'color': '#000000' 18 | }, 19 | 'polygon': { 20 | 'nb_sides': 5 21 | }, 22 | 'image': { 23 | 'src': 'img/github.svg', 24 | 'width': 100, 25 | 'height': 100 26 | } 27 | }, 28 | 'opacity': { 29 | 'value': 0.5, 30 | 'random': false, 31 | 'anim': { 32 | 'enable': false, 33 | 'speed': 1, 34 | 'opacity_min': 0.1, 35 | 'sync': false 36 | } 37 | }, 38 | 'size': { 39 | 'value': 3, 40 | 'random': true, 41 | 'anim': { 42 | 'enable': false, 43 | 'speed': 40, 44 | 'size_min': 0.1, 45 | 'sync': false 46 | } 47 | }, 48 | 'line_linked': { 49 | 'enable': true, 50 | 'distance': 150, 51 | 'color': '#ffffff', 52 | 'opacity': 0.4, 53 | 'width': 1 54 | }, 55 | 56 | 'move': { 57 | 'enable': true, 58 | 'speed': 2, 59 | 'direction': 'none', 60 | 'random': false, 61 | 'straight': false, 62 | 'out_mode': 'out', 63 | 'attract': { 64 | 'enable': false, 65 | 'rotateX': 600, 66 | 'rotateY': 1200 67 | } 68 | } 69 | }, 70 | 'interactivity': { 71 | 'detect_on': 'canvas', 72 | 'events': { 73 | 'onhover': { 74 | 'enable': true, 75 | 'mode': 'grab' 76 | }, 77 | 'onclick': { 78 | 'enable': true, 79 | 'mode': 'push' 80 | }, 81 | 'resize': true 82 | }, 83 | 'modes': { 84 | 'grab': { 85 | 'distance': 100, 86 | 'line_linked': { 87 | 'opacity': 1 88 | } 89 | }, 90 | 'bubble': { 91 | 'distance': 400, 92 | 'size': 40, 93 | 'duration': 2, 94 | 'opacity': 8, 95 | 'speed': 3 96 | }, 97 | 'repulse': { 98 | 'distance': 200 99 | }, 100 | 'push': { 101 | 'particles_nb': 4 102 | }, 103 | 'remove': { 104 | 'particles_nb': 2 105 | } 106 | } 107 | }, 108 | 'retina_detect': true, 109 | 'config_demo': { 110 | 'hide_card': false, 111 | 'background_color': '#b61924', 112 | 'background_image': '', 113 | 'background_position': '50% 50%', 114 | 'background_repeat': 'no-repeat', 115 | 'background_size': 'cover' 116 | } 117 | } 118 | 119 | export { 120 | particlesData 121 | } 122 | -------------------------------------------------------------------------------- /src/components/PermissionButton/index.js: -------------------------------------------------------------------------------- 1 | import PermissionButton from './src/index' 2 | PermissionButton.install = function(Vue) { 3 | Vue.component(PermissionButton.name, PermissionButton) 4 | } 5 | export default PermissionButton 6 | -------------------------------------------------------------------------------- /src/components/PermissionButton/src/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 57 | 58 | -------------------------------------------------------------------------------- /src/components/ScrollBar/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | 44 | 58 | -------------------------------------------------------------------------------- /src/components/ScrollPane/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 61 | 62 | 73 | -------------------------------------------------------------------------------- /src/components/SearchBox/index.js: -------------------------------------------------------------------------------- 1 | 2 | import component from './src/index' 3 | export default component 4 | -------------------------------------------------------------------------------- /src/components/SearchBox/src/js/index.js: -------------------------------------------------------------------------------- 1 | import { Clickoutside } from '@/components/components.js' 2 | import { fetchList } from '@/api/index.js' 3 | import props from './props.js' 4 | import model from './model.js' 5 | import watch from './watch.js' 6 | export default { 7 | name: 'SearchBox', 8 | mixins: [props, model, watch], 9 | directives: { 10 | Clickoutside 11 | }, 12 | created() { 13 | this.placeholder = this.data.placeholder 14 | }, 15 | methods: { 16 | compositioninput(event) { 17 | if (!this.isLock) { 18 | this.getPageList(1) 19 | } 20 | }, 21 | handleKeydown(e) { 22 | if (e.key === 'Backspace') { 23 | if (this.$util.isEmpty(this.text)) { 24 | this.selectRow = null 25 | this.emitParentData() 26 | } 27 | } 28 | }, 29 | getPageList() { 30 | if (!this.loading) { 31 | this.refresh = false 32 | this.loading = true 33 | this.progress = true 34 | this.loadingText = '加载中' 35 | const data = this.data 36 | const params = { 37 | itemType: data.type, 38 | codeOrName: !this.text || this.text == null ? '' : this.text 39 | } 40 | if (data.isUpLevel) { 41 | if (this.upLevelId) { 42 | params.upLevelId = this.upLevelId 43 | } 44 | } 45 | 46 | fetchList(params).then(response => { 47 | this.progress = false 48 | this.loading = false 49 | 50 | this.tableDate = response.data.items 51 | 52 | if (this.requestCount > 0) { 53 | this.requestCount = 0 54 | this.getPageList(1) 55 | } 56 | }) 57 | } 58 | }, 59 | handleFocus() { 60 | this.className = 'input-layout focus-border' 61 | this.$emit('foucs') 62 | if (!this.firstLoading) { 63 | if (this.data.isUpLevel && this.upLevelId == null) return 64 | this.getPageList(1) 65 | this.firstLoading = true 66 | } 67 | this.tableVisiable = true 68 | }, 69 | handleBlur() { 70 | this.className = 'input-layout' 71 | }, 72 | handleClose() { 73 | this.selectRow = null 74 | this.emitParentData() 75 | }, 76 | handleSizeChange(val) { 77 | this.pageSize = val 78 | this.getPageList(1) 79 | }, 80 | clickTableRow(item) { 81 | this.text = null 82 | this.selectRow = item 83 | this.emitParentData() 84 | }, 85 | emitParentData() { 86 | this.$emit('input', this.selectRow) 87 | }, 88 | close() { 89 | this.tableVisiable = false 90 | }, 91 | clickRefresh() { 92 | this.getPageList(this.currentPage) 93 | } 94 | }, 95 | computed: { 96 | 'currentWidth': function() { 97 | return this.width ? this.width : this.$refs.inputLayout.offsetWidth 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/components/SearchBox/src/js/model.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | url: null, // 请求地址 5 | placeholder: null, 6 | text: null, // 输入框文本 7 | tableVisiable: false, // 是否显示表格 8 | tableDate: [], // 当前下拉数据 9 | selectRow: null, // 当前选中行 10 | pageSize: 10, 11 | currentPage: 1, 12 | totalPages: 0, 13 | total: 0, 14 | loading: false, 15 | loadingText: null, 16 | refresh: false, 17 | progress: true, 18 | requestCount: 0, 19 | firstLoading: false, 20 | className: 'input-layout' 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/SearchBox/src/js/props.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | value: { 4 | required: true 5 | }, 6 | data: Object, // 要绑定的查询条件 url 类型等 7 | isUpLevel: false, // 是否是根据关联Id去获取数据 8 | upLevelId: String, // 关联的数据Id 9 | getKeys: String, // 动态返回要获取的某些字段 10 | pageVisiable: true, // 是否显示分页组件,默认显示 11 | displayType: { // 显示类型默认 0.名称 1.编码 12 | type: Number, 13 | default: 0 14 | }, 15 | titles: null, 16 | width: 0 // 组件宽 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/SearchBox/src/js/watch.js: -------------------------------------------------------------------------------- 1 | export default { 2 | watch: { 3 | value(val) { 4 | if (val === null || val === '') { 5 | this.selectRow = null 6 | this.text = null 7 | this.firstLoading = false 8 | } else { 9 | this.selectRow = val 10 | } 11 | }, 12 | selectRow: { 13 | handler(val) { 14 | if (!val) { 15 | this.placeholder = this.data.placeholder 16 | } else { 17 | this.placeholder = null 18 | } 19 | } 20 | }, 21 | upLevelId: { 22 | handler(val) { 23 | if (val) { 24 | this.firstLoading = false 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/SearchPage/index.js: -------------------------------------------------------------------------------- 1 | 2 | import component from './src/index' 3 | export default component 4 | -------------------------------------------------------------------------------- /src/components/SelectBox/index.js: -------------------------------------------------------------------------------- 1 | import SelectBox from './src/index' 2 | SelectBox.install = function(Vue) { 3 | Vue.component(SelectBox.name, SelectBox) 4 | } 5 | export default SelectBox 6 | -------------------------------------------------------------------------------- /src/components/SelectBox/src/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /src/components/TableContain/index.js: -------------------------------------------------------------------------------- 1 | import TableContain from './src/index' 2 | TableContain.install = function(Vue) { 3 | Vue.component(TableContain.name, TableContain) 4 | } 5 | export default TableContain 6 | -------------------------------------------------------------------------------- /src/components/TableContain/src/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/components.js: -------------------------------------------------------------------------------- 1 | // 非全局组件统一维护 方便其他组件引入 import { DataExport,....} from '@components/components' 2 | export { default as Clickoutside } from './Clickoutside/index' 3 | 4 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | // 全局组件 服务 对象等 2 | import Vue from 'vue' 3 | import MLoading from './MLoading/index' 4 | import SelectBox from './SelectBox/index' 5 | import SearchBox from './SearchBox/index' 6 | import SearchPage from './SearchPage/index' 7 | import PermissionButton from './PermissionButton/index' 8 | const components = [ 9 | SelectBox, 10 | SearchBox, 11 | SearchPage, 12 | PermissionButton 13 | ] 14 | const install = (Vue, opts) => { 15 | components.map(component => { 16 | Vue.component(component.name, component) 17 | }) 18 | Vue.prototype.$mloading = MLoading.service 19 | } 20 | 21 | if (typeof window !== 'undefined' && window.Vue) { 22 | install(window.Vue) 23 | } 24 | 25 | Vue.use(install) 26 | -------------------------------------------------------------------------------- /src/components/indexComponents.js: -------------------------------------------------------------------------------- 1 | export { default as SearchBar } from './SearchBar/index' 2 | export { default as TableContain } from './TableContain/index' 3 | -------------------------------------------------------------------------------- /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/filters/index.js: -------------------------------------------------------------------------------- 1 | function pluralize(time, label) { 2 | if (time === 1) { 3 | return time + label 4 | } 5 | return time + label + 's' 6 | } 7 | 8 | export function timeAgo(time) { 9 | const between = Date.now() / 1000 - Number(time) 10 | if (between < 3600) { 11 | return pluralize(~~(between / 60), ' minute') 12 | } else if (between < 86400) { 13 | return pluralize(~~(between / 3600), ' hour') 14 | } else { 15 | return pluralize(~~(between / 86400), ' day') 16 | } 17 | } 18 | 19 | export function parseTime(time, cFormat) { 20 | if (arguments.length === 0) { 21 | return null 22 | } 23 | 24 | if ((time + '').length === 10) { 25 | time = +time * 1000 26 | } 27 | 28 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 29 | let date 30 | if (typeof time === 'object') { 31 | date = time 32 | } else { 33 | date = new Date(parseInt(time)) 34 | } 35 | const formatObj = { 36 | y: date.getFullYear(), 37 | m: date.getMonth() + 1, 38 | d: date.getDate(), 39 | h: date.getHours(), 40 | i: date.getMinutes(), 41 | s: date.getSeconds(), 42 | a: date.getDay() 43 | } 44 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 45 | let value = formatObj[key] 46 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 47 | if (result.length > 0 && value < 10) { 48 | value = '0' + value 49 | } 50 | return value || 0 51 | }) 52 | return time_str 53 | } 54 | 55 | export function formatTime(time, option) { 56 | time = +time * 1000 57 | const d = new Date(time) 58 | const now = Date.now() 59 | 60 | const diff = (now - d) / 1000 61 | 62 | if (diff < 30) { 63 | return '刚刚' 64 | } else if (diff < 3600) { // less 1 hour 65 | return Math.ceil(diff / 60) + '分钟前' 66 | } else if (diff < 3600 * 24) { 67 | return Math.ceil(diff / 3600) + '小时前' 68 | } else if (diff < 3600 * 24 * 2) { 69 | return '1天前' 70 | } 71 | if (option) { 72 | return parseTime(time, option) 73 | } else { 74 | return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分' 75 | } 76 | } 77 | 78 | /* 数字 格式化*/ 79 | export function nFormatter(num, digits) { 80 | const si = [ 81 | { value: 1E18, symbol: 'E' }, 82 | { value: 1E15, symbol: 'P' }, 83 | { value: 1E12, symbol: 'T' }, 84 | { value: 1E9, symbol: 'G' }, 85 | { value: 1E6, symbol: 'M' }, 86 | { value: 1E3, symbol: 'k' } 87 | ] 88 | for (let i = 0; i < si.length; i++) { 89 | if (num >= si[i].value) { 90 | return (num / si[i].value + 0.1).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol 91 | } 92 | } 93 | return num.toString() 94 | } 95 | 96 | export function html2Text(val) { 97 | const div = document.createElement('div') 98 | div.innerHTML = val 99 | return div.textContent || div.innerText 100 | } 101 | 102 | export function toThousandslsFilter(num) { 103 | return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')) 104 | } 105 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg组件 3 | import generateIconsView from '@/views/svgIcons/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/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | route: { 3 | dashboard: 'Dashboard' 4 | }, 5 | navbar: { 6 | logOut: 'Log Out', 7 | dashboard: 'Dashboard', 8 | github: 'Github', 9 | screenfull: 'screenfull', 10 | theme: 'theme', 11 | juejin: 'JueJin', 12 | csdn: 'CSDN' 13 | }, 14 | login: { 15 | title: 'Login Form', 16 | logIn: 'Log in', 17 | username: 'Username', 18 | password: 'Password', 19 | any: 'any', 20 | thirdparty: 'Or connect with', 21 | thirdpartyTips: 'Can not be simulated on local, so please combine you own business simulation! ! !' 22 | }, 23 | permission: { 24 | roles: 'Your roles', 25 | switchRoles: 'Switch roles' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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') || 'zh', // set locale 24 | messages // set locale messages 25 | }) 26 | 27 | export default i18n 28 | -------------------------------------------------------------------------------- /src/lang/zh.js: -------------------------------------------------------------------------------- 1 | export default { 2 | route: { 3 | dashboard: '首页', 4 | systemAdmin: '系统管理', 5 | systemAdminUser: '用户管理', 6 | systemAdminMenu: '菜单管理', 7 | systemAdminRole: '角色管理', 8 | systemAdminUserGroup: '用户组管理', 9 | systemAdminResource: '资源管理', 10 | systemAdminPermission: '权限管理', 11 | test: '测试', 12 | test1: '测试1', 13 | test2: '测试2' 14 | }, 15 | navbar: { 16 | logOut: '退出登录', 17 | dashboard: '首页', 18 | github: '项目地址', 19 | screenfull: '全屏', 20 | theme: '换肤', 21 | juejin: '掘金专栏', 22 | csdn: '个人博客' 23 | }, 24 | login: { 25 | title: '系统登录', 26 | logIn: '登录', 27 | username: '账号', 28 | password: '密码', 29 | any: '随便填', 30 | thirdparty: '第三方登录', 31 | thirdpartyTips: '本地不能模拟,请结合自己业务进行模拟!!!' 32 | }, 33 | errorLog: { 34 | tips: '请点击右上角bug小图标', 35 | description: '现在的管理后台基本都是spa的形式了,它增强了用户体验,但同时也会增加页面出问题的可能性,可能一个小小的疏忽就导致整个页面的死锁。好在 Vue 官网提供了一个方法来捕获处理异常,你可以在其中进行错误处理或者异常上报。', 36 | documentation: '文档介绍' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css'// A modern alternative to CSS resets 4 | 5 | import Element from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | 8 | import '@/styles/index.scss' // global css 9 | 10 | import App from './App' 11 | import router from './router' 12 | import store from './store' 13 | 14 | import i18n from './lang' // Internationalization 15 | import './components' 16 | import './icons' // icon 17 | import './errorLog'// error log 18 | import './permission' // permission control 19 | import './mock' // simulation data 20 | import './public' 21 | 22 | import * as filters from './filters' // global filters 23 | 24 | Vue.use(Element, { 25 | size: 'medium', // set element-ui default size 26 | i18n: (key, value) => i18n.t(key, value) 27 | }) 28 | 29 | // register global utility filters. 30 | Object.keys(filters).forEach(key => { 31 | Vue.filter(key, filters[key]) 32 | }) 33 | 34 | Vue.config.productionTip = false 35 | 36 | new Vue({ 37 | el: '#app', 38 | router, 39 | store, 40 | i18n, 41 | template: '', 42 | components: { App } 43 | }) 44 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import loginAPI from './login' 3 | 4 | // Mock.setup({ 5 | // timeout: '350-600' 6 | // }) 7 | 8 | // 登录相关 9 | // Mock.mock(/\/login\/login/, 'post', loginAPI.loginByUsername) 10 | Mock.mock(/\/login\/logout/, 'post', loginAPI.logout) 11 | Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo) 12 | 13 | export default Mock 14 | -------------------------------------------------------------------------------- /src/mock/login.js: -------------------------------------------------------------------------------- 1 | import { param2Obj } from '@/utils' 2 | 3 | const userMap = { 4 | admin: { 5 | roles: ['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 | roles: ['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 userMap['editor'] 31 | // return false 32 | } 33 | }, 34 | logout: () => 'success' 35 | } 36 | -------------------------------------------------------------------------------- /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 { Message } from 'element-ui' 4 | import NProgress from 'nprogress' // progress bar 5 | import 'nprogress/nprogress.css'// progress bar style 6 | import { getToken } from '@/utils/auth' // getToken from cookie 7 | 8 | NProgress.configure({ showSpinner: false }) // NProgress Configuration 9 | 10 | // permissiom judge function 11 | function hasPermission(roles, permissionRoles) { 12 | if (roles.indexOf('admin') >= 0) return true // admin permission passed directly 13 | if (!permissionRoles) return true 14 | return roles.some(role => permissionRoles.indexOf(role) >= 0) 15 | } 16 | 17 | const whiteList = ['/login', '/authredirect']// no redirect whitelist 18 | 19 | router.beforeEach((to, from, next) => { 20 | NProgress.start() // start progress bar 21 | if (getToken()) { // determine if there has token 22 | /* has token*/ 23 | if (to.path === '/login') { 24 | next({ path: '/' }) 25 | NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it 26 | } else { 27 | if (store.getters.user === '') { // 判断当前用户是否已拉取完user_info信息 28 | store.dispatch('GetUserInfo').then(res => { // 拉取user_info 29 | store.dispatch('LoadUserMenuTree').then(data => { 30 | const roles = data 31 | store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表 32 | router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 33 | next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record 34 | }) 35 | }) 36 | }).catch(() => { 37 | store.dispatch('FedLogOut').then(() => { 38 | Message.error('Verification failed, please login again') 39 | next({ path: '/login' }) 40 | }) 41 | }) 42 | } else { 43 | // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ 44 | if (hasPermission(store.getters.roles, to.meta.roles)) { 45 | next()// 46 | } else { 47 | next({ path: '/401', replace: true, query: { noGoBack: true }}) 48 | } 49 | // 可删 ↑ 50 | } 51 | } 52 | } else { 53 | /* has no token*/ 54 | if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 55 | next() 56 | } else { 57 | next('/login') // 否则全部重定向到登录页 58 | NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it 59 | } 60 | } 61 | }) 62 | 63 | router.afterEach(() => { 64 | NProgress.done() // finish progress bar 65 | }) 66 | -------------------------------------------------------------------------------- /src/public/addModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author super 3 | * @description 引入并声明所有新增页面的公共变量及组件 4 | */ 5 | import parent from './parent.js' 6 | export default { 7 | mixins: [parent], 8 | props: { 9 | data: { // 用于给外部的数据传递 10 | type: Object, 11 | default: null 12 | } 13 | }, 14 | data() { 15 | return { 16 | dialog: { 17 | visiable: false, // 显示弹出框 18 | title: null // 弹出框标题 19 | }, 20 | button: { 21 | loading: false, // 确定按钮状态 22 | text: '确定' // 按钮默认文字 23 | }, 24 | originalData: null // 原始数据 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/public/index.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue' 3 | /** 4 | * @author super 5 | * @param {*} object 字符串 对象 数组 6 | * @description 判断字符串,数组,对象是否为空 对象 = {} 数据长度为 0 都返回为空的判断 7 | */ 8 | Vue.prototype.$empty = (obj) => { 9 | if (obj === undefined || obj === null || obj === '' || obj.length === 0) return true 10 | if (typeof obj === 'string') { 11 | if (obj.trim().length === 0) { 12 | return true 13 | } 14 | } else if (typeof obj === 'object') { 15 | if (JSON.stringify(obj) === '{}') { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | 22 | /** 23 | * @author super 24 | * @param {*} object 25 | * @description 复制一个全新的对象并返回 26 | */ 27 | Vue.prototype.$copy = (obj) => { 28 | if (obj === undefined) { 29 | return null 30 | } 31 | const newObj = {} 32 | for (const key in obj) { 33 | newObj[key] = obj[key] 34 | } 35 | return newObj 36 | } 37 | 38 | /** 39 | * @author super 40 | * @param {original} object 原始对象 41 | * @param {obj} object 包含原始对象需要赋值的key value集合 42 | * @description 给对象指定的key设置新的值,用于重置功能或大量字段初始化设值得功能,避免过多使用this.的这种方式 43 | */ 44 | Vue.prototype.$setKeyValue = (original, obj) => { 45 | for (const key in obj) { 46 | original[key] = obj[key] 47 | } 48 | } 49 | 50 | /** 51 | * @author super 52 | * @param {original} object 原始对象 53 | * @param {obj} object 新对象 54 | * @description 给原始对象指定的key设置成新对象的值,避免大量赋值工作 55 | */ 56 | Vue.prototype.$setOriginalKV = (original, newObj) => { 57 | for (const key in original) { 58 | original[key] = newObj[key] 59 | } 60 | } 61 | 62 | /** 63 | * @author super 64 | * @param {original} object 原始对象 65 | * @param {obj} object 包含原始对象需要判断的key value集合 66 | * @description 判断原始是否包含 新对象的 key 包括 key 的值是否和原始对象一致 67 | */ 68 | Vue.prototype.$compareObjValue = (original, obj) => { 69 | for (const key in obj) { 70 | if (!original.hasOwnProperty(key) || obj[key] !== original[key]) { 71 | return false 72 | } 73 | } 74 | return true 75 | } 76 | -------------------------------------------------------------------------------- /src/public/indexModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author super 3 | * @description 引入并声明所有页表页面的公共变量及组件 4 | */ 5 | import { SearchBar, TableContain } from '@/components/indexComponents.js' 6 | import parent from './parent.js' 7 | export default { 8 | components: { SearchBar, TableContain }, // 引入两个在列表页面都会用的组件 9 | mixins: [parent], 10 | data() { 11 | return { 12 | pagination: { 13 | pageSizes: [10, 20, 50, 100], // 默认分页可选择的每页显示的页数 14 | size: 10, // 分页每页默认显示10条 15 | page: 1, // 当前默认第一页 16 | total: 0 // 总条数 17 | }, 18 | table: { 19 | data: null, // 列表表格数据 20 | size: 'mini', // 列表表格大小 21 | maxHeight: 0 // 列表表格最大高度 22 | }, 23 | add: { 24 | visiable: false, // 是否显示新增组件 25 | data: null // 新增组件绑定的数据 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/public/parent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author super 3 | * @description 大部分页面都需要用到的字段以及默认方法 4 | */ 5 | export default { 6 | data() { 7 | return { 8 | mloading: null, // 加载框对象 9 | loading: { 10 | state: false, // 是否显示加载状态 11 | text: '加载中...' // 加载状态文字 12 | } 13 | } 14 | }, 15 | methods: { 16 | // 创建并返回一个全新的 MLoading 对象 17 | newMLoading(element, text = this.loading.text) { 18 | return this.$mloading({ 19 | target: element, 20 | text: text 21 | }) 22 | }, 23 | // 创建默认的MLoading 对象 并赋值给 this.mloading 24 | createDefaultMLoading(element) { 25 | if (this.mloading === null) { 26 | this.mloading = this.newMLoading(element) 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public/rules.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | rules: { 5 | input: [{ 6 | required: true, 7 | message: '请输入', 8 | trigger: 'change' 9 | }], 10 | select: [{ 11 | required: true, 12 | message: '请选择', 13 | trigger: 'change' 14 | }] 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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/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 | user: state => state.user.user, 8 | avatar: state => state.user.avatar, 9 | name: state => state.user.name, 10 | introduction: state => state.user.introduction, 11 | status: state => state.user.status, 12 | roles: state => state.user.roles, 13 | setting: state => state.user.setting, 14 | permission_routers: state => state.permission.routers, 15 | addRouters: state => state.permission.addRouters, 16 | errorLogs: state => state.errorLog.logs 17 | } 18 | export default getters 19 | -------------------------------------------------------------------------------- /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') || 'en' 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 | import { constantRouterMap, LayoutComponent, _import_ } from '@/router' 3 | 4 | /** 5 | * 通过递归的方式处理后端返回的路由列表 6 | * @param roles 7 | */ 8 | function filterRouter(roles) { 9 | roles.forEach(route => { 10 | route.meta = { title: route.name, icon: route.icon } 11 | if (route.children !== null && route.children.length > 0) { 12 | if (route.component === null) { 13 | route.componentName = 'LayoutComponent' 14 | route.component = LayoutComponent 15 | route.redirect = '/sys/admin/test/test1' 16 | } else { 17 | route.componentName = route.component 18 | route.component = _import_(route.component) 19 | route.redirect = '/sys/admin/test/test1' 20 | } 21 | filterRouter(route.children) 22 | } else { 23 | route.component = _import_(route.component) 24 | } 25 | }) 26 | return roles 27 | } 28 | 29 | const permission = { 30 | state: { 31 | routers: constantRouterMap, 32 | addRouters: [] 33 | }, 34 | mutations: { 35 | SET_ROUTERS: (state, routers) => { 36 | state.addRouters = routers 37 | state.routers = constantRouterMap.concat(routers) 38 | } 39 | }, 40 | actions: { 41 | GenerateRoutes({ commit }, data) { 42 | return new Promise(resolve => { 43 | const { roles } = data 44 | const consoleRoles = filterRouter(roles) 45 | commit('SET_ROUTERS', consoleRoles) 46 | resolve() 47 | }) 48 | } 49 | } 50 | } 51 | 52 | export default permission 53 | -------------------------------------------------------------------------------- /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, index + 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/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/list-page.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 列表界面头部样式 3 | * @author super 4 | * 5 | */ 6 | .search-bar { 7 | width: 100%; 8 | margin: 10px 0; 9 | // padding: 8px 5px; 10 | min-height: 42px; 11 | max-height: 190px; 12 | background-color: white; 13 | @include clearfix; 14 | .left { 15 | float: left; 16 | margin-left: 5px; 17 | height: 40px; 18 | .el-range-editor--small.el-input__inner { 19 | margin-top: 5px; 20 | padding-left: 5px; 21 | padding-right: 0; 22 | width: 215px; 23 | } 24 | .el-date-editor .el-range-separator { 25 | padding: 0; 26 | line-height: 24px; 27 | margin-left: 3px; 28 | margin-right: 5px; 29 | } 30 | .el-range-editor--small .el-range-input { 31 | width: 76px; 32 | } 33 | } 34 | .right { 35 | float: right; 36 | margin-right: 5px; 37 | } 38 | .el-input { 39 | float: left; 40 | margin-top: 5px; 41 | } 42 | .el-button { 43 | float: left; 44 | margin-top: 5px; 45 | } 46 | } 47 | 48 | div.el-pagination { 49 | background-color: white; 50 | width: 100%; 51 | text-align: center; 52 | padding: 20px 0; 53 | } -------------------------------------------------------------------------------- /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/navbar.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * nav-bar 样式 3 | */ 4 | .el-menu--horizontal { 5 | border-bottom: 1px solid rgba(232,232,232, 0.5) 6 | } 7 | 8 | -------------------------------------------------------------------------------- /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: 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: 70px!important; 32 | transition: width 0.28s; 33 | // overflow: inherit; 34 | } 35 | .sidebar-container .svg-icon { 36 | font-size: 18px; 37 | margin-left: 7px; 38 | transition: width 0.28s; 39 | } 40 | .main-container { 41 | transition: width 0.28s; 42 | margin-left: 70px; 43 | } 44 | } 45 | .hideSidebar { 46 | .submenu-title-noDropdown { 47 | // padding-left: 10px!important; 48 | position: relative; 49 | span { 50 | height: 0; 51 | width: 0; 52 | overflow: hidden; 53 | visibility: hidden; 54 | transition: opacity .3s cubic-bezier(.55, 0, .1, 1); 55 | opacity: 0; 56 | display: inline-block; 57 | } 58 | &:hover { 59 | span { 60 | display: block; 61 | border-radius: 3px; 62 | z-index: 1002; 63 | width: 140px; 64 | height: 46px; 65 | visibility: visible; 66 | position: absolute; 67 | right: -145px; 68 | text-align: left; 69 | text-indent: 20px; 70 | top: 0px; 71 | background-color: $menuHover!important; 72 | opacity: 1; 73 | } 74 | } 75 | } 76 | .el-submenu { 77 | &>.el-submenu__title { 78 | // padding-left: 10px!important; 79 | &>span { 80 | display: none; 81 | } 82 | .el-submenu__icon-arrow { 83 | display: none; 84 | } 85 | } 86 | .nest-menu { 87 | .el-submenu__icon-arrow { 88 | display: block!important; 89 | } 90 | span { 91 | display: inline-block!important; 92 | } 93 | } 94 | } 95 | } 96 | .nest-menu .el-submenu>.el-submenu__title, 97 | .el-submenu .el-menu-item { 98 | min-width: 180px!important; 99 | background-color: black!important; 100 | &:hover { 101 | background-color: $menuHover!important; 102 | color: rgb(64, 158, 255)!important; 103 | } 104 | } 105 | .el-menu--collapse .el-menu .el-submenu{ 106 | min-width: 180px!important; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/styles/theme-black.scss: -------------------------------------------------------------------------------- 1 | /** 定制 SiderBar black样式 start ***/ 2 | .el-submenu{ 3 | .el-submenu__title { 4 | height: 46px; 5 | line-height: 46px; 6 | } 7 | } 8 | 9 | .el-submenu { 10 | .el-menu-item{ 11 | height: 40px; 12 | line-height: 40px; 13 | .is-active { 14 | color: white!important; 15 | } 16 | } 17 | } 18 | 19 | .submenu-title-noDropdown { 20 | height: 46px!important; 21 | line-height: 46px!important; 22 | background-color: #001529!important; 23 | &:hover { 24 | color: rgb(64, 158, 255)!important; 25 | } 26 | } 27 | 28 | .el-submenu__title { 29 | background-color: #001529!important; 30 | &:hover { 31 | color: rgb(64, 158, 255)!important; 32 | } 33 | } 34 | 35 | .el-menu-item.is-active { 36 | color: rgb(64, 158, 255)!important; 37 | } 38 | 39 | .el-submenu.is-active.is-opened > .el-submenu__title { 40 | color: rgb(64, 158, 255)!important; 41 | } 42 | 43 | .el-submenu.is-active > .el-submenu__title > svg, 44 | .el-submenu.is-active > .el-submenu__title > span, 45 | .el-submenu.is-active > .el-submenu__title .el-submenu__icon-arrow{ 46 | color: rgb(64, 158, 255)!important; 47 | } 48 | 49 | .el-menu-item > span { 50 | padding-left: 13px; 51 | } 52 | 53 | .el-menu-item.submenu-title-noDropdown > span { 54 | padding-left: 0px; 55 | } 56 | .menu-wrapper .nest-menu .el-submenu > .el-submenu__title { 57 | padding-left: 53px !important; 58 | } 59 | // .menu-wrapper .nest-menu .el-submenu .el-menu .el-submenu > .el-submenu__title { 60 | // padding-left: 73px !important; 61 | // } 62 | .el-menu--popup-right-start .el-submenu .el-submenu__title { 63 | padding-left: 32px !important; 64 | } 65 | // .menu-wrapper .nest-menu .el-menu .el-menu--inline .el-menu-item { 66 | // padding-left: 78px!important; 67 | // } 68 | 69 | /** 定制 SiderBar black样式 end ***/ 70 | 71 | /** 定制 element ui button 样式颜色 start **/ 72 | .el-button--primary { 73 | background-color: #1890ff; 74 | border-color: #1890ff; 75 | } 76 | 77 | .el-button--default { 78 | color: rgba(0,0,0,.65); 79 | background-color: #fff; 80 | border-color: #d9d9d9; 81 | } 82 | 83 | /** 定制 element ui button 样式颜色 end **/ 84 | 85 | /** 定制 element ui table 样式颜色 start **/ 86 | .el-table th>.cell { 87 | color: rgba(0,0,0,.95); 88 | font-weight: 500; 89 | font-size: 14px; 90 | } 91 | .el-table td>.cell { 92 | color: rgba(0,0,0,.65); 93 | font-size: 13px; 94 | } 95 | /** 定制 element ui table样式颜色 start **/ -------------------------------------------------------------------------------- /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:#001529; 12 | $subMenuBg:black; 13 | $menuHover:black; 14 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | const TokenKey = 'Authorization' 2 | 3 | export function getToken() { 4 | return localStorage.getItem(TokenKey) 5 | } 6 | 7 | export function setToken(token) { 8 | return localStorage.setItem(TokenKey, 'bearer' + token) 9 | } 10 | 11 | export function removeToken() { 12 | return localStorage.removeItem(TokenKey) 13 | } 14 | -------------------------------------------------------------------------------- /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 | const hasKey = this.$te('route.' + title) 4 | const translatedTitle = this.$t('route.' + title) // $t :this method from vue-i18n, inject in @/lang/index.js 5 | 6 | if (hasKey) { 7 | return translatedTitle 8 | } 9 | return title 10 | } 11 | -------------------------------------------------------------------------------- /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 | const service = axios.create({ 12 | timeout: 5000 // request timeout 13 | }) 14 | 15 | // request interceptor 16 | service.interceptors.request.use(config => { 17 | if (config.url.indexOf('login/') !== -1) { 18 | return config 19 | } 20 | // const urls = config.url.split('/') 21 | // if (urls[1] === 'admin') { 22 | // config.url = config.url.replace('/admin', '') 23 | // } 24 | // Do something before request is sent 25 | if (store.getters.token) { 26 | config.headers['Authorization'] = getToken() // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改 27 | } 28 | return config 29 | }, error => { 30 | // Do something with request error 31 | console.log(error) // for debug 32 | Promise.reject(error) 33 | }) 34 | 35 | // respone interceptor 36 | service.interceptors.response.use( 37 | response => { 38 | if (JSON.stringify(response.headers) === '{}') { 39 | return response 40 | } else { 41 | const data = response.data 42 | if (data.code === 0 || data['access_token']) { 43 | return data // 正常处理直接返回需要接受的数据 44 | } else { 45 | // 这里假设code返回不为 0 表示, 就直接返回错误的处理 46 | return Promise.reject(data) 47 | } 48 | } 49 | }, 50 | /** 51 | * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页 52 | * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中 53 | */ 54 | // const res = response.data; 55 | // if (res.code !== 20000) { 56 | // Message({ 57 | // message: res.message, 58 | // type: 'error', 59 | // duration: 5 * 1000 60 | // }); 61 | // // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; 62 | // if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 63 | // MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { 64 | // confirmButtonText: '重新登录', 65 | // cancelButtonText: '取消', 66 | // type: 'warning' 67 | // }).then(() => { 68 | // store.dispatch('FedLogOut').then(() => { 69 | // location.reload();// 为了重新实例化vue-router对象 避免bug 70 | // }); 71 | // }) 72 | // } 73 | // return Promise.reject('error'); 74 | // } else { 75 | // return response.data; 76 | // } 77 | error => { 78 | console.log('err' + error)// for debug 79 | Message({ 80 | message: error.message, 81 | type: 'error', 82 | duration: 5 * 1000 83 | }) 84 | return Promise.reject(error) 85 | }) 86 | 87 | export default service 88 | -------------------------------------------------------------------------------- /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/views/admin/atest/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/views/admin/atest/test1/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/admin/atest/test2/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/admin/atest/test3/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/views/admin/atest/test3/test3-1/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/admin/atest/test3/test3-2/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/admin/component/MenuRelation/index.js: -------------------------------------------------------------------------------- 1 | import parent from '@/public/parent.js' 2 | import addModel from '@/public/addModel.js' 3 | import { noRelation, relation, batchSave, batchDelete } from '@/api/permissionMenu' 4 | export default { 5 | name: 'menu-relation', 6 | mixins: [parent, addModel], 7 | data() { 8 | return { 9 | defaultProps: { 10 | children: 'children', 11 | label: 'title' 12 | }, 13 | leftData: { 14 | filterName: null, 15 | treeList: null, 16 | selectList: [], 17 | button: { 18 | text: '关联', 19 | loading: false 20 | } 21 | }, 22 | rightData: { 23 | filterName: null, 24 | treeList: null, 25 | selectList: [], 26 | button: { 27 | text: '取消关联', 28 | loading: false 29 | } 30 | }, 31 | roleId: null 32 | } 33 | }, 34 | created() { 35 | this.$setKeyValue(this.dialog, { title: '权限关联菜单', visiable: true }) 36 | this.permissionId = this.data.obj.id 37 | }, 38 | mounted() { 39 | this.createDefaultMLoading('.el-dialog') 40 | this.loadNoRelation() 41 | this.loadRelation() 42 | }, 43 | methods: { 44 | loadNoRelation(params = {}) { 45 | this.mloading.show() 46 | params.permissionId = this.permissionId 47 | noRelation(params).then(({ data }) => { 48 | this.mloading.hide() 49 | this.leftData.treeList = data && data != null ? data : [] 50 | }).catch(error => { 51 | console.log(error) 52 | }) 53 | }, 54 | loadRelation(params = {}) { 55 | params.permissionId = this.permissionId 56 | relation(params).then(({ data }) => { 57 | this.rightData.treeList = data && data != null ? data : [] 58 | }).catch(error => { 59 | console.log(error) 60 | }) 61 | }, 62 | closeDialog() { 63 | this.$emit('input', false) 64 | }, 65 | clickRelation() { 66 | const selectList = this.$refs.leftTree.getCheckedNodes() 67 | if (!this.$empty(selectList)) { 68 | this.$setKeyValue(this.leftData.button, { text: '关联中..', loading: true }) 69 | const params = [] 70 | selectList.forEach(item => { 71 | params.push({ menuId: item.id, permissionId: this.permissionId }) 72 | }) 73 | batchSave(params).then(({ data }) => { 74 | this.$setKeyValue(this.leftData.button, { text: '关联', loading: false }) 75 | this.loadNoRelation() 76 | this.loadRelation() 77 | }).catch(() => { 78 | this.$setKeyValue(this.leftData.button, { text: '关联', loading: false }) 79 | this.$message({ type: 'error', message: '关联失败' }) 80 | }) 81 | } 82 | }, 83 | clickCancelRelation() { 84 | const selectList = this.$refs.rightTree.getCheckedNodes() 85 | if (!this.$empty(selectList)) { 86 | this.$setKeyValue(this.rightData.button, { text: '取消关联中..', loading: true }) 87 | const menuIds = [] 88 | selectList.forEach(item => { 89 | menuIds.push(item.id) 90 | }) 91 | const params = { permissionId: this.permissionId, menuIds: menuIds } 92 | batchDelete(params).then(() => { 93 | this.$setKeyValue(this.rightData.button, { text: '取消关联', loading: false }) 94 | this.loadNoRelation() 95 | this.loadRelation() 96 | }).catch(() => { 97 | this.$setKeyValue(this.rightData.button, { text: '取消关联', loading: false }) 98 | this.$message({ type: 'error', message: '取消关联失败' }) 99 | }) 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/views/admin/component/MenuRelation/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/views/admin/component/RelationDialog/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 77 | 78 | -------------------------------------------------------------------------------- /src/views/admin/component/RelationDialog/model.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | leftData: { 5 | filterName: null, 6 | tableData: null, 7 | multipleSelection: null, 8 | button: { 9 | text: '关联', 10 | loading: false 11 | }, 12 | pagination: { 13 | pageSizes: [10, 20, 50, 100], 14 | size: 20, 15 | page: 1, 16 | total: 0 17 | } 18 | }, 19 | rightData: { 20 | filterName: null, 21 | tableData: null, 22 | multipleSelection: null, 23 | button: { 24 | text: '取消关联', 25 | loading: false 26 | }, 27 | pagination: { 28 | pageSizes: [10, 20, 50, 100], 29 | size: 20, 30 | page: 1, 31 | total: 0 32 | } 33 | }, 34 | urls: { 35 | noRelation: null, 36 | relation: null, 37 | batchSave: null, 38 | batchDelete: null 39 | }, 40 | relationText: null, 41 | noRelationText: null, 42 | idKey: null, 43 | idVal: null, 44 | relationIdKey: null, 45 | multipleIdKey: null 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/views/admin/component/ResourceDialog/index.js: -------------------------------------------------------------------------------- 1 | import parent from '@/public/parent.js' 2 | import addModel from '@/public/addModel.js' 3 | import { fetch , curd } from '@/api/index.js' 4 | export default { 5 | name: 'resource-dialog', 6 | mixins: [parent, addModel], 7 | data() { 8 | return { 9 | permissionId: null, 10 | resourceTree: [], 11 | originalRequest: [] 12 | } 13 | }, 14 | created() { 15 | this.dialog = { title: '关联资源', visiable: true } 16 | this.button.text = '关联' 17 | this.permissionId = this.data.obj.id 18 | }, 19 | mounted() { 20 | this.createDefaultMLoading('.el-dialog') 21 | this.fetchData() 22 | }, 23 | methods: { 24 | fetchData() { 25 | this.mloading.show() 26 | fetch('/admin/api/sysPermission/permissionResource', { permissionId: this.permissionId }).then(({ data }) => { 27 | this.mloading.hide() 28 | data.forEach(element => { 29 | element.indeterminate = false 30 | if (element.checked === false) { 31 | for (let i = 0; i < element.children.length; i++) { 32 | const item = element.children[i] 33 | if (item.checked === true) { 34 | element.indeterminate = true 35 | element.checked = false 36 | break 37 | } 38 | } 39 | } 40 | }) 41 | this.resourceTree = data 42 | }).catch(error => { 43 | this.mloading.error(error.message, () => { 44 | this.fetchData() 45 | }) 46 | }) 47 | }, 48 | handleCheckAllChange(checked, index, item) { 49 | item.indeterminate = false 50 | item.children.forEach(child => { 51 | child.checked = checked 52 | }) 53 | }, 54 | handleCheckChange(checked, item, sub) { 55 | let count = 0 56 | item.children.forEach(child => { 57 | if (child.checked) { 58 | ++ count 59 | } 60 | }) 61 | if (count === 0) { 62 | item.checked = false 63 | item.indeterminate = false 64 | } else if (count === item.children.length) { 65 | item.checked = true 66 | item.indeterminate = false 67 | } else { 68 | item.checked = false 69 | item.indeterminate = true 70 | } 71 | }, 72 | closeDialog() { 73 | this.$emit('input', false) 74 | }, 75 | clickSave() { 76 | const request = [] 77 | this.resourceTree.forEach(item => { 78 | if (item.checked) { 79 | request.push({permissionId: this.permissionId, resourceId: item.id}) 80 | } 81 | item.children.forEach(child => { 82 | if (child.checked) { 83 | request.push({permissionId: this.permissionId, resourceId: child.id}) 84 | } 85 | }) 86 | }) 87 | if (request.length === 0) { 88 | this.$message({type: 'warning', message: '您并未关联任何资源'}) 89 | } else { 90 | curd('/admin/api/sysPermission/batchResourceAdd', 'post', request).then(() => { 91 | this.$message({type: 'success', message: '资源关联成功'}) 92 | }).catch(() => { 93 | this.$message({type: 'error', message: '资源关联失败'}) 94 | }) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/views/admin/component/ResourceDialog/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/views/admin/menu/component/MenuTree.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 87 | 88 | -------------------------------------------------------------------------------- /src/views/admin/menu/component/index.js: -------------------------------------------------------------------------------- 1 | import parent from '@/public/parent.js' 2 | import rules from '@/public/rules.js' 3 | import { fetchMenuTree, curdMenu } from '@/api/menu.js' 4 | import addModel from '@/public/addModel.js' 5 | import model from './model' 6 | import tree from './tree' 7 | 8 | export default { 9 | name: 'menu-tree', 10 | mixins: [parent, addModel, model, tree, rules], 11 | created() { 12 | this.form = this.$copy(this.originalForm) 13 | this.$setKeyValue(this.dialog, { title: '菜单树', visiable: true }) 14 | }, 15 | mounted() { 16 | this.createDefaultMLoading('#menu-tree') 17 | this.fetchData() 18 | }, 19 | methods: { 20 | fetchData() { 21 | fetchMenuTree(null).then(({ data }) => { 22 | this.mloading.hide() 23 | this.treeList = data 24 | }).catch((error) => { 25 | this.mloading.error(error.message, () => { 26 | this.fetchData() 27 | }) 28 | }) 29 | }, 30 | closeDialog() { 31 | this.$emit('input', false) 32 | }, 33 | clickSaveOrUpdate() { 34 | this.$refs['form'].validate(valid => { 35 | if (valid) { 36 | if (this.currentOperationTypes === this.OperationTypes.Edit && this.$compareObjValue(this.originalData, this.form)) { 37 | this.$message({ type: 'warning', message: '数据并未改变' }) 38 | return 39 | } 40 | this.$setKeyValue(this.button, { loading: true, text: '提交中..' }) 41 | const requestForm = this.$copy(this.form) 42 | delete requestForm.confirmPassword 43 | const url = '/admin/api/sysUser' 44 | if (this.currentOperationTypes === this.OperationTypes.Add) { 45 | delete requestForm.id 46 | curdMenu('post', this.form).then(() => this.success()).catch(() => this.error()) 47 | } else { 48 | curdMenu('put', this.form).then(() => this.success()).catch(() => this.error()) 49 | } 50 | } 51 | }) 52 | }, 53 | success() { 54 | this.$message({ type: 'success', message: this.dialog.title + '成功' }) 55 | this.closeDialog() 56 | if (this.data.type === 'add') { 57 | this.$emit('add') 58 | } else { 59 | this.$emit('edit') 60 | } 61 | }, 62 | error() { 63 | this.$message({ type: 'error', message: this.dialog.title + '失败' }) 64 | this.$setKeyValue(this.button, { loading: false, text: '确定' }) 65 | } 66 | }, 67 | computed: { 68 | placeholder: () => { 69 | console.log(this.OperationTypes) 70 | // if (this.currentOperationTypes === this.OperationTypes.Find) { 71 | // return null 72 | // } else { 73 | // return '请输入' 74 | // } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/views/admin/menu/component/model.js: -------------------------------------------------------------------------------- 1 | import { checkIsExist } from '@/api/menu.js' 2 | export default { 3 | data() { 4 | return { 5 | filterText: '', 6 | treeList: [], 7 | defaultProps: { 8 | children: 'children', 9 | label: 'title' 10 | }, 11 | OperationTypes: { 12 | Find: 0, 13 | Add: 1, 14 | Edit: 2, 15 | Delete: 3 16 | }, 17 | currentOperationTypes: 0, 18 | originalData: null, 19 | originalForm: { 20 | id: null, 21 | title: null, 22 | icon: null, 23 | sort: null, 24 | level: null, 25 | parentId: null, 26 | parentName: null, 27 | name: null, 28 | path: null, 29 | component: null, 30 | description: null, 31 | state: null 32 | }, 33 | form: null, 34 | options: { 35 | states: [ 36 | { value: 0, label: '禁用' }, 37 | { value: 1, label: '启用' } 38 | ] 39 | }, 40 | rules: { 41 | existName: (rule, value, callback) => { 42 | if (this.$empty(value)) { 43 | return callback(new Error('请输入')) 44 | } 45 | // 下面这个判断在编辑中用到,判断刚输入的名称是否和原始数据名称一致,注意rule.data对象在编辑的时候才会被设置,新增则不会去设置 46 | if (rule.data && value === rule.data.title) { 47 | return callback() 48 | } 49 | checkIsExist({ title: value }).then(({ data }) => { 50 | if (data) { 51 | callback(new Error('已存在,请勿重复添加')) 52 | } else { 53 | callback() 54 | } 55 | }) 56 | } 57 | }, 58 | isClickOperationBtn: false 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/views/admin/menu/component/tree.js: -------------------------------------------------------------------------------- 1 | import { curdMenu } from '@/api/menu.js' 2 | export default { 3 | watch: { 4 | filterText(val) { 5 | this.$refs.tree.filter(val) 6 | } 7 | }, 8 | methods: { 9 | treeNodeClick(data, node, tree) { 10 | if (!this.isClickOperationBtn) { 11 | this.$setOriginalKV(this.form, data) 12 | } 13 | }, 14 | filterNode(value, data) { 15 | if (!value) return true 16 | return data.label.indexOf(value) !== -1 17 | }, 18 | append(data) { 19 | this.isClickOperationBtn = true 20 | this.currentOperationTypes = this.OperationTypes.Add 21 | this.button.text = '添加' 22 | this.form = this.$copy(this.originalForm) 23 | this.$setKeyValue(this.form, { parentId: data.id, parentName: data.title, level: data.level + 1, state: 1 }) 24 | setTimeout(() => { 25 | this.isClickOperationBtn = false 26 | }, 200) 27 | }, 28 | edit(data) { 29 | this.isClickOperationBtn = true 30 | this.currentOperationTypes = this.OperationTypes.Edit 31 | this.button.text = '修改' 32 | this.originalData = this.$copy(data) 33 | delete this.originalData.children 34 | this.form = this.$copy(data) 35 | delete this.form.children 36 | setTimeout(() => { 37 | this.isClickOperationBtn = false 38 | }, 200) 39 | }, 40 | remove(node, data) { 41 | this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { 42 | confirmButtonText: '确定', 43 | cancelButtonText: '取消', 44 | type: 'warning' 45 | }).then(() => { 46 | curdMenu('delete', { id: data.id }).then(() => { 47 | const parent = node.parent 48 | const children = parent.data.children || parent.data 49 | const index = children.findIndex(d => d.id === data.id) 50 | children.splice(index, 1) 51 | this.$message({ type: 'success', message: '删除成功!' }) 52 | }).catch(() => { 53 | this.$message({ type: 'error', message: '删除失败!' }) 54 | }) 55 | }).catch(() => {}) 56 | }, 57 | renderContent(h, { node, data, store }) { 58 | if (data.children && data.children.length > 0) { 59 | return ( 60 | 61 | {node.label} 62 | 63 | this.append(data) }>添加 64 | this.edit(data) }>编辑 65 | 66 | 67 | ) 68 | } else { 69 | return ( 70 | 71 | {node.label} 72 | 73 | this.append(data) }>添加 74 | this.edit(data) }>编辑 75 | this.remove(node, data) }>删除 76 | 77 | 78 | ) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/views/admin/menu/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 95 | -------------------------------------------------------------------------------- /src/views/admin/user/add.vue: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | -------------------------------------------------------------------------------- /src/views/errorPage/401.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 52 | 53 | 89 | -------------------------------------------------------------------------------- /src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 33 | 34 | 79 | 80 | -------------------------------------------------------------------------------- /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/svgIcons/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/svgIcons/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 50 | 51 | 75 | -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/content.inline.min.css: -------------------------------------------------------------------------------- 1 | .mce-content-body .mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:black;font-family:Arial;font-size:11px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;line-height:normal;font-weight:normal;text-align:left;-webkit-tap-highlight-color:transparent;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-object{border:1px dotted #3A3A3A;background:#D5D5D5 url(img/object.gif) no-repeat center}.mce-preview-object{display:inline-block;position:relative;margin:0 2px 0 2px;line-height:0;border:1px solid gray}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-preview-object .mce-shim{position:absolute;top:0;left:0;width:100%;height:100%;background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}figure.align-left{float:left}figure.align-right{float:right}figure.image.align-center{display:table;margin-left:auto;margin-right:auto}figure.image{display:inline-block;border:1px solid gray;margin:0 2px 0 1px;background:#f5f2f0}figure.image img{margin:8px 8px 0 8px}figure.image figcaption{margin:6px 8px 6px 8px;text-align:center}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px;page-break-before:always}@media print{.mce-pagebreak{border:0}}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3A3A3A;background:#D5D5D5 url(img/anchor.gif) no-repeat center}.mce-nbsp,.mce-shy{background:#AAA}.mce-shy::after{content:'-'}hr{cursor:default}.mce-match-marker{background:#AAA;color:#fff}.mce-match-marker-selected{background:#3399ff;color:#fff}.mce-spellchecker-word{border-bottom:2px solid #F00;cursor:default}.mce-spellchecker-grammar{border-bottom:2px solid #008000;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td[data-mce-selected],th[data-mce-selected]{background-color:#3399ff !important}.mce-edit-focus{outline:1px dotted #333}.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus{outline:2px solid #2d8ac7}.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover{outline:2px solid #7ACAFF}.mce-content-body *[contentEditable=false][data-mce-selected]{outline:2px solid #2d8ac7}.mce-resize-bar-dragging{background-color:blue;opacity:.25;filter:alpha(opacity=25);zoom:1} -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce-small.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/fonts/tinymce-small.eot -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce-small.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/fonts/tinymce-small.ttf -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce-small.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/fonts/tinymce-small.woff -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/fonts/tinymce.eot -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/fonts/tinymce.ttf -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/fonts/tinymce.woff -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/anchor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/img/anchor.gif -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/img/loader.gif -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/object.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/img/object.gif -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/trans.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neveryielding/ny-vue/55c3ecee07d18c9c581b3fb063d57d8c260ef677/static/tinymce/skins/lightgray/img/trans.gif --------------------------------------------------------------------------------