├── .gitignore ├── README.md ├── egg-server ├── .autod.conf.js ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── README.zh-CN.md ├── app │ ├── controller │ │ ├── base.js │ │ ├── home.js │ │ └── login.js │ ├── model │ │ └── user.js │ └── router.js ├── appveyor.yml ├── config │ ├── config.default.js │ ├── menu.js │ └── plugin.js ├── nideadmin.sql ├── package.json └── test │ └── app │ └── controller │ └── home.test.js └── vue-client ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── babel.config.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html └── src ├── App.vue ├── assets └── logo.png ├── components └── HelloWorld.vue ├── config └── config.js ├── libs └── helper.js ├── main.js ├── plugins ├── axios.js └── iview.js ├── routers ├── content.js ├── home.js ├── index.js ├── setting.js ├── statistics.js ├── tool.js └── user.js ├── scss ├── _variables.scss └── app.scss ├── store.js └── views ├── Index.vue ├── Login.vue ├── content └── ContentManagement.vue ├── home ├── HomeCommonFunction.vue ├── HomeSummary.vue └── HomeTodo.vue ├── setting └── SettingSiteInfo.vue ├── statistics └── StatisticsSummary.vue ├── tool └── ToolUpdateCache.vue └── user └── UserManagement.vue /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | npm-debug.log* 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NideAdmin 2 | 3 | 基于 Vue.js + iView 的后台管理基础框架。 4 | 5 | + [线上体验](http://nideadmin-demo.nideshop.com/) 6 | + [开发文档](http://nideadmin.nideshop.com/) 7 | 8 | ### 使用方法 9 | ``` 10 | # 进入客户端目录 11 | cd vue-client 12 | 13 | # 安装依赖 14 | npm install --registry=https://registry.npm.taobao.org 15 | 16 | # 启动开发服务 17 | npm run serve 18 | 19 | # 构建生产环境 20 | npm run build 21 | ``` 22 | 23 | ### 演示账号 24 | + 用户名:admin 25 | + 密码:nideadmin 26 | 27 | ### 效果展示 28 | TODO -------------------------------------------------------------------------------- /egg-server/.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | plugin: 'autod-egg', 7 | test: [ 8 | 'test', 9 | 'benchmark', 10 | ], 11 | dep: [ 12 | 'egg', 13 | 'egg-scripts', 14 | ], 15 | devdep: [ 16 | 'egg-ci', 17 | 'egg-bin', 18 | 'egg-mock', 19 | 'autod', 20 | 'autod-egg', 21 | 'eslint', 22 | 'eslint-config-egg', 23 | 'webstorm-disable-index', 24 | ], 25 | exclude: [ 26 | './test/fixtures', 27 | './dist', 28 | ], 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /egg-server/.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /egg-server/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /egg-server/.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | yarn-error.log 4 | node_modules/ 5 | package-lock.json 6 | yarn.lock 7 | coverage/ 8 | .idea/ 9 | run/ 10 | .DS_Store 11 | *.sw* 12 | *.un~ 13 | -------------------------------------------------------------------------------- /egg-server/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | install: 6 | - npm i npminstall && npminstall 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npminstall codecov && codecov 11 | -------------------------------------------------------------------------------- /egg-server/README.md: -------------------------------------------------------------------------------- 1 | # nideadmin-egg-server 2 | 3 | 4 | 5 | ## QuickStart 6 | 7 | 8 | 9 | see [egg docs][egg] for more detail. 10 | 11 | ### Development 12 | 13 | ```bash 14 | $ npm i 15 | $ npm run dev 16 | $ open http://localhost:7001/ 17 | ``` 18 | 19 | ### Deploy 20 | 21 | ```bash 22 | $ npm start 23 | $ npm stop 24 | ``` 25 | 26 | ### npm scripts 27 | 28 | - Use `npm run lint` to check code style. 29 | - Use `npm test` to run unit test. 30 | - Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail. 31 | 32 | 33 | [egg]: https://eggjs.org -------------------------------------------------------------------------------- /egg-server/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # nideadmin-egg-server 2 | 3 | 4 | 5 | ## 快速入门 6 | 7 | 8 | 9 | 如需进一步了解,参见 [egg 文档][egg]。 10 | 11 | ### 本地开发 12 | 13 | ```bash 14 | $ npm i 15 | $ npm run dev 16 | $ open http://localhost:7001/ 17 | ``` 18 | 19 | ### 部署 20 | 21 | ```bash 22 | $ npm start 23 | $ npm stop 24 | ``` 25 | 26 | ### 单元测试 27 | 28 | - [egg-bin] 内置了 [mocha], [thunk-mocha], [power-assert], [istanbul] 等框架,让你可以专注于写单元测试,无需理会配套工具。 29 | - 断言库非常推荐使用 [power-assert]。 30 | - 具体参见 [egg 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。 31 | 32 | ### 内置指令 33 | 34 | - 使用 `npm run lint` 来做代码风格检查。 35 | - 使用 `npm test` 来执行单元测试。 36 | - 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。 37 | 38 | 39 | [egg]: https://eggjs.org 40 | -------------------------------------------------------------------------------- /egg-server/app/controller/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class BaseController extends Controller { 6 | // 响应成功 json 7 | success(data) { 8 | this.ctx.body = { code: 200, message: 'success', data }; 9 | return; 10 | } 11 | 12 | // 响应错误 json 13 | error(code, message) { 14 | this.ctx.body = { code, message, data: null }; 15 | } 16 | 17 | // 获取 post 表单数据 18 | post(name) { 19 | return this.ctx.request.body[name]; 20 | } 21 | 22 | // 获取 get 数据 23 | query(name) { 24 | return this.ctx.query[name]; 25 | } 26 | } 27 | 28 | module.exports = BaseController; 29 | -------------------------------------------------------------------------------- /egg-server/app/controller/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class HomeController extends Controller { 6 | async index() { 7 | this.ctx.body = 'hi, nideadmin'; 8 | } 9 | } 10 | 11 | module.exports = HomeController; 12 | -------------------------------------------------------------------------------- /egg-server/app/controller/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const jwt = require('jsonwebtoken'); 3 | const svgCaptcha = require('svg-captcha'); 4 | const md5 = require('md5'); 5 | const only = require('only'); 6 | const BaseController = require('./base'); 7 | const menus = require('../../config/menu'); 8 | 9 | class LoginController extends BaseController { 10 | async login() { 11 | const username = this.post('username'); 12 | const password = this.post('password'); 13 | const captcha = this.post('captcha'); 14 | const key = this.post('key'); 15 | 16 | // TODO 验证参数 17 | const rule = { 18 | username: { type: 'string', min: 2, max: 20 }, 19 | password: { type: 'string', min: 6 }, 20 | captcha: { type: 'string', min: 4, max: 4 }, 21 | key: { type: 'string', min: 36, max: 36 }, 22 | }; 23 | try { 24 | this.ctx.validate(rule, { username, password, captcha, key }); 25 | } catch (err) { 26 | this.error(400, '请求参数不合法'); 27 | return; 28 | } 29 | // 判断验证码 30 | const captchaCache = await this.app.cache.get(key); 31 | console.log(captchaCache, captcha); 32 | if (!captchaCache || captchaCache !== captcha.toLowerCase()) { 33 | this.error(400, '验证码错误'); 34 | return; 35 | } 36 | 37 | const user = await this.ctx.model.User.findByUsername(username); 38 | if (!user) { 39 | this.error(400, '用户不存在'); 40 | return; 41 | } 42 | 43 | const passwordMd5 = md5(password + 'nideadmin' + user.password_salt); 44 | if (passwordMd5 !== user.password) { 45 | this.error(400, '用户名或密码错误'); 46 | return; 47 | } 48 | 49 | // 返回登录成功信息 50 | const token = jwt.sign({ user_id: 1 }, this.config.jwt.secret); 51 | this.success({ user: only(user, [ 'id', 'username', 'avatar' ]), token, menus }); 52 | } 53 | 54 | async captcha() { 55 | const key = this.query('key'); 56 | const rule = { 57 | key: { type: 'string', min: 36, max: 36 }, 58 | }; 59 | try { 60 | this.ctx.validate(rule, { key }); 61 | } catch (err) { 62 | this.error(400, '请求参数不合法'); 63 | return; 64 | } 65 | const captcha = svgCaptcha.create({ 66 | size: 4, // 字符数量 67 | noise: 4, // 干扰线数量 68 | color: true, // 彩色显示 69 | ignoreChars: '0o1i', // 忽略的字符 70 | background: '#f8f8f9', // 背景色 71 | }); 72 | await this.app.cache.set(key, captcha.text.toLowerCase()); 73 | this.ctx.set('Content-Type', 'image/svg+xml'); 74 | this.ctx.body = captcha.data; 75 | } 76 | } 77 | 78 | module.exports = LoginController; 79 | -------------------------------------------------------------------------------- /egg-server/app/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | const { STRING, INTEGER, DATE } = app.Sequelize; 5 | 6 | const User = app.model.define('user', { 7 | id: { type: INTEGER, primaryKey: true, autoIncrement: true }, 8 | username: STRING(20), 9 | password: STRING(64), 10 | password_salt: STRING(6), 11 | avatar: STRING(255), 12 | created_at: DATE, 13 | updated_at: DATE, 14 | deleted_at: DATE, 15 | }); 16 | 17 | User.findByUsername = async username => { 18 | return await User.findOne({ 19 | where: { 20 | username, 21 | }, 22 | }); 23 | }; 24 | 25 | return User; 26 | }; 27 | -------------------------------------------------------------------------------- /egg-server/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {Egg.Application} app - egg application 5 | */ 6 | module.exports = app => { 7 | const { router, controller } = app; 8 | router.get('/', controller.home.index); 9 | router.get('/login/captcha', controller.login.captcha); 10 | router.post('/login', controller.login.login); 11 | }; 12 | -------------------------------------------------------------------------------- /egg-server/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /egg-server/config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = appInfo => { 4 | const config = exports = {}; 5 | 6 | // use for cookie sign key, should change to your own and keep security 7 | config.keys = appInfo.name + '_1540625711644_3325'; 8 | 9 | // add your config here 10 | config.middleware = []; 11 | 12 | config.security = { 13 | csrf: { 14 | enable: false, 15 | }, 16 | }; 17 | 18 | config.cors = { 19 | origin: '*', 20 | allowHeaders: 'Authorization,Origin, X-Requested-With, Content-Type, Accept, X-Admin-Token, X-Admin-Version', 21 | allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS', 22 | }; 23 | 24 | config.cache = { 25 | default: 'memory', 26 | stores: { 27 | memory: { 28 | driver: 'memory', 29 | max: 100, 30 | ttl: 3600 * 3, 31 | }, 32 | }, 33 | }; 34 | 35 | config.jwt = { 36 | secret: 'f40499b377933f39cc9e7634323669e0', 37 | }; 38 | 39 | config.sequelize = { 40 | dialect: 'mysql', 41 | host: '127.0.0.1', 42 | port: 3306, 43 | database: 'nideadmin', 44 | username: 'root', 45 | password: 'root', 46 | }; 47 | 48 | return config; 49 | }; 50 | -------------------------------------------------------------------------------- /egg-server/config/menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 菜单配置 4 | const menu = [{ 5 | name: '控制台', 6 | icon: '', 7 | active: false, 8 | route: 'home_summary', 9 | children: [{ 10 | name: '今日概况', 11 | icon: 'ios-color-palette-outline', 12 | active: false, 13 | route: 'home_summary', 14 | }, 15 | { 16 | name: '待办事项', 17 | icon: 'ios-list-box-outline', 18 | active: false, 19 | route: 'home_todo', 20 | }, 21 | { 22 | name: '常用操作', 23 | icon: 'ios-apps-outline', 24 | active: false, 25 | route: 'home_common-function', 26 | }, 27 | ], 28 | }, 29 | { 30 | name: '内容', 31 | icon: '', 32 | active: false, 33 | route: 'content_management', 34 | children: [{ 35 | name: '内容管理', 36 | icon: 'ios-cube-outline', 37 | active: false, 38 | route: 'content_management', 39 | }], 40 | }, 41 | { 42 | name: '用户', 43 | icon: '', 44 | active: false, 45 | route: 'user_management', 46 | children: [{ 47 | name: '用户管理', 48 | icon: 'ios-contact-outline', 49 | active: false, 50 | route: 'user_management', 51 | }], 52 | }, 53 | { 54 | name: '工具', 55 | icon: '', 56 | active: false, 57 | route: 'tool_update-cache', 58 | children: [{ 59 | name: '更新缓存', 60 | icon: 'ios-refresh', 61 | active: false, 62 | route: 'tool_update-cache', 63 | }], 64 | }, 65 | { 66 | name: '统计', 67 | icon: '', 68 | active: false, 69 | route: 'statistics_summary', 70 | children: [{ 71 | name: '统计概况', 72 | icon: 'ios-color-palette-outline', 73 | active: false, 74 | route: 'statistics_summary', 75 | }], 76 | }, 77 | { 78 | name: '设置', 79 | icon: '', 80 | active: false, 81 | route: 'setting_site-info', 82 | children: [{ 83 | name: '站点信息', 84 | icon: 'ios-color-palette-outline', 85 | active: false, 86 | route: 'setting_site-info', 87 | }], 88 | }, 89 | ]; 90 | module.exports = menu; 91 | -------------------------------------------------------------------------------- /egg-server/config/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // had enabled by egg 4 | // exports.static = true; 5 | exports.validate = { 6 | enable: true, 7 | package: 'egg-validate', 8 | }; 9 | 10 | exports.cors = { 11 | enable: true, 12 | package: 'egg-cors', 13 | }; 14 | 15 | exports.cache = { 16 | enable: true, 17 | package: 'egg-cache', 18 | }; 19 | 20 | exports.sequelize = { 21 | enable: true, 22 | package: 'egg-sequelize', 23 | }; 24 | -------------------------------------------------------------------------------- /egg-server/nideadmin.sql: -------------------------------------------------------------------------------- 1 | 2 | SET NAMES utf8mb4; 3 | SET FOREIGN_KEY_CHECKS = 0; 4 | 5 | -- ---------------------------- 6 | -- Table structure for users 7 | -- ---------------------------- 8 | DROP TABLE IF EXISTS `users`; 9 | CREATE TABLE `users` ( 10 | `id` int(11) NOT NULL AUTO_INCREMENT, 11 | `username` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名,用作登录', 12 | `avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '用户头像', 13 | `password` varchar(64) NOT NULL DEFAULT '' COMMENT '密码', 14 | `password_salt` varchar(6) NOT NULL DEFAULT '' COMMENT '密码盐', 15 | `created_at` datetime DEFAULT NULL COMMENT '创建时间', 16 | `updated_at` datetime DEFAULT NULL COMMENT '更新时间', 17 | `deleted_at` datetime DEFAULT NULL COMMENT '删除时间,不为 NULL 则表示数据已被软删除', 18 | PRIMARY KEY (`id`) 19 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; 20 | 21 | -- ---------------------------- 22 | -- Records of users 23 | -- ---------------------------- 24 | BEGIN; 25 | INSERT INTO `users` VALUES (1, 'admin', 'https://gitee.com/uploads/33/1446033_tumobi.png?1502068801', '81101c28a48e7408be53c6a0e1834abe', '389238', '2018-11-21 23:34:09', NULL, NULL); 26 | COMMIT; 27 | 28 | SET FOREIGN_KEY_CHECKS = 1; 29 | -------------------------------------------------------------------------------- /egg-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nideadmin-egg-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "dependencies": { 7 | "egg": "^2.2.1", 8 | "egg-cache": "0.0.5", 9 | "egg-cors": "^2.1.1", 10 | "egg-scripts": "^2.5.0", 11 | "egg-sequelize": "^4.2.0", 12 | "egg-validate": "^2.0.2", 13 | "jsonwebtoken": "^8.3.0", 14 | "md5": "^2.2.1", 15 | "mysql2": "^1.6.4", 16 | "only": "0.0.2", 17 | "svg-captcha": "^1.3.11" 18 | }, 19 | "devDependencies": { 20 | "autod": "^3.0.1", 21 | "autod-egg": "^1.0.0", 22 | "egg-bin": "^4.3.5", 23 | "egg-ci": "^1.8.0", 24 | "egg-mock": "^3.14.0", 25 | "eslint": "^4.11.0", 26 | "eslint-config-egg": "^6.0.0", 27 | "webstorm-disable-index": "^1.2.0" 28 | }, 29 | "engines": { 30 | "node": ">=8.9.0" 31 | }, 32 | "scripts": { 33 | "start": "egg-scripts start --daemon --title=egg-server-nideadmin-egg-server", 34 | "stop": "egg-scripts stop --title=egg-server-nideadmin-egg-server", 35 | "dev": "egg-bin dev", 36 | "debug": "egg-bin debug", 37 | "test": "npm run lint -- --fix && npm run test-local", 38 | "test-local": "egg-bin test", 39 | "cov": "egg-bin cov", 40 | "lint": "eslint .", 41 | "ci": "npm run lint && npm run cov", 42 | "autod": "autod" 43 | }, 44 | "ci": { 45 | "version": "8" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "" 50 | }, 51 | "author": "", 52 | "license": "MIT" 53 | } 54 | -------------------------------------------------------------------------------- /egg-server/test/app/controller/home.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { app, assert } = require('egg-mock/bootstrap'); 4 | 5 | describe('test/app/controller/home.test.js', () => { 6 | 7 | it('should assert', function* () { 8 | const pkg = require('../../../package.json'); 9 | assert(app.config.keys.startsWith(pkg.name)); 10 | 11 | // const ctx = app.mockContext({}); 12 | // yield ctx.service.xx(); 13 | }); 14 | 15 | it('should GET /', () => { 16 | return app.httpRequest() 17 | .get('/') 18 | .expect('hi, egg') 19 | .expect(200); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /vue-client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /vue-client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'vue/html-self-closing': 'off', 14 | 'vue/no-parsing-error': 'off' 15 | }, 16 | parserOptions: { 17 | parser: 'babel-eslint' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vue-client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /vue-client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nideadmin-vue-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "iview": "^3.0.1", 12 | "localforage": "^1.7.2", 13 | "uuid": "^3.3.2", 14 | "vue": "^2.5.17", 15 | "vue-router": "^3.0.1", 16 | "vuex": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-babel": "^3.0.1", 20 | "@vue/cli-plugin-eslint": "^3.0.1", 21 | "@vue/cli-service": "^3.0.1", 22 | "@vue/eslint-config-standard": "^3.0.5", 23 | "axios": "^0.18.0", 24 | "eslint": "^5.7.0", 25 | "eslint-plugin-vue": "^4.7.1", 26 | "node-sass": "^4.9.0", 27 | "sass-loader": "^7.0.1", 28 | "vue-cli-plugin-axios": "0.0.4", 29 | "vue-cli-plugin-iview": "^1.0.6", 30 | "vue-template-compiler": "^2.5.17" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vue-client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumobi/nideadmin/5a15dbc330795df220df1cf4bc33a81813efe871/vue-client/public/favicon.ico -------------------------------------------------------------------------------- /vue-client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | NideAdmin - 基于 Vue.js + iView 的后台管理基础框架 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue-client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /vue-client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumobi/nideadmin/5a15dbc330795df220df1cf4bc33a81813efe871/vue-client/src/assets/logo.png -------------------------------------------------------------------------------- /vue-client/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /vue-client/src/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | adminApiBaseUrl: 'http://localhost:7001/' 3 | } 4 | 5 | export default config 6 | -------------------------------------------------------------------------------- /vue-client/src/libs/helper.js: -------------------------------------------------------------------------------- 1 | import localforage from 'localforage' 2 | const uuidv4 = require('uuid/v4') 3 | 4 | localforage.config({ 5 | name: 'NideAdmin' 6 | }) 7 | 8 | const helper = {} 9 | 10 | helper.setStorage = async (key, value) => { 11 | const result = await localforage.setItem(key, value) 12 | return result 13 | } 14 | 15 | helper.getStorage = async (key) => { 16 | const result = await localforage.getItem(key) 17 | return result 18 | } 19 | 20 | helper.removeStorage = async (key) => { 21 | const result = await localforage.removeItem(key) 22 | return result 23 | } 24 | 25 | helper.clearStorage = async () => { 26 | const result = await localforage.clear() 27 | return result 28 | } 29 | 30 | helper.uuid = function () { 31 | return uuidv4() 32 | } 33 | 34 | export default helper 35 | -------------------------------------------------------------------------------- /vue-client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './routers' 4 | import store from './store' 5 | import './plugins/iview.js' 6 | import './plugins/axios' 7 | import './scss/app.scss' 8 | 9 | Vue.config.productionTip = false 10 | 11 | new Vue({ 12 | router, 13 | store, 14 | render: h => h(App) 15 | }).$mount('#app') 16 | -------------------------------------------------------------------------------- /vue-client/src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import iView from 'iview' 4 | import helper from '../libs/helper' 5 | 6 | // Full config: https://github.com/axios/axios#request-config 7 | // axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || ''; 8 | // axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; 9 | // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; 10 | 11 | let config = { 12 | // baseURL: process.env.baseURL || process.env.apiUrl || "" 13 | // timeout: 60 * 1000, // Timeout 14 | // withCredentials: true, // Check cross-site Access-Control 15 | } 16 | 17 | axios.defaults.headers.common['X-Admin-Version'] = '1.0.0' 18 | 19 | const _axios = axios.create(config) 20 | 21 | _axios.interceptors.request.use( 22 | async function (config) { 23 | let adminToken = '' 24 | try { 25 | adminToken = await helper.getStorage('admin_token') 26 | } catch (err) { 27 | adminToken = '' 28 | } 29 | config.headers['X-Admin-Token'] = adminToken 30 | return config 31 | }, 32 | function (error) { 33 | // Do something with request error 34 | return Promise.reject(error) 35 | } 36 | ) 37 | 38 | // Add a response interceptor 39 | _axios.interceptors.response.use( 40 | function (response) { 41 | // 正常处理 42 | const res = response.data 43 | if (res.code === 200) { 44 | return Promise.resolve(res.data) 45 | } 46 | 47 | // 未登录 48 | if (res.code === 401) { 49 | // 提示未登录 50 | iView.Message.error('请先登录...') 51 | window.location.href = '/login' 52 | return Promise.reject(res) 53 | } 54 | 55 | return Promise.reject(res) 56 | }, 57 | function (error) { 58 | // Do something with response error 59 | return Promise.reject(error) 60 | } 61 | ) 62 | 63 | Plugin.install = function (Vue, options) { 64 | Vue.axios = _axios 65 | window.axios = _axios 66 | Object.defineProperties(Vue.prototype, { 67 | axios: { 68 | get () { 69 | return _axios 70 | } 71 | }, 72 | $axios: { 73 | get () { 74 | return _axios 75 | } 76 | } 77 | }) 78 | } 79 | 80 | Vue.use(Plugin) 81 | 82 | export default Plugin 83 | -------------------------------------------------------------------------------- /vue-client/src/plugins/iview.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import iView from 'iview' 3 | 4 | import 'iview/dist/styles/iview.css' 5 | 6 | Vue.use(iView) 7 | -------------------------------------------------------------------------------- /vue-client/src/routers/content.js: -------------------------------------------------------------------------------- 1 | import ContentManagement from '../views/content/ContentManagement' 2 | 3 | export default [ 4 | { 5 | path: 'content/management', 6 | name: 'content_management', 7 | component: ContentManagement 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /vue-client/src/routers/home.js: -------------------------------------------------------------------------------- 1 | import HomeSummary from '../views/home/HomeSummary' 2 | import HomeTodo from '../views/home/HomeTodo' 3 | import HomeCommonFunction from '../views/home/HomeCommonFunction' 4 | 5 | export default [ 6 | { 7 | path: '/', 8 | redirect: '/home/summary' 9 | }, 10 | { 11 | path: 'home/summary', 12 | name: 'home_summary', 13 | component: HomeSummary 14 | }, 15 | { 16 | path: 'home/todo', 17 | name: 'home_todo', 18 | component: HomeTodo 19 | }, 20 | { 21 | path: 'home/common-function', 22 | name: 'home_common-function', 23 | component: HomeCommonFunction 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /vue-client/src/routers/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import iView from 'iview' 4 | 5 | import store from '../store' 6 | import helper from '../libs/helper' 7 | import Login from '../views/Login.vue' 8 | import Index from '../views/Index.vue' 9 | import home from './home' 10 | import content from './content' 11 | import user from './user' 12 | import statistics from './statistics' 13 | import tool from './tool' 14 | import setting from './setting' 15 | 16 | Vue.use(Router) 17 | 18 | const router = new Router({ 19 | mode: 'history', 20 | base: process.env.BASE_URL, 21 | routes: [{ 22 | path: '/', 23 | component: Index, 24 | children: [ 25 | ...home, 26 | ...content, 27 | ...user, 28 | ...statistics, 29 | ...tool, 30 | ...setting 31 | ] 32 | }, 33 | { 34 | path: '/login', 35 | name: 'login', 36 | component: Login 37 | }, 38 | { 39 | path: '*', 40 | redirect: '/home/summary' 41 | } 42 | ] 43 | }) 44 | 45 | router.beforeEach(async (to, from, next) => { 46 | iView.LoadingBar.start() 47 | try { 48 | // 检测登录状态 49 | let adminToken = '' 50 | adminToken = await helper.getStorage('admin_token') 51 | if (to.name !== 'login' && !adminToken) { 52 | return router.push({ 53 | name: 'login' 54 | }) 55 | } 56 | 57 | // 检测菜单 58 | if (to.name !== 'login' && (!Array.isArray(store.state.menus) || store.state.menus.length <= 0)) { 59 | const localMenus = await helper.getStorage('menus') || [] 60 | if (localMenus.length <= 0) { 61 | return router.push({ 62 | name: 'login' 63 | }) 64 | } 65 | store.commit('setMenus', localMenus) 66 | } 67 | // 根据路由改变菜单导航状态 68 | store.commit('changeMenuActive', to) 69 | next() 70 | } catch (err) { 71 | return router.push({ 72 | name: 'login' 73 | }) 74 | } 75 | }) 76 | 77 | router.afterEach(route => { 78 | iView.LoadingBar.finish() 79 | }) 80 | 81 | export default router 82 | -------------------------------------------------------------------------------- /vue-client/src/routers/setting.js: -------------------------------------------------------------------------------- 1 | import SettingSiteInfo from '../views/setting/SettingSiteInfo' 2 | 3 | export default [ 4 | { 5 | path: 'setting/site-info', 6 | name: 'setting_site-info', 7 | component: SettingSiteInfo 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /vue-client/src/routers/statistics.js: -------------------------------------------------------------------------------- 1 | import StatisticsSummary from '../views/statistics/StatisticsSummary' 2 | 3 | export default [ 4 | { 5 | path: 'statistics/summary', 6 | name: 'statistics_summary', 7 | component: StatisticsSummary 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /vue-client/src/routers/tool.js: -------------------------------------------------------------------------------- 1 | import ToolUpdateCache from '../views/tool/ToolUpdateCache' 2 | 3 | export default [ 4 | { 5 | path: 'tool/update-cache', 6 | name: 'tool_update-cache', 7 | component: ToolUpdateCache 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /vue-client/src/routers/user.js: -------------------------------------------------------------------------------- 1 | import UserManagement from '../views/user/UserManagement' 2 | 3 | export default [ 4 | { 5 | path: 'user/management', 6 | name: 'user_management', 7 | component: UserManagement 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /vue-client/src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $border-color:#e8eaec; 2 | 3 | $primary-color: #2d8cf0; 4 | 5 | $title-color: #17233d; 6 | 7 | // 页面背景色 8 | $background-color: #f8f8f9; 9 | 10 | // 左侧导航长度 11 | $sider-width: 220px; -------------------------------------------------------------------------------- /vue-client/src/scss/app.scss: -------------------------------------------------------------------------------- 1 | @import './variables'; 2 | 3 | * { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html, 10 | body, 11 | #app, 12 | .na-view { 13 | height: 100%; 14 | width: 100%; 15 | overflow: hidden; 16 | background: $background-color; 17 | } 18 | 19 | .na-view-login { 20 | width: 100%; 21 | height: 100%; 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | 26 | .login-box { 27 | width: 360px; 28 | height: auto; 29 | overflow: hidden; 30 | padding: 30px; 31 | border: 1px solid $border-color; 32 | background: #fff; 33 | 34 | .na-logo { 35 | height: 60px; 36 | display: flex; 37 | align-items: center; 38 | justify-content: center; 39 | margin-bottom: 20px; 40 | 41 | img { 42 | height: 60px; 43 | } 44 | } 45 | 46 | .form-item-captcha { 47 | display: flex; 48 | align-items: center; 49 | 50 | .captcha-img { 51 | cursor: pointer; 52 | margin-left: 10px; 53 | width: 100px; 54 | height: 36px; 55 | overflow: hidden; 56 | } 57 | } 58 | } 59 | } 60 | 61 | .na-layout { 62 | background: #fff; 63 | height: 100%; 64 | width: 100%; 65 | overflow: hidden; 66 | 67 | .na-header { 68 | position: fixed; 69 | top: 0; 70 | left: 0; 71 | z-index: 10; 72 | height: 65px; 73 | width: 100%; 74 | border-bottom: 1px solid $border-color; 75 | background: #fff; 76 | display: flex; 77 | align-items: center; 78 | 79 | .na-logo { 80 | height: 64px; 81 | width: $sider-width + 40px; 82 | overflow: hidden; 83 | display: flex; 84 | align-items: center; 85 | padding-left: 20px; 86 | 87 | img { 88 | max-height: 60px; 89 | } 90 | } 91 | 92 | .na-nav { 93 | flex: 1; 94 | height: 100%; 95 | display: flex; 96 | align-items: center; 97 | 98 | .item { 99 | height: 40px; 100 | line-height: 40px; 101 | padding: 0 15px; 102 | margin: 0 2px; 103 | 104 | a { 105 | color: #515a6e; 106 | font-size: 16px; 107 | } 108 | 109 | &.active { 110 | a { 111 | color: $primary-color; 112 | } 113 | } 114 | } 115 | } 116 | 117 | .na-user { 118 | width: auto; 119 | height: 100%; 120 | display: flex; 121 | align-items: center; 122 | justify-content: flex-end; 123 | margin-right: 20px; 124 | 125 | .item { 126 | margin-left: 30px; 127 | } 128 | } 129 | } 130 | 131 | .na-main { 132 | position: fixed; 133 | left: 0; 134 | top: 0; 135 | z-index: 1; 136 | height: 100%; 137 | width: 100%; 138 | display: flex; 139 | background: $background-color; 140 | padding: 80px 20px 20px 20px; 141 | 142 | .na-sider { 143 | width: $sider-width; 144 | height: 100%; 145 | overflow: hidden; 146 | margin-right: 20px; 147 | background: #fff; 148 | 149 | .title { 150 | color: #c5c8ce; 151 | font-size: 16px; 152 | padding-top: 20px; 153 | padding-left: 20px; 154 | } 155 | 156 | .na-sidebar { 157 | width: 100%; 158 | padding-top: 20px; 159 | 160 | li a { 161 | display: block; 162 | height: 48px; 163 | width: 100%; 164 | display: flex; 165 | align-items: center; 166 | padding: 0 20px; 167 | color: #515a6e; 168 | 169 | &.active { 170 | color: $primary-color; 171 | } 172 | 173 | .text { 174 | margin-left: 10px; 175 | font-size: 14px; 176 | } 177 | } 178 | } 179 | } 180 | 181 | .na-content { 182 | flex: 1; 183 | height: 100%; 184 | overflow: hidden; 185 | 186 | .ivu-card { 187 | border-radius: 0; 188 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.05); 189 | 190 | .na-content-title { 191 | display: flex; 192 | padding: 5px 0; 193 | align-items: center; 194 | 195 | .title { 196 | font-size: 18px; 197 | } 198 | 199 | .right-box { 200 | flex: 1; 201 | height: 100%; 202 | overflow: hidden; 203 | } 204 | 205 | .nav-list { 206 | display: flex; 207 | align-items: center; 208 | justify-content: flex-end; 209 | 210 | .nav-item { 211 | margin-left: 15px; 212 | } 213 | } 214 | } 215 | } 216 | } 217 | } 218 | } 219 | 220 | .na-table-after { 221 | margin-top: 10px; 222 | display: flex; 223 | align-items: center; 224 | justify-content: space-between; 225 | 226 | .na-table-after-action { 227 | .item { 228 | margin-right: 10px; 229 | } 230 | } 231 | } 232 | 233 | .ad-position-field-box { 234 | display: flex; 235 | border: 1px solid #e8eaec; 236 | width: 500px; 237 | flex-direction: column; 238 | 239 | .item { 240 | display: flex; 241 | border-bottom: 1px solid #e8eaec; 242 | 243 | &:last-child { 244 | border-bottom: none; 245 | } 246 | } 247 | 248 | .item-cell { 249 | padding: 5px; 250 | display: flex; 251 | align-items: center; 252 | justify-content: center; 253 | 254 | &.item-cell-field { 255 | flex: 1; 256 | } 257 | 258 | &.item-cell-value { 259 | flex: 1; 260 | border-left: 1px solid #e8eaec; 261 | border-right: 1px solid #e8eaec; 262 | } 263 | 264 | &.item-cell-action { 265 | width: 60px; 266 | } 267 | 268 | } 269 | 270 | .item-add { 271 | padding: 5px; 272 | } 273 | } 274 | 275 | .form-item-tip { 276 | color: #c5c8ce; 277 | } -------------------------------------------------------------------------------- /vue-client/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | menus: [], 9 | currentMenu: {} 10 | }, 11 | mutations: { 12 | changeMenuActive (state, route) { 13 | if (!route || !route.name) { 14 | return false 15 | } 16 | const currentRouteName = route.name 17 | if (!currentRouteName.includes('_')) { 18 | return false 19 | } 20 | const [topNavName] = currentRouteName.split('_') 21 | for (const item of state.menus) { 22 | // 头部导航状态 23 | item.active = item.route.startsWith(topNavName) 24 | if (item.active) { 25 | state.currentMenu = item 26 | } 27 | for (const childItem of item.children) { 28 | // 左侧导航状态 29 | childItem.active = childItem.route.startsWith(currentRouteName) 30 | } 31 | } 32 | }, 33 | setMenus (state, menus) { 34 | state.menus = menus 35 | } 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /vue-client/src/views/Index.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 104 | -------------------------------------------------------------------------------- /vue-client/src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 124 | -------------------------------------------------------------------------------- /vue-client/src/views/content/ContentManagement.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /vue-client/src/views/home/HomeCommonFunction.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /vue-client/src/views/home/HomeSummary.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /vue-client/src/views/home/HomeTodo.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /vue-client/src/views/setting/SettingSiteInfo.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /vue-client/src/views/statistics/StatisticsSummary.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /vue-client/src/views/tool/ToolUpdateCache.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /vue-client/src/views/user/UserManagement.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | --------------------------------------------------------------------------------