├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── LICENSE ├── README.md ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── index.html ├── package.json ├── src ├── App.vue ├── api │ ├── articles.js │ ├── columns.js │ ├── login.js │ ├── menus.js │ ├── roles.js │ ├── site.js │ ├── source-categories.js │ ├── sources.js │ ├── tags.js │ └── users.js ├── assets │ ├── 404.png │ ├── default-article-cover.png │ ├── default_user_image.jpg │ ├── icon-ontop.png │ ├── login-bg.jpeg │ ├── logo-main.jpg │ ├── logo.png │ ├── no-any-power.jpg │ ├── no-power.png │ └── wanger-qrcode.jpg ├── components │ └── more-icon │ │ ├── index.js │ │ ├── more-icon.vue │ │ └── svg-code.js ├── layout │ ├── index.js │ └── main-frame.vue ├── main.js ├── router │ ├── global-guard.js │ └── index.js ├── store │ └── index.js ├── styles │ └── common.css ├── utils │ ├── auth.js │ ├── common.js │ ├── request.js │ └── validate.js └── views │ ├── content-management │ ├── article-editing │ │ ├── article-edit-without-columns.vue │ │ ├── article-edit.vue │ │ ├── index.js │ │ └── page.css │ ├── article-management │ │ ├── articels.vue │ │ ├── index.js │ │ └── page.css │ ├── column-management │ │ ├── columns.vue │ │ └── index.js │ └── tag-management │ │ ├── index.js │ │ └── tags.vue │ ├── error-pages │ ├── 404 │ │ ├── 404.vue │ │ └── index.js │ ├── 500 │ │ ├── 500.vue │ │ └── index.js │ ├── no-any-power │ │ ├── index.js │ │ └── no-any-power.vue │ └── no-power │ │ ├── index.js │ │ └── no-power.vue │ ├── login │ ├── index.js │ └── login.vue │ └── system-management │ ├── menu-management │ ├── index.js │ └── menus.vue │ ├── roles-management │ ├── index.js │ └── roles.vue │ ├── site-configuration │ ├── index.js │ └── site-config.vue │ ├── source-category-management │ ├── index.js │ └── source-categories.vue │ ├── source-management │ ├── index.js │ └── sources.vue │ └── users-management │ ├── index.js │ ├── page.css │ └── users.vue ├── static └── .gitkeep └── yarn.lock /.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 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // 'plugin:vue/recommended', 16 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 17 | 'standard' 18 | ], 19 | // required to lint *.vue files 20 | plugins: [ 21 | 'vue' 22 | ], 23 | // add your custom rules here 24 | rules: { 25 | 'no-unexpected-multiline': 'off', 26 | 'eqeqeq': 'off', 27 | 'space-before-function-paren': 'off', 28 | // allow async-await 29 | 'generator-star-spacing': 'off', 30 | // allow debugger during development 31 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 32 | "vue/no-parsing-error": [2, { 33 | "x-invalid-end-tag": true, 34 | }] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | 问题记录0322.docx 16 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codingmore-admin-web 2 | 3 | > codingmore后台管理前端项目 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | yarn install 10 | 11 | # serve with hot reload at localhost:8080 12 | yarn run dev 13 | 14 | # build for production with minification 15 | yarn run build 16 | 17 | # build for production and view the bundle analyzer report 18 | yarn run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/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 | publicPath: '/admin/' 52 | }) 53 | } else { 54 | return ['vue-style-loader'].concat(loaders) 55 | } 56 | } 57 | 58 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 59 | return { 60 | css: generateLoaders(), 61 | postcss: generateLoaders(), 62 | less: generateLoaders('less'), 63 | sass: generateLoaders('sass', { indentedSyntax: true }), 64 | scss: generateLoaders('sass'), 65 | stylus: generateLoaders('stylus'), 66 | styl: generateLoaders('stylus') 67 | } 68 | } 69 | 70 | // Generate loaders for standalone style files (outside of .vue) 71 | exports.styleLoaders = function (options) { 72 | const output = [] 73 | const loaders = exports.cssLoaders(options) 74 | 75 | for (const extension in loaders) { 76 | const loader = loaders[extension] 77 | output.push({ 78 | test: new RegExp('\\.' + extension + '$'), 79 | use: loader 80 | }) 81 | } 82 | 83 | return output 84 | } 85 | 86 | exports.createNotifierCallback = () => { 87 | const notifier = require('node-notifier') 88 | 89 | return (severity, errors) => { 90 | if (severity !== 'error') return 91 | 92 | const error = errors[0] 93 | const filename = error.file && error.file.split('!').pop() 94 | 95 | notifier.notify({ 96 | title: packageConfig.name, 97 | message: severity + ': ' + error.name, 98 | subtitle: filename || '', 99 | icon: path.join(__dirname, 'logo.png') 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /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 | // externals: { 24 | // 'icon-park/vue': 'icon-park/vue' 25 | // }, 26 | context: path.resolve(__dirname, '../'), 27 | entry: { 28 | app: './src/main.js' 29 | }, 30 | output: { 31 | path: config.build.assetsRoot, 32 | filename: '[name].js', 33 | publicPath: process.env.NODE_ENV === 'production' 34 | ? config.build.assetsPublicPath 35 | : config.dev.assetsPublicPath 36 | }, 37 | resolve: { 38 | extensions: ['.js', '.vue', '.json'], 39 | alias: { 40 | 'vue$': 'vue/dist/vue.esm.js', 41 | '@': resolve('src'), 42 | } 43 | }, 44 | module: { 45 | rules: [ 46 | ...(config.dev.useEslint ? [createLintingRule()] : []), 47 | { 48 | test: /\.vue$/, 49 | loader: 'vue-loader', 50 | options: vueLoaderConfig 51 | }, 52 | { 53 | test: /\.js$/, 54 | loader: 'babel-loader', 55 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 56 | }, 57 | { 58 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 63 | } 64 | }, 65 | { 66 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 67 | loader: 'url-loader', 68 | options: { 69 | limit: 10000, 70 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 71 | } 72 | }, 73 | { 74 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 75 | loader: 'url-loader', 76 | options: { 77 | limit: 10000, 78 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 79 | } 80 | } 81 | ] 82 | }, 83 | node: { 84 | // prevent webpack from injecting useless setImmediate polyfill because Vue 85 | // source contains it (although only uses it if it's native). 86 | setImmediate: false, 87 | // prevent webpack from injecting mocks to Node native modules 88 | // that does not make sense for the client 89 | dgram: 'empty', 90 | fs: 'empty', 91 | net: 'empty', 92 | tls: 'empty', 93 | child_process: 'empty' 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 17 | 18 | const devWebpackConfig = merge(baseWebpackConfig, { 19 | module: { 20 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 21 | }, 22 | // cheap-module-eval-source-map is faster for development 23 | devtool: config.dev.devtool, 24 | 25 | // these devServer options should be customized in /config/index.js 26 | devServer: { 27 | clientLogLevel: 'warning', 28 | historyApiFallback: { 29 | rewrites: [ 30 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 31 | ], 32 | }, 33 | hot: true, 34 | contentBase: false, // since we use CopyWebpackPlugin. 35 | compress: true, 36 | host: HOST || config.dev.host, 37 | port: PORT || config.dev.port, 38 | open: config.dev.autoOpenBrowser, 39 | overlay: config.dev.errorOverlay 40 | ? { warnings: false, errors: true } 41 | : false, 42 | publicPath: config.dev.assetsPublicPath, 43 | proxy: config.dev.proxyTable, 44 | quiet: true, // necessary for FriendlyErrorsPlugin 45 | watchOptions: { 46 | poll: config.dev.poll, 47 | } 48 | }, 49 | plugins: [ 50 | // new BundleAnalyzerPlugin(), 51 | new webpack.DefinePlugin({ 52 | 'process.env': require('../config/dev.env') 53 | }), 54 | new webpack.HotModuleReplacementPlugin(), 55 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 56 | new webpack.NoEmitOnErrorsPlugin(), 57 | // https://github.com/ampedandwired/html-webpack-plugin 58 | new HtmlWebpackPlugin({ 59 | filename: 'index.html', 60 | template: 'index.html', 61 | inject: true 62 | }), 63 | // copy custom static assets 64 | new CopyWebpackPlugin([ 65 | { 66 | from: path.resolve(__dirname, '../static'), 67 | to: config.dev.assetsSubDirectory, 68 | ignore: ['.*'] 69 | } 70 | ]) 71 | ] 72 | }) 73 | 74 | module.exports = new Promise((resolve, reject) => { 75 | portfinder.basePort = process.env.PORT || config.dev.port 76 | portfinder.getPort((err, port) => { 77 | if (err) { 78 | reject(err) 79 | } else { 80 | // publish the new Port, necessary for e2e tests 81 | process.env.PORT = port 82 | // add port to devServer config 83 | devWebpackConfig.devServer.port = port 84 | 85 | // Add FriendlyErrorsPlugin 86 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 87 | compilationSuccessInfo: { 88 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 89 | }, 90 | onErrors: config.dev.notifyOnErrors 91 | ? utils.createNotifierCallback() 92 | : undefined 93 | })) 94 | 95 | resolve(devWebpackConfig) 96 | } 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /build/webpack.prod.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 CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | 18 | module: { 19 | rules: utils.styleLoaders({ 20 | sourceMap: config.build.productionSourceMap, 21 | extract: true, 22 | usePostCSS: true 23 | }) 24 | }, 25 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 26 | output: { 27 | path: config.build.assetsRoot, 28 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 29 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 30 | }, 31 | plugins: [ 32 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 33 | new webpack.DefinePlugin({ 34 | 'process.env': env 35 | }), 36 | new UglifyJsPlugin({ 37 | uglifyOptions: { 38 | compress: { 39 | warnings: false 40 | } 41 | }, 42 | sourceMap: config.build.productionSourceMap, 43 | parallel: true 44 | }), 45 | // extract css into its own file 46 | new ExtractTextPlugin({ 47 | filename: utils.assetsPath('css/[name].[contenthash].css'), 48 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 49 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 50 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 51 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 52 | allChunks: true, 53 | }), 54 | // Compress extracted CSS. We are using this plugin so that possible 55 | // duplicated CSS from different components can be deduped. 56 | new OptimizeCSSPlugin({ 57 | cssProcessorOptions: config.build.productionSourceMap 58 | ? { safe: true, map: { inline: false } } 59 | : { safe: true } 60 | }), 61 | // generate dist index.html with correct asset hash for caching. 62 | // you can customize output by editing /index.html 63 | // see https://github.com/ampedandwired/html-webpack-plugin 64 | new HtmlWebpackPlugin({ 65 | filename: config.build.index, 66 | template: 'index.html', 67 | inject: true, 68 | minify: { 69 | removeComments: true, 70 | collapseWhitespace: true, 71 | removeAttributeQuotes: true 72 | // more options: 73 | // https://github.com/kangax/html-minifier#options-quick-reference 74 | }, 75 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 76 | chunksSortMode: 'dependency' 77 | }), 78 | // keep module.id stable when vendor modules does not change 79 | new webpack.HashedModuleIdsPlugin(), 80 | // enable scope hoisting 81 | new webpack.optimize.ModuleConcatenationPlugin(), 82 | // split vendor js into its own file 83 | new webpack.optimize.CommonsChunkPlugin({ 84 | name: 'vendor', 85 | minChunks (module) { 86 | // any required modules inside node_modules are extracted to vendor 87 | return ( 88 | module.resource && 89 | /\.js$/.test(module.resource) && 90 | module.resource.indexOf( 91 | path.join(__dirname, '../node_modules') 92 | ) === 0 93 | ) 94 | } 95 | }), 96 | // extract webpack runtime and module manifest to its own file in order to 97 | // prevent vendor hash from being updated whenever app bundle is updated 98 | new webpack.optimize.CommonsChunkPlugin({ 99 | name: 'manifest', 100 | minChunks: Infinity 101 | }), 102 | // This instance extracts shared chunks from code splitted chunks and bundles them 103 | // in a separate chunk, similar to the vendor chunk 104 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 105 | new webpack.optimize.CommonsChunkPlugin({ 106 | name: 'app', 107 | async: 'vendor-async', 108 | children: true, 109 | minChunks: 3 110 | }), 111 | 112 | // copy custom static assets 113 | new CopyWebpackPlugin([ 114 | { 115 | from: path.resolve(__dirname, '../static'), 116 | to: config.build.assetsSubDirectory, 117 | ignore: ['.*'] 118 | } 119 | ]) 120 | ] 121 | }) 122 | 123 | if (config.build.productionGzip) { 124 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 125 | 126 | webpackConfig.plugins.push( 127 | new CompressionWebpackPlugin({ 128 | filename: '[path].gz[query]', 129 | algorithm: 'gzip', 130 | test: new RegExp( 131 | '\\.(' + 132 | config.build.productionGzipExtensions.join('|') + 133 | ')$' 134 | ), 135 | threshold: 10240, 136 | minRatio: 0.8 137 | }) 138 | ) 139 | } 140 | 141 | if (config.build.bundleAnalyzerReport) { 142 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 143 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 144 | } 145 | 146 | module.exports = webpackConfig 147 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | VUE_APP_BASE_API: '"/api"' 8 | }) 9 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 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 | '/api': { 15 | target: 'http://localhost:9002', // 你请求的第三方接口 16 | changeOrigin: false, // 在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,这样服务端和服务端进行数据的交互就不会有跨域问题 17 | pathRewrite: { // 路径重写, 18 | '^/api': '' // 替换target中的请求地址,也就是说以后你在请求http://api.codingmore.top/v2/XXXXX这个地址的时候直接写成/api即可。 19 | } 20 | }, 21 | }, 22 | 23 | // Various Dev Server settings 24 | host: 'localhost', // can be overwritten by process.env.HOST 25 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 26 | autoOpenBrowser: true, 27 | errorOverlay: true, 28 | notifyOnErrors: true, 29 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 30 | 31 | // Use Eslint Loader? 32 | // If true, your code will be linted during bundling and 33 | // linting errors and warnings will be shown in the console. 34 | useEslint: true, 35 | // If true, eslint errors and warnings will also be shown in the error overlay 36 | // in the browser. 37 | showEslintErrorsInOverlay: false, 38 | 39 | /** 40 | * Source Maps 41 | */ 42 | 43 | // https://webpack.js.org/configuration/devtool/#development 44 | devtool: 'cheap-module-eval-source-map', 45 | 46 | // If you have problems debugging vue-files in devtools, 47 | // set this to false - it *may* help 48 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 49 | cacheBusting: true, 50 | 51 | cssSourceMap: true 52 | }, 53 | 54 | build: { 55 | bundleAnalyzerReport: process.env.npm_config_report, 56 | // Template for index.html 57 | index: path.resolve(__dirname, '../dist/index.html'), 58 | 59 | // Paths 60 | assetsRoot: path.resolve(__dirname, '../dist'), 61 | assetsSubDirectory: 'static', 62 | assetsPublicPath: './', 63 | 64 | /** 65 | * Source Maps 66 | */ 67 | 68 | productionSourceMap: false, 69 | // https://webpack.js.org/configuration/devtool/#production 70 | devtool: '#source-map', 71 | 72 | // Gzip off by default as many popular static hosts such as 73 | // Surge or Netlify already gzip all static assets for you. 74 | // Before setting to `true`, make sure to: 75 | // npm install --save-dev compression-webpack-plugin 76 | productionGzip: true, 77 | productionGzipExtensions: ['js', 'css'], 78 | 79 | // Run the build command with an extra argument to 80 | // View the bundle analyzer report after build finishes: 81 | // `npm run build --report` 82 | // Set to `true` or `false` to always turn it on or off 83 | // bundleAnalyzerReport: process.env.npm_config_report 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | VUE_APP_BASE_API: '"/api"' 5 | } 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CodingMore后台管理端 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codingmore-admin-web", 3 | "version": "1.0.0", 4 | "description": "codingmore后台管理前端项目", 5 | "author": "ming.gu", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "@icon-park/vue": "^1.3.5", 15 | "axios": "^0.25.0", 16 | "compression-webpack-plugin": "1.1.12", 17 | "element-ui": "^2.15.6", 18 | "js-cookie": "^3.0.1", 19 | "mavon-editor": "^2.10.4", 20 | "nprogress": "^0.2.0", 21 | "vue": "^2.5.2", 22 | "vue-router": "^3.0.1", 23 | "vue-simple-password-meter": "^0.1.0", 24 | "vuex": "^3.6.2" 25 | }, 26 | "devDependencies": { 27 | "autoprefixer": "^7.1.2", 28 | "babel-core": "^6.22.1", 29 | "babel-eslint": "^8.2.1", 30 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 31 | "babel-loader": "^7.1.1", 32 | "babel-plugin-syntax-jsx": "^6.18.0", 33 | "babel-plugin-transform-runtime": "^6.22.0", 34 | "babel-plugin-transform-vue-jsx": "^3.5.0", 35 | "babel-preset-env": "^1.3.2", 36 | "babel-preset-stage-2": "^6.22.0", 37 | "chalk": "^2.0.1", 38 | "copy-webpack-plugin": "^4.0.1", 39 | "css-loader": "^0.28.0", 40 | "eslint": "^4.15.0", 41 | "eslint-config-standard": "^10.2.1", 42 | "eslint-friendly-formatter": "^3.0.0", 43 | "eslint-loader": "^1.7.1", 44 | "eslint-plugin-import": "^2.7.0", 45 | "eslint-plugin-node": "^5.2.0", 46 | "eslint-plugin-promise": "^3.4.0", 47 | "eslint-plugin-standard": "^3.0.1", 48 | "eslint-plugin-vue": "^4.0.0", 49 | "extract-text-webpack-plugin": "^3.0.0", 50 | "file-loader": "^1.1.4", 51 | "friendly-errors-webpack-plugin": "^1.6.1", 52 | "html-webpack-plugin": "^2.30.1", 53 | "node-notifier": "^5.1.2", 54 | "optimize-css-assets-webpack-plugin": "^3.2.0", 55 | "ora": "^1.2.0", 56 | "portfinder": "^1.0.13", 57 | "postcss-import": "^11.0.0", 58 | "postcss-loader": "^2.0.8", 59 | "postcss-url": "^7.2.1", 60 | "rimraf": "^2.6.0", 61 | "semver": "^5.3.0", 62 | "shelljs": "^0.7.6", 63 | "uglifyjs-webpack-plugin": "^1.1.1", 64 | "url-loader": "^0.5.8", 65 | "vue-loader": "^13.3.0", 66 | "vue-style-loader": "^3.0.1", 67 | "vue-template-compiler": "^2.5.2", 68 | "webpack": "^3.6.0", 69 | "webpack-bundle-analyzer": "^4.5.0", 70 | "webpack-dev-server": "^2.9.1", 71 | "webpack-merge": "^4.1.0" 72 | }, 73 | "engines": { 74 | "node": ">= 6.0.0", 75 | "npm": ">= 3.0.0" 76 | }, 77 | "browserslist": [ 78 | "> 1%", 79 | "last 2 versions", 80 | "not ie <= 8" 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/api/articles.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 查询文章分页列表的方法 4 | export function getArticlePagedList(params) { 5 | return request({ 6 | url: `/posts/queryPageable?_=${Math.random()}`, 7 | method: 'get', 8 | params 9 | }) 10 | } 11 | 12 | // 根据id查询文章详情的方法 13 | export function getArticleById(params) { 14 | return request({ 15 | url: `/posts/getById?_=${Math.random()}`, 16 | method: 'get', 17 | params 18 | }) 19 | } 20 | 21 | // 删除文章方法 22 | export function deleteArticle(params) { 23 | return request({ 24 | url: `/posts/delete?_=${Math.random()}`, 25 | method: 'get', 26 | params 27 | }) 28 | } 29 | 30 | // 添加文章保存方法 31 | export function createArticle(data) { 32 | return request({ 33 | url: '/posts/insert', 34 | method: 'post', 35 | data 36 | }) 37 | } 38 | 39 | // 更新文章保存方法 40 | export function updateArticle(data) { 41 | return request({ 42 | url: '/posts/update', 43 | method: 'post', 44 | data 45 | }) 46 | } 47 | 48 | // 查询文章标签方法 49 | export function getTagList(params) { 50 | return request({ 51 | url: `/postTag/queryPageable?_=${Math.random()}`, 52 | method: 'get', 53 | params 54 | }) 55 | } 56 | 57 | // 对接后台上传接口的方法 58 | export function mdEditorUploadImage(data) { 59 | return request({ 60 | url: '/ossController/upload', 61 | method: 'post', 62 | data, 63 | headers: { 'Content-Type': 'multipart/form-data' } 64 | }) 65 | } 66 | 67 | // 绑定文章到栏目上 68 | export function bindArticleToColumns(data) { 69 | return request({ 70 | url: '/posts/insertPostTermTaxonomy', 71 | method: 'post', 72 | data 73 | }) 74 | } 75 | 76 | // 文章置顶方法 77 | export function setArticleOnTop(data) { 78 | return request({ 79 | url: '/posts/setOnTop', 80 | method: 'post', 81 | data 82 | }) 83 | } 84 | 85 | // 取消文章置顶方法 86 | export function cancelArticleOnTop(data) { 87 | return request({ 88 | url: '/posts/cancelOnTop', 89 | method: 'post', 90 | data 91 | }) 92 | } 93 | 94 | export const uploadUrl = process.env.VUE_APP_BASE_API + '/ossController/upload' 95 | 96 | export const importMdUrl = process.env.VUE_APP_BASE_API + '/posts/uploadMd' 97 | -------------------------------------------------------------------------------- /src/api/columns.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 获得所有专栏 4 | export function getAllColumns(params) { 5 | return request({ 6 | url: `/termTaxonomy/getPyParentId`, 7 | method: 'get', 8 | params 9 | }) 10 | } 11 | 12 | // 根据id查询专栏详情 13 | export function getOneColumn(params) { 14 | return request({ 15 | url: `/termTaxonomy/getById`, 16 | method: 'get', 17 | params 18 | }) 19 | } 20 | 21 | // 更新专栏保存方法 22 | export function updateColumn(data) { 23 | return request({ 24 | url: '/termTaxonomy/update', 25 | method: 'post', 26 | data 27 | }) 28 | } 29 | 30 | // 添加专栏保存方法 31 | export function addColumn(data) { 32 | return request({ 33 | url: '/termTaxonomy/insert', 34 | method: 'post', 35 | data 36 | }) 37 | } 38 | 39 | // 删除专栏方法 40 | export function deleteColumn(id) { 41 | return request({ 42 | url: `/termTaxonomy/delete?_=${Math.random()}`, 43 | method: 'get', 44 | params: { 45 | termTaxonomyId: id 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 用户提交登陆请求 4 | export function UserLogin(data) { 5 | return request({ 6 | url: '/users/login', 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/api/menus.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 分页查询菜单列表方法 4 | export function getMenuList(params) { 5 | return request({ 6 | url: `/menu/queryPageable`, 7 | method: 'get', 8 | params 9 | }) 10 | } 11 | 12 | // 查询全部菜单,按照树形结构返回的方法 13 | export function getAllMenusTree(params) { 14 | return request({ 15 | url: `/menu/treeList`, 16 | method: 'get', 17 | params 18 | }) 19 | } 20 | 21 | // 根据id查询菜单详细信息的方法 22 | export function getMenuInfo(params) { 23 | return request({ 24 | url: `/menu`, 25 | method: 'get', 26 | params 27 | }) 28 | } 29 | 30 | // 新增保存菜单方法 31 | export function addMenu(data) { 32 | return request({ 33 | url: `/menu/create`, 34 | method: 'post', 35 | data 36 | }) 37 | } 38 | 39 | // 更新保存菜单方法 40 | export function updateMenu(data) { 41 | return request({ 42 | url: `/menu/update`, 43 | method: 'post', 44 | data 45 | }) 46 | } 47 | 48 | // 删除菜单方法 49 | export function deleteMenu(data) { 50 | return request({ 51 | url: `/menu/delete`, 52 | method: 'post', 53 | data 54 | }) 55 | } 56 | 57 | // 设置菜单显示状态方法 58 | export function setMenuVisible(data) { 59 | return request({ 60 | url: `/menu/updateHidden`, 61 | method: 'post', 62 | data 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /src/api/roles.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 获取所有角色方法 4 | export function getAllRoles() { 5 | return request({ 6 | url: '/role/listAll', 7 | method: 'get' 8 | }) 9 | } 10 | 11 | // 获取角色所拥有的菜单列表 12 | export function getRoleOwnedMenuList(params) { 13 | return request({ 14 | url: `/role/listMenu`, 15 | method: 'get', 16 | params 17 | }) 18 | } 19 | 20 | // 获取角色所拥有的资源列表 21 | export function getRoleOwnedSourceList(params) { 22 | return request({ 23 | url: '/role/listResource', 24 | method: 'get', 25 | params 26 | }) 27 | } 28 | 29 | // 分页查询角色列表 30 | export function getRoleList(params) { 31 | return request({ 32 | url: '/role/queryPageable', 33 | method: 'get', 34 | params 35 | }) 36 | } 37 | 38 | // 添加保存角色方法 39 | export function addRole(data) { 40 | return request({ 41 | url: '/role/create', 42 | method: 'post', 43 | data 44 | }) 45 | } 46 | 47 | // 修改保存角色方法 48 | export function updateRole(data) { 49 | return request({ 50 | url: '/role/update', 51 | method: 'post', 52 | data 53 | }) 54 | } 55 | 56 | // 修改保存角色状态方法 57 | export function setRoleStatus(data) { 58 | return request({ 59 | url: '/role/updateStatus', 60 | method: 'post', 61 | data 62 | }) 63 | } 64 | 65 | // 批量删除角色方法 66 | export function deleteRoleBatch(data) { 67 | return request({ 68 | url: '/role/delete', 69 | method: 'post', 70 | data 71 | }) 72 | } 73 | 74 | // 给角色分配菜单保存方法 75 | export function setMenuPowerForRole(data) { 76 | return request({ 77 | url: '/role/allocMenu', 78 | method: 'post', 79 | data 80 | }) 81 | } 82 | 83 | // 给角色分配资源保存方法 84 | export function setSourcePowerForRole(data) { 85 | return request({ 86 | url: '/role/allocResource', 87 | method: 'post', 88 | data 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /src/api/site.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 获得站点配置信息 4 | export function getSiteConfigInfo() { 5 | return request({ 6 | url: `/site/getById?_=${Math.random()}`, 7 | method: 'get' 8 | }) 9 | } 10 | 11 | // 更新站点配置信息 12 | export function updateSiteConfig(data) { 13 | return request({ 14 | url: `/site/update`, 15 | method: 'post', 16 | data 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/api/source-categories.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | // 查询资源分类列表方法 3 | export function getSourceCategoryList(params) { 4 | return request({ 5 | url: '/resourceCategory/listAll', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | // 添加资源分类方法 12 | export function addSourceCatagory(data) { 13 | return request({ 14 | url: '/resourceCategory/insert', 15 | method: 'post', 16 | data 17 | }) 18 | } 19 | 20 | // 更新资源分类方法 21 | export function updateSourceCatagory(data) { 22 | return request({ 23 | url: '/resourceCategory/update', 24 | method: 'post', 25 | data 26 | }) 27 | } 28 | 29 | // 删除资源分类方法 30 | export function deleteSourceCatagory(data) { 31 | return request({ 32 | url: '/resourceCategory/delete', 33 | method: 'post', 34 | data 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/api/sources.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 查询全部资源列表方法 4 | export function getSourceAllList(params) { 5 | return request({ 6 | url: '/resource/listAll', 7 | method: 'get', 8 | params 9 | }) 10 | } 11 | 12 | // 分页查询资源列表方法 13 | export function getSourceList(params) { 14 | return request({ 15 | url: '/resource/queryPageable', 16 | method: 'get', 17 | params 18 | }) 19 | } 20 | 21 | // 根据id获取资源详情方法 22 | export function getSourceInfo(params) { 23 | return request({ 24 | url: '/resource/id', 25 | method: 'get', 26 | params 27 | }) 28 | } 29 | 30 | // 添加资源方法 31 | export function addSource(data) { 32 | return request({ 33 | url: '/resource/create', 34 | method: 'post', 35 | data 36 | }) 37 | } 38 | 39 | // 更新资源方法 40 | export function updateSource(data) { 41 | return request({ 42 | url: '/resource/update', 43 | method: 'post', 44 | data 45 | }) 46 | } 47 | 48 | // 删除资源方法 49 | export function deleteSource(data) { 50 | return request({ 51 | url: '/resource/delete', 52 | method: 'post', 53 | data 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /src/api/tags.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 查询标签分页列表方法 4 | export function getTagList(params) { 5 | return request({ 6 | url: '/postTag/queryPageable', 7 | method: 'get', 8 | params 9 | }) 10 | } 11 | 12 | // 添加保存标签方法 13 | export function addTag(data) { 14 | return request({ 15 | url: '/postTag/insert', 16 | method: 'post', 17 | data 18 | }) 19 | } 20 | 21 | // 修改保存标签方法 22 | export function updateTag(data) { 23 | return request({ 24 | url: '/postTag/update', 25 | method: 'post', 26 | data 27 | }) 28 | } 29 | 30 | // 删除标签方法 31 | export function deleteTag(params) { 32 | return request({ 33 | url: '/postTag/delete', 34 | method: 'get', 35 | params 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/api/users.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | // 获取当前用户登录信息 4 | export function getLoginUserInfo() { 5 | return request({ 6 | url: '/users/info', 7 | method: 'get' 8 | }) 9 | } 10 | 11 | // 用户退出登陆方法 12 | export function userLogout() { 13 | return request({ 14 | url: '/users/logout', 15 | method: 'post' 16 | }) 17 | } 18 | 19 | // 登陆用户修改自己密码的方法 20 | export function modifyPassword(data) { 21 | return request({ 22 | url: '/users/updatePassword', 23 | method: 'post', 24 | data 25 | }) 26 | } 27 | 28 | // 根据用户id获取用户详细信息的方法 29 | export function getUserInfoById(params) { 30 | return request({ 31 | url: '/users/getById', 32 | method: 'get', 33 | params 34 | }) 35 | } 36 | 37 | // 更新一个用户基本信息的方法 38 | export function updateUserInfoById(data) { 39 | return request({ 40 | url: '/users/update', 41 | method: 'post', 42 | data 43 | }) 44 | } 45 | 46 | // 添加用户方法 47 | export function addUser(data) { 48 | return request({ 49 | url: '/users/register', 50 | method: 'post', 51 | data 52 | }) 53 | } 54 | 55 | // 删除用户方法 56 | export function deleteUser(params) { 57 | return request({ 58 | url: '/users/delete', 59 | method: 'get', 60 | params 61 | }) 62 | } 63 | 64 | // 查询用户列表方法 65 | export function getUserList(params) { 66 | return request({ 67 | url: '/users/queryPageable', 68 | method: 'get', 69 | params 70 | }) 71 | } 72 | 73 | // 查询用户当前拥有的角色的方法 74 | export function getUserRoles(params) { 75 | return request({ 76 | url: '/users/role', 77 | method: 'get', 78 | params 79 | }) 80 | } 81 | 82 | // 保存用户角色分配的设置方法 83 | export function setUserRoles(data) { 84 | return request({ 85 | url: '/users/role/update', 86 | method: 'post', 87 | data 88 | }) 89 | } 90 | 91 | // 修改用户当前启用禁用状态方法 92 | export function setUserStatus(data) { 93 | return request({ 94 | url: '/users/enableOrDisable', 95 | method: 'post', 96 | data 97 | }) 98 | } 99 | 100 | export const uploadUrl = process.env.VUE_APP_BASE_API + '/ossController/upload' 101 | -------------------------------------------------------------------------------- /src/assets/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/404.png -------------------------------------------------------------------------------- /src/assets/default-article-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/default-article-cover.png -------------------------------------------------------------------------------- /src/assets/default_user_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/default_user_image.jpg -------------------------------------------------------------------------------- /src/assets/icon-ontop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/icon-ontop.png -------------------------------------------------------------------------------- /src/assets/login-bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/login-bg.jpeg -------------------------------------------------------------------------------- /src/assets/logo-main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/logo-main.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/no-any-power.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/no-any-power.jpg -------------------------------------------------------------------------------- /src/assets/no-power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/no-power.png -------------------------------------------------------------------------------- /src/assets/wanger-qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/src/assets/wanger-qrcode.jpg -------------------------------------------------------------------------------- /src/components/more-icon/index.js: -------------------------------------------------------------------------------- 1 | import MoreIcon from './more-icon.vue' 2 | 3 | export default MoreIcon 4 | -------------------------------------------------------------------------------- /src/components/more-icon/more-icon.vue: -------------------------------------------------------------------------------- 1 | 14 | 48 | 61 | -------------------------------------------------------------------------------- /src/components/more-icon/svg-code.js: -------------------------------------------------------------------------------- 1 | // 通用svg解决方案:使用前提是你已经有svg文件或者代码了 2 | /** 3 | * 使用步骤: 4 | * 1. 在svgObj中起一个不重复的键,值就是已经复制好的svg代码 5 | * 2. 找到标签中定义的宽高,将 width="xxx" 替换为:width="__width__",将 height="xxx" 替换为:height="__height__" 6 | * 3. 找到svg中的所有标签,如果有fill定义,将 fill="xxx" 替换为: fill="__fill__" 7 | */ 8 | const svgObj = { 9 | 'iconfont-role-management': '', 10 | 'iconfont-menu-management': '', 11 | 'iconfont-source-management': '', 12 | 'iconfont-tag-management': '', 13 | 'iconfont-import': '' 14 | } 15 | 16 | const getSvg = (svgKey, width, height, fillColor) => { 17 | if (Object.keys(svgObj).indexOf(svgKey) > -1) { 18 | let widthValue = width && typeof width === 'number' ? width : 18 19 | let heightValue = height ? height && typeof height === 'number' : 18 20 | let fillColorValue = fillColor && typeof fillColor === 'string' ? fillColor : 'currentColor' 21 | return svgObj[svgKey].replace('__width__', widthValue).replace('__height__', heightValue).replace('__fill__', fillColorValue) 22 | } 23 | } 24 | 25 | // 全部支持的图标数组 26 | export const allIconArray = [ 27 | // el-icon-开头的都是elementUI自带的,这里没有全部列出,但是可以补全 28 | 'el-icon-s-help', 29 | 'el-icon-edit-outline', 30 | 'el-icon-s-operation', 31 | 'el-icon-coin', 32 | // iconpark-开头的是字节图标库的,后面可以按需引用添加即可 33 | 'iconpark-list-two', 34 | 'iconpark-config', 35 | 'el-icon-user', 36 | // 阿里矢量图标库的,按需添加即可 37 | 'iconfont-role-management', 38 | 'iconfont-menu-management', 39 | 'iconfont-source-management', 40 | 'iconfont-tag-management' 41 | ] 42 | 43 | export default getSvg 44 | -------------------------------------------------------------------------------- /src/layout/index.js: -------------------------------------------------------------------------------- 1 | import mainFrame from './main-frame.vue' 2 | 3 | export default mainFrame 4 | -------------------------------------------------------------------------------- /src/layout/main-frame.vue: -------------------------------------------------------------------------------- 1 | {{ item.meta.title }} 11 | 12 | 13 | 14 | {{ subitem.meta.title }} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 |
28 | 29 | {{ item.meta.title }} 30 | 31 |
32 | 33 |
34 | 35 | 36 | 41 | 42 | 43 | 44 | 修改密码 45 | 退出登陆 46 | 47 | 48 |
49 |
50 | 51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 取 消 70 | 确 定 71 | 72 | 73 | 74 | 75 | 76 | 298 | 299 | 435 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | // import Vue from 'vue/dist/vue.common.js' 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | import App from './App' 8 | import router from './router' 9 | import store from './store' 10 | import './router/global-guard' 11 | import './styles/common.css' 12 | import Router from 'vue-router' 13 | // import NProgress from 'nprogress' 14 | import 'nprogress/nprogress.css' // nprogress样式文件 15 | Vue.use(Router) 16 | Vue.use(ElementUI) 17 | Vue.config.productionTip = false 18 | 19 | const originalPush = Router.prototype.push 20 | const originalReplace = Router.prototype.replace 21 | 22 | // push 23 | Router.prototype.push = function push(location, onResolve, onReject) { 24 | if (onResolve || onReject) { return originalPush.call(this, location, onResolve, onReject) } 25 | return originalPush.call(this, location).catch(err => err) 26 | } 27 | 28 | // replace 29 | Router.prototype.replace = function push(location, onResolve, onReject) { 30 | if (onResolve || onReject) { return originalReplace.call(this, location, onResolve, onReject) } 31 | return originalReplace.call(this, location).catch(err => err) 32 | } 33 | 34 | /* eslint-disable no-new */ 35 | new Vue({ 36 | el: '#app', 37 | store, 38 | router, 39 | components: { 40 | App 41 | }, 42 | template: '' 43 | // render: h => h(App) 44 | }) 45 | -------------------------------------------------------------------------------- /src/router/global-guard.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import router, { 3 | pageRouters, 4 | systemRouters 5 | } from './index' 6 | import store from '../store' 7 | import { 8 | getToken 9 | } from '../utils/auth' 10 | import NProgress from 'nprogress' 11 | import 'nprogress/nprogress.css' // nprogress样式文件 12 | 13 | router.beforeEach(async (to, from, next) => { 14 | console.log('router.beforeEach参数:', 'to=', to, 'from=', from) 15 | console.log('router=', router) 16 | // 开启进度条 17 | NProgress.start() 18 | 19 | // 判断是否系统中支持跳转的路由 20 | if (!to.name) { 21 | next({ 22 | name: 'error-page404', 23 | replace: true 24 | }) 25 | } 26 | 27 | /* 28 | * 权限控制思路: 29 | * 1.判断是否有token 30 | * 2.没有token直接跳转登录页 31 | * 3.有token查看是否已经初始化用户权限,如果没有,则异步调用接口获得用户信息和权限信息 32 | * 4.判断用户是否有业务菜单权限,如果没有,直接跳转到没有任何权限页面 33 | * 5.判断特殊路由情况,login和刷新页面情况,两者都跳转到第一个有权限的路由名称即可 34 | * 6.判断当前要跳转的路由名称是否名称在用户允许访问的路由名称列表内 35 | * 7.不在可访问列表内,跳转无权限页面 36 | * 8.在可访问列表内,直接next()跳转目标路由 37 | */ 38 | 39 | const tokenValue = getToken() 40 | if (!tokenValue && to.name !== 'login') { 41 | // 关闭进度条 42 | NProgress.done() 43 | console.log('next:::0') 44 | next({ 45 | name: 'login', 46 | replace: true 47 | }) 48 | } 49 | 50 | if (tokenValue && !store.state.userInfo) { 51 | // 通过路由跳转动态请求并渲染可用菜单 52 | await initSysPower() 53 | } 54 | 55 | console.log('to.name=', to.name) 56 | const sysFind = systemRouters.find(x => x.name === to.name) 57 | const businessFind = pageRouters.find(x => { 58 | if (x.name === to.name) { 59 | console.log('返回x', x) 60 | return x 61 | } 62 | if (x.children) { 63 | const child = x.children.find(y => y.name === to.name) 64 | if (child) { 65 | console.log('返回x的child', child) 66 | return child 67 | } 68 | } 69 | return null 70 | }) 71 | if (tokenValue && store.state.userInfo) { 72 | console.log('store.state.userMenus=', store.state.userMenus) 73 | if (store.state.userMenus.length == 0 && to.name != 'error-no-any-power') { 74 | console.log('next:::1') 75 | NProgress.done() 76 | next({ 77 | name: 'error-no-any-power', 78 | replace: true 79 | }) 80 | } 81 | const powerFind = store.state.userMenus ? store.state.userMenus.find(x => x === to.name) : null 82 | // 业务路由匹配,权限没匹配,跳转无权限页面 83 | if (store.state.userMenus.length > 0 && businessFind && !powerFind && from.path !== '/') { 84 | console.log('next:::2, businessFind=', businessFind) 85 | NProgress.done() 86 | next({ 87 | name: 'error-no-power' 88 | }) 89 | } 90 | // 当有token还要跳转login的时候,或者直接跳转 / 的时候,跳转第一个有权限的菜单 91 | if (store.state.userMenus.length > 0 && !powerFind && (to.path === '/' || to.name === 'login' || from.path === '/')) { 92 | let targetName = store.state.userMenus[0] 93 | console.log('next:::3 targetName=', targetName) 94 | NProgress.done() 95 | next({ 96 | name: targetName, 97 | replace: true 98 | }) 99 | } 100 | } 101 | 102 | // 所有路由都没匹配,跳转404 103 | if (!sysFind && !businessFind) { 104 | console.log('next:::4') 105 | NProgress.done() 106 | next({ 107 | name: 'error-page404', 108 | replace: true 109 | }) 110 | } 111 | 112 | // 其他所有情况,直接跳转 113 | console.log('next:::5') 114 | next() 115 | }) 116 | 117 | router.afterEach((to, from) => { 118 | // 关闭进度条 119 | NProgress.done() 120 | }) 121 | 122 | async function initSysPower() { 123 | return store.dispatch('refleshUserInfo').then(menus => { 124 | console.log('menus=', menus) 125 | if (menus.length > 0) { 126 | // 存储最终要添加到客户端的菜单变量 127 | let userMenus = menus.map(item => item.name) 128 | store.dispatch('setUserPowers', userMenus) 129 | } 130 | }) 131 | } 132 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import page404 from '../views/error-pages/404' 4 | import page500 from '../views/error-pages/500' 5 | import pageNoAnyPower from '../views/error-pages/no-any-power' 6 | import pageNoPower from '../views/error-pages/no-power' 7 | import pageLogin from '../views/login' 8 | import mainFrame from '@/layout' 9 | import articles from '../views/content-management/article-management' 10 | import tags from '../views/content-management/tag-management' 11 | import articleEdit from '../views/content-management/article-editing' 12 | import users from '../views/system-management/users-management' 13 | import roles from '../views/system-management/roles-management' 14 | import menus from '../views/system-management/menu-management' 15 | import sourceCategories from '../views/system-management/source-category-management' 16 | import sources from '../views/system-management/source-management' 17 | import siteConfig from '../views/system-management/site-configuration' 18 | 19 | Vue.use(Router) 20 | 21 | // 定义路由数组,暂时前端写死,之后加入权限管理之后,读取权限之后要进行过滤 22 | 23 | // 不展示在菜单栏上,但是系统要使用到的数组 24 | export const systemRouters = [ 25 | { 26 | path: '/error/404', 27 | name: 'error-page404', 28 | component: page404 29 | }, 30 | { 31 | path: '/error/500', 32 | name: 'error-page500', 33 | component: page500 34 | }, 35 | { 36 | path: '/error/no-power', 37 | name: 'error-no-power', 38 | component: pageNoPower 39 | }, 40 | { 41 | path: '/error/no-any-power', 42 | name: 'error-no-any-power', 43 | component: pageNoAnyPower 44 | }, 45 | { 46 | path: '/login', 47 | name: 'login', 48 | component: pageLogin 49 | }, 50 | // { 51 | // path: '/content/article-editing', 52 | // name: 'article-editing', 53 | // component: articleEdit 54 | // }, 55 | { 56 | path: '/', 57 | name: 'root', 58 | // redirect: '/content/articles' 59 | component: mainFrame 60 | } 61 | ] 62 | 63 | export const pageRouters = [ 64 | { 65 | path: '/content', 66 | redirect: '/content/articles', 67 | name: 'content-management', 68 | component: mainFrame, 69 | icon: 'el-icon-s-help', // el-icon-s-help 70 | meta: { 71 | title: '内容管理' 72 | }, 73 | children: [ 74 | { 75 | path: 'article-editing', 76 | name: 'article-editing', 77 | icon: 'el-icon-edit-outline', 78 | component: articleEdit, 79 | meta: { 80 | title: '创建文章' 81 | } 82 | }, 83 | { 84 | path: 'article-modify', 85 | name: 'article-modify', 86 | icon: 'el-icon-edit-outline', 87 | component: articleEdit, 88 | meta: { 89 | title: '编辑文章' 90 | } 91 | }, 92 | { 93 | path: 'articles', 94 | name: 'article-management', 95 | icon: 'iconpark-list-two', 96 | component: articles, 97 | meta: { 98 | title: '文章列表' 99 | } 100 | }, 101 | { 102 | path: 'tags', 103 | name: 'tag-management', 104 | icon: 'iconfont-tag-management', 105 | component: tags, 106 | meta: { 107 | title: '标签管理' 108 | } 109 | } 110 | // { 111 | // path: 'columns', 112 | // name: 'column-management', 113 | // icon: 'el-icon-s-grid', 114 | // component: columns, 115 | // meta: { 116 | // title: '专栏管理' 117 | // } 118 | // } 119 | ] 120 | }, 121 | { 122 | path: '/system', 123 | redirect: '/system/site', 124 | name: 'system-management', 125 | component: mainFrame, 126 | icon: 'el-icon-s-operation', 127 | meta: { 128 | title: '系统管理' 129 | }, 130 | children: [ 131 | { 132 | path: 'site', 133 | name: 'site-configuration', 134 | component: siteConfig, 135 | icon: 'iconpark-config', 136 | meta: { 137 | title: '站点配置' 138 | } 139 | }, 140 | { 141 | path: 'users', 142 | name: 'users-management', 143 | component: users, 144 | icon: 'el-icon-user', 145 | meta: { 146 | title: '用户管理' 147 | } 148 | }, 149 | { 150 | path: 'roles', 151 | name: 'roles-management', 152 | component: roles, 153 | icon: 'iconfont-role-management', 154 | meta: { 155 | title: '角色管理' 156 | } 157 | }, 158 | { 159 | path: 'menus', 160 | name: 'menus-management', 161 | component: menus, 162 | icon: 'iconfont-menu-management', 163 | meta: { 164 | title: '菜单管理' 165 | } 166 | }, 167 | { 168 | path: 'sources', 169 | name: 'sources-management', 170 | component: sources, 171 | icon: 'iconfont-source-management', 172 | meta: { 173 | title: '资源管理' 174 | } 175 | }, 176 | { 177 | path: 'source-categories', 178 | name: 'source-categories-management', 179 | component: sourceCategories, 180 | icon: 'el-icon-coin', 181 | meta: { 182 | title: '资源分类管理' 183 | } 184 | } 185 | // { 186 | // path: 'log', 187 | // name: 'log-management', 188 | // component: log, 189 | // meta: { 190 | // title: '日志管理' 191 | // } 192 | // } 193 | ] 194 | } 195 | ] 196 | 197 | export default new Router({ 198 | routes: [...pageRouters, ...systemRouters] 199 | // routes: systemRouters 200 | }) 201 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { 4 | getLoginUserInfo 5 | } from '../api/users' 6 | import { 7 | systemRouters 8 | } from '../router' 9 | 10 | Vue.use(Vuex) 11 | 12 | export default new Vuex.Store({ 13 | state: { 14 | // 定义存储当前登陆用户信息的变量 15 | userInfo: null, 16 | // 用户可访问的业务菜单名称列表 17 | userMenus: [] 18 | }, 19 | mutations: { 20 | // 设置用户信息的方法 21 | SET_USER_INFO(state, data) { 22 | state.userInfo = data 23 | }, 24 | // 设置用户可访问业务路径的方法 25 | SET_PATHS_ALLOW(state, data) { 26 | state.powerPaths = data 27 | // 加入系统菜单允许访问的路径 28 | const sysPaths = systemRouters.filter(x => x.name != 'all').map(x => x.path) 29 | state.sysPaths = sysPaths 30 | }, 31 | SET_USER_MENUS(state, data) { 32 | state.userMenus = data 33 | } 34 | }, 35 | actions: { 36 | // 调用后端服务接口,获取当前用户信息并存入vuex 37 | refleshUserInfo({ 38 | commit 39 | }) { 40 | return getLoginUserInfo().then(res => { 41 | console.log('获取登录用户信息成功', res) 42 | commit('SET_USER_INFO', res) 43 | return res.menus 44 | }).catch(rej => { 45 | console.log('与服务器通信出现异常,response对象记录', rej) 46 | }) 47 | }, 48 | 49 | // 用户退出登陆时候,移除用户信息方法 50 | removeUserInfo({ 51 | commit 52 | }) { 53 | commit('SET_USER_INFO', null) 54 | }, 55 | 56 | // 动态获取权限后,设置用户可访问的业务页面路径 57 | setUserPowers({ 58 | commit 59 | }, menusArr) { 60 | commit('SET_USER_MENUS', menusArr) 61 | } 62 | } 63 | }) 64 | -------------------------------------------------------------------------------- /src/styles/common.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app, 4 | .el-container { 5 | height: 100%; 6 | overflow: auto; 7 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; 8 | font-size: 14px; 9 | line-height: 1.5; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | body { 15 | min-height: 650px; 16 | min-width: 650px; 17 | } 18 | 19 | #app .el-textarea__inner { 20 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; 21 | } 22 | 23 | .app-container { 24 | height: 100%; 25 | } 26 | 27 | .flex-row { 28 | display: flex; 29 | } 30 | 31 | .flex-auto-item { 32 | flex-grow: 1; 33 | } 34 | 35 | .flex-fixed-item { 36 | flex-shrink: 0; 37 | } 38 | 39 | .flex-row-ver-center { 40 | display: flex; 41 | justify-content: left; 42 | align-items: center; 43 | } 44 | 45 | /* 一些公用样式 */ 46 | .text-center { 47 | text-align: center; 48 | } 49 | 50 | .text-right { 51 | text-align: right; 52 | } 53 | 54 | .text-justify { 55 | text-align: justify; 56 | } 57 | 58 | .clear { 59 | clear: both; 60 | } 61 | 62 | .per100input { 63 | display: inline-block; 64 | width: 100%; 65 | } 66 | 67 | .red-tip { 68 | font-size: 12px; 69 | color: #ff0000; 70 | } 71 | 72 | /* 一些公用样式结束 */ 73 | .styleof-inlineblock { 74 | display: inline-block; 75 | vertical-align: top; 76 | box-sizing: border-box; 77 | } 78 | 79 | .flex-container { 80 | display: flex; 81 | box-sizing: border-box; 82 | } 83 | 84 | /* 修复elementui全局遮罩在有滚动条时候的问题 */ 85 | #app .el-loading-mask { 86 | position: fixed; 87 | } 88 | 89 | /* 指定表格溢出内容弹出框显示的最大宽度 */ 90 | .el-tooltip__popper { 91 | max-width: 800px; 92 | } 93 | 94 | /* placeholder样式定义 */ 95 | /* #app input::input-placeholder { 96 | color: #666; 97 | } 98 | 99 | #app input::-webkit-input-placeholder { 100 | color: #666; 101 | } 102 | 103 | #app input:-ms-input-placeholder { 104 | color: #666; 105 | } 106 | 107 | #app input:-moz-placeholder { 108 | color: #666; 109 | } 110 | 111 | #app input::-moz-placeholder { 112 | color: #666; 113 | } 114 | 115 | #app input:focus::-webkit-input-placeholder { 116 | color: #666; 117 | } 118 | 119 | #app input:focus::-moz-input-placeholder { 120 | color: #666; 121 | } */ 122 | 123 | /* placeholder样式定义结束 */ 124 | 125 | /* 定义icon图标默认大小 */ 126 | .more-icon { 127 | font-size: 14px; 128 | } 129 | 130 | i[class^="el-icon-"].more-icon { 131 | vertical-align: middle; 132 | margin-right: 5px; 133 | width: 24px; 134 | text-align: center; 135 | font-size: 18px; 136 | } 137 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | // import { createUuid } from './common' 3 | 4 | // 随机存储cookie的token key 5 | // 2022-02-11 改为不随机了,因为每次刷新页面会重新创建uuid,所以每次刷新页面会导致无法获取到本地已有缓存 6 | const TokenKey = '1D596CD8-8A20-4CEC-98DD-CDC12282D65C' // createUuid() 7 | 8 | export function getToken () { 9 | return Cookies.get(TokenKey) 10 | } 11 | 12 | export function setToken (token) { 13 | return Cookies.set(TokenKey, token) 14 | } 15 | 16 | export function removeToken () { 17 | return Cookies.remove(TokenKey) 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/common.js: -------------------------------------------------------------------------------- 1 | import { 2 | MessageBox 3 | } from 'element-ui' 4 | 5 | /** 6 | * 创建uuid方法 7 | */ 8 | export const createUuid = () => { 9 | var s = [] 10 | var hexDigits = '0123456789abcdef' 11 | for (var i = 0; i < 36; i++) { 12 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1) 13 | } 14 | s[14] = '4' 15 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) 16 | s[8] = s[13] = s[18] = s[23] = '-' 17 | 18 | var uuid = s.join('') 19 | return uuid 20 | } 21 | 22 | // 逐层展开当前节点 23 | export function loopExpendTree(treeObject, currentNode, rootId) { 24 | currentNode.expanded = true 25 | while (currentNode.data.parentId !== rootId) { 26 | currentNode = treeObject.getNode(currentNode.data.parentId) 27 | if (!currentNode.expanded) { 28 | currentNode.expanded = true 29 | } 30 | } 31 | } 32 | 33 | // 合并相同对象相同key值的方法 34 | export function assignSameProperty(targetObject, otherObject) { 35 | if (targetObject && otherObject) { 36 | let tempArr = [] 37 | Object.keys(otherObject).forEach(key => { 38 | if (!targetObject.hasOwnProperty(key)) { 39 | tempArr.push(key) 40 | } 41 | }) 42 | tempArr.forEach(key => { 43 | Reflect.deleteProperty(otherObject, key) 44 | }) 45 | return Object.assign(targetObject, otherObject) 46 | } else { 47 | return targetObject || otherObject 48 | } 49 | } 50 | 51 | /** 52 | * 从elementUI表单验证回调参数当中获取错误信息数组的方法 53 | * @param {object} formValidErrObject elementUI表单验证失败后回调的第二个参数 54 | */ 55 | export function getFormValidErrorMessageArray(formValidErrObject) { 56 | const retArr = [] 57 | Object.keys(formValidErrObject).forEach(key => { 58 | formValidErrObject[key].forEach(errObj => { 59 | retArr.push(errObj.message) 60 | }) 61 | }) 62 | return retArr 63 | } 64 | 65 | /** 66 | * 在禁用elementUI自带的表单提示的前提下,统一处理表单验证失败的方法 67 | * @param {object} formValidErrObject elementUI表单验证失败后回调的第二个参数 68 | */ 69 | export function handleFormValidError(formValidErrObject) { 70 | let errMsgArr = getFormValidErrorMessageArray(formValidErrObject) 71 | MessageBox({ 72 | dangerouslyUseHTMLString: true, 73 | message: errMsgArr.join('
'), 74 | type: 'error', 75 | title: '操作失败' 76 | }) 77 | } 78 | 79 | /** 80 | * html转纯文本方法 81 | * @param {string} html 传入html,返回纯文本 82 | */ 83 | export function getTextFormHtml(html) { 84 | return html.replace(/<(style|script|iframe)[^>]*?>[\s\S]+?<\/\1\s*>/gi, '').replace(/<[^>]+?>/g, '').replace(/\s+/g, ' ').replace(/ /g, ' ').replace(/>/g, ' ') 85 | } 86 | 87 | /** 88 | * json对象深拷贝方法 89 | * @param {*} obj 要拷贝的json对象 90 | */ 91 | export function deepCopy(obj) { 92 | return JSON.parse(JSON.stringify(obj)) 93 | } 94 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { 3 | Loading, 4 | MessageBox, 5 | Message 6 | } from 'element-ui' 7 | import { 8 | getToken, 9 | removeToken 10 | } from '@/utils/auth' 11 | import router from '../router' 12 | 13 | // #region 处理ajax效果 14 | // 标记页面加载对象是否存在 15 | let loadingService = null 16 | // 当前请求数量 17 | let ajaxCount = 0 18 | // 检查当前是否所有ajax都结束方法 19 | let checkAllAjaxDone = () => { 20 | if (ajaxCount === 0) { 21 | // console.log('所有ajax结束。。。。', loadingService); 22 | if (loadingService) { 23 | loadingService.close() 24 | loadingService = null 25 | } 26 | } 27 | } 28 | // #endregion 29 | 30 | // 创建请求对象 31 | const httpRequest = axios.create({ 32 | // 统一的ajax请求前缀 33 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 34 | // withCredentials: true, // send cookies when cross-domain requests 35 | timeout: 5000 // 请求超时时间 36 | }) 37 | 38 | // 请求封装 39 | httpRequest.interceptors.request.use( 40 | config => { 41 | // do something before request is sent 42 | // console.log('request config data:', config.data) 43 | if (getToken()) { 44 | // 设置请求头token 45 | config.headers['Authorization'] = getToken() 46 | } 47 | // #region 处理ajax效果 48 | // 如果loading对象不存在,当前又要发起请求,则创建loading对象,从而对所有ajax请求添加统一的请求等待效果 49 | if (!loadingService) { 50 | loadingService = Loading.service({ 51 | fullscreen: true, 52 | lock: true, 53 | text: '加载中,请稍后...' 54 | }) 55 | } 56 | // ajax数量+1 57 | ajaxCount++ 58 | // #endregion 59 | return config 60 | }, 61 | error => { 62 | // do something with request error 63 | console.log(`发送请求失败${error}`) // for debug 64 | return Promise.reject(error) 65 | } 66 | ) 67 | 68 | // 响应处理封装 69 | httpRequest.interceptors.response.use( 70 | response => { 71 | console.log('服务器返回最外层响应:', response) 72 | const { 73 | code, 74 | result, 75 | message 76 | } = response.data 77 | if ( // 请求正常的情况 78 | (code === 0 && response.status === 200) || 79 | (response.data instanceof Blob && response.status === 200) 80 | ) { 81 | // #region 处理ajax效果 82 | ajaxCount-- 83 | checkAllAjaxDone() 84 | // #endregion 85 | if (response.data instanceof Blob) { 86 | return response.data 87 | } else { 88 | return result 89 | } 90 | } else { 91 | // #region 处理ajax效果 92 | ajaxCount-- 93 | checkAllAjaxDone() 94 | // #endregion 95 | if (code === 401) { // 无效 token 96 | removeToken() // 删除本地缓存 97 | // 跳转到登录页面 98 | router.push('/login') 99 | } else { 100 | Message({ 101 | message, 102 | type: 'error', 103 | duration: 3 * 1000, 104 | showClose: true 105 | }) 106 | return Promise.reject(message) 107 | } 108 | } 109 | }, 110 | error => { 111 | // #region 处理ajax效果 112 | ajaxCount-- 113 | checkAllAjaxDone() 114 | // #endregion 115 | console.log('服务器返回异常信息:', error, error.response) 116 | 117 | // 当用户登录过期,直接跳转登录页面 118 | if (error.response.status === 401 || error.response.data.code === 401) { 119 | removeToken() 120 | router.push('/login') 121 | } 122 | 123 | // 如果用户没定义异常处理,弹出统一友好提示 124 | MessageBox({ 125 | type: 'error', 126 | message: '页面发生异常,请刷新页面重试或联系系统管理员', // error.response.data.message, 127 | title: '系统提示' 128 | }) 129 | // if (error.response && error.response.data && error.response.data.message) { 130 | 131 | // } 132 | return Promise.reject(error.response) 133 | } 134 | ) 135 | 136 | export default httpRequest 137 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape */ 2 | /** 3 | * Created by PanJiaChen on 16/11/18. 4 | */ 5 | 6 | /** 7 | * @param {string} path 8 | * @returns {Boolean} 9 | */ 10 | export function isExternal(path) { 11 | return /^(https?:|mailto:|tel:)/.test(path) 12 | } 13 | 14 | /** 15 | * @param {string} url 16 | * @returns {Boolean} 17 | */ 18 | export function validURL(url) { 19 | const reg = /^(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.,?'\\+&%$#=~_-]+))*$/ 20 | return reg.test(url) 21 | } 22 | 23 | /** 24 | * @param {string} str 25 | * @returns {Boolean} 26 | */ 27 | export function validLowerCase(str) { 28 | const reg = /^[a-z]+$/ 29 | return reg.test(str) 30 | } 31 | 32 | /** 33 | * @param {string} str 34 | * @returns {Boolean} 35 | */ 36 | export function validUpperCase(str) { 37 | const reg = /^[A-Z]+$/ 38 | return reg.test(str) 39 | } 40 | 41 | /** 42 | * @param {string} str 43 | * @returns {Boolean} 44 | */ 45 | export function validAlphabets(str) { 46 | const reg = /^[A-Za-z]+$/ 47 | return reg.test(str) 48 | } 49 | 50 | /** 51 | * @param {string} email 52 | * @returns {Boolean} 53 | */ 54 | export function validEmail(email) { 55 | const reg = /^(([^<>()\[\]\\.,;:\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,}))$/ 56 | return reg.test(email) 57 | } 58 | 59 | /** 60 | * @param {string} str 61 | * @returns {Boolean} 62 | */ 63 | export function isString(str) { 64 | if (typeof str === 'string' || str instanceof String) { 65 | return true 66 | } 67 | return false 68 | } 69 | 70 | /** 71 | * @param {Array} arg 72 | * @returns {Boolean} 73 | */ 74 | export function isArray(arg) { 75 | if (typeof Array.isArray === 'undefined') { 76 | return Object.prototype.toString.call(arg) === '[object Array]' 77 | } 78 | return Array.isArray(arg) 79 | } 80 | 81 | /** 82 | * 验证是否为空方法,因为element-ui自带的非空验证无法屏蔽空格,所以自定义此方法 83 | * @param {object} rule 校验规则对象 84 | * @param {any} value 表单值 85 | * @param {function} callback 系统回调函数 86 | */ 87 | export const emptyChecker = (rule, value, callback) => { 88 | if (typeof value === 'string') { 89 | value = value.trim() 90 | } 91 | 92 | if (!value && value !== 0) { 93 | // 参数无用 94 | callback(new Error('不能为空')) 95 | } 96 | callback() 97 | } 98 | 99 | /** 100 | * 校验规则,可通过的值为:空或者整数 101 | * @param {object} rule 校验规则对象 102 | * @param {any} value 表单值 103 | * @param {function} callback 系统回调函数 104 | */ 105 | export const emptyOrIntegerChecker = (rule, value, callback) => { 106 | let valueToValid = value 107 | if (typeof valueToValid !== 'string') { 108 | valueToValid = valueToValid.toString().trim() 109 | } 110 | 111 | if (valueToValid.length == 0) { 112 | callback() 113 | } 114 | 115 | const reg = /^(-|\d)*\d$/g 116 | if (!reg.test(valueToValid)) { 117 | callback(new Error('必须为整数')) 118 | } 119 | callback() 120 | } 121 | 122 | /** 123 | * 校验规则,可通过的值为:整数 124 | * @param {object} rule 校验规则对象 125 | * @param {any} value 表单值 126 | * @param {function} callback 系统回调函数 127 | */ 128 | export const integerChecker = (rule, value, callback) => { 129 | let valueToValid = value 130 | if (typeof valueToValid !== 'string') { 131 | valueToValid = valueToValid.toString().trim() 132 | } 133 | 134 | const reg = /^(-|\d)*\d$/g 135 | if (!reg.test(valueToValid)) { 136 | callback(new Error('必须为整数')) 137 | } 138 | callback() 139 | } 140 | -------------------------------------------------------------------------------- /src/views/content-management/article-editing/article-edit-without-columns.vue: -------------------------------------------------------------------------------- 1 | 74 | 422 | -------------------------------------------------------------------------------- /src/views/content-management/article-editing/index.js: -------------------------------------------------------------------------------- 1 | import articleEdit from './article-edit-without-columns.vue' 2 | 3 | export default articleEdit 4 | -------------------------------------------------------------------------------- /src/views/content-management/article-editing/page.css: -------------------------------------------------------------------------------- 1 | /* 用户头像样式 */ 2 | .user-image{ 3 | width: 50px; 4 | height: 50px; 5 | border-radius: 8px; 6 | cursor: pointer; 7 | margin-right: 5px; 8 | margin-top: 3px; 9 | } 10 | .logo-title { 11 | font-size: 20px; 12 | flex-grow: 1; 13 | } 14 | .user-area { 15 | margin-left: 15px; 16 | } 17 | 18 | .btn-area{ 19 | margin-left: 15px; 20 | } 21 | 22 | .article-tag.el-tag { 23 | margin-left: 10px; 24 | cursor: pointer; 25 | } 26 | 27 | /* 由于效果需要,用于修改elementui自带表单一些样式的样式 */ 28 | .article-edit-form .el-form-item{ 29 | margin-bottom: 0; 30 | } 31 | 32 | .article-edit-form .el-form-item.mb-8{ 33 | margin-bottom: 8px; 34 | } 35 | 36 | .article-edit-form .el-input__inner{ 37 | border: 0; 38 | font-size: 20px; 39 | padding-left: 0; 40 | } 41 | 42 | .article-cover{ 43 | width: 240px; 44 | height: 160px; 45 | position: relative; 46 | } 47 | 48 | .article-cover .op-layer{ 49 | position: absolute; 50 | top: 0; 51 | left: 0; 52 | width: 100%; 53 | display: flex; 54 | height: 100%; 55 | justify-content: center; 56 | align-items: center; 57 | background-color: rgba(0, 0, 0, 70%); 58 | } 59 | 60 | .article-cover .cover-uploader-icon { 61 | font-size: 28px; 62 | color: #8c939d; 63 | width: 240px; 64 | height: 160px; 65 | line-height: 160px; 66 | text-align: center; 67 | border: 1px dashed #d9d9d9; 68 | border-radius: 6px; 69 | } 70 | 71 | .article-cover .cover-uploader-icon:hover{ 72 | border-color: #409EFF; 73 | color: #409EFF; 74 | } 75 | 76 | /* 标签容器中的按钮样式 */ 77 | .tags-container .el-button, 78 | .tags-container .el-button + .el-button{ 79 | margin-left: 0; 80 | margin-right: 10px; 81 | margin-top: 10px; 82 | } 83 | .tags-selected{ 84 | display: inline-block; 85 | } 86 | .addtag-btn, 87 | .tags-selected .el-tag{ 88 | margin-right: 10px; 89 | } 90 | 91 | /* 添加样式让select显示为100%宽度 */ 92 | .el-select.full-row{ 93 | display: block; 94 | } 95 | 96 | .el-textarea .el-input__count{ 97 | line-height: 1.2; 98 | } 99 | 100 | .el-tag.el-tag--info { 101 | color: #333333; 102 | } 103 | -------------------------------------------------------------------------------- /src/views/content-management/article-management/articels.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 382 | -------------------------------------------------------------------------------- /src/views/content-management/article-management/index.js: -------------------------------------------------------------------------------- 1 | import articles from './articels.vue' 2 | 3 | export default articles 4 | -------------------------------------------------------------------------------- /src/views/content-management/article-management/page.css: -------------------------------------------------------------------------------- 1 | .left-part { 2 | width: 35%; 3 | padding-right: 10px; 4 | } 5 | .right-part { 6 | width: 65%; 7 | padding-left: 10px; 8 | } 9 | .tree-area { 10 | margin-top: 10px; 11 | height: 400px; 12 | overflow: auto; 13 | } 14 | 15 | .table-container { 16 | height: calc(100% - 32px - 41px); 17 | box-sizing: border-box; 18 | padding-top: 8px; 19 | } 20 | 21 | .icon-ontop { 22 | width: 16px; 23 | height: 16px; 24 | vertical-align: middle; 25 | } 26 | 27 | .el-table__body .article-cover-col{ 28 | padding: 5px; 29 | } 30 | 31 | .el-table__body .article-cover-col .cell { 32 | padding: 0; 33 | } 34 | 35 | /* .el-table__body .article-cover-col img{ 36 | width: 69px; 37 | height: 46px; 38 | } */ -------------------------------------------------------------------------------- /src/views/content-management/column-management/columns.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 328 | 347 | -------------------------------------------------------------------------------- /src/views/content-management/column-management/index.js: -------------------------------------------------------------------------------- 1 | import columns from './columns.vue' 2 | 3 | export default columns 4 | -------------------------------------------------------------------------------- /src/views/content-management/tag-management/index.js: -------------------------------------------------------------------------------- 1 | import tag from './tags.vue' 2 | 3 | export default tag 4 | -------------------------------------------------------------------------------- /src/views/content-management/tag-management/tags.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 186 | -------------------------------------------------------------------------------- /src/views/error-pages/404/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 22 | -------------------------------------------------------------------------------- /src/views/error-pages/404/index.js: -------------------------------------------------------------------------------- 1 | import page404 from './404.vue' 2 | 3 | export default page404 4 | -------------------------------------------------------------------------------- /src/views/error-pages/500/500.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/error-pages/500/index.js: -------------------------------------------------------------------------------- 1 | import page500 from './500.vue' 2 | 3 | export default page500 4 | -------------------------------------------------------------------------------- /src/views/error-pages/no-any-power/index.js: -------------------------------------------------------------------------------- 1 | import noAnyPower from './no-any-power.vue' 2 | 3 | export default noAnyPower 4 | -------------------------------------------------------------------------------- /src/views/error-pages/no-any-power/no-any-power.vue: -------------------------------------------------------------------------------- 1 | 6 | 32 | -------------------------------------------------------------------------------- /src/views/error-pages/no-power/index.js: -------------------------------------------------------------------------------- 1 | import noPower from './no-power.vue' 2 | 3 | export default noPower 4 | -------------------------------------------------------------------------------- /src/views/error-pages/no-power/no-power.vue: -------------------------------------------------------------------------------- 1 | 6 | 22 | -------------------------------------------------------------------------------- /src/views/login/index.js: -------------------------------------------------------------------------------- 1 | import login from './login.vue' 2 | 3 | export default login 4 | -------------------------------------------------------------------------------- /src/views/login/login.vue: -------------------------------------------------------------------------------- 1 | 38 | 80 | 113 | -------------------------------------------------------------------------------- /src/views/system-management/menu-management/index.js: -------------------------------------------------------------------------------- 1 | import menus from './menus.vue' 2 | 3 | export default menus 4 | -------------------------------------------------------------------------------- /src/views/system-management/menu-management/menus.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 248 | 253 | -------------------------------------------------------------------------------- /src/views/system-management/roles-management/index.js: -------------------------------------------------------------------------------- 1 | import roles from './roles.vue' 2 | 3 | export default roles 4 | -------------------------------------------------------------------------------- /src/views/system-management/site-configuration/index.js: -------------------------------------------------------------------------------- 1 | import siteConfig from './site-config.vue' 2 | 3 | export default siteConfig 4 | -------------------------------------------------------------------------------- /src/views/system-management/site-configuration/site-config.vue: -------------------------------------------------------------------------------- 1 | 49 | 125 | 131 | -------------------------------------------------------------------------------- /src/views/system-management/source-category-management/index.js: -------------------------------------------------------------------------------- 1 | import sourceCategories from './source-categories.vue' 2 | 3 | export default sourceCategories 4 | -------------------------------------------------------------------------------- /src/views/system-management/source-category-management/source-categories.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 166 | 171 | -------------------------------------------------------------------------------- /src/views/system-management/source-management/index.js: -------------------------------------------------------------------------------- 1 | import source from './sources.vue' 2 | 3 | export default source 4 | -------------------------------------------------------------------------------- /src/views/system-management/source-management/sources.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 264 | -------------------------------------------------------------------------------- /src/views/system-management/users-management/index.js: -------------------------------------------------------------------------------- 1 | import users from './users.vue' 2 | 3 | export default users 4 | -------------------------------------------------------------------------------- /src/views/system-management/users-management/page.css: -------------------------------------------------------------------------------- 1 | .tree-area { 2 | margin-top: 10px; 3 | height: 400px; 4 | overflow: auto; 5 | } 6 | 7 | .table-container { 8 | height: calc(100% - 32px - 41px); 9 | box-sizing: border-box; 10 | padding-top: 8px; 11 | } 12 | 13 | /* 上传头像相关样式 */ 14 | .avatar-uploader .el-upload { 15 | border: 1px dashed #d9d9d9; 16 | border-radius: 6px; 17 | cursor: pointer; 18 | position: relative; 19 | overflow: hidden; 20 | } 21 | .avatar-uploader .el-upload:hover { 22 | border-color: #409eff; 23 | } 24 | .avatar-uploader-icon { 25 | font-size: 28px; 26 | color: #8c939d; 27 | width: 148px; 28 | height: 148px; 29 | line-height: 148px; 30 | text-align: center; 31 | } 32 | .avatar { 33 | width: 148px; 34 | height: 148px; 35 | display: block; 36 | border-radius: 6px; 37 | } 38 | 39 | .user-image-container { 40 | position: relative; 41 | width: 148px; 42 | height: 148px; 43 | border: 1px solid #ccc; 44 | border-radius: 10px; 45 | } 46 | .user-image-float-layer { 47 | border-radius: 10px; 48 | display: flex; 49 | align-items: center; 50 | justify-content: center; 51 | position: absolute; 52 | top: 0; 53 | left: 0; 54 | width: 148px; 55 | height: 148px; 56 | z-index: 999; 57 | background-color: rgba(0, 0, 0, 70%); 58 | } 59 | 60 | .role-split-area{ 61 | max-height: 500px; 62 | overflow: auto; 63 | } -------------------------------------------------------------------------------- /src/views/system-management/users-management/users.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 362 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itwanger/codingmore-admin-web/bdaf105644be85cd06cec02d809b864ed5777f2f/static/.gitkeep --------------------------------------------------------------------------------