├── frontend
├── static
│ └── .gitkeep
├── .eslintignore
├── favicon.ico
├── build
│ ├── logo.png
│ ├── vue-loader.conf.js
│ ├── build.js
│ ├── check-versions.js
│ ├── webpack.dev.conf.js
│ ├── webpack.base.conf.js
│ ├── utils.js
│ └── webpack.prod.conf.js
├── src
│ ├── styles
│ │ ├── variables.scss
│ │ ├── mixin.scss
│ │ ├── element-ui.scss
│ │ ├── transition.scss
│ │ ├── index.scss
│ │ └── sidebar.scss
│ ├── assets
│ │ ├── face.gif
│ │ └── 404_images
│ │ │ ├── 404.png
│ │ │ └── 404_cloud.png
│ ├── views
│ │ ├── nested
│ │ │ ├── menu2
│ │ │ │ └── index.vue
│ │ │ └── menu1
│ │ │ │ ├── menu1-3
│ │ │ │ └── index.vue
│ │ │ │ ├── index.vue
│ │ │ │ ├── menu1-2
│ │ │ │ ├── menu1-2-1
│ │ │ │ │ └── index.vue
│ │ │ │ ├── menu1-2-2
│ │ │ │ │ └── index.vue
│ │ │ │ └── index.vue
│ │ │ │ └── menu1-1
│ │ │ │ └── index.vue
│ │ ├── layout
│ │ │ ├── components
│ │ │ │ ├── index.js
│ │ │ │ ├── AppMain.vue
│ │ │ │ ├── Sidebar
│ │ │ │ │ ├── index.vue
│ │ │ │ │ └── SidebarItem.vue
│ │ │ │ └── Navbar.vue
│ │ │ ├── mixin
│ │ │ │ └── ResizeHandler.js
│ │ │ └── Layout.vue
│ │ ├── dashboard
│ │ │ └── index.vue
│ │ ├── table
│ │ │ └── index.vue
│ │ ├── tree
│ │ │ └── index.vue
│ │ ├── form
│ │ │ ├── createuser.vue
│ │ │ └── index.vue
│ │ ├── login
│ │ │ └── index.vue
│ │ └── 404.vue
│ ├── api
│ │ ├── table.js
│ │ ├── createuser.js
│ │ └── login.js
│ ├── App.vue
│ ├── directive
│ │ └── waves
│ │ │ ├── index.js
│ │ │ ├── waves.css
│ │ │ └── waves.js
│ ├── icons
│ │ ├── index.js
│ │ └── svg
│ │ │ ├── table.svg
│ │ │ ├── user.svg
│ │ │ ├── example.svg
│ │ │ ├── nested.svg
│ │ │ ├── password.svg
│ │ │ ├── form.svg
│ │ │ ├── eye.svg
│ │ │ └── tree.svg
│ ├── utils
│ │ ├── auth.js
│ │ ├── validate.js
│ │ ├── index.js
│ │ ├── scrollTo.js
│ │ ├── request.js
│ │ └── drag.js
│ ├── store
│ │ ├── index.js
│ │ ├── getters.js
│ │ └── modules
│ │ │ ├── app.js
│ │ │ └── user.js
│ ├── components
│ │ ├── SvgIcon
│ │ │ └── index.vue
│ │ ├── Breadcrumb
│ │ │ └── index.vue
│ │ ├── Hamburger
│ │ │ └── index.vue
│ │ └── Pagination
│ │ │ └── index.vue
│ ├── permission.js
│ ├── main.js
│ └── router
│ │ └── index.js
├── .travis.yml
├── config
│ ├── prod.env.js
│ ├── dev.env.js
│ └── index.js
├── .gitignore
├── .babelrc
├── .postcssrc.js
├── .editorconfig
├── index.html
├── LICENSE
├── package.json
└── .eslintrc.js
├── backend
├── app
│ ├── config
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── dev.py
│ │ └── prod.py
│ ├── api
│ │ ├── user
│ │ │ ├── __init__.py
│ │ │ ├── .DS_Store
│ │ │ └── user.py
│ │ └── __init__.py
│ ├── utils
│ │ ├── __init__.py
│ │ ├── token.py
│ │ └── log.py
│ ├── .DS_Store
│ ├── models
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── role.py
│ │ └── user.py
│ ├── marshalling
│ │ ├── __init__.py
│ │ ├── general.py
│ │ ├── role.py
│ │ └── user.py
│ └── __init__.py
├── .gitignore
├── .DS_Store
├── app.py
├── .idea
│ ├── vcs.xml
│ ├── modules.xml
│ ├── misc.xml
│ ├── restplus.iml
│ ├── inspectionProfiles
│ │ └── Project_Default.xml
│ └── workspace.xml
├── manage.py
├── requirements.txt
└── run.py
├── img
├── backend.jpg
├── backend2.jpg
├── frontend.jpg
└── frontend2.jpg
├── .idea
├── vcs.xml
├── misc.xml
├── modules.xml
├── flask-vue-template.iml
├── inspectionProfiles
│ └── Project_Default.xml
└── workspace.xml
└── README.md
/frontend/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/app/config/__init__.py:
--------------------------------------------------------------------------------
1 | from .settings import APP_ENV
--------------------------------------------------------------------------------
/backend/app/api/user/__init__.py:
--------------------------------------------------------------------------------
1 | from .user import UserView,RoleView
--------------------------------------------------------------------------------
/frontend/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 | src/assets
4 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | *__pycache__
2 | migrations/
3 | *.pyc
4 | venv/
5 | logs/
6 |
--------------------------------------------------------------------------------
/backend/app/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .token import token_required
2 | from .log import log
3 |
--------------------------------------------------------------------------------
/backend/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/backend/.DS_Store
--------------------------------------------------------------------------------
/img/backend.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/img/backend.jpg
--------------------------------------------------------------------------------
/img/backend2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/img/backend2.jpg
--------------------------------------------------------------------------------
/img/frontend.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/img/frontend.jpg
--------------------------------------------------------------------------------
/img/frontend2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/img/frontend2.jpg
--------------------------------------------------------------------------------
/backend/app/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/backend/app/.DS_Store
--------------------------------------------------------------------------------
/frontend/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/frontend/favicon.ico
--------------------------------------------------------------------------------
/frontend/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/frontend/build/logo.png
--------------------------------------------------------------------------------
/frontend/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | //sidebar
2 | $menuBg:#304156;
3 | $subMenuBg:#1f2d3d;
4 | $menuHover:#001528;
5 |
--------------------------------------------------------------------------------
/frontend/src/assets/face.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/frontend/src/assets/face.gif
--------------------------------------------------------------------------------
/backend/app/api/user/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/backend/app/api/user/.DS_Store
--------------------------------------------------------------------------------
/frontend/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: stable
3 | script: npm run test
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/frontend/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/frontend/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/frontend/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"',
4 | BASE_API: '"http://127.0.0.1:5000/v1"',
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fishforks/flask-vue-template/HEAD/frontend/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/backend/app/models/__init__.py:
--------------------------------------------------------------------------------
1 | from flask_sqlalchemy import SQLAlchemy
2 |
3 | db = SQLAlchemy()
4 |
5 | from .user import User
6 | from .role import Role
7 |
--------------------------------------------------------------------------------
/backend/app.py:
--------------------------------------------------------------------------------
1 | from app import create_app
2 |
3 | app = create_app()
4 |
5 | if __name__ == '__main__':
6 | app.run(host='0.0.0.0', port='5000', debug=True)
7 |
--------------------------------------------------------------------------------
/frontend/src/views/nested/menu2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/backend/app/config/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 | from .dev import DevelopmentConfig
4 | from .prod import ProductionConfig
5 |
6 |
7 | APP_ENV = DevelopmentConfig
8 |
--------------------------------------------------------------------------------
/frontend/src/api/table.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getList() {
4 | return request({
5 | url: '/users/',
6 | method: 'get'
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/src/views/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar'
2 | export { default as Sidebar } from './Sidebar'
3 | export { default as AppMain } from './AppMain'
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/frontend/src/views/nested/menu1/menu1-3/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/frontend/src/views/nested/menu1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/frontend/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/frontend/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/backend/app/marshalling/__init__.py:
--------------------------------------------------------------------------------
1 | from flask_marshmallow import Marshmallow
2 | ma = Marshmallow()
3 | from .general import gma
4 | from .role import role_schema,roles_schema
5 | from .user import user_schema,users_schema
6 |
--------------------------------------------------------------------------------
/frontend/src/views/nested/menu1/menu1-1/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/frontend/src/views/nested/menu1/menu1-2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/frontend/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 | BASE_API: '"http://127.0.0.1:5000/v1"',
8 | })
9 |
--------------------------------------------------------------------------------
/backend/app/marshalling/general.py:
--------------------------------------------------------------------------------
1 | from . import ma
2 | from flask_marshmallow import base_fields
3 | # 通用序列化{"code":xxx,"data":xxx}
4 | class GeneralSchema(ma.Schema):
5 | code = base_fields.Integer()
6 | data = ma.Dict()
7 |
8 | gma = GeneralSchema()
--------------------------------------------------------------------------------
/backend/app/models/base.py:
--------------------------------------------------------------------------------
1 | from . import db
2 | from datetime import datetime
3 |
4 | class Base(db.Model):
5 | __abstract__ = True
6 | # id = db.Column(db.Integer, primary_key=True)
7 | # create_time = db.Column(db.DateTime,default=datetime.now)
8 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json
8 |
9 | # Editor directories and files
10 | .idea
11 | .vscode
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 |
--------------------------------------------------------------------------------
/backend/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/frontend/.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 |
--------------------------------------------------------------------------------
/backend/app/marshalling/role.py:
--------------------------------------------------------------------------------
1 | from . import ma
2 | from app.models import Role
3 | # 序列化角色表
4 | class RoleSchema(ma.ModelSchema):
5 | class Meta:
6 | # fields = ('name',)
7 | model = Role
8 |
9 | role_schema = RoleSchema()
10 | roles_schema = RoleSchema(many=True)
11 |
12 |
13 |
--------------------------------------------------------------------------------
/backend/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/frontend/.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 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/frontend/src/api/createuser.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function createuser(username, password, role) {
4 | return request({
5 | url: '/user/createuser/',
6 | method: 'post',
7 | data: {
8 | username,
9 | password,
10 | role
11 | }
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/backend/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/frontend/src/directive/waves/index.js:
--------------------------------------------------------------------------------
1 | import waves from './waves'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('waves', waves)
5 | }
6 |
7 | if (window.Vue) {
8 | window.waves = waves
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | waves.install = install
13 | export default waves
14 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Template
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/frontend/src/icons/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon'// svg组件
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const requireAll = requireContext => requireContext.keys().map(requireContext)
8 | const req = require.context('./svg', false, /\.svg$/)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/frontend/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const TokenKey = 'Admin-Token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TokenKey)
7 | }
8 |
9 | export function setToken(token) {
10 | return Cookies.set(TokenKey, token)
11 | }
12 |
13 | export function removeToken() {
14 | return Cookies.remove(TokenKey)
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import app from './modules/app'
4 | import user from './modules/user'
5 | import getters from './getters'
6 |
7 | Vue.use(Vuex)
8 |
9 | const store = new Vuex.Store({
10 | modules: {
11 | app,
12 | user
13 | },
14 | getters
15 | })
16 |
17 | export default store
18 |
--------------------------------------------------------------------------------
/frontend/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: state => state.app.sidebar,
3 | device: state => state.app.device,
4 | token: state => state.user.token,
5 | avatar: state => state.user.avatar,
6 | name: state => state.user.name,
7 | roles: state => state.user.roles,
8 | userinfo: state => state.user.userinfo
9 | }
10 | export default getters
11 |
--------------------------------------------------------------------------------
/backend/app/api/__init__.py:
--------------------------------------------------------------------------------
1 | from flask_restplus import Api
2 |
3 | authorizations = {
4 | 'apikey': {
5 | 'type': 'apiKey',
6 | 'in': 'header',
7 | 'name': 'X-Token'
8 | }
9 | }
10 |
11 | api = Api(version='1.0', title='OPS API', description='OPS API', authorizations=authorizations)
12 | api.namespaces.pop(0)
13 | ns = api.namespace('v1', description='这是自定义名称空间')
14 | from .user import UserView
15 |
--------------------------------------------------------------------------------
/backend/app/models/role.py:
--------------------------------------------------------------------------------
1 | from . import db
2 | from .base import Base
3 | from datetime import datetime
4 |
5 | class Role(Base):
6 | __tablename__ = 'roles'
7 | id = db.Column(db.Integer, primary_key=True)
8 | name = db.Column(db.String(80), unique=True)
9 | users = db.relationship('User',backref='role',lazy='dynamic')
10 | create_time = db.Column(db.DateTime, default=datetime.now)
11 |
12 | def __repr__(self):
13 | return '' % self.name
--------------------------------------------------------------------------------
/.idea/flask-vue-template.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/frontend/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 | &::-webkit-scrollbar {
14 | width: 6px;
15 | }
16 | &::-webkit-scrollbar-thumb {
17 | background: #99a9bf;
18 | border-radius: 20px;
19 | }
20 | }
21 |
22 | @mixin relative {
23 | position: relative;
24 | width: 100%;
25 | height: 100%;
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/backend/app/marshalling/user.py:
--------------------------------------------------------------------------------
1 | from . import ma
2 | from app.models import User
3 | from flask_marshmallow import base_fields
4 |
5 | # 序列化用户信息
6 | class UserInfoSchema(ma.Schema):
7 | code = base_fields.Integer()
8 | data = ma.Dict()
9 | user_info_schema = UserInfoSchema()
10 |
11 | # 序列化用户表
12 | class UserSchema(ma.ModelSchema):
13 | class Meta:
14 | # fields = ('username','password')
15 | model = User
16 | role = ma.HyperlinkRelated('role')
17 | user_schema = UserSchema()
18 | users_schema = UserSchema(many=True)
--------------------------------------------------------------------------------
/frontend/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/api/login.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function login(username, password) {
4 | return request({
5 | url: '/user/login',
6 | method: 'post',
7 | data: {
8 | username,
9 | password
10 | }
11 | })
12 | }
13 |
14 | export function getInfo(token) {
15 | return request({
16 | url: '/user/info',
17 | method: 'get',
18 | params: { token }
19 | })
20 | }
21 |
22 | export function logout() {
23 | return request({
24 | url: '/user/logout',
25 | method: 'post'
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | //to reset element-ui default css
2 | .el-upload {
3 | input[type="file"] {
4 | display: none !important;
5 | }
6 | }
7 |
8 | .el-upload__input {
9 | display: none;
10 | }
11 |
12 | //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
13 | .el-dialog {
14 | transform: none;
15 | left: 0;
16 | position: relative;
17 | margin: 0 auto;
18 | }
19 |
20 | //element ui upload
21 | .upload-container {
22 | .el-upload {
23 | width: 100%;
24 | .el-upload-dragger {
25 | width: 100%;
26 | height: 200px;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | //globl transition css
2 |
3 | /*fade*/
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /*fade*/
15 | .breadcrumb-enter-active,
16 | .breadcrumb-leave-active {
17 | transition: all .5s;
18 | }
19 |
20 | .breadcrumb-enter,
21 | .breadcrumb-leave-active {
22 | opacity: 0;
23 | transform: translateX(20px);
24 | }
25 |
26 | .breadcrumb-move {
27 | transition: all .5s;
28 | }
29 |
30 | .breadcrumb-leave-active {
31 | position: absolute;
32 | }
33 |
--------------------------------------------------------------------------------
/backend/app/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from .models import db
3 | from .api import api
4 | from .marshalling import ma
5 | from flask_cors import CORS
6 | from app.utils import log
7 |
8 | cors = CORS()
9 |
10 | def register_plugin(app):
11 | api.init_app(app)
12 | db.init_app(app)
13 | ma.init_app(app)
14 | cors.init_app(app)
15 | log.init_app(app)
16 |
17 | with app.app_context():
18 | # db.drop_all()
19 | db.create_all()
20 |
21 |
22 | def create_app():
23 | app = Flask(__name__)
24 | app.config.from_object('app.config.APP_ENV')
25 | register_plugin(app)
26 | return app
--------------------------------------------------------------------------------
/backend/manage.py:
--------------------------------------------------------------------------------
1 | from app.models import db
2 | from app import create_app
3 | from app.models.base import Base
4 | from app.models.role import Role
5 | from app.models.user import User
6 | from flask_script import Manager, Shell
7 | from flask_migrate import Migrate, MigrateCommand
8 |
9 |
10 | '''
11 | python3 manage.py db init
12 | python3 manage.py db migrate -m "initial migration"
13 | python3 manage.py db upgrade
14 | '''
15 |
16 |
17 | app = create_app()
18 | manager = Manager(app)
19 | migrate = Migrate(app, db)
20 |
21 | manager.add_command('db', MigrateCommand)
22 |
23 | if __name__ == '__main__':
24 | manager.run()
25 |
--------------------------------------------------------------------------------
/frontend/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 |
--------------------------------------------------------------------------------
/frontend/src/views/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
20 |
21 |
29 |
--------------------------------------------------------------------------------
/frontend/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
name:{{name}}
4 |
roles:{{role}}
5 |
6 |
7 |
8 |
21 |
22 |
33 |
--------------------------------------------------------------------------------
/frontend/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | aniso8601==3.0.2
2 | apispec==0.39.0
3 | asn1crypto==0.24.0
4 | cffi==1.11.5
5 | click==6.7
6 | cryptography==2.3
7 | Flask==1.0.2
8 | Flask-Cors==3.0.6
9 | Flask-HTTPAuth==3.2.4
10 | flask-marshmallow==0.9.0
11 | flask-restplus==0.11.0
12 | flask-restplus-marshmallow==0.2.20
13 | Flask-SQLAlchemy==2.3.2
14 | idna==2.7
15 | itsdangerous==0.24
16 | Jinja2==2.10
17 | jsonschema==2.6.0
18 | MarkupSafe==1.0
19 | marshmallow==2.15.4
20 | marshmallow-sqlalchemy==0.14.0
21 | passlib==1.7.1
22 | pycparser==2.18
23 | PyMySQL==0.9.2
24 | pytz==2018.5
25 | PyYAML==3.13
26 | six==1.11.0
27 | SQLAlchemy==1.2.10
28 | webargs==4.0.0
29 | Werkzeug==0.14.1
30 | certifi==2018.8.24
31 | chardet==3.0.4
32 | idna==2.7
33 | multi-key-dict==2.0.3
34 | pbr==4.2.0
35 | python-gitlab==1.6.0
36 | python-jenkins==1.2.1
37 | requests==2.19.1
38 | six==1.11.0
39 | urllib3==1.23
--------------------------------------------------------------------------------
/frontend/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
33 |
34 |
43 |
--------------------------------------------------------------------------------
/backend/app/config/dev.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 |
4 | # Dev config file
5 | basedir = os.path.abspath(os.path.dirname(__file__))
6 |
7 | class DbConf:
8 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True
9 | # SQLALCHEMY_TRACH_MODIFICATIONS = False
10 | SQLALCHEMY_TRACK_MODIFICATIONS = True
11 | SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
12 | # 安全配置
13 | CSRF_ENABLED = True
14 | SECRET_KEY = 'jklklsadhfjkhwbii9/sdf\sdf'
15 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@127.0.0.1:3306/PMS?charset=utf8mb4'
16 | # SQLALCHEMY_ECHO = True
17 |
18 |
19 | class LogConf:
20 | LOGPATH = "logs"
21 | LOGNAME = time.strftime('%Y-%m-%d', time.localtime(time.time()))
22 | LOGFORMAT = "%(asctime)s - %(levelname)s - %(filename)s - %(lineno)s - %(message)s"
23 | LOGLEVEL = "INFO"
24 |
25 |
26 | class DevelopmentConfig(DbConf,LogConf):
27 | pass
--------------------------------------------------------------------------------
/backend/.idea/restplus.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/frontend/src/directive/waves/waves.css:
--------------------------------------------------------------------------------
1 | .waves-ripple {
2 | position: absolute;
3 | border-radius: 100%;
4 | background-color: rgba(0, 0, 0, 0.15);
5 | background-clip: padding-box;
6 | pointer-events: none;
7 | -webkit-user-select: none;
8 | -moz-user-select: none;
9 | -ms-user-select: none;
10 | user-select: none;
11 | -webkit-transform: scale(0);
12 | -ms-transform: scale(0);
13 | transform: scale(0);
14 | opacity: 1;
15 | }
16 |
17 | .waves-ripple.z-active {
18 | opacity: 0;
19 | -webkit-transform: scale(2);
20 | -ms-transform: scale(2);
21 | transform: scale(2);
22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out;
25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
26 | }
--------------------------------------------------------------------------------
/backend/app/config/prod.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 |
4 | # Prod config file
5 | basedir = os.path.abspath(os.path.dirname(__file__))
6 |
7 | class DbConf:
8 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True
9 | # SQLALCHEMY_TRACH_MODIFICATIONS = False
10 | SQLALCHEMY_TRACK_MODIFICATIONS = True
11 | SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
12 | # 安全配置
13 | CSRF_ENABLED = True
14 | SECRET_KEY = 'jklklsadhfjkhwbii9/sdf\sdf'
15 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://devops:devopsSunmi666@104.250.34.119:3306/athena_dev?charset=utf8'
16 | # SQLALCHEMY_ECHO = True
17 |
18 |
19 | class LogConf:
20 | LOGPATH = "logs"
21 | LOGNAME = time.strftime('%Y-%m-%d', time.localtime(time.time()))
22 | LOGFORMAT = "%(asctime)s - %(levelname)s - %(filename)s - %(lineno)s - %(message)s"
23 | LOGLEVEL = "INFO"
24 |
25 |
26 | class ProductionConfig(DbConf,LogConf):
27 | pass
--------------------------------------------------------------------------------
/backend/app/utils/token.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 | from flask import request
3 | from itsdangerous import SignatureExpired, BadSignature
4 | from app.models import User
5 | from flask import jsonify
6 |
7 | def token_required(f):
8 | '''
9 | 验证前端发来请求头中携带的token
10 | '''
11 | @wraps(f)
12 | def decorated(*args,**kwargs):
13 | token = None
14 | if 'X-Token' in request.headers:
15 | token = request.headers['X-Token']
16 | try:
17 | User.verify_auth_token(token)
18 | except SignatureExpired as e:
19 | return jsonify({"code":50014,"message": "token过期"}) # valid token, but expired
20 | except BadSignature as e:
21 | return jsonify({"code": 50008, "message": "无效token"}) # invalid token
22 | except Exception as e:
23 | return jsonify({"code": 50012, "message": "其他客户端登录"})
24 | return f(*args,**kwargs)
25 | return decorated
26 |
--------------------------------------------------------------------------------
/frontend/src/views/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
17 |
36 |
--------------------------------------------------------------------------------
/backend/app/utils/log.py:
--------------------------------------------------------------------------------
1 | import logging,os
2 | from app.config import APP_ENV
3 |
4 | class Log:
5 | '''
6 | 日志工具类,使用示例:
7 | from flask import current_app
8 | current_app.logger.info('yyyy')
9 | '''
10 | def __init__(self):
11 | # 创建日志目录
12 | log_path = APP_ENV.LOGPATH
13 | log_name = APP_ENV.LOGNAME + '.log'
14 | log_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) + os.sep + log_path
15 | if not os.path.exists(log_path):
16 | os.mkdir(log_path)
17 | # 日志文件路径
18 | log_file = log_path + os.sep + log_name
19 | # 日志处理器
20 | handler = logging.FileHandler(log_file, encoding='UTF-8')
21 | # 日志格式
22 | logging_format = logging.Formatter(APP_ENV.LOGFORMAT)
23 | handler.setFormatter(logging_format)
24 | self.handler = handler
25 |
26 | def init_app(self,app):
27 | # 通过log.init_app(app)加载到Flask实例中
28 | app.logger.setLevel(APP_ENV.LOGLEVEL)
29 | app.logger.addHandler(self.handler)
30 |
31 |
32 | log = Log()
33 |
--------------------------------------------------------------------------------
/frontend/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jiachenpan on 16/11/18.
3 | */
4 |
5 | export function isvalidUsername(str) {
6 | // const valid_map = ['admin', 'editor', 'sunmi', 'ryan', 'test']
7 | // return valid_map.indexOf(str.trim()) >= 0
8 | return true
9 | }
10 |
11 | /* 合法uri*/
12 | export function validateURL(textval) {
13 | const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
14 | return urlregex.test(textval)
15 | }
16 |
17 | /* 小写字母*/
18 | export function validateLowerCase(str) {
19 | const reg = /^[a-z]+$/
20 | return reg.test(str)
21 | }
22 |
23 | /* 大写字母*/
24 | export function validateUpperCase(str) {
25 | const reg = /^[A-Z]+$/
26 | return reg.test(str)
27 | }
28 |
29 | /* 大小写字母*/
30 | export function validatAlphabets(str) {
31 | const reg = /^[A-Za-z]+$/
32 | return reg.test(str)
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/frontend/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present PanJiaChen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/frontend/src/views/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | const { body } = document
4 | const WIDTH = 1024
5 | const RATIO = 3
6 |
7 | export default {
8 | watch: {
9 | $route(route) {
10 | if (this.device === 'mobile' && this.sidebar.opened) {
11 | store.dispatch('CloseSideBar', { withoutAnimation: false })
12 | }
13 | }
14 | },
15 | beforeMount() {
16 | window.addEventListener('resize', this.resizeHandler)
17 | },
18 | mounted() {
19 | const isMobile = this.isMobile()
20 | if (isMobile) {
21 | store.dispatch('ToggleDevice', 'mobile')
22 | store.dispatch('CloseSideBar', { withoutAnimation: true })
23 | }
24 | },
25 | methods: {
26 | isMobile() {
27 | const rect = body.getBoundingClientRect()
28 | return rect.width - RATIO < WIDTH
29 | },
30 | resizeHandler() {
31 | if (!document.hidden) {
32 | const isMobile = this.isMobile()
33 | store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
34 |
35 | if (isMobile) {
36 | store.dispatch('CloseSideBar', { withoutAnimation: true })
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/src/icons/svg/nested.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const app = {
4 | state: {
5 | sidebar: {
6 | opened: !+Cookies.get('sidebarStatus'),
7 | withoutAnimation: false
8 | },
9 | device: 'desktop'
10 | },
11 | mutations: {
12 | TOGGLE_SIDEBAR: state => {
13 | if (state.sidebar.opened) {
14 | Cookies.set('sidebarStatus', 1)
15 | } else {
16 | Cookies.set('sidebarStatus', 0)
17 | }
18 | state.sidebar.opened = !state.sidebar.opened
19 | state.sidebar.withoutAnimation = false
20 | },
21 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
22 | Cookies.set('sidebarStatus', 1)
23 | state.sidebar.opened = false
24 | state.sidebar.withoutAnimation = withoutAnimation
25 | },
26 | TOGGLE_DEVICE: (state, device) => {
27 | state.device = device
28 | }
29 | },
30 | actions: {
31 | ToggleSideBar: ({ commit }) => {
32 | commit('TOGGLE_SIDEBAR')
33 | },
34 | CloseSideBar({ commit }, { withoutAnimation }) {
35 | commit('CLOSE_SIDEBAR', withoutAnimation)
36 | },
37 | ToggleDevice({ commit }, device) {
38 | commit('TOGGLE_DEVICE', device)
39 | }
40 | }
41 | }
42 |
43 | export default app
44 |
--------------------------------------------------------------------------------
/frontend/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,
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 |
--------------------------------------------------------------------------------
/frontend/src/icons/svg/form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | import NProgress from 'nprogress' // Progress 进度条
4 | import 'nprogress/nprogress.css'// Progress 进度条样式
5 | import { Message } from 'element-ui'
6 | import { getToken } from '@/utils/auth' // 验权
7 |
8 | const whiteList = ['/login'] // 不重定向白名单
9 | router.beforeEach((to, from, next) => {
10 | NProgress.start()
11 | if (getToken()) {
12 | if (to.path === '/login') {
13 | next({ path: '/' })
14 | NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
15 | } else {
16 | if (store.getters.roles.length === 0) {
17 | store.dispatch('GetInfo').then(() => { // 拉取用户信息
18 | next()
19 | }).catch((err) => {
20 | store.dispatch('FedLogOut').then(() => {
21 | Message.error(err || 'Verification failed, please login again')
22 | next({ path: '/' })
23 | })
24 | })
25 | } else {
26 | next()
27 | }
28 | }
29 | } else {
30 | if (whiteList.indexOf(to.path) !== -1) {
31 | next()
32 | } else {
33 | next('/login')
34 | NProgress.done()
35 | }
36 | }
37 | })
38 |
39 | router.afterEach(() => {
40 | NProgress.done() // 结束Progress
41 | })
42 |
--------------------------------------------------------------------------------
/frontend/src/views/table/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{scope.row.id}}
7 |
8 |
9 |
10 |
11 |
12 | {{scope.row.username}}
13 |
14 |
15 |
16 |
17 | {{scope.row.password}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flask-vue-template
2 | 基于flask和vue的前后端整合框架,开箱即用
3 | 后端使用flask-restplus开发,自带swagger,基于flask_marshmallow序列化对象,orm使用flask-sqlalchemy,已经集成基于token的用户认证,日志功能
4 |
5 | # 后端backend
6 |
7 | 使用方法:
8 |
9 | 修改app/config/settings.py指定开发环境配置文件
10 | ```
11 | APP_ENV = DevelopmentConfig
12 | ```
13 |
14 | 根据自己情况修改app/config/dev.py配置数据库信息,数据库提前创建
15 | ```
16 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@127.0.0.1:3306/PMS?charset=utf8mb4'
17 | ```
18 |
19 | 初始化数据库
20 | ```
21 | python3 manage.py db init
22 | python3 manage.py db migrate -m "initial migration"
23 | python3 manage.py db upgrade
24 | ```
25 |
26 | 默认监听地址:host='0.0.0.0', port='5000' ,可以在app.py中修改
27 |
28 | 
29 |
30 | 在swagger页面创建用户admin/admin
31 | 
32 |
33 | # 前端frontend
34 |
35 | 使用方法:
36 |
37 | 修改开发环境配置文件config/dev.env.js ,根据自己情况配置后端API地址,默认本机5000端口
38 | ```
39 | BASE_API: '"http://127.0.0.1:5000/v1"'
40 | ```
41 |
42 | 启动
43 | ```
44 | npm i
45 | npm run dev
46 | ```
47 |
48 | 默认 http://0.0.0.0:9005 ,在config/index.js中修改
49 | ```
50 | host: '0.0.0.0',
51 | port: 9005,
52 | ```
53 |
54 | 
55 |
56 | 登录后
57 |
58 | 
59 |
--------------------------------------------------------------------------------
/frontend/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 | @import './transition.scss';
4 | @import './element-ui.scss';
5 | @import './sidebar.scss';
6 |
7 | body {
8 | height: 100%;
9 | -moz-osx-font-smoothing: grayscale;
10 | -webkit-font-smoothing: antialiased;
11 | text-rendering: optimizeLegibility;
12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13 | }
14 |
15 | label {
16 | font-weight: 700;
17 | }
18 |
19 | html {
20 | height: 100%;
21 | box-sizing: border-box;
22 | }
23 |
24 | #app{
25 | height: 100%;
26 | }
27 |
28 | *,
29 | *:before,
30 | *:after {
31 | box-sizing: inherit;
32 | }
33 |
34 | a,
35 | a:focus,
36 | a:hover {
37 | cursor: pointer;
38 | color: inherit;
39 | outline: none;
40 | text-decoration: none;
41 | }
42 |
43 | div:focus{
44 | outline: none;
45 | }
46 |
47 | a:focus,
48 | a:active {
49 | outline: none;
50 | }
51 |
52 | a,
53 | a:focus,
54 | a:hover {
55 | cursor: pointer;
56 | color: inherit;
57 | text-decoration: none;
58 | }
59 |
60 | .clearfix {
61 | &:after {
62 | visibility: hidden;
63 | display: block;
64 | font-size: 0;
65 | content: " ";
66 | clear: both;
67 | height: 0;
68 | }
69 | }
70 |
71 | //main-container全局样式
72 | .app-main{
73 | min-height: 100%
74 | }
75 |
76 | .app-container {
77 | padding: 20px;
78 | }
79 |
--------------------------------------------------------------------------------
/frontend/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 |
--------------------------------------------------------------------------------
/frontend/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{item.meta.title}}
6 | {{item.meta.title}}
7 |
8 |
9 |
10 |
11 |
12 |
39 |
40 |
52 |
--------------------------------------------------------------------------------
/frontend/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jiachenpan on 16/11/18.
3 | */
4 |
5 | export function parseTime(time, cFormat) {
6 | if (arguments.length === 0) {
7 | return null
8 | }
9 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
10 | let date
11 | if (typeof time === 'object') {
12 | date = time
13 | } else {
14 | if (('' + time).length === 10) time = parseInt(time) * 1000
15 | date = new Date(time)
16 | }
17 | const formatObj = {
18 | y: date.getFullYear(),
19 | m: date.getMonth() + 1,
20 | d: date.getDate(),
21 | h: date.getHours(),
22 | i: date.getMinutes(),
23 | s: date.getSeconds(),
24 | a: date.getDay()
25 | }
26 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
27 | let value = formatObj[key]
28 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
29 | if (result.length > 0 && value < 10) {
30 | value = '0' + value
31 | }
32 | return value || 0
33 | })
34 | return time_str
35 | }
36 |
37 | export function formatTime(time, option) {
38 | time = +time * 1000
39 | const d = new Date(time)
40 | const now = Date.now()
41 |
42 | const diff = (now - d) / 1000
43 |
44 | if (diff < 30) {
45 | return '刚刚'
46 | } else if (diff < 3600) { // less 1 hour
47 | return Math.ceil(diff / 60) + '分钟前'
48 | } else if (diff < 3600 * 24) {
49 | return Math.ceil(diff / 3600) + '小时前'
50 | } else if (diff < 3600 * 24 * 2) {
51 | return '1天前'
52 | }
53 | if (option) {
54 | return parseTime(time, option)
55 | } else {
56 | return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import 'normalize.css/normalize.css'// A modern alternative to CSS resets
4 |
5 | import ElementUI from 'element-ui'
6 | import 'element-ui/lib/theme-chalk/index.css'
7 | import locale from 'element-ui/lib/locale/lang/en' // lang i18n
8 |
9 | import '@/styles/index.scss' // global css
10 |
11 | import App from './App'
12 | import router from './router'
13 | import store from './store'
14 |
15 | import '@/icons' // icon
16 | import '@/permission' // permission control
17 |
18 | Vue.use(ElementUI, { locale })
19 | Vue.config.productionTip = false
20 |
21 | Vue.directive('move', {
22 | inserted: function(a) {
23 | // 鼠标按下事件
24 | a.onmousedown = function(e) {
25 | var disX = e.clientX - a.offsetLeft
26 | var disY = e.clientY - a.offsetTop
27 |
28 | if (a.setCapture) {
29 | a.setCapture()
30 | }
31 | // 鼠标移动事件-----给文档流绑定移动事件
32 | document.onmousemove = function(e) {
33 | e.preventDefault()
34 | var L = e.clientX - disX
35 | var T = e.clientY - disY
36 |
37 | L = Math.min(Math.max(L, 0), document.documentElement.clientWidth - a.offsetWidth)
38 | T = Math.min(Math.max(T, 0), document.documentElement.clientHeight - a.offsetHeight)
39 |
40 | a.style.left = L + 'px'
41 | a.style.top = T + 'px'
42 | }
43 | // 鼠标离开事件
44 | document.onmouseup = function() {
45 | document.onmousemove = document.onmousedown = null
46 | if (a.releaseCapture) {
47 | a.releaseCapture()// 拖动后在解除事件锁定
48 | }
49 | }
50 | }
51 | }
52 | })
53 |
54 | new Vue({
55 | el: '#app',
56 | router,
57 | store,
58 | render: h => h(App)
59 | })
60 |
--------------------------------------------------------------------------------
/frontend/src/views/tree/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
71 |
72 |
--------------------------------------------------------------------------------
/frontend/src/views/layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
47 |
48 |
70 |
--------------------------------------------------------------------------------
/frontend/src/directive/waves/waves.js:
--------------------------------------------------------------------------------
1 | import './waves.css'
2 |
3 | export default{
4 | bind(el, binding) {
5 | el.addEventListener('click', e => {
6 | const customOpts = Object.assign({}, binding.value)
7 | const opts = Object.assign({
8 | ele: el, // 波纹作用元素
9 | type: 'hit', // hit 点击位置扩散 center中心点扩展
10 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
11 | }, customOpts)
12 | const target = opts.ele
13 | if (target) {
14 | target.style.position = 'relative'
15 | target.style.overflow = 'hidden'
16 | const rect = target.getBoundingClientRect()
17 | let ripple = target.querySelector('.waves-ripple')
18 | if (!ripple) {
19 | ripple = document.createElement('span')
20 | ripple.className = 'waves-ripple'
21 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
22 | target.appendChild(ripple)
23 | } else {
24 | ripple.className = 'waves-ripple'
25 | }
26 | switch (opts.type) {
27 | case 'center':
28 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
29 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
30 | break
31 | default:
32 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || document.body.scrollTop) + 'px'
33 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || document.body.scrollLeft) + 'px'
34 | }
35 | ripple.style.backgroundColor = opts.color
36 | ripple.className = 'waves-ripple z-active'
37 | return false
38 | }
39 | }, false)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/utils/scrollTo.js:
--------------------------------------------------------------------------------
1 | Math.easeInOutQuad = function(t, b, c, d) {
2 | t /= d / 2
3 | if (t < 1) {
4 | return c / 2 * t * t + b
5 | }
6 | t--
7 | return -c / 2 * (t * (t - 2) - 1) + b
8 | }
9 |
10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
11 | var requestAnimFrame = (function() {
12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
13 | })()
14 |
15 | // because it's so fucking difficult to detect the scrolling element, just move them all
16 | function move(amount) {
17 | document.documentElement.scrollTop = amount
18 | document.body.parentNode.scrollTop = amount
19 | document.body.scrollTop = amount
20 | }
21 |
22 | function position() {
23 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
24 | }
25 |
26 | export function scrollTo(to, duration, callback) {
27 | const start = position()
28 | const change = to - start
29 | const increment = 20
30 | let currentTime = 0
31 | duration = (typeof (duration) === 'undefined') ? 500 : duration
32 | var animateScroll = function() {
33 | // increment the time
34 | currentTime += increment
35 | // find the value with the quadratic in-out easing function
36 | var val = Math.easeInOutQuad(currentTime, start, change, duration)
37 | // move the document.body
38 | move(val)
39 | // do the animation unless its over
40 | if (currentTime < duration) {
41 | requestAnimFrame(animateScroll)
42 | } else {
43 | if (callback && typeof (callback) === 'function') {
44 | // the animation is done so lets callback
45 | callback()
46 | }
47 | }
48 | }
49 | animateScroll()
50 | }
51 |
--------------------------------------------------------------------------------
/frontend/src/views/form/createuser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
9 | 用户名
10 |
11 |
12 |
13 |
14 |
17 | 密码
18 |
19 |
20 |
21 |
22 |
24 | 角色
25 |
26 |
27 |
28 |
29 | 立即创建
31 | 取消
32 |
33 |
34 |
35 |
36 |
37 |
68 |
69 |
71 |
--------------------------------------------------------------------------------
/frontend/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
30 |
31 |
45 |
--------------------------------------------------------------------------------
/backend/app/models/user.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 |
3 | from . import db
4 | from .base import Base
5 | from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpired, BadSignature
6 | from passlib.apps import custom_app_context
7 | from flask import current_app
8 | from datetime import datetime
9 |
10 |
11 | class User(Base):
12 | __tablename__ = 'users'
13 | id = db.Column(db.Integer, primary_key=True)
14 | username = db.Column(db.String(80), unique=True)
15 | password = db.Column('password',db.String(200))
16 | role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
17 | create_time = db.Column(db.DateTime, default=datetime.now)
18 |
19 | def __init__(self,username,password=None,role=None):
20 | self.role = role
21 | self.username = username
22 | if password == None:
23 | password = "123456"
24 | self.hash_password(password)
25 | # 密码加密
26 | def hash_password(self, password):
27 | self.password = custom_app_context.encrypt(password)
28 |
29 | # 密码解析
30 | def verify_password(self, password):
31 | return custom_app_context.verify(password, self.password)
32 |
33 | # 获取token,有效时间10min
34 | def generate_auth_token(self, expiration=600000):
35 | s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration)
36 | return s.dumps({'id': self.id})
37 |
38 | # 解析token,确认登录的用户身份
39 | @staticmethod
40 | def verify_auth_token(token):
41 | s = Serializer(current_app.config['SECRET_KEY'])
42 | try:
43 | data = s.loads(token)
44 | except SignatureExpired as e:
45 | raise e # valid token, but expired
46 | except BadSignature as e:
47 | raise e # invalid token
48 | except Exception as e:
49 | raise e
50 | user = User.query.get(data['id'])
51 | return user
52 |
53 | def __repr__(self):
54 | return '' % self.username
--------------------------------------------------------------------------------
/frontend/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { Message, MessageBox } from 'element-ui'
3 | import store from '../store'
4 | import { getToken } from '@/utils/auth'
5 |
6 | // 创建axios实例
7 | const service = axios.create({
8 | baseURL: process.env.BASE_API, // api 的 base_url
9 | timeout: 5000 // 请求超时时间
10 | })
11 |
12 | // request拦截器
13 | service.interceptors.request.use(
14 | config => {
15 | if (store.getters.token) {
16 | config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
17 | }
18 | return config
19 | },
20 | error => {
21 | // Do something with request error
22 | console.log(error) // for debug
23 | Promise.reject(error)
24 | }
25 | )
26 |
27 | // response 拦截器
28 | service.interceptors.response.use(
29 | response => {
30 | /**
31 | * code为非20000是抛错 可结合自己业务进行修改
32 | */
33 | const res = response.data
34 | if (res.code !== 20000) {
35 | Message({
36 | message: res.message,
37 | type: 'error',
38 | duration: 5 * 1000
39 | })
40 |
41 | // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
42 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
43 | MessageBox.confirm(
44 | '你已被登出,可以取消继续留在该页面,或者重新登录',
45 | '确定登出',
46 | {
47 | confirmButtonText: '重新登录',
48 | cancelButtonText: '取消',
49 | type: 'warning'
50 | }
51 | ).then(() => {
52 | store.dispatch('FedLogOut').then(() => {
53 | location.reload() // 为了重新实例化vue-router对象 避免bug
54 | })
55 | }).catch(e => { console.log(e) })
56 | }
57 | // return Promise.reject('error')
58 | } else {
59 | return response.data
60 | }
61 | },
62 | error => {
63 | console.log('err' + error) // for debug
64 | Message({
65 | message: error.message,
66 | type: 'error',
67 | duration: 5 * 1000
68 | })
69 | return Promise.reject(error)
70 | }
71 | )
72 |
73 | export default service
74 |
--------------------------------------------------------------------------------
/frontend/src/utils/drag.js:
--------------------------------------------------------------------------------
1 | function inserted(el, binding, vNode) {
2 | el.setAttribute('style', 'position: fixed; z-index: 9999')
3 | }
4 |
5 | function bind(el, bindding, vNode) {
6 | el.setAttribute('draggable', true)
7 | let left, top, width, height
8 | el._dragstart = function(event) {
9 | event.stopPropagation()
10 | left = event.clientX - el.offsetLeft
11 | top = event.clientY - el.offsetTop
12 | width = el.offsetWidth
13 | height = el.offsetHeight
14 | }
15 | el._checkPosition = function() { // 防止被拖出边界
16 | const width = el.offsetWidth
17 | const height = el.offsetHeight
18 | let left = Math.min(el.offsetLeft, document.body.clientWidth - width)
19 | left = Math.max(0, left)
20 | let top = Math.min(el.offsetTop, document.body.clientHeight - height)
21 | top = Math.max(0, top)
22 | el.style.left = left + 'px'
23 | el.style.top = top + 'px'
24 | el.style.width = width + 'px'
25 | el.style.height = height + 'px'
26 | }
27 | el._dragEnd = function(event) {
28 | event.stopPropagation()
29 | left = event.clientX - left
30 | top = event.clientY - top
31 | el.style.left = left + 'px'
32 | el.style.top = top + 'px'
33 | el.style.width = width + 'px'
34 | el.style.height = height + 'px'
35 | el._checkPosition()
36 | }
37 | el._documentAllowDraop = function(event) {
38 | event.preventDefault()
39 | }
40 | document.body.addEventListener('dragover', el._documentAllowDraop)
41 | el.addEventListener('dragstart', el._dragstart)
42 | el.addEventListener('dragend', el._dragEnd)
43 | window.addEventListener('resize', el._checkPosition)
44 | }
45 |
46 | function unbind(el, bindding, vNode) {
47 | document.body.removeEventListener('dragover', el._documentAllowDraop)
48 | el.removeEventListener('dragstart', el._dragstart)
49 | el.removeEventListener('dragend', el._dragEnd)
50 | window.removeEventListener('resize', el._checkPosition)
51 | delete el._documentAllowDraop
52 | delete el._dragstart
53 | delete el._dragEnd
54 | delete el._checkPosition
55 | }
56 |
57 | export default {
58 | bind,
59 | unbind,
60 | inserted
61 | }
62 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/backend/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/components/Pagination/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
89 |
90 |
--------------------------------------------------------------------------------
/frontend/src/icons/svg/tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/views/layout/components/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |

9 |
10 |
11 |
12 |
13 |
14 | Home
15 |
16 |
17 |
18 | LogOut
19 |
20 |
21 |
22 |
23 |
24 |
25 |
53 |
54 |
95 |
96 |
--------------------------------------------------------------------------------
/backend/run.py:
--------------------------------------------------------------------------------
1 | # 示例
2 | from flask import Flask
3 | from flask_restplus import Api, Resource, fields
4 | from werkzeug.contrib.fixers import ProxyFix
5 |
6 | app = Flask(__name__)
7 | app.wsgi_app = ProxyFix(app.wsgi_app)
8 |
9 | api = Api(app, version='1.0', title='TodoMVC API',
10 | description='A simple TodoMVC API',
11 | )
12 |
13 | # 定义命名空间
14 | ns = api.namespace('v1', description='TODO operations')
15 |
16 | todo = api.model('Todo', {
17 | 'id': fields.Integer(readOnly=True, description='The task unique identifier'),
18 | 'task': fields.String(required=True, description='The task details')
19 | })
20 |
21 |
22 | class TodoDAO(object):
23 | def __init__(self):
24 | self.counter = 0
25 | self.todos = []
26 |
27 | def get(self, id):
28 | for todo in self.todos:
29 | if todo['id'] == id:
30 | return todo
31 | api.abort(404, "Todo {} doesn't exist".format(id))
32 |
33 | def create(self, data):
34 | todo = data
35 | todo['id'] = self.counter = self.counter + 1
36 | self.todos.append(todo)
37 | return todo
38 |
39 | def update(self, id, data):
40 | todo = self.get(id)
41 | todo.update(data)
42 | return todo
43 |
44 | def delete(self, id):
45 | todo = self.get(id)
46 | self.todos.remove(todo)
47 |
48 |
49 | DAO = TodoDAO()
50 | DAO.create({'task': 'Build an API'})
51 | DAO.create({'task': '?????'})
52 | DAO.create({'task': 'profit!'})
53 |
54 |
55 | @ns.route('/todo')
56 | class TodoList(Resource):
57 | '''获取所有todos元素,并允许通过POST来添加新的task'''
58 | @ns.doc('list_todos')
59 | @ns.marshal_list_with(todo)
60 | def get(self):
61 | '''返回所有task'''
62 | return DAO.todos
63 |
64 | @ns.doc('create_todo')
65 | @ns.expect(todo)
66 | @ns.marshal_with(todo, code=201)
67 | def post(self):
68 | '''创建一个新的task'''
69 | return DAO.create(api.payload), 201
70 |
71 |
72 | @ns.route('/todo/')
73 | @ns.response(404, 'Todo not found')
74 | @ns.param('id', 'The task identifier')
75 | class Todo(Resource):
76 | '''获取单个todo项,并允许删除操作'''
77 | @ns.doc('get_todo')
78 | @ns.marshal_with(todo)
79 | def get(self, id):
80 | '''获取id指定的todo项'''
81 | return DAO.get(id)
82 |
83 | @ns.doc('delete_todo')
84 | @ns.response(204, 'Todo deleted')
85 | def delete(self, id):
86 | '''根据id删除对应的task'''
87 | DAO.delete(id)
88 | return '', 204
89 |
90 | @ns.expect(todo)
91 | @ns.marshal_with(todo)
92 | def put(self, id):
93 | '''更新id指定的task'''
94 | return DAO.update(id, api.payload)
95 |
96 |
97 | if __name__ == '__main__':
98 | app.run(debug=True)
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-admin-template",
3 | "version": "3.6.0",
4 | "license": "MIT",
5 | "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
6 | "author": "Pan ",
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "build": "node build/build.js",
11 | "build:report": "npm_config_report=true node build/build.js",
12 | "lint": "eslint --ext .js,.vue src",
13 | "test": "npm run lint"
14 | },
15 | "dependencies": {
16 | "axios": "0.17.1",
17 | "element-ui": "2.3.4",
18 | "js-cookie": "2.2.0",
19 | "normalize.css": "7.0.0",
20 | "nprogress": "0.2.0",
21 | "stylus-loader": "^3.0.2",
22 | "vue": "2.5.10",
23 | "vue-router": "3.0.1",
24 | "vuex": "3.0.1"
25 | },
26 | "devDependencies": {
27 | "autoprefixer": "7.2.3",
28 | "babel-core": "6.26.0",
29 | "babel-eslint": "8.0.3",
30 | "babel-helper-vue-jsx-merge-props": "2.0.3",
31 | "babel-loader": "7.1.2",
32 | "babel-plugin-syntax-jsx": "6.18.0",
33 | "babel-plugin-transform-runtime": "6.23.0",
34 | "babel-plugin-transform-vue-jsx": "3.5.0",
35 | "babel-preset-env": "1.6.1",
36 | "babel-preset-stage-2": "6.24.1",
37 | "chalk": "2.3.0",
38 | "copy-webpack-plugin": "4.2.3",
39 | "css-loader": "0.28.7",
40 | "eslint": "4.13.1",
41 | "eslint-friendly-formatter": "3.0.0",
42 | "eslint-loader": "1.9.0",
43 | "eslint-plugin-html": "4.0.1",
44 | "eventsource-polyfill": "0.9.6",
45 | "extract-text-webpack-plugin": "3.0.2",
46 | "file-loader": "1.1.5",
47 | "friendly-errors-webpack-plugin": "1.6.1",
48 | "html-webpack-plugin": "2.30.1",
49 | "node-notifier": "5.1.2",
50 | "node-sass": "^4.7.2",
51 | "optimize-css-assets-webpack-plugin": "3.2.0",
52 | "ora": "1.3.0",
53 | "portfinder": "1.0.13",
54 | "postcss-import": "11.0.0",
55 | "postcss-loader": "2.0.9",
56 | "postcss-url": "7.3.0",
57 | "rimraf": "2.6.2",
58 | "sass-loader": "6.0.6",
59 | "semver": "5.4.1",
60 | "shelljs": "0.7.8",
61 | "stylus": "^0.54.5",
62 | "svg-sprite-loader": "3.5.2",
63 | "uglifyjs-webpack-plugin": "1.1.3",
64 | "url-loader": "0.6.2",
65 | "vue-loader": "13.7.2",
66 | "vue-style-loader": "3.0.3",
67 | "vue-template-compiler": "2.5.10",
68 | "webpack": "3.10.0",
69 | "webpack-bundle-analyzer": "2.9.1",
70 | "webpack-dev-server": "2.9.7",
71 | "webpack-merge": "4.1.1"
72 | },
73 | "engines": {
74 | "node": ">= 4.0.0",
75 | "npm": ">= 3.0.0"
76 | },
77 | "browserslist": [
78 | "> 1%",
79 | "last 2 versions",
80 | "not ie <= 8"
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/frontend/src/views/layout/components/Sidebar/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
32 |
79 |
--------------------------------------------------------------------------------
/frontend/src/views/form/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Create
44 | Cancel
45 |
46 |
47 |
48 |
49 |
50 |
79 |
80 |
85 |
86 |
--------------------------------------------------------------------------------
/frontend/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const HtmlWebpackPlugin = require('html-webpack-plugin')
9 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
10 | const portfinder = require('portfinder')
11 |
12 | function resolve (dir) {
13 | return path.join(__dirname, '..', dir)
14 | }
15 |
16 | const HOST = process.env.HOST
17 | const PORT = process.env.PORT && Number(process.env.PORT)
18 |
19 | const devWebpackConfig = merge(baseWebpackConfig, {
20 | module: {
21 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
22 | },
23 | // cheap-module-eval-source-map is faster for development
24 | devtool: config.dev.devtool,
25 |
26 | // these devServer options should be customized in /config/index.js
27 | devServer: {
28 | clientLogLevel: 'warning',
29 | historyApiFallback: true,
30 | hot: true,
31 | compress: true,
32 | host: HOST || config.dev.host,
33 | port: PORT || config.dev.port,
34 | open: config.dev.autoOpenBrowser,
35 | overlay: config.dev.errorOverlay
36 | ? { warnings: false, errors: true }
37 | : false,
38 | publicPath: config.dev.assetsPublicPath,
39 | proxy: config.dev.proxyTable,
40 | quiet: true, // necessary for FriendlyErrorsPlugin
41 | watchOptions: {
42 | poll: config.dev.poll,
43 | }
44 | },
45 | plugins: [
46 | new webpack.DefinePlugin({
47 | 'process.env': require('../config/dev.env')
48 | }),
49 | new webpack.HotModuleReplacementPlugin(),
50 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
51 | new webpack.NoEmitOnErrorsPlugin(),
52 | // https://github.com/ampedandwired/html-webpack-plugin
53 | new HtmlWebpackPlugin({
54 | filename: 'index.html',
55 | template: 'index.html',
56 | inject: true,
57 | favicon: resolve('favicon.ico'),
58 | title: 'vue-element-admin'
59 | }),
60 | ]
61 | })
62 |
63 | module.exports = new Promise((resolve, reject) => {
64 | portfinder.basePort = process.env.PORT || config.dev.port
65 | portfinder.getPort((err, port) => {
66 | if (err) {
67 | reject(err)
68 | } else {
69 | // publish the new Port, necessary for e2e tests
70 | process.env.PORT = port
71 | // add port to devServer config
72 | devWebpackConfig.devServer.port = port
73 |
74 | // Add FriendlyErrorsPlugin
75 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
76 | compilationSuccessInfo: {
77 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
78 | },
79 | onErrors: config.dev.notifyOnErrors
80 | ? utils.createNotifierCallback()
81 | : undefined
82 | }))
83 |
84 | resolve(devWebpackConfig)
85 | }
86 | })
87 | })
88 |
--------------------------------------------------------------------------------
/frontend/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 | const createLintingRule = () => ({
12 | test: /\.(js|vue)$/,
13 | loader: 'eslint-loader',
14 | enforce: 'pre',
15 | include: [resolve('src'), resolve('test')],
16 | options: {
17 | formatter: require('eslint-friendly-formatter'),
18 | emitWarning: !config.dev.showEslintErrorsInOverlay
19 | }
20 | })
21 |
22 | module.exports = {
23 | context: path.resolve(__dirname, '../'),
24 | entry: {
25 | app: './src/main.js'
26 | },
27 | output: {
28 | path: config.build.assetsRoot,
29 | filename: '[name].js',
30 | publicPath: process.env.NODE_ENV === 'production'
31 | ? config.build.assetsPublicPath
32 | : config.dev.assetsPublicPath
33 | },
34 | resolve: {
35 | extensions: ['.js', '.vue', '.json'],
36 | alias: {
37 | '@': resolve('src')
38 | }
39 | },
40 | module: {
41 | rules: [
42 | ...(config.dev.useEslint ? [createLintingRule()] : []),
43 | {
44 | test: /\.vue$/,
45 | loader: 'vue-loader',
46 | options: vueLoaderConfig
47 | },
48 | {
49 | test: /\.js$/,
50 | loader: 'babel-loader',
51 | include: [resolve('src'), resolve('test') ,resolve('node_modules/webpack-dev-server/client')]
52 | },
53 | {
54 | test: /\.svg$/,
55 | loader: 'svg-sprite-loader',
56 | include: [resolve('src/icons')],
57 | options: {
58 | symbolId: 'icon-[name]'
59 | }
60 | },
61 | {
62 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
63 | loader: 'url-loader',
64 | exclude: [resolve('src/icons')],
65 | options: {
66 | limit: 10000,
67 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
68 | }
69 | },
70 | {
71 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
72 | loader: 'url-loader',
73 | options: {
74 | limit: 10000,
75 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
76 | }
77 | },
78 | {
79 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
80 | loader: 'url-loader',
81 | options: {
82 | limit: 10000,
83 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
84 | }
85 | }
86 | ]
87 | },
88 | node: {
89 | // prevent webpack from injecting useless setImmediate polyfill because Vue
90 | // source contains it (although only uses it if it's native).
91 | setImmediate: false,
92 | // prevent webpack from injecting mocks to Node native modules
93 | // that does not make sense for the client
94 | dgram: 'empty',
95 | fs: 'empty',
96 | net: 'empty',
97 | tls: 'empty',
98 | child_process: 'empty'
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/frontend/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/frontend/src/styles/sidebar.scss:
--------------------------------------------------------------------------------
1 | #app {
2 | // 主体区域
3 | .main-container {
4 | min-height: 100%;
5 | transition: margin-left .28s;
6 | margin-left: 180px;
7 | position: relative;
8 | }
9 | // 侧边栏
10 | .sidebar-container {
11 | transition: width 0.28s;
12 | width: 180px !important;
13 | height: 100%;
14 | position: fixed;
15 | font-size: 0px;
16 | top: 0;
17 | bottom: 0;
18 | left: 0;
19 | z-index: 1001;
20 | overflow: hidden;
21 | //reset element-ui css
22 | .horizontal-collapse-transition {
23 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
24 | }
25 | .scrollbar-wrapper {
26 | height: calc(100% + 15px);
27 | .el-scrollbar__view {
28 | height: 100%;
29 | }
30 | }
31 | .is-horizontal {
32 | display: none;
33 | }
34 | a {
35 | display: inline-block;
36 | width: 100%;
37 | overflow: hidden;
38 | }
39 | .svg-icon {
40 | margin-right: 16px;
41 | }
42 | .el-menu {
43 | border: none;
44 | height: 100%;
45 | width: 100% !important;
46 | }
47 | }
48 | .hideSidebar {
49 | .sidebar-container {
50 | width: 36px !important;
51 | }
52 | .main-container {
53 | margin-left: 36px;
54 | }
55 | .submenu-title-noDropdown {
56 | padding-left: 10px !important;
57 | position: relative;
58 | .el-tooltip {
59 | padding: 0 10px !important;
60 | }
61 | }
62 | .el-submenu {
63 | overflow: hidden;
64 | &>.el-submenu__title {
65 | padding-left: 10px !important;
66 | .el-submenu__icon-arrow {
67 | display: none;
68 | }
69 | }
70 | }
71 | .el-menu--collapse {
72 | .el-submenu {
73 | &>.el-submenu__title {
74 | &>span {
75 | height: 0;
76 | width: 0;
77 | overflow: hidden;
78 | visibility: hidden;
79 | display: inline-block;
80 | }
81 | }
82 | }
83 | }
84 | }
85 | .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
86 | .sidebar-container .el-submenu .el-menu-item {
87 | min-width: 180px !important;
88 | background-color: $subMenuBg !important;
89 | &:hover {
90 | background-color: $menuHover !important;
91 | }
92 | }
93 | .el-menu--collapse .el-menu .el-submenu {
94 | min-width: 180px !important;
95 | }
96 |
97 | //适配移动端
98 | .mobile {
99 | .main-container {
100 | margin-left: 0px;
101 | }
102 | .sidebar-container {
103 | transition: transform .28s;
104 | width: 180px !important;
105 | }
106 | &.hideSidebar {
107 | .sidebar-container {
108 | transition-duration: 0.3s;
109 | transform: translate3d(-180px, 0, 0);
110 | }
111 | }
112 | }
113 | .withoutAnimation {
114 | .main-container,
115 | .sidebar-container {
116 | transition: none;
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { login, logout, getInfo } from '@/api/login'
2 | import { getToken, setToken, removeToken } from '@/utils/auth'
3 | import { createuser } from '@/api/createuser'
4 |
5 | const user = {
6 | state: {
7 | token: getToken(),
8 | name: '',
9 | avatar: '',
10 | roles: [],
11 | userinfo: ''
12 | },
13 |
14 | mutations: {
15 | SET_TOKEN: (state, token) => {
16 | state.token = token
17 | },
18 | SET_NAME: (state, name) => {
19 | state.name = name
20 | },
21 | SET_AVATAR: (state, avatar) => {
22 | state.avatar = avatar
23 | },
24 | SET_ROLES: (state, roles) => {
25 | state.roles = roles
26 | },
27 | CREATE_USER: (state, userinfo) => {
28 | state.userinfo = userinfo
29 | }
30 | },
31 |
32 | actions: {
33 | // 登录
34 | Login({ commit }, userInfo) {
35 | const username = userInfo.username.trim()
36 | return new Promise((resolve, reject) => {
37 | login(username, userInfo.password).then(response => {
38 | const data = response.data
39 | setToken(data.token)
40 | commit('SET_TOKEN', data.token)
41 | resolve()
42 | }).catch(error => {
43 | reject(error)
44 | })
45 | })
46 | },
47 |
48 | // 获取用户信息
49 | GetInfo({ commit, state }) {
50 | return new Promise((resolve, reject) => {
51 | getInfo(state.token).then(response => {
52 | const data = response.data
53 | if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
54 | commit('SET_ROLES', data.roles)
55 | } else {
56 | reject('getInfo: roles must be a non-null array !')
57 | }
58 | commit('SET_NAME', data.name)
59 | commit('SET_AVATAR', data.avatar)
60 | resolve(response)
61 | }).catch(error => {
62 | reject(error)
63 | })
64 | })
65 | },
66 |
67 | // 登出
68 | LogOut({ commit, state }) {
69 | return new Promise((resolve, reject) => {
70 | logout(state.token).then(() => {
71 | commit('SET_TOKEN', '')
72 | commit('SET_ROLES', [])
73 | removeToken()
74 | resolve()
75 | }).catch(error => {
76 | reject(error)
77 | })
78 | })
79 | },
80 |
81 | // 前端 登出
82 | FedLogOut({ commit }) {
83 | return new Promise(resolve => {
84 | commit('SET_TOKEN', '')
85 | removeToken()
86 | resolve()
87 | })
88 | },
89 |
90 | // 用户注册
91 | Createuser({ commit }, userInfo) {
92 | const username = userInfo.username.trim()
93 | const password = userInfo.password.trim()
94 | const role = userInfo.role.trim()
95 | return new Promise((resolve, reject) => {
96 | createuser(username, password, role).then(response => {
97 | resolve()
98 | }).catch(error => {
99 | reject(error)
100 | })
101 | })
102 | }
103 |
104 | }
105 | }
106 |
107 | export default user
108 |
--------------------------------------------------------------------------------
/frontend/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.2.6
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: '0.0.0.0', // can be overwritten by process.env.HOST
17 | port: 9005, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: true,
19 | errorOverlay: true,
20 | notifyOnErrors: false,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 | // Use Eslint Loader?
24 | // If true, your code will be linted during bundling and
25 | // linting errors and warnings will be shown in the console.
26 | useEslint: true,
27 | // If true, eslint errors and warnings will also be shown in the error overlay
28 | // in the browser.
29 | showEslintErrorsInOverlay: false,
30 |
31 | /**
32 | * Source Maps
33 | */
34 |
35 | // https://webpack.js.org/configuration/devtool/#development
36 | devtool: 'cheap-source-map',
37 |
38 | // If you have problems debugging vue-files in devtools,
39 | // set this to false - it *may* help
40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
41 | cacheBusting: true,
42 |
43 | // CSS Sourcemaps off by default because relative paths are "buggy"
44 | // with this option, according to the CSS-Loader README
45 | // (https://github.com/webpack/css-loader#sourcemaps)
46 | // In our experience, they generally work as expected,
47 | // just be aware of this issue when enabling this option.
48 | cssSourceMap: false,
49 | },
50 |
51 | build: {
52 | // Template for index.html
53 | index: path.resolve(__dirname, '../dist/index.html'),
54 |
55 | // Paths
56 | assetsRoot: path.resolve(__dirname, '../dist'),
57 | assetsSubDirectory: 'static',
58 |
59 | /**
60 | * You can set by youself according to actual condition
61 | * You will need to set this if you plan to deploy your site under a sub path,
62 | * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
63 | * then assetsPublicPath should be set to "/bar/".
64 | * In most cases please use '/' !!!
65 | */
66 | assetsPublicPath: '/vueAdmin-template/', // If you are deployed on the root path, please use '/'
67 |
68 | /**
69 | * Source Maps
70 | */
71 |
72 | productionSourceMap: false,
73 | // https://webpack.js.org/configuration/devtool/#production
74 | devtool: '#source-map',
75 |
76 | // Gzip off by default as many popular static hosts such as
77 | // Surge or Netlify already gzip all static assets for you.
78 | // Before setting to `true`, make sure to:
79 | // npm install --save-dev compression-webpack-plugin
80 | productionGzip: false,
81 | productionGzipExtensions: ['js', 'css'],
82 |
83 | // Run the build command with an extra argument to
84 | // View the bundle analyzer report after build finishes:
85 | // `npm run build --report`
86 | // Set to `true` or `false` to always turn it on or off
87 | bundleAnalyzerReport: process.env.npm_config_report
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/frontend/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | // in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
5 | // detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
6 |
7 | Vue.use(Router)
8 |
9 | /* Layout */
10 | import Layout from '../views/layout/Layout'
11 |
12 | /**
13 | * hidden: true if `hidden:true` will not show in the sidebar(default is false)
14 | * alwaysShow: true if set true, will always show the root menu, whatever its child routes length
15 | * if not set alwaysShow, only more than one route under the children
16 | * it will becomes nested mode, otherwise not show the root menu
17 | * redirect: noredirect if `redirect:noredirect` will no redirct in the breadcrumb
18 | * name:'router-name' the name is used by (must set!!!)
19 | * meta : {
20 | title: 'title' the name show in submenu and breadcrumb (recommend set)
21 | icon: 'svg-name' the icon show in the sidebar,
22 | }
23 | **/
24 | export const constantRouterMap = [
25 | { path: '/login', component: () => import('@/views/login/index'), hidden: true },
26 | { path: '/404', component: () => import('@/views/404'), hidden: true },
27 |
28 | {
29 | path: '/',
30 | component: Layout,
31 | redirect: '/dashboard',
32 | name: 'Dashboard',
33 | hidden: true,
34 | children: [{
35 | path: 'dashboard',
36 | component: () => import('@/views/dashboard/index')
37 | }]
38 | },
39 |
40 | {
41 | path: '/example',
42 | component: Layout,
43 | redirect: '/example/table',
44 | name: 'Example',
45 | hidden: false,
46 | meta: { title: '表格-树', icon: 'example' },
47 | children: [
48 | {
49 | path: 'table',
50 | name: 'Table',
51 | component: () => import('@/views/table/index'),
52 | meta: { title: '用户信息', icon: 'table' }
53 | },
54 | {
55 | path: 'tree',
56 | name: 'Tree',
57 | component: () => import('@/views/tree/index'),
58 | meta: { title: 'Tree', icon: 'tree' }
59 | }
60 | ]
61 | },
62 |
63 | {
64 | path: '/form',
65 | component: Layout,
66 | redirect: '/example/form',
67 | name: 'Forms',
68 | hidden: false,
69 | meta: { title: '表单', icon: 'form' },
70 | children: [
71 | {
72 | path: 'createuser',
73 | name: 'Form1',
74 | component: () => import('@/views/form/createuser'),
75 | meta: { title: '创建用户', icon: 'form' }
76 | },
77 | {
78 | path: 'index',
79 | name: 'Form',
80 | component: () => import('@/views/form/index'),
81 | meta: { title: 'Form', icon: 'form' }
82 | }
83 | ]
84 | },
85 |
86 | {
87 | path: '/nested',
88 | component: Layout,
89 | redirect: '/nested/menu1',
90 | name: 'nested',
91 | hidden: false,
92 | meta: {
93 | title: 'nested',
94 | icon: 'nested'
95 | },
96 | children: [
97 | {
98 | path: 'menu1',
99 | component: () => import('@/views/nested/menu1/index'), // Parent router-view
100 | name: 'menu1',
101 | meta: { title: 'menu1' },
102 | children: [
103 | {
104 | path: 'menu1-1',
105 | component: () => import('@/views/nested/menu1/menu1-1'),
106 | name: 'menu1-1',
107 | meta: { title: 'menu1-1' }
108 | },
109 | {
110 | path: 'menu1-2',
111 | component: () => import('@/views/nested/menu1/menu1-2'),
112 | name: 'menu1-2',
113 | meta: { title: 'menu1-2' },
114 | children: [
115 | {
116 | path: 'menu1-2-1',
117 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
118 | name: 'menu1-2-1',
119 | meta: { title: 'menu1-2-1' }
120 | },
121 | {
122 | path: 'menu1-2-2',
123 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
124 | name: 'menu1-2-2',
125 | meta: { title: 'menu1-2-2' }
126 | }
127 | ]
128 | },
129 | {
130 | path: 'menu1-3',
131 | component: () => import('@/views/nested/menu1/menu1-3'),
132 | name: 'menu1-3',
133 | meta: { title: 'menu1-3' }
134 | }
135 | ]
136 | },
137 | {
138 | path: 'menu2',
139 | component: () => import('@/views/nested/menu2/index'),
140 | meta: { title: 'menu2' }
141 | }
142 | ]
143 | },
144 |
145 | { path: '*', redirect: '/404', hidden: true }
146 | ]
147 |
148 | export default new Router({
149 | // mode: 'history', //后端支持可开
150 | scrollBehavior: () => ({ y: 0 }),
151 | routes: constantRouterMap
152 | })
153 |
154 |
--------------------------------------------------------------------------------
/backend/app/api/user/user.py:
--------------------------------------------------------------------------------
1 | from flask_restplus import Resource,fields as filed
2 | from app.api import ns
3 | from app.models import User,Role
4 | from app.marshalling import user_schema,users_schema,role_schema,gma
5 | from flask import request,jsonify,current_app
6 | from app.utils import token_required
7 |
8 | # 给swagger用
9 | class Model(object):
10 | post_model = ns.model('填写用户信息', {
11 | 'username': filed.String,
12 | 'password': filed.String,
13 | 'role': filed.String
14 | })
15 | login_model = ns.model('登陆信息', {
16 | 'username': filed.String,
17 | 'password': filed.String
18 | })
19 | token_model = ns.model('Token',{
20 | 'token': filed.String
21 | })
22 |
23 |
24 | @ns.route('/user/createuser/',endpoint='createuser',methods=['POST'])
25 | class CreateUserView(Resource):
26 | @ns.doc(body=Model.post_model, desciption='填写用户信息')
27 | def post(self):
28 | '''
29 | 创建用户
30 | '''
31 | json_data = request.get_json()
32 | username = json_data['username']
33 | password = json_data['password']
34 | role = json_data['role']
35 | from app.models import db
36 | if Role.query.filter_by(name=role).first():
37 | role = Role.query.filter_by(name=role).first()
38 | else:
39 | role = Role(name=role)
40 | if not User.query.filter_by(username=username).first():
41 | user = User(username=username, password=password, role=role)
42 | db.session.add(user)
43 | db.session.commit()
44 | data = {
45 | "code": 20000,
46 | "data":None
47 | }
48 | return gma.dump(data)
49 |
50 | @ns.route('/users/',endpoint='users',methods=['GET','OPSTIONS'])
51 | class UserView(Resource):
52 | # @ns.doc(security='apikey')
53 | # decorators = [token_required] 方法一
54 | # method_decorators = [token_required] # 方法二
55 | # @token_required 方法三
56 | def get(self):
57 | '''
58 | 获取用户列表
59 | '''
60 | users = User.query.all()
61 | result = users_schema.dump(users)
62 | data = {
63 | 'code': 20000,
64 | 'data': result
65 | }
66 | current_app.logger.info('获取用户列表')
67 | return gma.dump(data)
68 |
69 | @ns.route('/user/',endpoint='user',doc=False)
70 | class UserView(Resource):
71 | def get(self,id):
72 | '''
73 | 获取用户名、密码
74 | '''
75 | user = User.query.filter_by(id=id).first()
76 | result = user_schema.dump(user)
77 | data = {
78 | "code":20000,
79 | "data":result
80 | }
81 | return gma.dump(data)
82 |
83 | @ns.route('/role/',endpoint='role',doc=False)
84 | class RoleView(Resource):
85 | def get(self,id):
86 | '''
87 | 根据用户id获取角色
88 | '''
89 | role = Role.query.filter_by(id=id).first()
90 | result = role_schema.dump(role)
91 | data = {
92 | "code":20000,
93 | "data":result
94 | }
95 | return gma.dump(data)
96 |
97 | @ns.route('/user/login',endpoint='login',methods=['POST'])
98 | class LoginView(Resource):
99 | @ns.doc(body=Model.login_model, desciption='登录信息')
100 | def post(self):
101 | '''
102 | 用户登录,获取token
103 | '''
104 | json_data = request.get_json()
105 | username = json_data['username']
106 | password = json_data['password']
107 | user = User.query.filter_by(username=username).first()
108 | if user.verify_password(password):
109 | token = user.generate_auth_token()
110 | else:
111 | return '验证失败'
112 | data = {
113 | 'code':20000,
114 | 'data':{
115 | "token":token.decode('ascii')
116 | }
117 | }
118 | current_app.logger.info('登录用户')
119 | return gma.dump(data)
120 |
121 |
122 | @ns.route('/user/info',endpoint='getinfo',methods=['GET'])
123 | class UserInfo(Resource):
124 | @ns.doc(params={'token': '根据token获取用户信息'})
125 | # @token_required
126 | def get(self,*args,**kwargs):
127 | '''
128 | 根据token获取用户信息
129 | '''
130 | token = request.args.get('token')
131 | try:
132 | user = User.verify_auth_token(token)
133 | except Exception as e:
134 | return jsonify({"code": 50012, "message": "token过期"})
135 |
136 | data = {
137 | 'code': 20000,
138 | "data":{
139 | 'token':token,
140 | 'roles':user.role.name,
141 | 'name':user.username,
142 | 'avatar': None
143 | }
144 | }
145 | current_app.logger.info('获取用户信息')
146 | return gma.dump(data)
147 |
148 |
149 | @ns.route('/user/logout',endpoint='logout',methods=['POST'])
150 | class LogoutView(Resource):
151 | @ns.doc(body=Model.token_model, desciption='token')
152 | def post(self):
153 | '''
154 | 用户登出
155 | '''
156 | data = {
157 | 'code' : 20000,
158 | "data":{'token':None,"message": "用户登出"}
159 | }
160 | return gma.dump(data)
161 |
--------------------------------------------------------------------------------
/frontend/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 | function resolve (dir) {
15 | return path.join(__dirname, '..', dir)
16 | }
17 |
18 | const env = require('../config/prod.env')
19 |
20 | const webpackConfig = merge(baseWebpackConfig, {
21 | module: {
22 | rules: utils.styleLoaders({
23 | sourceMap: config.build.productionSourceMap,
24 | extract: true,
25 | usePostCSS: true
26 | })
27 | },
28 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
29 | output: {
30 | path: config.build.assetsRoot,
31 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
32 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
33 | },
34 | plugins: [
35 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
36 | new webpack.DefinePlugin({
37 | 'process.env': env
38 | }),
39 | new UglifyJsPlugin({
40 | uglifyOptions: {
41 | compress: {
42 | warnings: false
43 | }
44 | },
45 | sourceMap: config.build.productionSourceMap,
46 | parallel: true
47 | }),
48 | // extract css into its own file
49 | new ExtractTextPlugin({
50 | filename: utils.assetsPath('css/[name].[contenthash].css'),
51 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
52 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
53 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
54 | allChunks: false,
55 | }),
56 | // Compress extracted CSS. We are using this plugin so that possible
57 | // duplicated CSS from different components can be deduped.
58 | new OptimizeCSSPlugin({
59 | cssProcessorOptions: config.build.productionSourceMap
60 | ? { safe: true, map: { inline: false } }
61 | : { safe: true }
62 | }),
63 | // generate dist index.html with correct asset hash for caching.
64 | // you can customize output by editing /index.html
65 | // see https://github.com/ampedandwired/html-webpack-plugin
66 | new HtmlWebpackPlugin({
67 | filename: config.build.index,
68 | template: 'index.html',
69 | inject: true,
70 | favicon: resolve('favicon.ico'),
71 | title: 'vue-element-admin',
72 | minify: {
73 | removeComments: true,
74 | collapseWhitespace: true,
75 | removeAttributeQuotes: true
76 | // more options:
77 | // https://github.com/kangax/html-minifier#options-quick-reference
78 | },
79 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
80 | chunksSortMode: 'dependency'
81 | }),
82 | // keep module.id stable when vender modules does not change
83 | new webpack.HashedModuleIdsPlugin(),
84 | // enable scope hoisting
85 | new webpack.optimize.ModuleConcatenationPlugin(),
86 | // split vendor js into its own file
87 | new webpack.optimize.CommonsChunkPlugin({
88 | name: 'vendor',
89 | minChunks (module) {
90 | // any required modules inside node_modules are extracted to vendor
91 | return (
92 | module.resource &&
93 | /\.js$/.test(module.resource) &&
94 | module.resource.indexOf(
95 | path.join(__dirname, '../node_modules')
96 | ) === 0
97 | )
98 | }
99 | }),
100 | // extract webpack runtime and module manifest to its own file in order to
101 | // prevent vendor hash from being updated whenever app bundle is updated
102 | new webpack.optimize.CommonsChunkPlugin({
103 | name: 'manifest',
104 | minChunks: Infinity
105 | }),
106 | // This instance extracts shared chunks from code splitted chunks and bundles them
107 | // in a separate chunk, similar to the vendor chunk
108 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
109 | new webpack.optimize.CommonsChunkPlugin({
110 | name: 'app',
111 | async: 'vendor-async',
112 | children: true,
113 | minChunks: 3
114 | }),
115 |
116 | // copy custom static assets
117 | new CopyWebpackPlugin([
118 | {
119 | from: path.resolve(__dirname, '../static'),
120 | to: config.build.assetsSubDirectory,
121 | ignore: ['.*']
122 | }
123 | ])
124 | ]
125 | })
126 |
127 | if (config.build.productionGzip) {
128 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
129 |
130 | webpackConfig.plugins.push(
131 | new CompressionWebpackPlugin({
132 | asset: '[path].gz[query]',
133 | algorithm: 'gzip',
134 | test: new RegExp(
135 | '\\.(' +
136 | config.build.productionGzipExtensions.join('|') +
137 | ')$'
138 | ),
139 | threshold: 10240,
140 | minRatio: 0.8
141 | })
142 | )
143 | }
144 |
145 | if (config.build.bundleAnalyzerReport) {
146 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
147 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
148 | }
149 |
150 | module.exports = webpackConfig
151 |
--------------------------------------------------------------------------------
/frontend/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | OPS PLATFORM
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
32 |
33 |
34 |
35 |
36 |
40 | Sign in
41 |
42 |
43 |
44 | username: admin
45 | password: admin
46 |
47 |
48 |
49 |
50 |
51 |
111 |
112 |
144 |
145 |
201 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: 'babel-eslint',
4 | parserOptions: {
5 | sourceType: 'module'
6 | },
7 | env: {
8 | browser: true,
9 | node: true,
10 | es6: true,
11 | },
12 | extends: 'eslint:recommended',
13 | // required to lint *.vue files
14 | plugins: [
15 | 'html'
16 | ],
17 | // check if imports actually resolve
18 | 'settings': {
19 | 'import/resolver': {
20 | 'webpack': {
21 | 'config': 'build/webpack.base.conf.js'
22 | }
23 | }
24 | },
25 | // add your custom rules here
26 | //it is base on https://github.com/vuejs/eslint-config-vue
27 | rules: {
28 | 'accessor-pairs': 2,
29 | 'arrow-spacing': [2, {
30 | 'before': true,
31 | 'after': true
32 | }],
33 | 'block-spacing': [2, 'always'],
34 | 'brace-style': [2, '1tbs', {
35 | 'allowSingleLine': true
36 | }],
37 | 'camelcase': [0, {
38 | 'properties': 'always'
39 | }],
40 | 'comma-dangle': [2, 'never'],
41 | 'comma-spacing': [2, {
42 | 'before': false,
43 | 'after': true
44 | }],
45 | 'comma-style': [2, 'last'],
46 | 'constructor-super': 2,
47 | 'curly': [2, 'multi-line'],
48 | 'dot-location': [2, 'property'],
49 | 'eol-last': 2,
50 | 'eqeqeq': [2, 'allow-null'],
51 | 'generator-star-spacing': [2, {
52 | 'before': true,
53 | 'after': true
54 | }],
55 | 'handle-callback-err': [2, '^(err|error)$'],
56 | 'indent': [2, 2, {
57 | 'SwitchCase': 1
58 | }],
59 | 'jsx-quotes': [2, 'prefer-single'],
60 | 'key-spacing': [2, {
61 | 'beforeColon': false,
62 | 'afterColon': true
63 | }],
64 | 'keyword-spacing': [2, {
65 | 'before': true,
66 | 'after': true
67 | }],
68 | 'new-cap': [2, {
69 | 'newIsCap': true,
70 | 'capIsNew': false
71 | }],
72 | 'new-parens': 2,
73 | 'no-array-constructor': 2,
74 | 'no-caller': 2,
75 | 'no-console': 'off',
76 | 'no-class-assign': 2,
77 | 'no-cond-assign': 2,
78 | 'no-const-assign': 2,
79 | 'no-control-regex': 2,
80 | 'no-delete-var': 2,
81 | 'no-dupe-args': 2,
82 | 'no-dupe-class-members': 2,
83 | 'no-dupe-keys': 2,
84 | 'no-duplicate-case': 2,
85 | 'no-empty-character-class': 2,
86 | 'no-empty-pattern': 2,
87 | 'no-eval': 2,
88 | 'no-ex-assign': 2,
89 | 'no-extend-native': 2,
90 | 'no-extra-bind': 2,
91 | 'no-extra-boolean-cast': 2,
92 | 'no-extra-parens': [2, 'functions'],
93 | 'no-fallthrough': 2,
94 | 'no-floating-decimal': 2,
95 | 'no-func-assign': 2,
96 | 'no-implied-eval': 2,
97 | 'no-inner-declarations': [2, 'functions'],
98 | 'no-invalid-regexp': 2,
99 | 'no-irregular-whitespace': 2,
100 | 'no-iterator': 2,
101 | 'no-label-var': 2,
102 | 'no-labels': [2, {
103 | 'allowLoop': false,
104 | 'allowSwitch': false
105 | }],
106 | 'no-lone-blocks': 2,
107 | 'no-mixed-spaces-and-tabs': 2,
108 | 'no-multi-spaces': 2,
109 | 'no-multi-str': 2,
110 | 'no-multiple-empty-lines': [2, {
111 | 'max': 1
112 | }],
113 | 'no-native-reassign': 2,
114 | 'no-negated-in-lhs': 2,
115 | 'no-new-object': 2,
116 | 'no-new-require': 2,
117 | 'no-new-symbol': 2,
118 | 'no-new-wrappers': 2,
119 | 'no-obj-calls': 2,
120 | 'no-octal': 2,
121 | 'no-octal-escape': 2,
122 | 'no-path-concat': 2,
123 | 'no-proto': 2,
124 | 'no-redeclare': 2,
125 | 'no-regex-spaces': 2,
126 | 'no-return-assign': [2, 'except-parens'],
127 | 'no-self-assign': 2,
128 | 'no-self-compare': 2,
129 | 'no-sequences': 2,
130 | 'no-shadow-restricted-names': 2,
131 | 'no-spaced-func': 2,
132 | 'no-sparse-arrays': 2,
133 | 'no-this-before-super': 2,
134 | 'no-throw-literal': 2,
135 | 'no-trailing-spaces': 2,
136 | 'no-undef': 2,
137 | 'no-undef-init': 2,
138 | 'no-unexpected-multiline': 2,
139 | 'no-unmodified-loop-condition': 2,
140 | 'no-unneeded-ternary': [2, {
141 | 'defaultAssignment': false
142 | }],
143 | 'no-unreachable': 2,
144 | 'no-unsafe-finally': 2,
145 | 'no-unused-vars': [2, {
146 | 'vars': 'all',
147 | 'args': 'none'
148 | }],
149 | 'no-useless-call': 2,
150 | 'no-useless-computed-key': 2,
151 | 'no-useless-constructor': 2,
152 | 'no-useless-escape': 0,
153 | 'no-whitespace-before-property': 2,
154 | 'no-with': 2,
155 | 'one-var': [2, {
156 | 'initialized': 'never'
157 | }],
158 | 'operator-linebreak': [2, 'after', {
159 | 'overrides': {
160 | '?': 'before',
161 | ':': 'before'
162 | }
163 | }],
164 | 'padded-blocks': [2, 'never'],
165 | 'quotes': [2, 'single', {
166 | 'avoidEscape': true,
167 | 'allowTemplateLiterals': true
168 | }],
169 | 'semi': [2, 'never'],
170 | 'semi-spacing': [2, {
171 | 'before': false,
172 | 'after': true
173 | }],
174 | 'space-before-blocks': [2, 'always'],
175 | 'space-before-function-paren': [2, 'never'],
176 | 'space-in-parens': [2, 'never'],
177 | 'space-infix-ops': 2,
178 | 'space-unary-ops': [2, {
179 | 'words': true,
180 | 'nonwords': false
181 | }],
182 | 'spaced-comment': [2, 'always', {
183 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
184 | }],
185 | 'template-curly-spacing': [2, 'never'],
186 | 'use-isnan': 2,
187 | 'valid-typeof': 2,
188 | 'wrap-iife': [2, 'any'],
189 | 'yield-star-spacing': [2, 'both'],
190 | 'yoda': [2, 'never'],
191 | 'prefer-const': 2,
192 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
193 | 'object-curly-spacing': [2, 'always', {
194 | objectsInObjects: false
195 | }],
196 | 'array-bracket-spacing': [2, 'never']
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/frontend/src/views/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
OOPS!
12 |
15 |
{{ message }}
16 |
请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告
17 |
返回首页
18 |
19 |
20 |
21 |
22 |
23 |
42 |
43 |
237 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | true
46 | DEFINITION_ORDER
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 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | 1567565191649
111 |
112 |
113 | 1567565191649
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
--------------------------------------------------------------------------------
/backend/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
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 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 | model
188 | created_at
189 | header
190 | post_model
191 | stack
192 | info
193 | info(self
194 | INFO
195 | INFO(se
196 | Logger
197 | _log
198 | log(
199 | token
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 | true
263 | DEFINITION_ORDER
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 | 1533648006300
526 |
527 |
528 | 1533648006300
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
--------------------------------------------------------------------------------