├── README.md
├── fe
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .roadhogrc.js
├── LICENSE
├── README.md
├── package.json
├── src
│ ├── components
│ │ ├── DataTable
│ │ │ ├── AnimTableBody.js
│ │ │ ├── DataTable.js
│ │ │ ├── DataTable.less
│ │ │ └── package.json
│ │ ├── DownloadButton
│ │ │ ├── icon.js
│ │ │ └── index.js
│ │ ├── DropOption
│ │ │ ├── DropOption.js
│ │ │ └── package.json
│ │ ├── Editor
│ │ │ ├── Editor.js
│ │ │ ├── Editor.less
│ │ │ └── package.json
│ │ ├── FilterItem
│ │ │ ├── FilterItem.js
│ │ │ ├── FilterItem.less
│ │ │ └── package.json
│ │ ├── Iconfont
│ │ │ ├── Iconfont.js
│ │ │ ├── iconfont.less
│ │ │ └── package.json
│ │ ├── InputClose
│ │ │ ├── InputClose.js
│ │ │ ├── InputClose.less
│ │ │ └── package.json
│ │ ├── Layout
│ │ │ ├── Bread.js
│ │ │ ├── Bread.less
│ │ │ ├── Footer.js
│ │ │ ├── Footer.less
│ │ │ ├── Header.js
│ │ │ ├── Header.less
│ │ │ ├── Layout.less
│ │ │ ├── Menu.js
│ │ │ ├── Sider.js
│ │ │ ├── Sider.less
│ │ │ └── index.js
│ │ ├── Loader
│ │ │ ├── Loader.js
│ │ │ ├── Loader.less
│ │ │ └── package.json
│ │ ├── Search
│ │ │ ├── Search.js
│ │ │ ├── Search.less
│ │ │ └── package.json
│ │ ├── SearchTree
│ │ │ ├── SearchTree.js
│ │ │ ├── SearchTree.json
│ │ │ └── SearchTree.less
│ │ ├── index.js
│ │ └── layer
│ │ │ ├── layer.js
│ │ │ ├── layer.less
│ │ │ └── package.json
│ ├── entry.ejs
│ ├── index.js
│ ├── models
│ │ ├── app.js
│ │ ├── common.js
│ │ ├── dashboard.js
│ │ ├── login.js
│ │ ├── sys
│ │ │ ├── account.js
│ │ │ ├── menu.js
│ │ │ ├── permission.js
│ │ │ └── role.js
│ │ └── user.js
│ ├── public
│ │ ├── QR_code.jpg
│ │ ├── antd
│ │ │ ├── iconfont.eot
│ │ │ ├── iconfont.svg
│ │ │ ├── iconfont.ttf
│ │ │ └── iconfont.woff
│ │ ├── bg_image.jpg
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.js
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── logo.png
│ ├── router.js
│ ├── routes
│ │ ├── app.js
│ │ ├── app.less
│ │ ├── dashboard
│ │ │ ├── components
│ │ │ │ ├── browser.js
│ │ │ │ ├── browser.less
│ │ │ │ ├── comments.js
│ │ │ │ ├── comments.less
│ │ │ │ ├── completed.js
│ │ │ │ ├── completed.less
│ │ │ │ ├── cpu.js
│ │ │ │ ├── cpu.less
│ │ │ │ ├── index.js
│ │ │ │ ├── numberCard.js
│ │ │ │ ├── numberCard.less
│ │ │ │ ├── quote.js
│ │ │ │ ├── quote.less
│ │ │ │ ├── recentSales.js
│ │ │ │ ├── recentSales.less
│ │ │ │ ├── sales.js
│ │ │ │ ├── sales.less
│ │ │ │ ├── user-background.png
│ │ │ │ ├── user.js
│ │ │ │ ├── user.less
│ │ │ │ ├── weather.js
│ │ │ │ └── weather.less
│ │ │ ├── index.js
│ │ │ └── index.less
│ │ ├── error
│ │ │ ├── index.js
│ │ │ └── index.less
│ │ ├── login
│ │ │ ├── index.js
│ │ │ └── index.less
│ │ └── sys
│ │ │ ├── account
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ ├── Modal.js
│ │ │ ├── Modal.less
│ │ │ └── index.js
│ │ │ ├── menu
│ │ │ ├── Form.js
│ │ │ ├── Form.less
│ │ │ ├── Tree.js
│ │ │ └── index.js
│ │ │ ├── permission
│ │ │ ├── Form.js
│ │ │ ├── Form.less
│ │ │ ├── Tree.js
│ │ │ └── index.js
│ │ │ └── role
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ ├── Modal.js
│ │ │ ├── Modal.less
│ │ │ └── index.js
│ ├── services
│ │ ├── app.js
│ │ ├── common.js
│ │ ├── dashboard.js
│ │ ├── login.js
│ │ ├── sys
│ │ │ ├── account.js
│ │ │ ├── dataSync.js
│ │ │ ├── menu.js
│ │ │ ├── permission.js
│ │ │ ├── role.js
│ │ │ └── schedule.js
│ │ ├── user.js
│ │ ├── users.js
│ │ └── weather.js
│ ├── svg
│ │ ├── CDR.svg
│ │ ├── cute
│ │ │ ├── congratulations.svg
│ │ │ ├── cry.svg
│ │ │ ├── kiss.svg
│ │ │ ├── leisurely.svg
│ │ │ ├── notice.svg
│ │ │ ├── proud.svg
│ │ │ ├── shy.svg
│ │ │ ├── sweat.svg
│ │ │ └── think.svg
│ │ └── emoji
│ │ │ ├── smirking.svg
│ │ │ ├── surprised.svg
│ │ │ ├── tired.svg
│ │ │ ├── tongue.svg
│ │ │ ├── unamused.svg
│ │ │ ├── vomiting.svg
│ │ │ ├── wink.svg
│ │ │ └── zombie.svg
│ ├── themes
│ │ ├── default.less
│ │ ├── index.less
│ │ ├── mixin.less
│ │ └── vars.less
│ └── utils
│ │ ├── city.js
│ │ ├── config.js
│ │ ├── date.js
│ │ ├── index.js
│ │ ├── request.js
│ │ └── theme.js
├── theme.config.js
└── webpack.config.js
├── pom.xml
├── sql
└── aas_db.sql
└── src
└── main
├── java
└── me
│ └── hrps
│ └── aas
│ ├── Application.java
│ ├── core
│ ├── config
│ │ ├── ShrioConfig.java
│ │ └── WebConfig.java
│ ├── constant
│ │ └── AppConsts.java
│ ├── exception
│ │ ├── ServiceException.java
│ │ └── ValidatorException.java
│ ├── mvc
│ │ └── advice
│ │ │ ├── MyResponseBodyAdvice.java
│ │ │ └── ServiceExceptionHandler.java
│ ├── mybatis
│ │ └── domain
│ │ │ ├── PageFilter.java
│ │ │ └── TableResponse.java
│ ├── shiro
│ │ ├── domain
│ │ │ └── ShiroUser.java
│ │ ├── filter
│ │ │ ├── MyFormAuthenticationFilter.java
│ │ │ ├── MyLogoutFilter.java
│ │ │ └── MyUserFilter.java
│ │ └── realm
│ │ │ └── ShiroDbRealm.java
│ └── utils
│ │ ├── Digests.java
│ │ ├── Exceptions.java
│ │ ├── PasswordUtils.java
│ │ ├── Securitys.java
│ │ └── Utils.java
│ └── web
│ └── sys
│ ├── controller
│ ├── AccountController.java
│ └── SysController.java
│ ├── domain
│ ├── SysAccount.java
│ ├── SysAccountExample.java
│ ├── SysAccountRoleExample.java
│ ├── SysAccountRoleKey.java
│ ├── SysMenu.java
│ ├── SysMenuExample.java
│ ├── SysPermission.java
│ ├── SysPermissionExample.java
│ ├── SysRole.java
│ ├── SysRoleExample.java
│ ├── SysRolePermissionExample.java
│ └── SysRolePermissionKey.java
│ ├── filter
│ ├── AccountFilter.java
│ └── RoleFilter.java
│ ├── mapper
│ ├── SysAccountMapper.java
│ ├── SysAccountRoleMapper.java
│ ├── SysMenuMapper.java
│ ├── SysPermissionMapper.java
│ ├── SysRoleMapper.java
│ └── SysRolePermissionMapper.java
│ ├── qo
│ └── TreeSortQO.java
│ ├── service
│ ├── AccountService.java
│ └── SysService.java
│ └── vo
│ ├── MenuPermissionTreeVO.java
│ ├── MenuVO.java
│ ├── SysAccountVO.java
│ ├── SysRoleVO.java
│ └── UserVO.java
└── resources
├── application.yml
├── mybatis
├── SysAccountMapper.xml
├── SysAccountRoleMapper.xml
├── SysMenuMapper.xml
├── SysPermissionMapper.xml
├── SysRoleMapper.xml
└── SysRolePermissionMapper.xml
├── mybatisGeneratorConfig.xml
└── security
└── ehcache-shiro.xml
/README.md:
--------------------------------------------------------------------------------
1 | # Antd Admin Springboot
2 |
3 | [](https://github.com/facebook/react)
4 | [](https://github.com/ant-design/ant-design)
5 | [](https://github.com/dvajs/dva)
6 | [](https://github.com/spring-projects/spring-boot)
7 |
8 | [](https://github.com/javahuang/antd-admin-springboot/issues)
9 | [](https://github.com/javahuang/antd-admin-springboot/pulls)
10 | [](http://opensource.org/licenses/MIT)
11 | [](http://standardjs.com)
12 |
13 | 演示地址
14 |
15 | admin/hello,请不要修改管理员账号的密码。
16 |
17 | ## 特性
18 |
19 | 原数据库使用的是 oracle,整个代码我重新整理一遍,但迁移稍微仓促,没有测试,页面有不少小的 bug,后期有时间我再一一修正
20 |
21 | - 前端基于最新版本的 [antd-admin](https://github.com/zuiidea/antd-admin)
22 | - 后端基于 springboot,同时整合了 shiro、mybatis,数据库使用的 mysql
23 | - 后端添加了自动分页插件
24 | - 基于 shiro 的 RBAC 权限控制,能动态配置菜单、 角色、权限等(用户对应角色(1对多),角色对应权限(1对多),菜单对应权限(1对1))
25 | - 统一异常处理及返回数据统一封装
26 | - 后端 RESTful api 设计
27 | - 开发、部署简单,能实现前后端同时开发且无需额外修改配置
28 | - ...
29 |
30 |
31 | ### 目录结构
32 | 前端目录结构参见 [目录结构](https://github.com/zuiidea/antd-admin#目录结构)
33 |
34 |
35 | ### 快速开始
36 |
37 | 克隆项目文件:
38 |
39 | ```bash
40 | git clone https://github.com/javahuang/antd-admin-springboot
41 | ```
42 |
43 | 进入目录安装依赖:
44 |
45 | ```bash
46 | # 前端
47 | # 开始前请确保没有安装roadhog、webpack到NPM全局目录
48 | npm i 或者 yarn install
49 |
50 | # 后台
51 | # 根据 pom.xml 配置下载 maven 依赖包
52 | ```
53 |
54 | 开发:
55 |
56 | ```bash
57 | # 前端
58 | npm run build:dll #第一次npm run dev时需运行此命令,使开发时编译更快
59 | npm run dev
60 | 打开 http://localhost:8000
61 |
62 | # 后台
63 | # 直接运行 me.hrps.aas.Application-main() 方法启动后台服务
64 | ```
65 |
66 | 构建:
67 |
68 | ```bash
69 | # 前端
70 | npm run build
71 | 将会打包至dist/{version}目录 #package.json里version字段
72 | 将 dist 目录下面所有文件拷贝到 src/main/resources/static 目录下面
73 |
74 | # 后台
75 | mvn clean install
76 | ```
77 |
78 | 部署:
79 |
80 | ```bash
81 | # 上一步会在生成 war/ROOT.war
82 | # 方式1,直接运行 war 包
83 | nohup java -jar ROOT.war &
84 | # 方式2,丢到 tomcat 的 webapps 目录下面
85 | ```
86 |
87 |
88 |
--------------------------------------------------------------------------------
/fe/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/fe/.eslintignore:
--------------------------------------------------------------------------------
1 | src/**/*-test.js
2 | src/public
--------------------------------------------------------------------------------
/fe/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "semi": [
5 | 2,
6 | "never"
7 | ],
8 | "no-console": 0,
9 | "comma-dangle": [
10 | 2,
11 | "always-multiline"
12 | ],
13 | "max-len": 0,
14 | "react/jsx-first-prop-new-line": 0,
15 | "react/jsx-filename-extension": 0,
16 | "space-before-function-paren": [
17 | 2,
18 | "always"
19 | ],
20 | "no-unused-expressions": [
21 | 0,
22 | {
23 | "allowShortCircuit": true,
24 | "allowTernary": true
25 | }
26 | ],
27 | "arrow-body-style": [
28 | 0,
29 | "never"
30 | ],
31 | "func-names": 0,
32 | "prefer-const": 0,
33 | "no-extend-native": 0,
34 | "no-param-reassign": 0,
35 | "no-restricted-syntax": 0,
36 | "no-eval": 0,
37 | "no-continue": 0,
38 | "no-script-url": "off",
39 | "react/jsx-no-bind": 0,
40 | "no-unused-vars": [
41 | 2,
42 | {
43 | "ignoreRestSiblings": true
44 | }
45 | ],
46 | "no-underscore-dangle": 0,
47 | "global-require": 0,
48 | "import/no-unresolved": 0,
49 | "import/extensions": 0,
50 | "jsx-a11y/href-no-hash": 0,
51 | "react/no-array-index-key": 0,
52 | "react/require-default-props": 0,
53 | "react/forbid-prop-types": 0,
54 | "react/no-string-refs": 0,
55 | "react/no-find-dom-node": 0,
56 | "import/no-extraneous-dependencies": 0,
57 | "import/prefer-default-export": 0,
58 | "react/no-danger": 0,
59 | "jsx-a11y/no-static-element-interactions": 0
60 | },
61 | "parser": "babel-eslint",
62 | "parserOptions": {
63 | "sourceType": "module",
64 | "ecmaVersion": 8,
65 | "ecmaFeatures": {
66 | "jsx": true,
67 | "experimentalObjectRestSpread": true
68 | }
69 | },
70 | "globals": {
71 | "document": true,
72 | "window": true
73 | },
74 | "env": {
75 | "browser": true
76 | },
77 | "settings": {
78 | "import/resolver": "node"
79 | }
80 | }
--------------------------------------------------------------------------------
/fe/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | yarn.lock
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/fe/.roadhogrc.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { version } = require('./package.json')
3 |
4 | const svgSpriteDirs = [
5 | path.resolve(__dirname, 'src/svg/'),
6 | require.resolve('antd').replace(/index\.js$/, ''),
7 | ]
8 |
9 | export default {
10 | entry: 'src/index.js',
11 | svgSpriteLoaderDirs: svgSpriteDirs,
12 | theme: "./theme.config.js",
13 | publicPath: `/${version}/`,
14 | outputPath: `./dist/${version}`,
15 | "extraBabelPlugins": ["transform-runtime"],
16 | "env": {
17 | "development": {
18 | "extraBabelPlugins": [
19 | "dva-hmr",
20 | ["import", {
21 | "libraryName": "antd",
22 | "style": true
23 | }]
24 | ],
25 | "proxy": [{
26 | "context": ['/api/v1', '/common'],
27 | "target": "http://localhost:7000/",
28 | "changeOrigin": true,
29 | "pathRewrite": { "^/api/v1": "" }
30 | }],
31 | },
32 | "production": {
33 | "extraBabelPlugins": [
34 | ["import", { "libraryName": "antd", "style": true }]
35 | ]
36 | }
37 | },
38 | "dllPlugin": {
39 | exclude: ["babel-runtime", "roadhog", "cross-env"],
40 | inclue: ["dva/router", "dva/saga", "dva/fetch"]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/fe/LICENSE:
--------------------------------------------------------------------------------
1 | MIT LICENSE
2 |
3 | Copyright (c) 2016 zuiidea (zuiiidea@gmail.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/fe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "antd-admin-springboot",
4 | "version": "4.3.4",
5 | "dependencies": {
6 | "antd": "^2.13.0",
7 | "array-to-tree": "^2.2.1",
8 | "axios": "^0.19.2",
9 | "babel-runtime": "^6.26.0",
10 | "classnames": "^2.2.5",
11 | "cross-env": "^5.0.5",
12 | "draftjs-to-markdown": "^0.4.4",
13 | "dva": "^2.0.1",
14 | "dva-loading": "^0.2.0",
15 | "ejs-loader": "^0.3.0",
16 | "echarts-for-react": "^1.4.4",
17 | "history": "^4.7.2",
18 | "js-beautify": "^1.6.14",
19 | "jsonp": "^0.2.1",
20 | "less-vars-to-js": "^1.1.2",
21 | "lodash": "^4.17.4",
22 | "moment": "^2.18.1",
23 | "nprogress": "^0.2.0",
24 | "qs": "^6.5.1",
25 | "query-string": "^5.0.0",
26 | "rc-tween-one": "^1.3.1",
27 | "react": "^15.6.1",
28 | "react-countup": "1.3.0",
29 | "react-dom": "^15.6.1",
30 | "react-draft-wysiwyg": "^1.10.8",
31 | "react-helmet": "^5.2.0"
32 | },
33 | "devDependencies": {
34 | "babel-eslint": "^7.2.3",
35 | "babel-plugin-dev-expression": "^0.2.1",
36 | "babel-plugin-dva-hmr": "^0.3.2",
37 | "babel-plugin-import": "^1.4.0",
38 | "babel-plugin-transform-runtime": "^6.23.0",
39 | "babel-polyfill": "^6.26.0",
40 | "copy-webpack-plugin": "^4.0.1",
41 | "draftjs-to-html": "^0.7.5",
42 | "dva-model-extend": "^0.1.1",
43 | "eslint": "^4.1.1",
44 | "eslint-config-airbnb": "^15.0.2",
45 | "eslint-import-resolver-node": "^0.3.1",
46 | "eslint-loader": "^1.9.0",
47 | "eslint-plugin-import": "^2.6.1",
48 | "eslint-plugin-jsx-a11y": "^6.0.2",
49 | "eslint-plugin-react": "^7.1.0",
50 | "html-webpack-plugin": "^2.29.0",
51 | "mockjs": "^1.0.1-beta3",
52 | "rc-checkbox": "^2.0.3",
53 | "redbox-react": "^1.5.0",
54 | "roadhog": "^1.2.2",
55 | "recharts": "^0.19.0"
56 | },
57 | "pre-commit": [
58 | "lint"
59 | ],
60 | "scripts": {
61 | "dev": "cross-env BROWSER=none HOST=0.0.0.0 roadhog server",
62 | "lint": "eslint --fix --ext .js src",
63 | "build": "roadhog build",
64 | "build:dll": "roadhog buildDll"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/fe/src/components/DataTable/AnimTableBody.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { TweenOneGroup } from 'rc-tween-one'
4 |
5 | const enterAnim = [
6 | {
7 | opacity: 0,
8 | x: 30,
9 | backgroundColor: '#fffeee',
10 | duration: 0,
11 | }, {
12 | height: 0,
13 | duration: 200,
14 | type: 'from',
15 | delay: 250,
16 | ease: 'easeOutQuad',
17 | onComplete: (e) => {
18 | e.target.style.height = 'auto'
19 | },
20 | }, {
21 | opacity: 1,
22 | x: 0,
23 | duration: 250,
24 | ease: 'easeOutQuad',
25 | }, {
26 | delay: 1000,
27 | backgroundColor: '#fff',
28 | },
29 | ]
30 |
31 | const leaveAnim = [
32 | {
33 | duration: 250,
34 | x: -30,
35 | opacity: 0,
36 | }, {
37 | height: 0,
38 | duration: 200,
39 | ease: 'easeOutQuad',
40 | },
41 | ]
42 |
43 | const AnimTableBody = ({ body, page = 1, current }) => {
44 | if (current !== +page) {
45 | return body
46 | }
47 |
48 | return (
49 |
56 | {body.props.children}
57 |
58 | )
59 | }
60 |
61 | AnimTableBody.propTypes = {
62 | body: PropTypes.element,
63 | page: PropTypes.any,
64 | current: PropTypes.number.isRequired,
65 | }
66 |
67 | export default AnimTableBody
68 |
--------------------------------------------------------------------------------
/fe/src/components/DataTable/DataTable.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table } from 'antd'
4 | import { request } from '../../utils'
5 | import lodash from 'lodash'
6 | import './DataTable.less'
7 |
8 | class DataTable extends React.Component {
9 | constructor(props) {
10 | super(props)
11 | const {
12 | dataSource, pagination = {
13 | showSizeChanger: true,
14 | showQuickJumper: true,
15 | showTotal: total => `共 ${total} 条`,
16 | current: 1,
17 | total: 100
18 | },
19 | } = props
20 | this.state = {
21 | loading: false,
22 | dataSource,
23 | fetchData: {},
24 | pagination,
25 | }
26 | }
27 |
28 | componentDidMount() {
29 | if (this.props.fetch) {
30 | this.fetch()
31 | }
32 | }
33 |
34 | componentWillReceiveProps(nextProps) {
35 | const staticNextProps = lodash.cloneDeep(nextProps)
36 | delete staticNextProps.columns
37 | const { columns, ...otherProps } = this.props
38 |
39 | if (!lodash.isEqual(staticNextProps, otherProps)) {
40 | this.props = nextProps
41 | this.fetch()
42 | }
43 | }
44 |
45 | handleTableChange = (pagination, filters, sorter) => {
46 | const pager = this.state.pagination
47 | pager.current = pagination.current
48 | this.setState({
49 | pagination: pager,
50 | fetchData: {
51 | results: pagination.pageSize,
52 | page: pagination.current,
53 | sortField: sorter.field,
54 | sortOrder: sorter.order,
55 | ...filters,
56 | },
57 | }, () => {
58 | this.fetch()
59 | })
60 | }
61 |
62 | fetch = () => {
63 | const { fetch: { url, data, dataKey } } = this.props
64 | const { fetchData } = this.state
65 | this.setState({ loading: true })
66 | this.promise = request({
67 | url,
68 | data: {
69 | ...data,
70 | ...fetchData,
71 | },
72 | }).then((result) => {
73 | if (!this.refs.DataTable) {
74 | return
75 | }
76 | const { pagination } = this.state
77 | pagination.total = result.total || pagination.total
78 | this.setState({
79 | loading: false,
80 | dataSource: dataKey ? result[dataKey] : result.data,
81 | pagination,
82 | })
83 | })
84 | }
85 |
86 | render() {
87 | const { fetch, ...tableProps } = this.props
88 | const { loading, dataSource, pagination } = this.state
89 |
90 | return (
)
99 | }
100 | }
101 |
102 |
103 | DataTable.propTypes = {
104 | fetch: PropTypes.object,
105 | rowKey: PropTypes.string,
106 | pagination: React.PropTypes.oneOfType([
107 | React.PropTypes.bool,
108 | React.PropTypes.object,
109 | ]),
110 | columns: PropTypes.array,
111 | dataSource: PropTypes.array,
112 | }
113 |
114 | export default DataTable
115 |
--------------------------------------------------------------------------------
/fe/src/components/DataTable/DataTable.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/components/DataTable/DataTable.less
--------------------------------------------------------------------------------
/fe/src/components/DataTable/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DataTable",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./DataTable.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/DownloadButton/icon.js:
--------------------------------------------------------------------------------
1 | module.exports = (
2 | ''
8 | );
9 |
--------------------------------------------------------------------------------
/fe/src/components/DownloadButton/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 | import DownloadIcon from './icon';
4 |
5 | class DownloadButton extends Component {
6 | constructor() {
7 | super();
8 | }
9 |
10 | render() {
11 | return (
12 |
34 | )
35 | }
36 | }
37 |
38 | export default DownloadButton;
39 |
--------------------------------------------------------------------------------
/fe/src/components/DropOption/DropOption.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Dropdown, Button, Icon, Menu } from 'antd'
4 |
5 | const DropOption = ({ onMenuClick, menuOptions = [], buttonStyle, dropdownProps }) => {
6 | const menu = menuOptions.map(item => {item.name})
7 | return ({menu}}
9 | {...dropdownProps}
10 | >
11 |
15 | )
16 | }
17 |
18 | DropOption.propTypes = {
19 | onMenuClick: PropTypes.func,
20 | menuOptions: PropTypes.array.isRequired,
21 | buttonStyle: PropTypes.object,
22 | dropdownProps: PropTypes.object,
23 | }
24 |
25 | export default DropOption
26 |
--------------------------------------------------------------------------------
/fe/src/components/DropOption/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DropOption",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./DropOption.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/Editor/Editor.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Editor } from 'react-draft-wysiwyg'
3 | import styles from './Editor.less'
4 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
5 |
6 |
7 | const DraftEditor = (props) => {
8 | return ()
9 | }
10 |
11 | export default DraftEditor
12 |
--------------------------------------------------------------------------------
/fe/src/components/Editor/Editor.less:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | height: 500px;
3 |
4 | :global {
5 | .rdw-dropdownoption-default {
6 | padding: 6px;
7 | }
8 |
9 | .rdw-dropdown-optionwrapper {
10 | box-sizing: content-box;
11 | width: 100%;
12 | border-radius: 0 0 2px 2px;
13 | }
14 |
15 | .rdw-inline-wrapper {
16 | flex-wrap: wrap;
17 | margin-bottom: 0;
18 |
19 | .rdw-option-wrapper {
20 | margin-bottom: 6px;
21 | }
22 | }
23 |
24 | .rdw-option-active {
25 | box-shadow: 1px 1px 0 #e8e8e8 inset;
26 | }
27 |
28 | .rdw-dropdown-optionwrapper {
29 | &:hover {
30 | box-shadow: none;
31 | }
32 | }
33 |
34 | .rdw-colorpicker-option {
35 | box-shadow: none;
36 | }
37 |
38 | .rdw-colorpicker-modal,
39 | .rdw-embedded-modal,
40 | .rdw-emoji-modal,
41 | .rdw-image-modal,
42 | .rdw-link-modal {
43 | box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
44 | }
45 |
46 | .rdw-colorpicker-modal,
47 | .rdw-embedded-modal,
48 | .rdw-link-modal {
49 | height: auto;
50 | }
51 |
52 | .rdw-emoji-modal {
53 | width: 214px;
54 | }
55 |
56 | .rdw-colorpicker-modal {
57 | width: auto;
58 | }
59 |
60 | .rdw-embedded-modal-btn,
61 | .rdw-image-modal-btn,
62 | .rdw-link-modal-btn {
63 | height: 32px;
64 | margin-top: 12px;
65 | }
66 |
67 | .rdw-embedded-modal-input,
68 | .rdw-embedded-modal-size-input,
69 | .rdw-link-modal-input {
70 | padding: 2px 6px;
71 | height: 32px;
72 | }
73 |
74 | .rdw-dropdown-selectedtext {
75 | color: #000;
76 | }
77 |
78 | .rdw-dropdown-wrapper,
79 | .rdw-option-wrapper {
80 | min-width: 36px;
81 | transition: all 0.2s ease;
82 | height: 30px;
83 |
84 | &:active {
85 | box-shadow: 1px 1px 0 #e8e8e8 inset;
86 | }
87 |
88 | &:hover {
89 | box-shadow: 1px 1px 0 #e8e8e8;
90 | }
91 | }
92 |
93 | .rdw-dropdown-wrapper {
94 | min-width: 60px;
95 | }
96 |
97 | .rdw-editor-main {
98 | box-sizing: border-box;
99 | }
100 | }
101 |
102 | .editor {
103 | border: 1px solid #F1F1F1;
104 | padding: 5px;
105 | border-radius: 2px;
106 | height: auto;
107 | min-height: 200px;
108 | }
109 |
110 | .toolbar {
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/fe/src/components/Editor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Editor",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./Editor.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/FilterItem/FilterItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import styles from './FilterItem.less'
4 |
5 | const FilterItem = ({
6 | label = '',
7 | children,
8 | }) => {
9 | const labelArray = label.split('')
10 | return (
11 |
12 | {labelArray.length > 0
13 | ?
14 | {labelArray.map((item, index) => {item})}
15 |
16 | : ''}
17 |
18 | {children}
19 |
20 |
21 | )
22 | }
23 |
24 | FilterItem.propTypes = {
25 | label: PropTypes.string,
26 | children: PropTypes.any,
27 | }
28 |
29 | export default FilterItem
30 |
--------------------------------------------------------------------------------
/fe/src/components/FilterItem/FilterItem.less:
--------------------------------------------------------------------------------
1 | .filterItem {
2 | display: flex;
3 | justify-content: space-between;
4 |
5 | .labelWrap {
6 | width: 64px;
7 | line-height: 28px;
8 | margin-right: 12px;
9 | justify-content: space-between;
10 | display: flex;
11 | overflow: hidden;
12 | }
13 |
14 | .item {
15 | flex: 1;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/fe/src/components/FilterItem/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FilterItem",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./FilterItem.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/Iconfont/Iconfont.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import './iconfont.less'
4 |
5 | const Iconfont = ({ type, colorful }) => {
6 | if (colorful) {
7 | return (`,
10 | }}
11 | />)
12 | }
13 | return
14 | }
15 |
16 | Iconfont.propTypes = {
17 | type: PropTypes.string,
18 | colorful: PropTypes.bool,
19 | }
20 |
21 | export default Iconfont
22 |
--------------------------------------------------------------------------------
/fe/src/components/Iconfont/iconfont.less:
--------------------------------------------------------------------------------
1 | :global .colorful-icon {
2 | width: 1em;
3 | height: 1em;
4 | vertical-align: -0.15em;
5 | fill: currentColor;
6 | overflow: hidden;
7 | font-size: 16px;
8 | }
9 |
--------------------------------------------------------------------------------
/fe/src/components/Iconfont/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Iconfont",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./Iconfont.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/InputClose/InputClose.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactDOM from 'react-dom'
4 | import styles from './InputClose.less'
5 | import { Input, Select, Button, Icon } from 'antd'
6 |
7 | class InputClose extends React.Component {
8 |
9 | // props 设置默认值
10 | static defaultProps = {
11 | name: 'default'
12 | }
13 |
14 | constructor(props) {
15 | super(props)
16 | this.state = {
17 | value: this.props.value,
18 | show: false
19 | }
20 | }
21 |
22 | // https://facebook.github.io/react/docs/handling-events.html
23 | // 如果使用 emitEmpty(){...} 就需要在组件里面通过 this.emitEmpty.bind(this)
24 | emitEmpty = () => {
25 | // 找到真实 dom
26 | // ReactDOM.findDOMNode(this.refs.searchInput).querySelector('input').value=""
27 | this.refs.searchInput.refs.input.value = ''
28 | this.setState({
29 | show: false,
30 | value: ''
31 | })
32 | // 默认页面链接 ?name=xiaoming
33 | // 如果使用 null,则链接为 ?name
34 | // 如果使用'',页面链接为 ?name=
35 | // 如果使用 undefined,则页面链接不会出现该字段
36 | this.props.onChange(undefined)
37 | }
38 |
39 | change = (v) => {
40 | this.setState({
41 | show: !!v.target.value,
42 | value: v.target.value
43 | })
44 | this.props.onChange(v.target.value || undefined)
45 | }
46 |
47 | // TODO:通过getFieldDecorator包装之后,resetFields不会刷新
48 | render() {
49 | return (
50 |
51 | }
57 | />
58 |
59 | )
60 | }
61 | }
62 |
63 | InputClose.propTypes = {
64 | style: PropTypes.object,
65 | placeholder: PropTypes.string,
66 | initialValue: PropTypes.string,
67 | onChange: PropTypes.func
68 | }
69 |
70 | export default InputClose
71 |
--------------------------------------------------------------------------------
/fe/src/components/InputClose/InputClose.less:
--------------------------------------------------------------------------------
1 | .show {
2 | cursor: pointer;
3 | color: #ffffff;
4 |
5 | &:hover {
6 | cursor: pointer;
7 | color: #999;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/fe/src/components/InputClose/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "InputClose",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./InputClose.js",
6 | "description": "拓展原生 Input,添加 autoClear"
7 | }
8 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Bread.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Breadcrumb, Icon } from 'antd'
4 | import { Link } from 'react-router-dom'
5 | import pathToRegexp from 'path-to-regexp'
6 | import styles from './Bread.less'
7 | import { queryArray } from '../../utils'
8 |
9 | const Bread = ({ menu }) => {
10 | // 匹配当前路由
11 | let pathArray = []
12 | let current
13 | for (let index in menu) {
14 | if (menu[index].router && pathToRegexp(menu[index].router).exec(location.pathname)) {
15 | current = menu[index]
16 | break
17 | }
18 | }
19 |
20 | const getPathArray = (item) => {
21 | pathArray.unshift(item)
22 | if (item.bpid) {
23 | getPathArray(queryArray(menu, item.bpid, 'id'))
24 | }
25 | }
26 |
27 | if (!current) {
28 | pathArray.push(menu[0])
29 | pathArray.push({
30 | id: 404,
31 | name: 'Not Found',
32 | })
33 | } else {
34 | getPathArray(current)
35 | }
36 |
37 | // 递归查找父级
38 | const breads = pathArray.map((item, key) => {
39 | const content = (
40 | {(item && item.icon)
41 | ?
42 | : ''}{item && item.name}
43 | )
44 | return (
45 |
46 | {((pathArray.length - 1) !== key)
47 | ?
48 | {content}
49 |
50 | : content}
51 |
52 | )
53 | })
54 |
55 | return (
56 |
57 |
58 | {breads}
59 |
60 |
61 | )
62 | }
63 |
64 | Bread.propTypes = {
65 | menu: PropTypes.array,
66 | }
67 |
68 | export default Bread
69 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Bread.less:
--------------------------------------------------------------------------------
1 | @import "../../themes/vars.less";
2 |
3 | .bread {
4 | height: 64px;
5 | line-height: 64px;
6 | padding: 0 24px;
7 | margin-bottom: -24px;
8 | }
9 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { config } from 'utils'
3 | import { Link } from 'dva/router'
4 | import styles from './Footer.less'
5 |
6 | const Footer = () => (
7 | {config.footerText}
8 | {`V ${config.version}`}
9 |
)
10 |
11 | export default Footer
12 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Footer.less:
--------------------------------------------------------------------------------
1 | @import "../../themes/vars.less";
2 |
3 | .footer {
4 | height: 48px;
5 | line-height: 48px;
6 | text-align: center;
7 | font-size: 12px;
8 | color: #999;
9 | background: #fff;
10 | box-shadow: @shadow-2;
11 | width: 100%;
12 | }
13 | .version {
14 | float:right;
15 | margin-right: 10px;
16 | }
17 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Menu, Icon, Popover } from 'antd'
4 | import styles from './Header.less'
5 | import Menus from './Menu'
6 |
7 | const SubMenu = Menu.SubMenu
8 |
9 | const Header = ({ user, logout, switchSider, siderFold, isNavbar, menuPopoverVisible, location, switchMenuPopover, navOpenKeys, changeOpenKeys, menu }) => {
10 | let handleClickMenu = e => e.key === 'logout' && logout()
11 | const menusProps = {
12 | menu,
13 | siderFold: false,
14 | darkTheme: false,
15 | isNavbar,
16 | handleClickNavMenu: switchMenuPopover,
17 | location,
18 | navOpenKeys,
19 | changeOpenKeys,
20 | }
21 | return (
22 |
23 | {isNavbar
24 | ?
}>
25 |
26 |
27 |
28 |
29 | :
30 |
31 |
}
32 |
33 |
34 |
35 |
36 |
43 |
44 |
45 | )
46 | }
47 |
48 | Header.propTypes = {
49 | menu: PropTypes.array,
50 | user: PropTypes.object,
51 | logout: PropTypes.func,
52 | switchSider: PropTypes.func,
53 | siderFold: PropTypes.bool,
54 | isNavbar: PropTypes.bool,
55 | menuPopoverVisible: PropTypes.bool,
56 | location: PropTypes.object,
57 | switchMenuPopover: PropTypes.func,
58 | navOpenKeys: PropTypes.array,
59 | changeOpenKeys: PropTypes.func,
60 | }
61 |
62 | export default Header
63 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Header.less:
--------------------------------------------------------------------------------
1 | @import "../../themes/vars.less";
2 |
3 | .header {
4 | :global .ant-menu-horizontal {
5 | & > .ant-menu-submenu {
6 | float: right;
7 | }
8 | border: none;
9 | display: inline-block;
10 | }
11 | box-shadow: @shadow-2;
12 | position: relative;
13 | display: flex;
14 | justify-content: space-between;
15 | height: 47px;
16 | background-color: #fff;
17 |
18 | .rightWarpper {
19 | display: flex;
20 | padding-right: 16px;
21 | }
22 |
23 | .button {
24 | width: 47px;
25 | height: 47px;
26 | line-height: 47px;
27 | text-align: center;
28 | font-size: 18px;
29 | cursor: pointer;
30 | transition: @transition-ease-in;
31 |
32 | &:hover {
33 | color: @primary-color;
34 | background-color: fade(@primary-color, 15%);
35 | }
36 | }
37 | }
38 |
39 | .popovermenu {
40 | width: 280px;
41 | margin-left: 6px;
42 |
43 | :global .ant-popover-inner-content {
44 | padding: 0;
45 |
46 | .ant-menu-inline .ant-menu-item,
47 | .ant-menu-vertical .ant-menu-item {
48 | border-right: 0;
49 | height: 48px;
50 | line-height: 48px;
51 | }
52 |
53 | .ant-menu-inline .ant-menu-item-selected,
54 | .ant-menu-inline .ant-menu-selected {
55 | border-right: 0;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Sider.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Icon, Switch } from 'antd'
4 | import styles from './Layout.less'
5 | import { config } from '../../utils'
6 | import Menus from './Menu'
7 |
8 | const Sider = ({ siderFold, darkTheme, location, changeTheme, navOpenKeys, changeOpenKeys, menu }) => {
9 | const menusProps = {
10 | menu,
11 | siderFold,
12 | darkTheme,
13 | location,
14 | navOpenKeys,
15 | changeOpenKeys,
16 | }
17 | return (
18 |
19 |
20 |

21 | {siderFold ? '' :
{config.name}}
22 |
23 |
24 | {!siderFold ?
25 | 切换主题
26 |
27 |
: ''}
28 |
29 | )
30 | }
31 |
32 | Sider.propTypes = {
33 | menu: PropTypes.array,
34 | siderFold: PropTypes.bool,
35 | darkTheme: PropTypes.bool,
36 | location: PropTypes.object,
37 | changeTheme: PropTypes.func,
38 | navOpenKeys: PropTypes.array,
39 | changeOpenKeys: PropTypes.func,
40 | }
41 |
42 | export default Sider
43 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/Sider.less:
--------------------------------------------------------------------------------
1 | @import "../../themes/vars.less";
2 |
--------------------------------------------------------------------------------
/fe/src/components/Layout/index.js:
--------------------------------------------------------------------------------
1 | import Header from './Header'
2 | import Menu from './Menu'
3 | import Bread from './Bread'
4 | import Sider from './Sider'
5 | import Footer from './Footer'
6 | import styles from './Layout.less'
7 |
8 | export { Header, Menu, Bread, Sider, Footer, styles }
9 |
--------------------------------------------------------------------------------
/fe/src/components/Loader/Loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classNames from 'classnames'
4 | import styles from './Loader.less'
5 |
6 | const Loader = ({ spinning, fullScreen }) => {
7 | return ()
17 | }
18 |
19 |
20 | Loader.propTypes = {
21 | spinning: PropTypes.bool,
22 | fullScreen: PropTypes.bool,
23 | }
24 |
25 | export default Loader
26 |
--------------------------------------------------------------------------------
/fe/src/components/Loader/Loader.less:
--------------------------------------------------------------------------------
1 | .loader {
2 | display: block;
3 | background-color: #fff;
4 | width: 100%;
5 | position: absolute;
6 | top: 0;
7 | bottom: 0;
8 | left: 0;
9 | z-index: 100000;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | opacity: 1;
14 | text-align: center;
15 |
16 | &.fullScreen {
17 | position: fixed;
18 | }
19 |
20 | .warpper {
21 | width: 100px;
22 | height: 100px;
23 | display: inline-flex;
24 | flex-direction: column;
25 | justify-content: space-around;
26 | }
27 |
28 | .inner {
29 | width: 48px;
30 | height: 48px;
31 | margin: 0 auto;
32 | text-indent: -12345px;
33 | border-top: 1px solid rgba(0, 0, 0, 0.08);
34 | border-right: 1px solid rgba(0, 0, 0, 0.08);
35 | border-bottom: 1px solid rgba(0, 0, 0, 0.08);
36 | border-left: 1px solid rgba(0, 0, 0, 0.7);
37 | border-radius: 50%;
38 | z-index: 100001;
39 |
40 | :local {
41 | animation: spinner 600ms infinite linear;
42 | }
43 | }
44 |
45 | .text {
46 | width: 100px;
47 | height: 20px;
48 | text-align: center;
49 | font-size: 12px;
50 | letter-spacing: 4px;
51 | color: #000;
52 | }
53 |
54 | &.hidden {
55 | z-index: -1;
56 | opacity: 0;
57 | transition: opacity 1s ease 0.5s, z-index 0.1s ease 1.5s;
58 | }
59 | }
60 | @keyframes spinner {
61 | 0% {
62 | transform: rotate(0deg);
63 | }
64 |
65 | 100% {
66 | transform: rotate(360deg);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/fe/src/components/Loader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Loader",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./Loader.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/Search/Search.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactDOM from 'react-dom'
4 | import styles from './Search.less'
5 | import { Input, Select, Button, Icon } from 'antd'
6 |
7 | class Search extends React.Component {
8 | state = {
9 | clearVisible: false,
10 | selectValue: (this.props.select && this.props.selectProps) ? this.props.selectProps.defaultValue : '',
11 | }
12 | handleSearch = () => {
13 | const data = {
14 | keyword: ReactDOM.findDOMNode(this.refs.searchInput).value,
15 | }
16 | if (this.props.select) {
17 | data.field = this.state.selectValue
18 | }
19 | if (this.props.onSearch) this.props.onSearch(data)
20 | }
21 | handleInputChange = e => {
22 | this.setState({
23 | ...this.state,
24 | clearVisible: e.target.value !== '',
25 | })
26 | }
27 | handeleSelectChange = value => {
28 | this.setState({
29 | ...this.state,
30 | selectValue: value,
31 | })
32 | }
33 | handleClearInput = () => {
34 | ReactDOM.findDOMNode(this.refs.searchInput).value = ''
35 | this.setState({
36 | clearVisible: false,
37 | })
38 | this.handleSearch()
39 | }
40 |
41 | render() {
42 | const { size, select, selectOptions, selectProps, style, keyword } = this.props
43 | const { clearVisible } = this.state
44 | return (
45 |
46 | {select && }
49 |
50 |
51 | {clearVisible && }
52 |
53 | )
54 | }
55 | }
56 |
57 |
58 | Search.propTypes = {
59 | size: PropTypes.string,
60 | select: PropTypes.bool,
61 | selectProps: PropTypes.object,
62 | onSearch: PropTypes.func,
63 | selectOptions: PropTypes.array,
64 | style: PropTypes.object,
65 | keyword: PropTypes.string,
66 | }
67 |
68 | export default Search
69 |
--------------------------------------------------------------------------------
/fe/src/components/Search/Search.less:
--------------------------------------------------------------------------------
1 | .no-highlight() {
2 | border-color: #e5e5e5;
3 | box-shadow: none;
4 | }
5 |
6 | .search {
7 | display: flex;
8 | width: 100%;
9 | position: relative;
10 |
11 | :global {
12 | .anticon-cross {
13 | position: absolute;
14 | width: 18px;
15 | height: 18px;
16 | right: 84px;
17 | cursor: pointer;
18 | color: #fff;
19 | line-height: 18px;
20 | border-radius: 50% !important;
21 | background-color: rgba(0, 0, 0, .16);
22 | top: 7px;
23 | // opacity:
24 | }
25 |
26 | .ant-select {
27 | width: 80px;
28 | flex-shrink: 0;
29 | flex-grow: 0;
30 |
31 | &.ant-select-focused .ant-select-selection,
32 | &.ant-select-open .ant-select-selection,
33 | .ant-select-selection:active,
34 | .ant-select-selection:focus,
35 | .ant-select-selection:hover {
36 | .no-highlight();
37 | }
38 | }
39 |
40 | .ant-input {
41 | &:focus,
42 | &:hover {
43 | .no-highlight();
44 | }
45 | flex-shrink: 1;
46 | flex-grow: 1;
47 | }
48 |
49 | .ant-btn {
50 | width: 80px;
51 | flex-shrink: 0;
52 | flex-grow: 0;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/fe/src/components/Search/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Search",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./Search.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/SearchTree/SearchTree.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Tree, Input, } from 'antd';
3 |
4 | const Search = Input.Search;
5 | const TreeNode = Tree.TreeNode;
6 |
7 | const getParentKey = (key, tree) => {
8 | let parentKey;
9 | for (let i = 0; i < tree.length; i++) {
10 | const node = tree[i];
11 | if (node.children) {
12 | if (node.children.some(item => item.key === key)) {
13 | parentKey = node.key;
14 | } else if (getParentKey(key, node.children)) {
15 | parentKey = getParentKey(key, node.children);
16 | }
17 | }
18 | }
19 | return parentKey;
20 | };
21 |
22 | class SearchTree extends React.Component {
23 | state = {
24 | expandedKeys: [],
25 | searchValue: '',
26 | autoExpandParent: true,
27 | }
28 | onExpand = (expandedKeys) => {
29 | this.setState({
30 | expandedKeys,
31 | autoExpandParent: false,
32 | });
33 | }
34 | onChange = (e) => {
35 | const value = e.target.value;
36 | const expandedKeys = dataList.map((item) => {
37 | if (item.key.indexOf(value) > -1) {
38 | return getParentKey(item.key, gData);
39 | }
40 | return null;
41 | }).filter((item, i, self) => item && self.indexOf(item) === i);
42 | this.setState({
43 | expandedKeys,
44 | searchValue: value,
45 | autoExpandParent: true,
46 | });
47 | }
48 |
49 | render() {
50 | const { searchValue, expandedKeys, autoExpandParent } = this.state;
51 | const loop = data => data.map((item) => {
52 | const index = item.key.search(searchValue);
53 | const beforeStr = item.key.substr(0, index);
54 | const afterStr = item.key.substr(index + searchValue.length);
55 | const title = index > -1 ? (
56 |
57 | {beforeStr}
58 | {searchValue}
59 | {afterStr}
60 |
61 | ) : {item.key};
62 | if (item.children) {
63 | return (
64 |
65 | {loop(item.children)}
66 |
67 | );
68 | }
69 | return ;
70 | });
71 | return (
72 |
73 |
74 |
79 | {loop(gData)}
80 |
81 |
82 | );
83 | }
84 | }
85 |
86 | export default SearchTree
87 |
--------------------------------------------------------------------------------
/fe/src/components/SearchTree/SearchTree.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SearchTree",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./SearchTree.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/components/SearchTree/SearchTree.less:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | height: 500px;
3 |
4 | :global {
5 | .rdw-dropdownoption-default {
6 | padding: 6px;
7 | }
8 |
9 | .rdw-dropdown-optionwrapper {
10 | box-sizing: content-box;
11 | width: 100%;
12 | border-radius: 0 0 2px 2px;
13 | }
14 |
15 | .rdw-inline-wrapper {
16 | flex-wrap: wrap;
17 | margin-bottom: 0;
18 |
19 | .rdw-option-wrapper {
20 | margin-bottom: 6px;
21 | }
22 | }
23 |
24 | .rdw-option-active {
25 | box-shadow: 1px 1px 0 #e8e8e8 inset;
26 | }
27 |
28 | .rdw-dropdown-optionwrapper {
29 | &:hover {
30 | box-shadow: none;
31 | }
32 | }
33 |
34 | .rdw-colorpicker-option {
35 | box-shadow: none;
36 | }
37 |
38 | .rdw-colorpicker-modal,
39 | .rdw-embedded-modal,
40 | .rdw-emoji-modal,
41 | .rdw-image-modal,
42 | .rdw-link-modal {
43 | box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
44 | }
45 |
46 | .rdw-colorpicker-modal,
47 | .rdw-embedded-modal,
48 | .rdw-link-modal {
49 | height: auto;
50 | }
51 |
52 | .rdw-emoji-modal {
53 | width: 214px;
54 | }
55 |
56 | .rdw-colorpicker-modal {
57 | width: auto;
58 | }
59 |
60 | .rdw-embedded-modal-btn,
61 | .rdw-image-modal-btn,
62 | .rdw-link-modal-btn {
63 | height: 32px;
64 | margin-top: 12px;
65 | }
66 |
67 | .rdw-embedded-modal-input,
68 | .rdw-embedded-modal-size-input,
69 | .rdw-link-modal-input {
70 | padding: 2px 6px;
71 | height: 32px;
72 | }
73 |
74 | .rdw-dropdown-selectedtext {
75 | color: #000;
76 | }
77 |
78 | .rdw-dropdown-wrapper,
79 | .rdw-option-wrapper {
80 | min-width: 36px;
81 | transition: all 0.2s ease;
82 | height: 30px;
83 |
84 | &:active {
85 | box-shadow: 1px 1px 0 #e8e8e8 inset;
86 | }
87 |
88 | &:hover {
89 | box-shadow: 1px 1px 0 #e8e8e8;
90 | }
91 | }
92 |
93 | .rdw-dropdown-wrapper {
94 | min-width: 60px;
95 | }
96 |
97 | .rdw-editor-main {
98 | box-sizing: border-box;
99 | }
100 | }
101 |
102 | .editor {
103 | border: 1px solid #F1F1F1;
104 | padding: 5px;
105 | border-radius: 2px;
106 | height: auto;
107 | min-height: 200px;
108 | }
109 |
110 | .toolbar {
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/fe/src/components/index.js:
--------------------------------------------------------------------------------
1 | import DataTable from './DataTable'
2 | import DropOption from './DropOption'
3 | import Iconfont from './Iconfont'
4 | import Search from './Search'
5 | import Editor from './Editor'
6 | import FilterItem from './FilterItem'
7 | import Loader from './Loader'
8 | import * as Layout from './Layout/index.js'
9 | import layer from './layer'
10 |
11 | export {
12 | Layout,
13 | DataTable,
14 | DropOption,
15 | Iconfont,
16 | Search,
17 | Editor,
18 | FilterItem,
19 | Loader,
20 | layer,
21 | }
22 |
--------------------------------------------------------------------------------
/fe/src/components/layer/layer.js:
--------------------------------------------------------------------------------
1 | import { Modal, message } from 'antd'
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import classnames from 'classnames'
5 | import styles from './layer.less'
6 | const { info, success, error, warning, confirm } = Modal
7 |
8 | const layer = {
9 | prefixCls: 'ant-layer',
10 | index: 1,
11 | info,
12 | success,
13 | error,
14 | warning,
15 | confirm,
16 | }
17 |
18 | layer.close = (index) => new Promise((resolve, reject) => {
19 | const { prefixCls } = layer
20 | let div = document.getElementById(`${prefixCls}-reference-${index}`)
21 | if (index === undefined) {
22 | const references = document.querySelectorAll(`.${prefixCls}-reference`)
23 | div = references[references.length - 1]
24 | }
25 | if (!div) {
26 | message.error('关闭失败,未找到Dom')
27 | return
28 | }
29 | const unmountResult = ReactDOM.unmountComponentAtNode(div)
30 | if (unmountResult && div.parentNode) {
31 | div.parentNode.removeChild(div)
32 | resolve(index)
33 | } else {
34 | reject(index)
35 | }
36 | })
37 |
38 | layer.closeAll = () => {
39 | const { prefixCls } = layer
40 | const references = document.querySelectorAll(`.${prefixCls}-reference`)
41 | let i = 0
42 | while (i < references.length) {
43 | layer.close()
44 | i++
45 | }
46 | }
47 |
48 | layer.open = (config) => {
49 | const props = Object.assign({}, config)
50 | const { content, ...modalProps } = props
51 | const { className, wrapClassName = '', verticalCenter = true } = modalProps
52 | const { prefixCls } = layer
53 | const index = layer.index++
54 | let div = document.createElement('div')
55 | div.id = `${prefixCls}-reference-${index}`
56 | div.className = `${prefixCls}-reference`
57 | document.body.appendChild(div)
58 |
59 | ReactDOM.render(
60 | {
66 | layer.close(index)
67 | }}
68 | onOk={() => {
69 | layer.close(index)
70 | }}
71 | {...modalProps}
72 | wrapClassName={classnames({ [styles.verticalCenter]: verticalCenter, [wrapClassName]: true })}
73 | className={classnames(prefixCls, className, [`${prefixCls}-${index}`])}
74 | >
75 |
76 | {content}
77 |
78 | , div)
79 |
80 | return index
81 | }
82 |
83 | export default layer
84 |
--------------------------------------------------------------------------------
/fe/src/components/layer/layer.less:
--------------------------------------------------------------------------------
1 | .verticalCenter {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 |
6 | :global .ant-modal {
7 | top: 0;
8 | padding-bottom: 0;
9 |
10 | .ant-modal-body {
11 | overflow-y: auto;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/fe/src/components/layer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "layer",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./layer.js"
6 | }
7 |
--------------------------------------------------------------------------------
/fe/src/entry.ejs:
--------------------------------------------------------------------------------
1 | <% htmlWebpackPlugin.options.headScripts = htmlWebpackPlugin.options.headScripts || [] %>
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | antd admin spring-boot
11 |
15 | <% for (item of htmlWebpackPlugin.options.headScripts) { %>
16 |
17 | <% } %>
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/fe/src/index.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 | import dva from 'dva'
3 | import createLoading from 'dva-loading'
4 | import createHistory from 'history/createBrowserHistory'
5 | import { message } from 'antd'
6 |
7 | // 1. Initialize
8 | const app = dva({
9 | ...createLoading({
10 | effects: true,
11 | }),
12 | history: createHistory(),
13 | onError (error) {
14 | console.log(error)
15 | message.error(error.message, 3)
16 | },
17 | })
18 |
19 | // 2. Model
20 | app.model(require('./models/app'))
21 |
22 | // 3. Router
23 | app.router(require('./router'))
24 |
25 | // 4. Start
26 | app.start('#root')
27 |
--------------------------------------------------------------------------------
/fe/src/models/common.js:
--------------------------------------------------------------------------------
1 | import modelExtend from 'dva-model-extend'
2 |
3 | const model = {
4 | reducers: {
5 | updateState (state, { payload }) {
6 | return {
7 | ...state,
8 | ...payload,
9 | }
10 | },
11 | },
12 | }
13 |
14 | const pageModel = modelExtend(model, {
15 |
16 | state: {
17 | list: [],
18 | pagination: {
19 | showSizeChanger: true,
20 | showQuickJumper: true,
21 | showTotal: total => `Total ${total} Items`,
22 | current: 1,
23 | total: 0,
24 | },
25 | },
26 |
27 | reducers: {
28 | querySuccess (state, { payload }) {
29 | const { list, pagination } = payload
30 | return {
31 | ...state,
32 | list,
33 | pagination: {
34 | ...state.pagination,
35 | ...pagination,
36 | },
37 | }
38 | },
39 | },
40 |
41 | })
42 |
43 | module.exports = {
44 | model,
45 | pageModel,
46 | }
47 |
--------------------------------------------------------------------------------
/fe/src/models/dashboard.js:
--------------------------------------------------------------------------------
1 | import { parse } from 'qs'
2 | import modelExtend from 'dva-model-extend'
3 | import { query } from '../services/dashboard'
4 | import { model } from '../models/common'
5 | import * as weatherService from '../services/weather'
6 |
7 | export default modelExtend(model, {
8 | namespace: 'dashboard',
9 | state: {
10 | weather: {
11 | city: '北京',
12 | dateTime: new Date().format('MM-dd hh:mm'),
13 | temperature: '30',
14 | name: '晴',
15 | icon: '//s5.sencdn.com/web/icons/3d_50/2.png',
16 | },
17 | sales: [],
18 | quote: {
19 | avatar: 'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
20 | },
21 | numbers: [],
22 | recentSales: [],
23 | comments: [],
24 | completed: [],
25 | browser: [],
26 | cpu: {},
27 | user: {
28 | avatar: 'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
29 | },
30 | },
31 | subscriptions: {
32 | setup ({ dispatch, history }) {
33 | history.listen(({ pathname }) => {
34 | if (pathname === '/dashboard' || pathname === '/') {
35 | // dispatch({ type: 'query' })
36 | //dispatch({ type: 'queryWeather' })
37 | }
38 | })
39 | },
40 | },
41 |
42 | effects: {
43 |
44 | *query ({ payload }, { call, put }) {
45 | const data = yield call(query, parse(payload))
46 | yield put({
47 | type: 'updateState',
48 | payload: data,
49 | })
50 | },
51 |
52 | *queryWeather ({ payload = {}, }, { call, put }) {
53 | payload.location = 'beijing'
54 | const result = yield call(weatherService.query, payload)
55 | const { success } = result
56 | if (success) {
57 | const data = result.results[0]
58 | const weather = {
59 | city: data.location.name,
60 | dateTime: new Date().format('MM-dd hh:mm'),
61 | temperature: data.now.temperature,
62 | name: data.now.text,
63 | icon: `//s5.sencdn.com/web/icons/3d_50/${data.now.code}.png`,
64 | }
65 | yield put({
66 | type: 'updateState',
67 | payload: {
68 | weather,
69 | },
70 | })
71 | }
72 | },
73 | },
74 |
75 | })
76 |
--------------------------------------------------------------------------------
/fe/src/models/login.js:
--------------------------------------------------------------------------------
1 | import { routerRedux } from 'dva/router'
2 | import { login } from 'services/login'
3 |
4 | export default {
5 | namespace: 'login',
6 |
7 | state: {},
8 |
9 | effects: {
10 | * login ({
11 | payload,
12 | }, { put, call, select }) {
13 | const data = yield call(login, payload)
14 | const { locationQuery } = yield select(_ => _.app)
15 | if (data.success) {
16 | const { from } = locationQuery
17 | yield put({ type: 'app/query' })
18 | if (from) {
19 | yield put(routerRedux.push(from))
20 | } else {
21 | yield put(routerRedux.push('/dashboard'))
22 | }
23 | } else {
24 | throw data.message
25 | }
26 | },
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/fe/src/models/user.js:
--------------------------------------------------------------------------------
1 | import { parse } from 'qs'
2 | import { create, remove, update } from '../services/user'
3 | import { query } from '../services/users'
4 |
5 | export default {
6 |
7 | namespace: 'user',
8 |
9 | state: {
10 | list: [],
11 | currentItem: {},
12 | modalVisible: false,
13 | modalType: 'create',
14 | isMotion: localStorage.getItem('antdAdminUserIsMotion') === 'true',
15 | pagination: {
16 | showSizeChanger: true,
17 | showQuickJumper: true,
18 | showTotal: total => `共 ${total} 条`,
19 | current: 1,
20 | total: null,
21 | },
22 | },
23 |
24 | subscriptions: {
25 | setup ({ dispatch, history }) {
26 | history.listen((location) => {
27 | if (location.pathname === '/user') {
28 | dispatch({
29 | type: 'query',
30 | payload: location.query,
31 | })
32 | }
33 | })
34 | },
35 | },
36 |
37 | effects: {
38 |
39 | * query ({ payload }, { call, put }) {
40 | payload = parse(location.search.substr(1))
41 | const data = yield call(query, payload)
42 | if (data) {
43 | yield put({
44 | type: 'querySuccess',
45 | payload: {
46 | list: data.list,
47 | pagination: {
48 | current: Number(payload.page) || 1,
49 | pageSize: Number(payload.pageSize) || 10,
50 | total: data.total,
51 | },
52 | },
53 | })
54 | }
55 | },
56 |
57 | * delete ({ payload }, { call, put }) {
58 | const data = yield call(remove, { id: payload })
59 | if (data.success) {
60 | yield put({ type: 'query' })
61 | } else {
62 | throw data
63 | }
64 | },
65 |
66 | * create ({ payload }, { call, put }) {
67 | const data = yield call(create, payload)
68 | if (data.success) {
69 | yield put({ type: 'hideModal' })
70 | yield put({ type: 'query' })
71 | } else {
72 | throw data
73 | }
74 | },
75 |
76 | * update ({ payload }, { select, call, put }) {
77 | const id = yield select(({ user }) => user.currentItem.id)
78 | const newUser = { ...payload, id }
79 | const data = yield call(update, newUser)
80 | if (data.success) {
81 | yield put({ type: 'hideModal' })
82 | yield put({ type: 'query' })
83 | } else {
84 | throw data
85 | }
86 | },
87 |
88 | },
89 |
90 | reducers: {
91 |
92 | querySuccess (state, action) {
93 | const { list, pagination } = action.payload
94 | return {
95 | ...state,
96 | list,
97 | pagination: {
98 | ...state.pagination,
99 | ...pagination,
100 | },
101 | }
102 | },
103 |
104 | showModal (state, action) {
105 | return { ...state, ...action.payload, modalVisible: true }
106 | },
107 |
108 | hideModal (state) {
109 | return { ...state, modalVisible: false }
110 | },
111 |
112 | switchIsMotion (state) {
113 | localStorage.setItem('antdAdminUserIsMotion', !state.isMotion)
114 | return { ...state, isMotion: !state.isMotion }
115 | },
116 |
117 | },
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/fe/src/public/QR_code.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/QR_code.jpg
--------------------------------------------------------------------------------
/fe/src/public/antd/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/antd/iconfont.eot
--------------------------------------------------------------------------------
/fe/src/public/antd/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/antd/iconfont.ttf
--------------------------------------------------------------------------------
/fe/src/public/antd/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/antd/iconfont.woff
--------------------------------------------------------------------------------
/fe/src/public/bg_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/bg_image.jpg
--------------------------------------------------------------------------------
/fe/src/public/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "antdadmin";
3 | src: url('iconfont.eot?t=1494298210172'); /* IE9*/
4 | src: url('iconfont.eot?t=1494298210172#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('iconfont.woff?t=1494298210172') format('woff'), /* chrome, firefox */
6 | url('iconfont.ttf?t=1494298210172') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1494298210172#antdadmin') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .antdadmin {
11 | font-family:"antdadmin" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-home:before { content: "\e600"; }
19 |
20 | .icon-user:before { content: "\e601"; }
21 |
22 | .icon-timelimit:before { content: "\e602"; }
23 |
24 | .icon-shopcart:before { content: "\e603"; }
25 |
26 | .icon-message:before { content: "\e604"; }
27 |
28 | .icon-remind:before { content: "\e605"; }
29 |
30 | .icon-service:before { content: "\e606"; }
31 |
32 | .icon-shop:before { content: "\e607"; }
33 |
34 | .icon-sweep:before { content: "\e609"; }
35 |
36 | .icon-express:before { content: "\e60a"; }
37 |
38 | .icon-payment:before { content: "\e60b"; }
39 |
40 | .icon-search:before { content: "\e60c"; }
41 |
42 | .icon-feedback:before { content: "\e60d"; }
43 |
44 | .icon-pencil:before { content: "\e60e"; }
45 |
46 | .icon-setting:before { content: "\e60f"; }
47 |
48 | .icon-refund:before { content: "\e610"; }
49 |
50 | .icon-delete:before { content: "\e611"; }
51 |
52 | .icon-star:before { content: "\e612"; }
53 |
54 | .icon-heart:before { content: "\e613"; }
55 |
56 | .icon-share:before { content: "\e615"; }
57 |
58 | .icon-location:before { content: "\e616"; }
59 |
60 | .icon-position:before { content: "\e617"; }
61 |
62 | .icon-console:before { content: "\e618"; }
63 |
64 | .icon-mobile:before { content: "\e61a"; }
65 |
66 |
--------------------------------------------------------------------------------
/fe/src/public/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/iconfont.eot
--------------------------------------------------------------------------------
/fe/src/public/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/iconfont.ttf
--------------------------------------------------------------------------------
/fe/src/public/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/iconfont.woff
--------------------------------------------------------------------------------
/fe/src/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/public/logo.png
--------------------------------------------------------------------------------
/fe/src/router.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Route, Switch, Redirect, routerRedux } from 'dva/router'
4 | import dynamic from 'dva/dynamic'
5 | import App from 'routes/app'
6 |
7 | const { ConnectedRouter } = routerRedux
8 |
9 | const Routers = function ({ history, app }) {
10 | const error = dynamic({
11 | app,
12 | component: () => import('./routes/error'),
13 | })
14 | const routes = [
15 | {
16 | path: '/dashboard',
17 | models: () => [import('./models/dashboard')],
18 | component: () => import('./routes/dashboard/'),
19 | },
20 | {
21 | path: '/sys/account',
22 | models: () => [import('./models/sys/account')],
23 | component: () => import('./routes/sys/account/'),
24 | },
25 | {
26 | path: '/sys/menu',
27 | models: () => [import('./models/sys/menu')],
28 | component: () => import('./routes/sys/menu/'),
29 | },
30 | {
31 | path: '/sys/permission',
32 | models: () => [import('./models/sys/permission')],
33 | component: () => import('./routes/sys/permission/'),
34 | },
35 | {
36 | path: '/sys/role',
37 | models: () => [import('./models/sys/role')],
38 | component: () => import('./routes/sys/role/'),
39 | },
40 | {
41 | path: '/login',
42 | models: () => [import('./models/login')],
43 | component: () => import('./routes/login/'),
44 | },
45 | ]
46 |
47 | return (
48 |
49 |
50 |
51 | ()} />
52 | {
53 | routes.map(({ path, ...dynamics }, key) => (
54 |
55 | ))
56 | }
57 |
58 |
59 |
60 |
61 | )
62 | }
63 |
64 | Routers.propTypes = {
65 | history: PropTypes.object,
66 | app: PropTypes.object,
67 | }
68 |
69 | export default Routers
70 |
--------------------------------------------------------------------------------
/fe/src/routes/app.less:
--------------------------------------------------------------------------------
1 | @import "../themes/vars.less";
2 |
3 | :global {
4 | body {
5 | background-image: none;
6 | }
7 |
8 | pre {
9 | background: none;
10 | }
11 |
12 | #nprogress {
13 | pointer-events: none;
14 |
15 | .bar {
16 | background: @primary-color;
17 | position: fixed;
18 | z-index: 1024;
19 | top: 0;
20 | left: 0;
21 | right: 0;
22 | width: 100%;
23 | height: 2px;
24 | }
25 |
26 | .peg {
27 | display: block;
28 | position: absolute;
29 | right: 0;
30 | width: 100px;
31 | height: 100%;
32 | box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color;
33 | opacity: 1.0;
34 | transform: rotate(3deg) translate(0px, -4px);
35 | }
36 |
37 | .spinner {
38 | display: block;
39 | position: fixed;
40 | z-index: 1031;
41 | top: 15px;
42 | right: 15px;
43 | }
44 |
45 | .spinner-icon {
46 | width: 18px;
47 | height: 18px;
48 | box-sizing: border-box;
49 | border: solid 2px transparent;
50 | border-top-color: @primary-color;
51 | border-left-color: @primary-color;
52 | border-radius: 50%;
53 |
54 | :local {
55 | animation: nprogress-spinner 400ms linear infinite;
56 | }
57 | }
58 | }
59 |
60 | .nprogress-custom-parent {
61 | overflow: hidden;
62 | position: relative;
63 |
64 | #nprogress {
65 | .bar,
66 | .spinner {
67 | position: absolute;
68 | }
69 | }
70 | }
71 | }
72 |
73 | @keyframes nprogress-spinner {
74 | 0% {
75 | transform: rotate(0deg);
76 | }
77 |
78 | 100% {
79 | transform: rotate(360deg);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/browser.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Tag } from 'antd'
4 | import styles from './browser.less'
5 | import { color } from '../../../utils'
6 |
7 | const status = {
8 | 1: {
9 | color: color.green,
10 | },
11 | 2: {
12 | color: color.red,
13 | },
14 | 3: {
15 | color: color.blue,
16 | },
17 | 4: {
18 | color: color.yellow,
19 | },
20 | }
21 |
22 | function Browser ({ data }) {
23 | const columns = [
24 | {
25 | title: 'name',
26 | dataIndex: 'name',
27 | className: styles.name,
28 | }, {
29 | title: 'percent',
30 | dataIndex: 'percent',
31 | className: styles.percent,
32 | render: (text, it) => {text}%,
33 | },
34 | ]
35 | return key} dataSource={data} />
36 | }
37 |
38 | Browser.propTypes = {
39 | data: PropTypes.array,
40 | }
41 |
42 | export default Browser
43 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/browser.less:
--------------------------------------------------------------------------------
1 | .percent {
2 | text-align: right !important;
3 | }
4 |
5 | .name {
6 | text-align: left !important;
7 | }
8 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/comments.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Tag } from 'antd'
4 | import styles from './comments.less'
5 | import { color } from '../../../utils'
6 |
7 | const status = {
8 | 1: {
9 | color: color.green,
10 | text: 'APPROVED',
11 | },
12 | 2: {
13 | color: color.yellow,
14 | text: 'PENDING',
15 | },
16 | 3: {
17 | color: color.red,
18 | text: 'REJECTED',
19 | },
20 | }
21 |
22 | function Comments ({ data }) {
23 | const columns = [
24 | {
25 | title: 'avatar',
26 | dataIndex: 'avatar',
27 | width: 48,
28 | className: styles.avatarcolumn,
29 | render: text => ,
30 | }, {
31 | title: 'content',
32 | dataIndex: 'content',
33 | render: (text, it) => (
34 |
{it.name}
35 |
{it.content}
36 |
37 | {status[it.status].text}
38 | {it.date}
39 |
40 |
),
41 | },
42 | ]
43 | return (
44 |
45 |
key} dataSource={data.filter((item, key) => key < 3)} />
46 |
47 | )
48 | }
49 |
50 | Comments.propTypes = {
51 | data: PropTypes.array,
52 | }
53 |
54 | export default Comments
55 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/comments.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars.less";
2 |
3 | .comments {
4 | :global .ant-table-thead > tr > th {
5 | background: #fff;
6 | border-bottom: solid 1px @border-color-base;
7 | }
8 |
9 | .avatar {
10 | width: 48px;
11 | height: 48px;
12 | background-position: center;
13 | background-size: cover;
14 | border-radius: 50%;
15 | background: #f8f8f8;
16 | display: inline-block;
17 | }
18 |
19 | .content {
20 | text-align: left;
21 | color: #757575;
22 | }
23 |
24 | .date {
25 | color: #a3a3a3;
26 | line-height: 30px;
27 | }
28 |
29 | .daterow {
30 | display: flex;
31 | justify-content: space-between;
32 | }
33 |
34 | .name {
35 | font-size: 14px;
36 | color: #474747;
37 | text-align: left;
38 | }
39 |
40 | .avatarcolumn {
41 | vertical-align: top;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/completed.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 | import styles from './completed.less'
5 | import { color } from '../../../utils'
6 | import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
7 |
8 | function Completed ({ data }) {
9 | return (
10 |
11 |
TEAM TOTAL COMPLETED
12 |
13 |
14 |
37 |
38 |
39 | )
40 | }
41 |
42 | Completed.propTypes = {
43 | data: PropTypes.array,
44 | }
45 |
46 | export default Completed
47 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/completed.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars.less";
2 |
3 | .sales {
4 | .title {
5 | margin-left: 32px;
6 | font-size: 16px;
7 | }
8 | }
9 |
10 | .radiusdot {
11 | width: 12px;
12 | height: 12px;
13 | margin-right: 8px;
14 | border-radius: 50%;
15 | display: inline-block;
16 | }
17 |
18 | .legend {
19 | color: @dark-half;
20 | text-align: right;
21 | color: #999;
22 | font-size: 14px;
23 |
24 | li {
25 | height: 48px;
26 | line-height: 48px;
27 | display: inline-block;
28 |
29 | & + li {
30 | margin-left: 24px;
31 | }
32 | }
33 | }
34 |
35 | .tooltip {
36 | background: #fff;
37 | padding: 20px;
38 | font-size: 14px;
39 |
40 | .tiptitle {
41 | font-weight: 700;
42 | font-size: 16px;
43 | margin-bottom: 8px;
44 | }
45 |
46 | .tipitem {
47 | height: 32px;
48 | line-height: 32px;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/cpu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import CountUp from 'react-countup'
4 | import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts'
5 | import { color } from '../../../utils'
6 | import styles from './cpu.less'
7 |
8 | const countUpProps = {
9 | start: 0,
10 | duration: 2.75,
11 | useEasing: true,
12 | useGrouping: true,
13 | separator: ',',
14 | }
15 |
16 | function Cpu ({ usage, space, cpu, data }) {
17 | return (
18 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
)
53 | }
54 |
55 | Cpu.propTypes = {
56 | data: PropTypes.array,
57 | usage: PropTypes.number,
58 | space: PropTypes.number,
59 | cpu: PropTypes.number,
60 | }
61 |
62 | export default Cpu
63 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/cpu.less:
--------------------------------------------------------------------------------
1 | .cpu {
2 | .number {
3 | display: flex;
4 | height: 64px;
5 | justify-content: space-between;
6 | margin-bottom: 32px;
7 |
8 | .item {
9 | text-align: center;
10 | height: 64px;
11 | width: 100%;
12 | position: relative;
13 |
14 | & + .item {
15 | &::before {
16 | content: '';
17 | display: block;
18 | width: 1px;
19 | height: 40px;
20 | position: absolute;
21 | background: #f5f5f5;
22 | top: 12px;
23 | }
24 | }
25 |
26 | p {
27 | color: #757575;
28 |
29 | &:first-child {
30 | font-size: 16px;
31 | }
32 |
33 | &:last-child {
34 | font-size: 20px;
35 | font-weight: 700;
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/index.js:
--------------------------------------------------------------------------------
1 | import NumberCard from './numberCard'
2 | import Quote from './quote'
3 | import Sales from './sales'
4 | import Weather from './weather'
5 | import RecentSales from './recentSales'
6 | import Comments from './comments'
7 | import Completed from './completed'
8 | import Browser from './browser'
9 | import Cpu from './cpu'
10 | import User from './user'
11 |
12 | export { NumberCard, Quote, Sales, Weather, RecentSales, Comments, Completed, Browser, Cpu, User }
13 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/numberCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Icon, Card } from 'antd'
4 | import CountUp from 'react-countup'
5 | import styles from './numberCard.less'
6 |
7 | function NumberCard ({ icon, color, title, number, countUp }) {
8 | return (
9 |
10 |
11 |
12 |
{title || 'No Title'}
13 |
14 |
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | NumberCard.propTypes = {
30 | icon: PropTypes.string,
31 | color: PropTypes.string,
32 | title: PropTypes.string,
33 | number: PropTypes.number,
34 | countUp: PropTypes.object,
35 | }
36 |
37 | export default NumberCard
38 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/numberCard.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars.less";
2 |
3 | .numberCard {
4 | padding: 32px;
5 | margin-bottom: 24px;
6 | cursor: pointer;
7 |
8 | .iconWarp {
9 | font-size: 54px;
10 | float: left;
11 | }
12 |
13 | .content {
14 | width: 100%;
15 | padding-left: 78px;
16 |
17 | .title {
18 | line-height: 16px;
19 | font-size: 16px;
20 | margin-bottom: 8px;
21 | height: 16px;
22 | .text-overflow();
23 | }
24 |
25 | .number {
26 | line-height: 32px;
27 | font-size: 24px;
28 | height: 32px;
29 | .text-overflow();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/quote.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import styles from './quote.less'
4 |
5 | function Quote ({ name, content, title, avatar }) {
6 | return (
7 |
8 |
9 | {content}
10 |
11 |
12 |
13 |
-{name}-
14 |
{title}
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | Quote.propTypes = {
23 | name: PropTypes.string,
24 | content: PropTypes.string,
25 | title: PropTypes.string,
26 | avatar: PropTypes.string,
27 | }
28 |
29 | export default Quote
30 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/quote.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars.less";
2 |
3 | .quote {
4 | color: #fff;
5 | height: 100%;
6 | width: 100%;
7 | padding: 24px;
8 | font-size: 16px;
9 | font-weight: 700;
10 |
11 | .inner {
12 | text-overflow: ellipsis;
13 | word-wrap: normal;
14 | display: -webkit-box;
15 | -webkit-box-orient: vertical;
16 | -webkit-line-clamp: 4;
17 | overflow: hidden;
18 | text-indent: 24px;
19 | }
20 |
21 | .footer {
22 | position: relative;
23 | margin-top: 14px;
24 |
25 | .description {
26 | width: 100%;
27 |
28 | p {
29 | overflow: hidden;
30 | text-overflow: ellipsis;
31 | white-space: nowrap;
32 | margin-right: 64px;
33 | text-align: right;
34 |
35 | &:last-child {
36 | font-weight: 100;
37 | }
38 | }
39 | }
40 |
41 | .avatar {
42 | width: 48px;
43 | height: 48px;
44 | background-position: center;
45 | background-size: cover;
46 | border-radius: 50%;
47 | position: absolute;
48 | right: 0;
49 | top: 0;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/recentSales.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Tag } from 'antd'
4 | import styles from './recentSales.less'
5 | import { color } from '../../../utils'
6 |
7 | const status = {
8 | 1: {
9 | color: color.green,
10 | text: 'SALE',
11 | },
12 | 2: {
13 | color: color.yellow,
14 | text: 'REJECT',
15 | },
16 | 3: {
17 | color: color.red,
18 | text: 'TAX',
19 | },
20 | 4: {
21 | color: color.blue,
22 | text: 'EXTENDED',
23 | },
24 | }
25 |
26 | function RecentSales ({ data }) {
27 | const columns = [
28 | {
29 | title: 'NAME',
30 | dataIndex: 'name',
31 | }, {
32 | title: 'STATUS',
33 | dataIndex: 'status',
34 | render: text => {status[text].text},
35 | }, {
36 | title: 'DATE',
37 | dataIndex: 'date',
38 | render: text => new Date(text).format('yyyy-MM-dd'),
39 | }, {
40 | title: 'PRICE',
41 | dataIndex: 'price',
42 | render: (text, it) => ${text},
43 | },
44 | ]
45 | return (
46 |
47 |
key} dataSource={data.filter((item, key) => key < 5)} />
48 |
49 | )
50 | }
51 |
52 | RecentSales.propTypes = {
53 | data: PropTypes.array,
54 | }
55 |
56 | export default RecentSales
57 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/recentSales.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars.less";
2 |
3 | .recentsales {
4 | :global .ant-table-thead > tr > th {
5 | background: #fff;
6 | border-bottom: solid 1px @border-color-base;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/sales.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
4 | import classnames from 'classnames'
5 | import styles from './sales.less'
6 | import { color } from '../../../utils'
7 |
8 |
9 | function Sales ({ data }) {
10 | return (
11 |
12 |
Dashboard
13 |
14 |
15 |
40 |
41 |
42 | )
43 | }
44 |
45 | Sales.propTypes = {
46 | data: PropTypes.array,
47 | }
48 |
49 | export default Sales
50 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/sales.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars.less";
2 |
3 | .sales {
4 | .title {
5 | margin-left: 32px;
6 | font-size: 16px;
7 | }
8 | }
9 |
10 | .radiusdot {
11 | width: 12px;
12 | height: 12px;
13 | margin-right: 8px;
14 | border-radius: 50%;
15 | display: inline-block;
16 | }
17 |
18 | .legend {
19 | color: @dark-half;
20 | text-align: right;
21 | color: #999;
22 | font-size: 14px;
23 |
24 | li {
25 | height: 48px;
26 | line-height: 48px;
27 | display: inline-block;
28 |
29 | & + li {
30 | margin-left: 24px;
31 | }
32 | }
33 | }
34 |
35 | .tooltip {
36 | background: #fff;
37 | padding: 20px;
38 | font-size: 14px;
39 |
40 | .tiptitle {
41 | font-weight: 700;
42 | font-size: 16px;
43 | margin-bottom: 8px;
44 | }
45 |
46 | .tipitem {
47 | height: 32px;
48 | line-height: 32px;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/user-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javahuang/antd-admin-springboot/87186297e8676e320dc03d8271fe72f10fa60ff2/fe/src/routes/dashboard/components/user-background.png
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Button } from 'antd'
4 | import styles from './user.less'
5 | import CountUp from 'react-countup'
6 | import { color } from '../../../utils'
7 | const countUpProps = {
8 | start: 0,
9 | duration: 2.75,
10 | useEasing: true,
11 | useGrouping: true,
12 | separator: ',',
13 | }
14 |
15 | function User({ avatar, name, email, sales, sold }) {
16 | return (
17 |
18 |
19 |
20 |
{name}
21 |
{email}
22 |
23 |
24 |
25 |
26 |
EARNING SALES
27 |
32 |
33 |
40 |
41 |
42 |
43 |
44 |
)
45 | }
46 |
47 | User.propTypes = {
48 | avatar: PropTypes.string,
49 | name: PropTypes.string,
50 | email: PropTypes.string,
51 | sales: PropTypes.number,
52 | sold: PropTypes.number,
53 | }
54 |
55 | export default User
56 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/user.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars.less";
2 |
3 | .user {
4 | .header {
5 | display: flex;
6 | justify-content: center;
7 | text-align: center;
8 | color: #fff;
9 | height: 200px;
10 | background-image: ~ "url(./user-background.png)";
11 | background-repeat: no-repeat;
12 | background-size: cover;
13 | align-items: center;
14 |
15 | .headerinner {
16 | z-index: 2;
17 | }
18 |
19 | &::after {
20 | content: '';
21 | background: @purple;
22 | position: absolute;
23 | width: 100%;
24 | height: 200px;
25 | left: 0;
26 | top: 0;
27 | opacity: 0.4;
28 | z-index: 1;
29 | }
30 |
31 | .avatar {
32 | width: 64px;
33 | height: 64px;
34 | background-position: center;
35 | background-size: cover;
36 | border-radius: 50%;
37 | display: inline-block;
38 | }
39 |
40 | .name {
41 | font-size: 16px;
42 | }
43 | }
44 |
45 | .number {
46 | display: flex;
47 | height: 116px;
48 | justify-content: space-between;
49 | border-bottom: solid 1px #f5f5f5;
50 |
51 | .item {
52 | text-align: center;
53 | height: 116px;
54 | width: 100%;
55 | position: relative;
56 | padding: 30px 0;
57 |
58 | & + .item {
59 | &::before {
60 | content: '';
61 | display: block;
62 | width: 1px;
63 | height: 116px;
64 | position: absolute;
65 | background: #f5f5f5;
66 | top: 0;
67 | }
68 | }
69 |
70 | p {
71 | color: #757575;
72 |
73 | &:first-child {
74 | font-size: 16px;
75 | }
76 |
77 | &:last-child {
78 | font-size: 20px;
79 | font-weight: 700;
80 | }
81 | }
82 | }
83 | }
84 |
85 | .footer {
86 | height: 116px;
87 | display: flex;
88 | justify-content: center;
89 | align-items: center;
90 |
91 | :global .ant-btn {
92 | color: @purple;
93 | border-color: @purple;
94 | padding: 6px 16px;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/weather.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Spin } from 'antd'
4 | import styles from './weather.less'
5 |
6 | function Weather ({ city, icon, dateTime, temperature, name, loading }) {
7 | return (
8 |
9 |
10 |
18 |
19 |
{`${temperature}°`}
20 |
{city}, {dateTime}
21 |
22 |
23 | )
24 | }
25 |
26 | Weather.propTypes = {
27 | city: PropTypes.string,
28 | icon: PropTypes.string,
29 | dateTime: PropTypes.string,
30 | temperature: PropTypes.string,
31 | name: PropTypes.string,
32 | loading: PropTypes.bool,
33 | }
34 |
35 | export default Weather
36 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/components/weather.less:
--------------------------------------------------------------------------------
1 | @import "../../../themes/vars";
2 |
3 | .weather {
4 | color: #fff;
5 | height: 204px;
6 | padding: 24px;
7 | justify-content: space-between;
8 | display: flex;
9 | font-size: 14px;
10 |
11 | .left {
12 | display: flex;
13 | flex-direction: column;
14 | width: 64px;
15 | padding-top: 55px;
16 |
17 | .icon {
18 | width: 64px;
19 | height: 64px;
20 | background-position: center;
21 | background-size: contain;
22 | }
23 |
24 | p {
25 | margin-top: 16px;
26 | }
27 | }
28 |
29 | .right {
30 | display: flex;
31 | flex-direction: column;
32 | width: 50%;
33 |
34 | .temperature {
35 | font-size: 36px;
36 | text-align: right;
37 | height: 64px;
38 | color: #fff;
39 | }
40 |
41 | .description {
42 | overflow: hidden;
43 | text-overflow: ellipsis;
44 | white-space: nowrap;
45 | text-align: right;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Row, Col, Card } from 'antd'
5 | import { NumberCard, Sales } from './components'
6 |
7 | function Dashboard ({ dashboard }) {
8 | const { sales, numbers } = dashboard
9 | const numberCards = numbers.map((item, key) => (
10 |
11 | ))
12 |
13 | return (
14 |
15 |
16 | {numberCards}
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | Dashboard.propTypes = {
32 | dashboard: PropTypes.object,
33 | }
34 |
35 | export default connect(({ dashboard }) => ({ dashboard }))(Dashboard)
36 |
--------------------------------------------------------------------------------
/fe/src/routes/dashboard/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .ant-card {
3 | border-radius: 0;
4 | margin-bottom: 24px;
5 | }
6 | }
7 |
8 | .weather {
9 | &:hover {
10 | box-shadow: 4px 4px 40px rgba(143, 201, 251, .6);
11 | }
12 | }
13 |
14 | .quote {
15 | &:hover {
16 | box-shadow: 4px 4px 40px rgba(246, 152, 153, .6);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/fe/src/routes/error/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Icon } from 'antd'
3 | import styles from './index.less'
4 |
5 | const Error = () => (
6 |
7 |
8 |
404 Not Found
9 |
10 |
)
11 |
12 | export default Error
13 |
--------------------------------------------------------------------------------
/fe/src/routes/error/index.less:
--------------------------------------------------------------------------------
1 | .error {
2 | color: black;
3 | text-align: center;
4 | position: absolute;
5 | top: 30%;
6 | margin-top: -50px;
7 | left: 50%;
8 | margin-left: -100px;
9 | width: 200px;
10 |
11 | :global .anticon {
12 | font-size: 48px;
13 | margin-bottom: 16px;
14 | }
15 |
16 | h1 {
17 | font-family: cursive;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/fe/src/routes/login/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Icon, Button, Checkbox, Row, Form, Input, Tabs } from 'antd'
5 | import { config } from 'utils'
6 | import styles from './index.less'
7 |
8 | const FormItem = Form.Item
9 | const TabPane = Tabs.TabPane
10 |
11 | const Login = ({
12 | loading,
13 | dispatch,
14 | form: {
15 | getFieldDecorator,
16 | validateFieldsAndScroll,
17 | },
18 | }) => {
19 | function handleOk () {
20 | validateFieldsAndScroll((errors, values) => {
21 | if (errors) {
22 | return
23 | }
24 | dispatch({ type: 'login/login', payload: values })
25 | })
26 | }
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | )
72 | }
73 |
74 | Login.propTypes = {
75 | form: PropTypes.object,
76 | dispatch: PropTypes.func,
77 | loading: PropTypes.object,
78 | }
79 |
80 | export default connect(({ loading }) => ({ loading }))(Form.create()(Login))
81 |
--------------------------------------------------------------------------------
/fe/src/routes/login/index.less:
--------------------------------------------------------------------------------
1 |
2 | .form {
3 | border-radius: 4px;
4 | background-color: rgba(25, 48, 65, 0.54);
5 | position: absolute;
6 | top: 50%;
7 | right: 120px;
8 | margin: -160px 0 0 -160px;
9 | width: 320px;
10 | height: 320px;
11 | padding: 36px;
12 | box-shadow: 0 0 100px rgba(0, 0, 0, .08);
13 |
14 | button {
15 | width: 100%;
16 | }
17 |
18 | p {
19 | color: rgb(204, 204, 204);
20 | text-align: center;
21 | margin-top: 16px;
22 |
23 | span {
24 | &:first-child {
25 | margin-right: 16px;
26 | }
27 | }
28 | }
29 |
30 | :global {
31 | .ant-tabs .ant-tabs-nav {
32 | width: 100% !important;
33 |
34 | div {
35 | color: #ded4d4;
36 | width: 50%;
37 | }
38 |
39 | }
40 | }
41 | }
42 |
43 | .ant-spin-container,
44 | .ant-spin-nested-loading {
45 | height: 100%;
46 | }
47 |
48 | .tab {
49 | text-align: center;
50 | }
51 |
52 | .color {
53 | color: #ded4d4;
54 | }
55 |
56 | .bgImage {
57 | background: url("../../public/bg_image.jpg") round;
58 | }
59 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/account/Filter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Form, Button, Row, Col, Input, AutoComplete } from 'antd'
4 |
5 |
6 | const ColProps = {
7 | xs: 24,
8 | sm: 12,
9 | style: {
10 | marginBottom: 16,
11 | },
12 | }
13 |
14 | const Filter = ({
15 | nameDataSource,
16 | onFilterChange,
17 | onAdd,
18 | changeNameDataSource,
19 | filter,
20 | form: {
21 | getFieldDecorator,
22 | getFieldsValue,
23 | setFieldsValue,
24 | },
25 | }) => {
26 | const handleSubmit = () => {
27 | let fields = getFieldsValue()
28 | onFilterChange(fields)
29 | }
30 |
31 | const handlerSearch = (val) => {
32 | setFieldsValue({ searchId: '' })
33 | changeNameDataSource(val)
34 | }
35 |
36 | const handlerSelect = (val) => {
37 | setFieldsValue({ searchId: val })
38 | }
39 |
40 |
41 | const { name } = filter
42 |
43 | return (
44 |
45 |
46 | {getFieldDecorator('searchName', { initialValue: name })(
47 |
55 | )}
56 | {getFieldDecorator('searchId')(
57 |
58 | )}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | Filter.propTypes = {
71 | onAdd: PropTypes.func,
72 | isMotion: PropTypes.bool,
73 | switchIsMotion: PropTypes.func,
74 | form: PropTypes.object,
75 | filter: PropTypes.object,
76 | onFilterChange: PropTypes.func,
77 | nameDataSource: PropTypes.array,
78 | changeNameDataSource: PropTypes.func,
79 | }
80 |
81 | export default Form.create()(Filter)
82 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/account/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal } from 'antd'
4 | import classnames from 'classnames'
5 | import { Link } from 'dva/router'
6 | import queryString from 'query-string'
7 | import AnimTableBody from '../../../components/DataTable/AnimTableBody'
8 | import { DropOption } from '../../../components'
9 | import styles from './List.less'
10 |
11 | const confirm = Modal.confirm
12 |
13 | const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) => {
14 | location.query = queryString.parse(location.search)
15 | const handleMenuClick = (record, e) => {
16 | if (e.key === '1') {
17 | onEditItem(record)
18 | } else if (e.key === '2') {
19 | confirm({
20 | title: '确定删除当前数据?',
21 | onOk () {
22 | onDeleteItem(record.id)
23 | },
24 | })
25 | }
26 | }
27 |
28 | const columns = [
29 | {
30 | title: 'id',
31 | dataIndex: 'id',
32 | key: 'id',
33 | },
34 | {
35 | title: '姓名',
36 | dataIndex: 'name',
37 | key: 'name',
38 | render: (text, record) => {text},
39 | }, {
40 | title: '登录账号',
41 | dataIndex: 'loginName',
42 | key: 'loginName',
43 | }, {
44 | title: '邮箱',
45 | dataIndex: 'email',
46 | key: 'email',
47 | }, {
48 | title: '是否管理员',
49 | dataIndex: 'isAdmin',
50 | key: 'isAdmin',
51 | render: text => ({text
52 | ? '是'
53 | : '否'}),
54 | }, {
55 | title: '角色列表',
56 | dataIndex: 'roleNames',
57 | key: 'roleNames',
58 | }, {
59 | title: '创建日期',
60 | dataIndex: 'createDatetime',
61 | key: 'createDatetime',
62 | render: text => (text ? new Date(text).format('yyyy-MM-dd') : ''),
63 | },
64 | {
65 | title: '操作',
66 | key: 'operation',
67 | width: 100,
68 | render: (text, record) => (
69 | handleMenuClick(record, e)} menuOptions={[{ key: '1', name: '修改' }, { key: '2', name: '删除' }]} />
70 | ),
71 | },
72 | ]
73 |
74 | const getBodyWrapperProps = {
75 | page: location.query.page,
76 | current: tableProps.pagination.current,
77 | }
78 |
79 | const getBodyWrapper = (body) => {
80 | return isMotion ? : body
81 | }
82 |
83 | return (
84 |
85 |
record.id}
92 | getBodyWrapper={getBodyWrapper}
93 | />
94 |
95 | )
96 | }
97 |
98 | List.propTypes = {
99 | onDeleteItem: PropTypes.func,
100 | onEditItem: PropTypes.func,
101 | isMotion: PropTypes.bool,
102 | location: PropTypes.object,
103 | }
104 |
105 | export default List
106 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/account/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | td.avatar {
3 | img {
4 | border-radius: 50%;
5 | }
6 | }
7 |
8 | :global {
9 | .ant-table-tbody > tr > td,
10 | .ant-table-thead > tr > th {
11 | height: 62px;
12 | }
13 | }
14 |
15 | &.motion {
16 | :global {
17 | .ant-table-tbody > tr > td,
18 | .ant-table-thead > tr > th {
19 | &:nth-child(1) {
20 | display: none;
21 | }
22 |
23 | &:nth-child(2) {
24 | width: 10%;
25 | }
26 |
27 | &:nth-child(3) {
28 | width: 10%;
29 | }
30 |
31 | &:nth-child(4) {
32 | width: 15%;
33 | }
34 |
35 | &:nth-child(5) {
36 | width: 10%;
37 | }
38 | &:nth-child(6) {
39 | width: 25%;
40 | }
41 | &:nth-child(7) {
42 | width: 15%;
43 | }
44 | &:nth-child(8) {
45 | width: 15%;
46 | }
47 | }
48 |
49 | .ant-table-thead {
50 | & > tr {
51 | transition: none;
52 | display: block;
53 |
54 | & > th {
55 | display: inline-flex;
56 | align-items: center;
57 | justify-content: center;
58 | }
59 | }
60 | }
61 |
62 | .ant-table-tbody {
63 | & > tr {
64 | transition: none;
65 | display: block;
66 | border-bottom: 1px solid #f5f5f5;
67 |
68 | & > td {
69 | border-bottom: none;
70 | display: inline-flex;
71 | align-items: center;
72 | justify-content: center;
73 | }
74 |
75 | &.ant-table-expanded-row-level-1 > td {
76 | height: auto;
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
84 | .hidden {
85 | display: none !important;
86 | }
87 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/account/Modal.less:
--------------------------------------------------------------------------------
1 | .hidden {
2 | display: none;
3 | }
4 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/menu/Form.less:
--------------------------------------------------------------------------------
1 | .hidden {
2 | display: none;
3 | }
4 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/menu/Tree.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Tree, Input, Popover, Button, Icon, Popconfirm } from 'antd'
4 | import arrToTree from 'array-to-tree'
5 |
6 |
7 | const TreeNode = Tree.TreeNode
8 | const Search = Input.Search
9 |
10 |
11 | const MenuTree = ({
12 | autoExpandParent, expandedKeys, onExpand, onDrop, showOrHideForm, searchValue, onKeyChange,
13 | onAdd, onEdit, onSearch, deleteItem, treeData,
14 | }) => {
15 | const onSelect = (selectKeys) => {
16 | let currKey = selectKeys[0]
17 | onKeyChange(currKey)
18 | }
19 |
20 | // 如果操作框打开,则隐藏form
21 | const onVisibleChange = (visible) => {
22 | if (visible) {
23 | showOrHideForm(false)
24 | }
25 | }
26 |
27 | const content = (item) => {
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | const searchPermission = (e) => {
49 | const value = e.target.value
50 | onSearch(value)
51 | }
52 |
53 | const loop = data => data.map((item) => {
54 | const index = item.name.search(searchValue)
55 | const beforeStr = item.name.substr(0, index)
56 | const afterStr = item.name.substr(index + searchValue.length)
57 | const title = index > -1 ? (
58 |
59 |
60 |
61 | {beforeStr}
62 | {searchValue}
63 | {afterStr}
64 |
65 |
66 | ) : {item.name}
67 | if (item.children) {
68 | return (
69 |
70 | {loop(item.children)}
71 |
72 | )
73 | }
74 | return
75 | })
76 |
77 |
78 | return (
79 |
80 |
81 |
90 | {loop(arrToTree(treeData, { parentProperty: 'mpid', rootID: 1 }))}
91 |
92 |
93 | )
94 | }
95 |
96 | MenuTree.propTypes = {
97 | autoExpandParent: PropTypes.any,
98 | expandedKeys: PropTypes.any,
99 | onExpand: PropTypes.any,
100 | onDrop: PropTypes.any,
101 | showOrHideForm: PropTypes.any,
102 | searchValue: PropTypes.any,
103 | onKeyChange: PropTypes.any,
104 | onAdd: PropTypes.any,
105 | onEdit: PropTypes.any,
106 | onSearch: PropTypes.any,
107 | deleteItem: PropTypes.any,
108 | treeData: PropTypes.any,
109 | }
110 |
111 | export default MenuTree
112 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/menu/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Row, Col } from 'antd'
4 | import { connect } from 'dva'
5 | import MenuForm from './Form'
6 | import MenuTree from './Tree'
7 |
8 |
9 | const Menu = ({ location, dispatch, menu, loading }) => {
10 | const {
11 | expandedKeys, searchValue, autoExpandParent,
12 | treeData, menuPermissionTreeData, currItem, formVisible,
13 | } = menu
14 |
15 | const treeProps = {
16 | treeData,
17 | currItem,
18 | formVisible,
19 | expandedKeys,
20 | onKeyChange (key) {
21 | dispatch({
22 | type: 'menu/change',
23 | payload: {
24 | key,
25 | },
26 | })
27 | },
28 | showOrHideForm (showOrHide) {
29 | dispatch({
30 | type: 'menu/showOrHideForm',
31 | payload: {
32 | showOrHide,
33 | },
34 | })
35 | },
36 | onAdd (isRoot) {
37 | dispatch({
38 | type: 'menu/onAdd',
39 | payload: {
40 | isRoot,
41 | },
42 | })
43 | },
44 | onEdit () {
45 | dispatch({
46 | type: 'menu/onEdit',
47 | })
48 | },
49 | deleteItem () {
50 | dispatch({
51 | type: 'menu/delete',
52 | })
53 | },
54 | onSearch (val) {
55 | dispatch({
56 | type: 'menu/onSearch',
57 | payload: {
58 | searchValue: val,
59 | },
60 | })
61 | },
62 | onExpand (keys) {
63 | dispatch({
64 | type: 'menu/onExpand',
65 | payload: {
66 | expandedKeys: keys,
67 | },
68 | })
69 | },
70 | onDrop (info) {
71 | const dropKey = info.node.props.eventKey
72 | const dragKey = info.dragNode.props.eventKey
73 | const dropPos = info.node.props.pos.split('-')
74 | const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
75 | dispatch({
76 | type: 'menu/sort',
77 | payload: {
78 | dropKey,
79 | dragKey,
80 | dropPosition,
81 | },
82 | })
83 | },
84 | searchValue,
85 | autoExpandParent,
86 | loading: loading.effects['menu/query'],
87 | location,
88 | }
89 |
90 |
91 | const formProps = {
92 | currItem,
93 | treeData,
94 | menuPermissionTreeData,
95 | loading: loading.effects[`${currItem.id ? 'menu/update' : 'menu/create'}`],
96 | onOk (data) {
97 | if (data.id) {
98 | dispatch({
99 | type: 'menu/update',
100 | payload: data,
101 | })
102 | } else {
103 | dispatch({
104 | type: 'menu/create',
105 | payload: data,
106 | })
107 | }
108 | },
109 | }
110 |
111 | return (
112 |
113 |
114 |
115 |
116 | {formVisible && }
117 |
118 |
119 |
120 | )
121 | }
122 |
123 | Menu.propTypes = {
124 | menu: PropTypes.object,
125 | location: PropTypes.object,
126 | dispatch: PropTypes.func,
127 | loading: PropTypes.object,
128 | }
129 |
130 | export default connect(({ menu, loading }) => ({ menu, loading }))(Menu)
131 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/permission/Form.less:
--------------------------------------------------------------------------------
1 | .hidden {
2 | display: none;
3 | }
4 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/permission/Tree.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Tree, Input, Popover, Button, Popconfirm } from 'antd'
4 | import arrToTree from 'array-to-tree'
5 |
6 |
7 | const TreeNode = Tree.TreeNode
8 | const Search = Input.Search
9 |
10 |
11 | const PermissionTree = ({
12 | autoExpandParent, expandedKeys, onExpand, onDrop, showOrHideForm, searchValue, onKeyChange,
13 | onAdd, onEdit, onSearch, deleteItem, treeData,
14 | }) => {
15 | const onSelect = (selectKeys) => {
16 | let currKey = selectKeys[0]
17 | onKeyChange(currKey)
18 | }
19 |
20 | // 如果操作框打开,则隐藏form
21 | const onVisibleChange = (visible) => {
22 | if (visible) {
23 | showOrHideForm(false)
24 | }
25 | }
26 |
27 | const content = (item) => {
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | const searchPermission = (e) => {
49 | const value = e.target.value
50 | onSearch(value)
51 | }
52 |
53 | const loop = data => data.map((item) => {
54 | const index = item.name.search(searchValue)
55 | const beforeStr = item.name.substr(0, index)
56 | const afterStr = item.name.substr(index + searchValue.length)
57 | const title = index > -1 ? (
58 |
59 |
60 | {beforeStr}
61 | {searchValue}
62 | {afterStr}
63 |
64 |
65 | ) : {item.name}
66 | if (item.children) {
67 | return (
68 |
69 | {loop(item.children)}
70 |
71 | )
72 | }
73 | return
74 | })
75 |
76 | return (
77 |
78 |
79 |
88 | {loop(arrToTree(treeData, { parentProperty: 'parentId', rootID: 1 }))}
89 |
90 |
91 | )
92 | }
93 |
94 | PermissionTree.propTypes = {
95 | autoExpandParent: PropTypes.any,
96 | expandedKeys: PropTypes.any,
97 | onExpand: PropTypes.func,
98 | onDrop: PropTypes.func,
99 | showOrHideForm: PropTypes.any,
100 | searchValue: PropTypes.any,
101 | onKeyChange: PropTypes.any,
102 | onAdd: PropTypes.any,
103 | onEdit: PropTypes.any,
104 | onSearch: PropTypes.any,
105 | deleteItem: PropTypes.any,
106 | treeData: PropTypes.any,
107 | }
108 |
109 | export default PermissionTree
110 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/permission/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Row, Col } from 'antd'
4 | import { connect } from 'dva'
5 | import PermissionForm from './Form'
6 | import PermissionTree from './Tree'
7 |
8 |
9 | const Permission = ({ location, dispatch, permission, loading }) => {
10 | const {
11 | expandedKeys, searchValue, autoExpandParent,
12 | treeData, currItem, formVisible,
13 | } = permission
14 |
15 | const treeProps = {
16 | treeData,
17 | currItem,
18 | formVisible,
19 | expandedKeys,
20 | onKeyChange (key) {
21 | dispatch({
22 | type: 'permission/change',
23 | payload: {
24 | key,
25 | },
26 | })
27 | },
28 | showOrHideForm (showOrHide) {
29 | dispatch({
30 | type: 'permission/showOrHideForm',
31 | payload: {
32 | showOrHide,
33 | },
34 | })
35 | },
36 | onAdd (isRoot) {
37 | dispatch({
38 | type: 'permission/onAdd',
39 | payload: {
40 | isRoot,
41 | },
42 | })
43 | },
44 | onEdit () {
45 | dispatch({
46 | type: 'permission/onEdit',
47 | })
48 | },
49 | deleteItem () {
50 | dispatch({
51 | type: 'permission/delete',
52 | })
53 | },
54 | onSearch (val) {
55 | dispatch({
56 | type: 'permission/onSearch',
57 | payload: {
58 | searchValue: val,
59 | },
60 | })
61 | },
62 | onExpand (keys) {
63 | dispatch({
64 | type: 'permission/onExpand',
65 | payload: {
66 | expandedKeys: keys,
67 | },
68 | })
69 | },
70 | onDrop (info) {
71 | const dropKey = info.node.props.eventKey
72 | const dragKey = info.dragNode.props.eventKey
73 | const dropPos = info.node.props.pos.split('-')
74 | const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
75 | dispatch({
76 | type: 'permission/sort',
77 | payload: {
78 | dropKey,
79 | dragKey,
80 | dropPosition,
81 | },
82 | })
83 | },
84 | searchValue,
85 | autoExpandParent,
86 | loading: loading.effects['permission/query'],
87 | location,
88 | }
89 |
90 |
91 | const formProps = {
92 | currItem,
93 | treeData,
94 | loading: loading.effects[`${currItem.id ? 'permission/update' : 'permission/create'}`],
95 | onOk (data) {
96 | if (data.id) {
97 | dispatch({
98 | type: 'permission/update',
99 | payload: data,
100 | })
101 | } else {
102 | dispatch({
103 | type: 'permission/create',
104 | payload: data,
105 | })
106 | }
107 | },
108 | }
109 |
110 | return (
111 |
112 |
113 |
114 |
115 | {formVisible && }
116 |
117 |
118 |
119 | )
120 | }
121 |
122 | Permission.propTypes = {
123 | permission: PropTypes.object,
124 | location: PropTypes.object,
125 | dispatch: PropTypes.func,
126 | loading: PropTypes.object,
127 | }
128 |
129 | export default connect(({ permission, loading }) => ({ permission, loading }))(Permission)
130 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/role/Filter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Form, Button, Row, Col, Input, AutoComplete } from 'antd'
4 |
5 | /**
6 | * autoComplete 插件动态数据源只能通过修改 state
7 | */
8 |
9 | const ColProps = {
10 | xs: 24,
11 | sm: 12,
12 | style: {
13 | marginBottom: 16,
14 | },
15 | }
16 |
17 | const Filter = ({
18 | nameDataSource,
19 | onFilterChange,
20 | onAdd,
21 | changeNameDataSource,
22 | filter,
23 | form: {
24 | getFieldDecorator,
25 | getFieldsValue,
26 | setFieldsValue,
27 | },
28 | }) => {
29 | const handleSubmit = () => {
30 | let fields = getFieldsValue()
31 | onFilterChange(fields)
32 | }
33 |
34 | const handlerSearch = (val) => {
35 | setFieldsValue({ searchId: '' })
36 | changeNameDataSource(val)
37 | }
38 |
39 | const handlerSelect = (val) => {
40 | setFieldsValue({ searchId: val })
41 | }
42 |
43 |
44 | const { name } = filter
45 |
46 | return (
47 |
48 |
49 | {getFieldDecorator('searchName', { initialValue: name })(
50 |
58 | )}
59 | {getFieldDecorator('searchId')(
60 |
61 | )}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
73 | Filter.propTypes = {
74 | onAdd: PropTypes.func,
75 | isMotion: PropTypes.bool,
76 | switchIsMotion: PropTypes.func,
77 | form: PropTypes.object,
78 | filter: PropTypes.object,
79 | onFilterChange: PropTypes.func,
80 | nameDataSource: PropTypes.any,
81 | changeNameDataSource: PropTypes.func,
82 | }
83 |
84 | export default Form.create()(Filter)
85 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/role/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal } from 'antd'
4 | import classnames from 'classnames'
5 | import AnimTableBody from '../../../components/DataTable/AnimTableBody'
6 | import { DropOption } from '../../../components'
7 | import styles from './List.less'
8 |
9 | const confirm = Modal.confirm
10 |
11 | const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) => {
12 | const handleMenuClick = (record, e) => {
13 | if (e.key === '1') {
14 | onEditItem(record)
15 | } else if (e.key === '2') {
16 | confirm({
17 | title: '确定删除当前数据?',
18 | onOk () {
19 | onDeleteItem(record.id)
20 | },
21 | })
22 | }
23 | }
24 |
25 | const columns = [
26 | {
27 | title: 'id',
28 | dataIndex: 'id',
29 | key: 'id',
30 | },
31 | {
32 | title: '名称',
33 | dataIndex: 'name',
34 | key: 'name',
35 | },
36 | {
37 | title: '权限列表',
38 | dataIndex: 'permissionNames',
39 | key: 'permissionNames',
40 | }, {
41 | title: '创建日期',
42 | dataIndex: 'createTime',
43 | key: 'createTime',
44 | render: text => (text ? new Date(text).format('yyyy-MM-dd') : ''),
45 | }, {
46 | title: '操作',
47 | key: 'operation',
48 | width: 100,
49 | render: (text, record) => (
50 | handleMenuClick(record, e)} menuOptions={[{ key: '1', name: '修改' }, { key: '2', name: '删除' }]} />
51 | ),
52 | },
53 | ]
54 |
55 | const getBodyWrapperProps = {
56 | page: location.query.page,
57 | current: tableProps.pagination.current,
58 | }
59 |
60 | const getBodyWrapper = body => (isMotion ? : body)
61 |
62 | return (
63 |
64 |
record.id}
71 | getBodyWrapper={getBodyWrapper}
72 | />
73 |
74 | )
75 | }
76 |
77 | List.propTypes = {
78 | onDeleteItem: PropTypes.func,
79 | onEditItem: PropTypes.func,
80 | isMotion: PropTypes.bool,
81 | location: PropTypes.object,
82 | }
83 |
84 | export default List
85 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/role/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | td.avatar {
3 | img {
4 | border-radius: 50%;
5 | }
6 | }
7 |
8 | :global {
9 | .ant-table-tbody > tr > td,
10 | .ant-table-thead > tr > th {
11 | height: 62px;
12 | }
13 | }
14 |
15 | &.motion {
16 | :global {
17 | .ant-table-tbody > tr > td,
18 | .ant-table-thead > tr > th {
19 | &:nth-child(1) {
20 | display: none;
21 | }
22 |
23 | &:nth-child(2) {
24 | width: 30%;
25 | }
26 |
27 | &:nth-child(3) {
28 | width: 20%;
29 | }
30 |
31 | &:nth-child(4) {
32 | width: 30%;
33 | }
34 |
35 | &:nth-child(5) {
36 | width: 20%;
37 | }
38 | }
39 |
40 | .ant-table-thead {
41 | & > tr {
42 | transition: none;
43 | display: block;
44 |
45 | & > th {
46 | display: inline-flex;
47 | align-items: center;
48 | justify-content: center;
49 | }
50 | }
51 | }
52 |
53 | .ant-table-tbody {
54 | & > tr {
55 | transition: none;
56 | display: block;
57 | border-bottom: 1px solid #f5f5f5;
58 |
59 | & > td {
60 | border-bottom: none;
61 | display: inline-flex;
62 | align-items: center;
63 | justify-content: center;
64 | }
65 |
66 | &.ant-table-expanded-row-level-1 > td {
67 | height: auto;
68 | }
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
75 | .hidden {
76 | display: none !important;
77 | }
78 |
--------------------------------------------------------------------------------
/fe/src/routes/sys/role/Modal.less:
--------------------------------------------------------------------------------
1 | .hidden {
2 | display: none;
3 | }
4 |
--------------------------------------------------------------------------------
/fe/src/services/app.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../utils'
2 |
3 | const { api } = config
4 | const { user, userLogout, userLogin } = api
5 |
6 | export async function login (params) {
7 | return request({
8 | url: userLogin,
9 | method: 'post',
10 | data: params,
11 | })
12 | }
13 |
14 | export async function logout (params) {
15 | return request({
16 | url: userLogout,
17 | method: 'get',
18 | data: params,
19 | })
20 | }
21 |
22 | export async function query (params) {
23 | return request({
24 | url: user.replace('/:id', ''),
25 | method: 'get',
26 | data: params,
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/fe/src/services/common.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangrupeng on 17/5/24.
3 | */
4 | import { request, config } from '../utils'
5 |
6 |
7 | const { api } = config
8 | const { common } = api
9 | const { autoComplete, select } = common
10 |
11 | export async function queryAutoComplete (params) {
12 | return request({
13 | url: autoComplete,
14 | method: 'get',
15 | data: params,
16 | })
17 | }
18 |
19 | export async function querySelect (params) {
20 | return request({
21 | url: select,
22 | method: 'get',
23 | data: params,
24 | })
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/fe/src/services/dashboard.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../utils'
2 |
3 | const { api } = config
4 | const { dashboard } = api
5 |
6 | export async function query (params) {
7 | return request({
8 | url: dashboard,
9 | method: 'get',
10 | data: params,
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/fe/src/services/login.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../utils'
2 |
3 | const { api } = config
4 | const { userLogin } = api
5 |
6 | export async function login (data) {
7 | return request({
8 | url: userLogin,
9 | method: 'post',
10 | data,
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/fe/src/services/sys/account.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../../utils'
2 |
3 | const { api } = config
4 | const { sys } = api
5 | const { accounts } = sys
6 |
7 | export async function query (params) {
8 | return request({
9 | url: accounts,
10 | method: 'get',
11 | data: params,
12 | })
13 | }
14 |
15 | export async function create (params) {
16 | return request({
17 | url: accounts.replace('/:id', ''),
18 | method: 'post',
19 | data: params,
20 | })
21 | }
22 |
23 | export async function remove (params) {
24 | return request({
25 | url: accounts,
26 | method: 'delete',
27 | data: params,
28 | })
29 | }
30 |
31 | export async function update (params) {
32 | return request({
33 | url: accounts,
34 | method: 'patch',
35 | data: params,
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/fe/src/services/sys/dataSync.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../../utils'
2 |
3 | const { api } = config
4 | const { sys } = api
5 | const { dataSync, loginLog } = sys
6 |
7 | export async function query (params) {
8 | return request({
9 | url: dataSync,
10 | method: 'get',
11 | data: params,
12 | })
13 | }
14 |
15 | export async function createOrUpdate (params) {
16 | return request({
17 | url: dataSync,
18 | method: 'post',
19 | data: params,
20 | })
21 | }
22 |
23 | export async function queryLogs (params) {
24 | return request({
25 | url: `${dataSync}/:syncId/logs`,
26 | method: 'get',
27 | data: params,
28 | })
29 | }
30 | export async function queryLoginLog (params) {
31 | return request({
32 | url: loginLog,
33 | method: 'get',
34 | data: params,
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/fe/src/services/sys/menu.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../../utils'
2 |
3 | const { api } = config
4 | const { sys } = api
5 | const { menus } = sys
6 |
7 | export async function query (params) {
8 | return request({
9 | url: menus,
10 | method: 'get',
11 | data: params,
12 | })
13 | }
14 |
15 | export async function create (params) {
16 | return request({
17 | url: menus.replace('/:id', ''),
18 | method: 'post',
19 | data: params,
20 | })
21 | }
22 |
23 | export async function remove (params) {
24 | return request({
25 | url: menus,
26 | method: 'delete',
27 | data: params,
28 | })
29 | }
30 |
31 | export async function update (params) {
32 | return request({
33 | url: menus,
34 | method: 'patch',
35 | data: params,
36 | })
37 | }
38 |
39 | export async function sort (params) {
40 | return request({
41 | url: `${menus}/sort`,
42 | method: 'patch',
43 | data: params,
44 | })
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/fe/src/services/sys/permission.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../../utils'
2 |
3 | const { api } = config
4 | const { sys } = api
5 | const { permissions } = sys
6 |
7 | export default async function query (params) {
8 | return request({
9 | url: permissions,
10 | method: 'get',
11 | data: params,
12 | })
13 | }
14 |
15 | export async function create (params) {
16 | return request({
17 | url: permissions.replace('/:id', ''),
18 | method: 'post',
19 | data: params,
20 | })
21 | }
22 |
23 | export async function remove (params) {
24 | return request({
25 | url: permissions,
26 | method: 'delete',
27 | data: params,
28 | })
29 | }
30 |
31 | export async function update (params) {
32 | return request({
33 | url: permissions,
34 | method: 'patch',
35 | data: params,
36 | })
37 | }
38 |
39 | export async function sort (params) {
40 | return request({
41 | url: `${permissions}/sort`,
42 | method: 'patch',
43 | data: params,
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/fe/src/services/sys/role.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../../utils'
2 |
3 | const { api } = config
4 | const { sys } = api
5 | const { roles, permissions } = sys
6 |
7 | export default async function query (params) {
8 | return request({
9 | url: roles,
10 | method: 'get',
11 | data: params,
12 | })
13 | }
14 |
15 | export async function create (params) {
16 | return request({
17 | url: roles.replace('/:id', ''),
18 | method: 'post',
19 | data: params,
20 | })
21 | }
22 |
23 | export async function remove (params) {
24 | return request({
25 | url: roles,
26 | method: 'delete',
27 | data: params,
28 | })
29 | }
30 |
31 | export async function update (params) {
32 | return request({
33 | url: roles,
34 | method: 'patch',
35 | data: params,
36 | })
37 | }
38 |
39 | export async function queryPermissions (params) {
40 | return request({
41 | url: permissions,
42 | method: 'get',
43 | data: params,
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/fe/src/services/sys/schedule.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../../utils'
2 |
3 | const { api } = config
4 | const { sys } = api
5 | const { schedule } = sys
6 |
7 | export async function query (params) {
8 | return request({
9 | url: schedule,
10 | method: 'get',
11 | data: params,
12 | })
13 | }
14 |
15 | export async function update (params) {
16 | return request({
17 | url: schedule,
18 | method: 'post',
19 | data: params,
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/fe/src/services/user.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../utils'
2 |
3 | const { api } = config
4 | const { user } = api
5 |
6 | export async function query (params) {
7 | return request({
8 | url: user,
9 | method: 'get',
10 | data: params,
11 | })
12 | }
13 |
14 | export async function create (params) {
15 | return request({
16 | url: user.replace('/:id', ''),
17 | method: 'post',
18 | data: params,
19 | })
20 | }
21 |
22 | export async function remove (params) {
23 | return request({
24 | url: user,
25 | method: 'delete',
26 | data: params,
27 | })
28 | }
29 |
30 | export async function update (params) {
31 | return request({
32 | url: user,
33 | method: 'patch',
34 | data: params,
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/fe/src/services/users.js:
--------------------------------------------------------------------------------
1 | import { request, config } from '../utils'
2 |
3 | const { api } = config
4 | const { users } = api
5 |
6 | export async function query (params) {
7 | return request({
8 | url: users,
9 | method: 'get',
10 | data: params,
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/fe/src/services/weather.js:
--------------------------------------------------------------------------------
1 | import { request } from '../utils'
2 |
3 | export async function query (params) {
4 | params.key = 'i7sau1babuzwhycn'
5 | return request({
6 | url: '/weather/now.json',
7 | method: 'get',
8 | data: params,
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/fe/src/svg/emoji/smirking.svg:
--------------------------------------------------------------------------------
1 |
2 |
27 |
--------------------------------------------------------------------------------
/fe/src/svg/emoji/surprised.svg:
--------------------------------------------------------------------------------
1 |
2 |
23 |
--------------------------------------------------------------------------------
/fe/src/svg/emoji/tired.svg:
--------------------------------------------------------------------------------
1 |
2 |
24 |
--------------------------------------------------------------------------------
/fe/src/svg/emoji/unamused.svg:
--------------------------------------------------------------------------------
1 |
2 |
27 |
--------------------------------------------------------------------------------
/fe/src/svg/emoji/wink.svg:
--------------------------------------------------------------------------------
1 |
2 |
25 |
--------------------------------------------------------------------------------
/fe/src/themes/default.less:
--------------------------------------------------------------------------------
1 | // 本文件是对 ant-design:
2 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
3 | // 相应变量值的覆盖
4 | // 注意:只需写出要覆盖的变量即可(不需要覆盖的变量不要写)
5 | // http://www.iconfont.cn/collections/detail?cid=790
6 | @import "../../node_modules/antd/lib/style/themes/default.less";
7 | @border-radius-base: 3px;
8 | @border-radius-sm: 2px;
9 | @shadow-color: rgba(0,0,0,0.05);
10 | @shadow-1-down: 4px 4px 40px @shadow-color;
11 | @border-color-split: #f4f4f4;
12 | @border-color-base: #e5e5e5;
13 | @menu-dark-bg: #3e3e3e;
14 | @text-color: #666;
15 | @font-family: "AvenirNext-Regular", "Helvetica Neue", "lucida grande", "PingFangHK-Light", "STHeiti", "Heiti SC", "Hiragino Sans GB", "Microsoft JhengHei", "Microsoft Yahei", SimHei, "WenQuanYi Micro Hei", "Droid Sans", "Roboto", Helvetica, Tahoma, Arial, "sans-serif";
16 | @icon-url: "/antd/iconfont";
17 |
--------------------------------------------------------------------------------
/fe/src/themes/index.less:
--------------------------------------------------------------------------------
1 | @import "~themes/vars";
2 |
3 | body {
4 | height: 100%;
5 | overflow-y: hidden;
6 | background-color: #f8f8f8;
7 | }
8 |
9 | ::-webkit-scrollbar-thumb {
10 | background-color: #e6e6e6;
11 | }
12 |
13 | ::-webkit-scrollbar {
14 | width: 8px;
15 | height: 8px;
16 | }
17 |
18 | :global {
19 | .ant-breadcrumb {
20 | & > span {
21 | &:last-child {
22 | color: #999;
23 | font-weight: normal;
24 | }
25 | }
26 | }
27 |
28 | .ant-breadcrumb-link {
29 | .anticon + span {
30 | margin-left: 4px;
31 | }
32 | }
33 |
34 | .ant-table {
35 | .ant-table-thead > tr > th {
36 | text-align: center;
37 | }
38 |
39 | .ant-table-tbody > tr > td {
40 | text-align: center;
41 | }
42 |
43 | &.ant-table-small {
44 | .ant-table-thead > tr > th {
45 | background: #f7f7f7;
46 | }
47 |
48 | .ant-table-body > table {
49 | padding: 0;
50 | }
51 | }
52 | }
53 |
54 | .ant-table-pagination {
55 | float: none!important;
56 | display: table;
57 | margin: 16px auto !important;
58 | }
59 |
60 | .ant-popover-inner {
61 | border: none;
62 | border-radius: 0;
63 | box-shadow: 0 0 20px rgba(100, 100, 100, 0.2);
64 | }
65 |
66 | .vertical-center-modal {
67 | display: flex;
68 | align-items: center;
69 | justify-content: center;
70 |
71 | .ant-modal {
72 | top: 0;
73 |
74 | .ant-modal-body {
75 | max-height: 80vh;
76 | overflow-y: auto;
77 | }
78 | }
79 | }
80 |
81 | .ant-form-item-control {
82 | vertical-align: middle;
83 | }
84 |
85 | .ant-modal-mask {
86 | background-color: rgba(55, 55, 55, 0.2);
87 | }
88 |
89 | .ant-modal-content {
90 | box-shadow: none;
91 | }
92 |
93 | .ant-select-dropdown-menu-item {
94 | padding: 12px 16px !important;
95 | }
96 |
97 | .margin-right {
98 | margin-right: 16px;
99 | }
100 |
101 | a:focus {
102 | text-decoration: none;
103 | }
104 | }
105 | @media (min-width: 1600px) {
106 | :global {
107 | .ant-col-xl-48 {
108 | width: 20%;
109 | }
110 |
111 | .ant-col-xl-96 {
112 | width: 40%;
113 | }
114 | }
115 | }
116 | @media (max-width: 767px) {
117 | :global {
118 | .ant-pagination-item,
119 | .ant-pagination-next,
120 | .ant-pagination-options,
121 | .ant-pagination-prev {
122 | margin-bottom: 8px;
123 | }
124 |
125 | .ant-card {
126 | .ant-card-head {
127 | padding: 0 12px;
128 | }
129 |
130 | .ant-card-body {
131 | padding: 12px;
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/fe/src/themes/mixin.less:
--------------------------------------------------------------------------------
1 | @import "./default.less"; // antd-admin
2 | @dark-half: #494949;
3 | @purple: #d897eb;
4 | @shadow-1: 4px 4px 20px 0 rgba(0, 0, 0, 0.01);
5 | @shadow-2: 4px 4px 40px 0 rgba(0, 0, 0, 0.05);
6 | @transition-ease-in: all 0.3s ease-out;
7 | @transition-ease-out: all 0.3s ease-out;
8 | @ease-in: ease-in;
9 |
10 | .text-overflow {
11 | white-space: nowrap;
12 | text-overflow: ellipsis;
13 | overflow: hidden;
14 | }
15 |
--------------------------------------------------------------------------------
/fe/src/themes/vars.less:
--------------------------------------------------------------------------------
1 | @import "./default.less";
2 | @import "./mixin.less";
3 |
--------------------------------------------------------------------------------
/fe/src/utils/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'antd admin spring-boot',
3 | version: '0.9.0',
4 | prefix: 'antdAdmin',
5 | footerText: 'Copyright © 2017. 进击的大黄 . All rights reserved',
6 | logo: '/logo.png',
7 | qrCode: '/QR_code.jpg',
8 | iconFontCSS: '/iconfont.css',
9 | iconFontJS: '/iconfont.js',
10 | CORS: [],
11 | openPages: ['/login'],
12 | api: {
13 | userLogin: '/user/login',
14 | userLogout: '/user/logout',
15 | userInfo: '/userInfo',
16 | users: '/users',
17 | user: '/user/:id',
18 | weather: '/weather',
19 | dashboard: '/dashboard',
20 | common: {
21 | autoComplete: '/common/combox/autoComplete',
22 | select: '/common/combox/select',
23 | upload: '/common/file/upload',
24 | preview: '/common/file/preview',
25 | download: '/common/file/download',
26 | },
27 | sys: {
28 | accounts: '/sys/accounts',
29 | menus: '/sys/menus',
30 | permissions: '/sys/permissions',
31 | roles: '/sys/roles',
32 | },
33 | },
34 | }
35 |
--------------------------------------------------------------------------------
/fe/src/utils/date.js:
--------------------------------------------------------------------------------
1 |
2 | Date.prototype.format = function() {
3 | let s = '';
4 | const mouth = (this.getMonth() + 1)>=10?(this.getMonth() + 1):('0'+(this.getMonth() + 1));
5 | const day = this.getDate()>=10?this.getDate():('0'+this.getDate());
6 | s += this.getFullYear() + '-'; // 获取年份。
7 | s += mouth + "-"; // 获取月份。
8 | s += day; // 获取日。
9 | return (s); // 返回日期。
10 | };
11 |
12 | /**
13 | * 获取两个日期(格式:yyyy-MM-dd)内所有的日期
14 | * @param begin
15 | * @param end
16 | */
17 | const getAllByDateStr = (begin, end) => {
18 | const ab = begin.split("-");
19 | const ae = end.split("-");
20 | let db = new Date();
21 | db.setUTCFullYear(ab[0], ab[1] - 1, ab[2]);
22 | let de = new Date();
23 | de.setUTCFullYear(ae[0], ae[1] - 1, ae[2]);
24 | const unixDb = db.getTime();
25 | const unixDe = de.getTime();
26 | const result = [];
27 | for (let k = unixDb; k <= unixDe;) {
28 | result.push((new Date(parseInt(k))).format());
29 | k = k + 24 * 60 * 60 * 1000;
30 | }
31 | return result;
32 | };
33 |
34 | /**
35 | * 根据传入的时间毫秒数获取两个时间内所有的天数
36 | * @param begin
37 | * @param end
38 | */
39 | const getAllByMilliSecones = (begin, end) => {
40 | const beginStr = (new Date(parseInt(begin))).format();
41 | const endStr = (new Date(parseInt(end))).format();
42 | const ab = beginStr.split("-");
43 | const ae = endStr.split("-");
44 | let db = new Date();
45 | db.setUTCFullYear(ab[0], ab[1] - 1, ab[2]);
46 | let de = new Date();
47 | de.setUTCFullYear(ae[0], ae[1] - 1, ae[2]);
48 | const unixDb = db.getTime();
49 | const unixDe = de.getTime();
50 | const result = [];
51 | for (let k = unixDb; k <= unixDe;) {
52 | result.push((new Date(parseInt(k))).format());
53 | k = k + 24 * 60 * 60 * 1000;
54 | }
55 | return result;
56 | };
57 |
58 | module.exports = {
59 | getAllByDateStr,
60 | getAllByMilliSecones,
61 | };
62 |
--------------------------------------------------------------------------------
/fe/src/utils/theme.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | color: {
3 | green: '#64ea91',
4 | blue: '#8fc9fb',
5 | purple: '#d897eb',
6 | red: '#f69899',
7 | yellow: '#f8c82e',
8 | peach: '#f797d6',
9 | borderBase: '#e5e5e5',
10 | borderSplit: '#f4f4f4',
11 | grass: '#d6fbb5',
12 | sky: '#c1e0fc',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/fe/theme.config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const lessToJs = require('less-vars-to-js')
4 |
5 | module.exports = () => {
6 | const themePath = path.join(__dirname, './src/themes/default.less')
7 | return lessToJs(fs.readFileSync(themePath, 'utf8'))
8 | }
9 |
--------------------------------------------------------------------------------
/fe/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin')
2 | const CopyWebpackPlugin = require('copy-webpack-plugin')
3 | const webpack = require('webpack')
4 |
5 | module.exports = (webpackConfig, env) => {
6 | const production = env === 'production'
7 | // FilenameHash
8 | webpackConfig.output.chunkFilename = '[name].[chunkhash].js'
9 |
10 | if (production) {
11 | if (webpackConfig.module) {
12 | // ClassnameHash
13 | webpackConfig.module.rules.map((item) => {
14 | if (String(item.test) === '/\\.less$/' || String(item.test) === '/\\.css/') {
15 | item.use.filter(iitem => iitem.loader === 'css')[0].options.localIdentName = '[hash:base64:5]'
16 | }
17 | return item
18 | })
19 | }
20 | webpackConfig.plugins.push(
21 | new webpack.LoaderOptionsPlugin({
22 | minimize: true,
23 | debug: false,
24 | })
25 | )
26 | }
27 |
28 | webpackConfig.plugins = webpackConfig.plugins.concat([
29 | new CopyWebpackPlugin([
30 | {
31 | from: 'src/public',
32 | to: production ? '../' : webpackConfig.output.outputPath,
33 | },
34 | ]),
35 | new HtmlWebpackPlugin({
36 | template: `${__dirname}/src/entry.ejs`,
37 | filename: production ? '../index.html' : 'index.html',
38 | minify: production ? {
39 | collapseWhitespace: true,
40 | } : null,
41 | hash: true,
42 | headScripts: production ? null : ['/roadhog.dll.js'],
43 | }),
44 | new webpack.DefinePlugin({
45 | BASE_URL: production ? JSON.stringify('') : JSON.stringify('http://localhost:8000/api/v1'),
46 | }),
47 | ])
48 |
49 | // Alias
50 | webpackConfig.resolve.alias = {
51 | components: `${__dirname}/src/components`,
52 | utils: `${__dirname}/src/utils`,
53 | config: `${__dirname}/src/utils/config`,
54 | enums: `${__dirname}/src/utils/enums`,
55 | services: `${__dirname}/src/services`,
56 | models: `${__dirname}/src/models`,
57 | routes: `${__dirname}/src/routes`,
58 | themes: `${__dirname}/src/themes`,
59 | }
60 |
61 | return webpackConfig
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/Application.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.builder.SpringApplicationBuilder;
6 | import org.springframework.boot.web.support.SpringBootServletInitializer;
7 | import org.springframework.transaction.annotation.EnableTransactionManagement;
8 |
9 | /**
10 | * Description:
11 | *
12 | *
13 | * Author: javahuang
14 | * Create: 17/9/16 上午3:59
15 | */
16 | @SpringBootApplication
17 | @EnableTransactionManagement
18 | public class Application extends SpringBootServletInitializer {
19 |
20 | public static void main(String[] args) {
21 | SpringApplication.run(Application.class, args);
22 | }
23 |
24 | @Override
25 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
26 | return application.sources(Application.class);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/config/WebConfig.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.config;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.core.io.Resource;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.GetMapping;
8 | import org.springframework.web.bind.annotation.RestController;
9 | import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
10 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
12 |
13 | /**
14 | * Description:
15 | *
16 | *
17 | * Author: javahuang
18 | * Create: 17/9/16 上午3:59
19 | */
20 | @Configuration
21 | @RestController
22 | public class WebConfig extends WebMvcConfigurerAdapter {
23 |
24 | @Value("classpath:/static/index.html")
25 | private Resource indexHtml;
26 |
27 | // 匹配类型的静态资源都会被 ResourceHandler 来处理
28 | private static final String[] STATIC_RESOURCES = {
29 | "/**/*.css", "/**/*.js",
30 | "/**/*.jpg", "/**/*.png", "/**/*.svg", // 图片
31 | "/**/*.eot", "/**/*.ttf", "/**/*.woff" // 字体文件
32 | };
33 |
34 | @Override
35 | public void addResourceHandlers(ResourceHandlerRegistry registry) {
36 | registry.setOrder(-1) // 设置静态资源映射优先级高于下面配置的 @GetMapping
37 | .addResourceHandler(STATIC_RESOURCES)
38 | .addResourceLocations("classpath:/static/");
39 | }
40 |
41 | @Override
42 | public void configurePathMatch(PathMatchConfigurer configurer) {
43 | configurer.setUseSuffixPatternMatch(false); // 默认 /index 会匹配 /index.js
44 | }
45 |
46 | /**
47 | * @return
48 | */
49 | @GetMapping
50 | public Object index() {
51 | return ResponseEntity.ok().body(indexHtml);
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/constant/AppConsts.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.constant;
2 |
3 | /**
4 | * Description:
5 | *
6 | *
7 | * Author: javahuang
8 | * Create: 17/9/16 上午4:17
9 | */
10 | public class AppConsts {
11 | /**
12 | * 系统里面所有 true 对应 1,数据库表字段类型为 number(1)
13 | */
14 | public static final Short TRUE = 1;
15 | /**
16 | * 系统里面所有 false 对应 0,数据库表字段类型为 number(1)
17 | */
18 | public static final Short FALSE = 0;
19 |
20 | /**
21 | * 菜单
22 | */
23 | public static final Byte IS_RESOURCE_MENU = 1;
24 | /**
25 | * 有路由配置,但不在导航栏显示的菜单
26 | */
27 | public static final String SUB_MENU = "submenu";
28 | /**
29 | * 面包屑菜单导航父节点 ID
30 | */
31 | public static final String MENU_ROOT_BPID = "4dcab7f2ad5f4166996de3ff910ac8c2";
32 |
33 | /**
34 | * 登录URI
35 | */
36 | public static final String QUERY_USER_URI = "/user";
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/exception/ServiceException.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.exception;
2 |
3 | /**
4 | * Description:
5 | *
6 | *
7 | * Author: javahuang
8 | * Create: 17/9/16 上午4:30
9 | */
10 | public class ServiceException extends RuntimeException{
11 |
12 | public ServiceException() {
13 | super();
14 | }
15 |
16 | public ServiceException(String message) {
17 | super(message);
18 | }
19 |
20 | public ServiceException(String message, Throwable cause) {
21 | super(message, cause);
22 | }
23 |
24 | public ServiceException(Throwable cause) {
25 | super(cause);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/exception/ValidatorException.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.exception;
2 |
3 | /**
4 | * Description:
5 | *
6 | * 表单远程校验异常
7 | *
8 | * Author: javahuang
9 | * Create: 17/9/26 下午5:38
10 | */
11 | public class ValidatorException extends RuntimeException {
12 |
13 | public ValidatorException() {
14 | }
15 |
16 | public ValidatorException(String message) {
17 | super(message);
18 | }
19 |
20 | public ValidatorException(String message, Throwable cause) {
21 | super(message, cause);
22 | }
23 |
24 | public ValidatorException(Throwable cause) {
25 | super(cause);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/mvc/advice/MyResponseBodyAdvice.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.mvc.advice;
2 |
3 | import com.github.pagehelper.Page;
4 | import com.google.common.collect.Maps;
5 | import me.hrps.aas.core.mybatis.domain.TableResponse;
6 | import org.springframework.core.MethodParameter;
7 | import org.springframework.core.io.Resource;
8 | import org.springframework.http.MediaType;
9 | import org.springframework.http.server.ServerHttpRequest;
10 | import org.springframework.http.server.ServerHttpResponse;
11 | import org.springframework.web.bind.annotation.ControllerAdvice;
12 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
13 |
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | /**
18 | * Description:
19 | *
20 | * 包装返回资源 匹配前端
21 | *
22 | * Author: javahuang
23 | * Create: 17/9/16 上午4:21
24 | */
25 | @ControllerAdvice
26 | public class MyResponseBodyAdvice implements ResponseBodyAdvice{
27 |
28 | @Override
29 | public boolean supports(MethodParameter returnType, Class converterType) {
30 | return true;
31 | }
32 |
33 | @Override
34 | public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
35 | // 静态页面无需拦截
36 | if (body instanceof Resource) {
37 | return body;
38 | }
39 | if (body == null) {
40 | return null;
41 | }
42 | // 分页
43 | if (body instanceof Page) {
44 | Page page = (Page) body;
45 | TableResponse result = new TableResponse<>();
46 | result.setTotal(page.getTotal());
47 | result.setList(page);
48 | return result;
49 | }
50 | // 如果返回的是数组,前端通过 data.array 来获取
51 | if (body instanceof List || body.getClass().isArray()) {
52 | Map result = Maps.newHashMap();
53 | result.put("array", body);
54 | return result;
55 | }
56 | return body;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/mvc/advice/ServiceExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.mvc.advice;
2 |
3 | import com.google.common.collect.Maps;
4 | import me.hrps.aas.core.exception.ServiceException;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.core.env.Environment;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.jdbc.UncategorizedSQLException;
12 | import org.springframework.web.HttpMediaTypeNotSupportedException;
13 | import org.springframework.web.bind.annotation.ExceptionHandler;
14 | import org.springframework.web.bind.annotation.RestControllerAdvice;
15 | import org.springframework.web.context.request.ServletWebRequest;
16 | import org.springframework.web.context.request.WebRequest;
17 | import sun.security.validator.ValidatorException;
18 |
19 | import java.util.Map;
20 |
21 | /**
22 | * Description:
23 | *
24 | * 统一处理所有的异常,方便业务类直接处理
25 | *
26 | * Author: javahuang
27 | * Create: 17/9/16 上午4:28
28 | */
29 | @RestControllerAdvice
30 | public class ServiceExceptionHandler {
31 |
32 | private Logger logger = LoggerFactory.getLogger(ServiceException.class);
33 |
34 | private final Environment env;
35 |
36 | @Autowired
37 | public ServiceExceptionHandler(Environment env) {
38 | this.env = env;
39 | }
40 |
41 | @ExceptionHandler(Exception.class)
42 | public ResponseEntity> handlerError(Exception ex, WebRequest request) {
43 | Map resp = Maps.newHashMap();
44 | HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
45 | String url = ((ServletWebRequest) request).getRequest().getRequestURI(); // 当前请求url
46 | String msg = env.getProperty("err.default");
47 | if (ex instanceof UncategorizedSQLException) {
48 | // sql 异常
49 | msg = env.getProperty("err.sql");
50 | } else if (ex instanceof ValidatorException) {
51 | // 表单校验异常
52 | msg = ex.getMessage();
53 | status = HttpStatus.BAD_REQUEST;
54 | } else if (ex instanceof HttpMediaTypeNotSupportedException) {
55 | // 媒体类型不被支持
56 | } else {
57 | msg = ex.getMessage();
58 | }
59 | ex.printStackTrace();
60 | resp.put("message", msg);
61 | logger.error("url:{},error:{}", url, ex.getMessage());
62 | return ResponseEntity.status(status).body(resp);
63 | }
64 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/mybatis/domain/PageFilter.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.mybatis.domain;
2 |
3 | /**
4 | * Description:
5 | *
6 | *
7 | * Author: javahuang
8 | * Create: 2017/9/26 下午1:00
9 | */
10 | public class PageFilter {
11 |
12 | // 当前页码
13 | private Integer page = 1;
14 | // 分页数量
15 | private Integer pageSize = 10;
16 |
17 | public Integer getPage() {
18 | return page;
19 | }
20 |
21 | public void setPage(Integer page) {
22 | this.page = page;
23 | }
24 |
25 | public Integer getPageSize() {
26 | return pageSize;
27 | }
28 |
29 | public void setPageSize(Integer pageSize) {
30 | this.pageSize = pageSize;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/mybatis/domain/TableResponse.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.mybatis.domain;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * Description:
7 | *
8 | * 封装的页面表格数据
9 | *
10 | * Author: javahuang
11 | * Create: 2017/9/26 下午1:01
12 | */
13 | public class TableResponse {
14 |
15 | private Long total;
16 | private List list;
17 |
18 | public Long getTotal() {
19 | return total;
20 | }
21 |
22 | public void setTotal(Long total) {
23 | this.total = total;
24 | }
25 |
26 | public List getList() {
27 | return list;
28 | }
29 |
30 | public void setList(List list) {
31 | this.list = list;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/shiro/domain/ShiroUser.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.shiro.domain;
2 |
3 | import me.hrps.aas.web.sys.vo.MenuVO;
4 |
5 | import java.io.Serializable;
6 | import java.util.List;
7 |
8 | /**
9 | * Description:
10 | *
11 | *
12 | * Author: javahuang
13 | * Create: 17/9/16 上午4:04
14 | */
15 | public class ShiroUser implements Serializable{
16 |
17 | private String accountId;
18 | private String loginName;
19 | private String name;
20 | private Boolean isAdmin;
21 | private transient List roleIdList;
22 | private transient List menus;
23 | private transient List roles;
24 | private transient List permissions;
25 |
26 |
27 | public String getAccountId() {
28 | return accountId;
29 | }
30 |
31 | public void setAccountId(String accountId) {
32 | this.accountId = accountId;
33 | }
34 |
35 | public String getLoginName() {
36 | return loginName;
37 | }
38 |
39 | public void setLoginName(String loginName) {
40 | this.loginName = loginName;
41 | }
42 |
43 | public String getName() {
44 | return name;
45 | }
46 |
47 | public void setName(String name) {
48 | this.name = name;
49 | }
50 |
51 | public boolean isAdmin() {
52 | return isAdmin;
53 | }
54 |
55 | public void setAdmin(Boolean admin) {
56 | isAdmin = admin;
57 | }
58 |
59 | public List getRoleIdList() {
60 | return roleIdList;
61 | }
62 |
63 | public void setRoleIdList(List roleIdList) {
64 | this.roleIdList = roleIdList;
65 | }
66 |
67 | public List getMenus() {
68 | return menus;
69 | }
70 |
71 | public void setMenus(List menus) {
72 | this.menus = menus;
73 | }
74 |
75 | public List getRoles() {
76 | return roles;
77 | }
78 |
79 | public void setRoles(List roles) {
80 | this.roles = roles;
81 | }
82 |
83 | public List getPermissions() {
84 | return permissions;
85 | }
86 |
87 | public void setPermissions(List permissions) {
88 | this.permissions = permissions;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/shiro/filter/MyFormAuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.shiro.filter;
2 |
3 | import org.apache.shiro.authc.AuthenticationException;
4 | import org.apache.shiro.authc.AuthenticationToken;
5 | import org.apache.shiro.subject.Subject;
6 | import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
7 |
8 | import javax.servlet.ServletRequest;
9 | import javax.servlet.ServletResponse;
10 |
11 | /**
12 | * Description:
13 | *
14 | *
15 | *
16 | * Author: javahuang
17 | * Create: 17/9/16 上午4:05
18 | */
19 | public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
20 |
21 | @Override
22 | protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
23 | AuthenticationToken token = createToken(request, response);
24 | if (token == null) {
25 | String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
26 | "must be created in order to execute a login attempt.";
27 | throw new IllegalStateException(msg);
28 | }
29 | try {
30 | Subject subject = getSubject(request, response);
31 | subject.login(token);
32 | // 初始化授权操作
33 | subject.isPermitted(" init");
34 | return onLoginSuccess(token, subject, request, response);
35 | } catch (AuthenticationException e) {
36 | //throw e;
37 | return onLoginFailure(token, e, request, response);
38 | }
39 | }
40 |
41 | protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
42 | ServletRequest request, ServletResponse response) throws Exception {
43 | //we handled the success redirect directly, prevent the chain from continuing:
44 | return false;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/shiro/filter/MyLogoutFilter.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.shiro.filter;
2 |
3 | import org.apache.shiro.session.SessionException;
4 | import org.apache.shiro.subject.Subject;
5 | import org.apache.shiro.web.filter.authc.LogoutFilter;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import javax.servlet.ServletRequest;
10 | import javax.servlet.ServletResponse;
11 |
12 | /**
13 | * Description:
14 | *
15 | *
16 | * Author: javahuang
17 | * Create: 17/9/18 下午10:34
18 | */
19 | public class MyLogoutFilter extends LogoutFilter {
20 |
21 | private Logger log = LoggerFactory.getLogger(MyLogoutFilter.class);
22 |
23 | @Override
24 | protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
25 | Subject subject = getSubject(request, response);
26 | //try/catch added for SHIRO-298:
27 | try {
28 | subject.logout();
29 | } catch (SessionException ise) {
30 | log.debug("Encountered session exception during logout. This can generally safely be ignored.", ise);
31 | }
32 | return false;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/shiro/filter/MyUserFilter.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.shiro.filter;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.apache.shiro.subject.Subject;
5 | import org.apache.shiro.web.filter.authc.UserFilter;
6 | import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.util.AntPathMatcher;
9 |
10 | import javax.servlet.ServletRequest;
11 | import javax.servlet.ServletResponse;
12 | import javax.servlet.http.HttpServletResponse;
13 |
14 | /**
15 | * Description:
16 | *
17 | *
18 | * Author: javahuang
19 | * Create: 17/9/16 上午4:05
20 | */
21 | public class MyUserFilter extends UserFilter {
22 |
23 | /**
24 | * 所有的静态资源请求都允许通过
25 | *
26 | * @param request
27 | * @param response
28 | * @param mappedValue
29 | * @return
30 | */
31 | protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
32 | if (isLoginRequest(request, response) || isStaticResourceRequest(request)) {
33 | return true;
34 | } else {
35 | Subject subject = getSubject(request, response);
36 | return subject.getPrincipal() != null;
37 | }
38 | }
39 |
40 | @Override
41 | protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
42 | HttpServletResponse httpServletResponse = (HttpServletResponse) response;
43 | httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
44 | return false;
45 | }
46 |
47 | /**
48 | * 静态资源请求都允许通过
49 | *
50 | * @return
51 | */
52 | private boolean isStaticResourceRequest(ServletRequest request) {
53 | String url = ((ShiroHttpServletRequest) request).getRequestURI();
54 | if ("/".equals(url)) {
55 | return true;
56 | }
57 | String[] staticResources =
58 | {".html", ".js", ".css",
59 | ".eot", ".svg", ".ttf", ".woff",
60 | ".png", ".jpg", ".ico"};
61 | if (StringUtils.endsWithAny(url, staticResources)) {
62 | return true;
63 | }
64 | AntPathMatcher matcher = new AntPathMatcher();
65 | return false;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/shiro/realm/ShiroDbRealm.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.shiro.realm;
2 |
3 | import me.hrps.aas.core.shiro.domain.ShiroUser;
4 | import me.hrps.aas.web.sys.service.AccountService;
5 | import org.apache.shiro.authc.*;
6 | import org.apache.shiro.authz.AuthorizationInfo;
7 | import org.apache.shiro.authz.SimpleAuthorizationInfo;
8 | import org.apache.shiro.realm.AuthorizingRealm;
9 | import org.apache.shiro.subject.PrincipalCollection;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 |
12 | /**
13 | * Description:
14 | *
15 | *
16 | *
17 | * Author: javahuang
18 | * Create: 17/5/16 下午12:59
19 | */
20 | public class ShiroDbRealm extends AuthorizingRealm {
21 |
22 | @Autowired
23 | private AccountService accountService;
24 |
25 | /**
26 | * 认证
27 | *
28 | * @param token
29 | * @return
30 | * @throws AuthenticationException
31 | */
32 | @Override
33 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
34 | UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
35 | String username = usernamePasswordToken.getUsername();
36 | String password = new String(usernamePasswordToken.getPassword());
37 | return new SimpleAuthenticationInfo(accountService.login(username, password),
38 | usernamePasswordToken.getPassword(), getName());
39 | }
40 |
41 | /**
42 | * 授权,只有代码中第一次鉴权的时候才会触发此方法的执行
43 | *
44 | * @param principals
45 | * @return
46 | */
47 | @Override
48 | protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
49 | ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
50 | SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
51 | info.addRoles(shiroUser.getRoles());
52 | info.addStringPermissions(shiroUser.getPermissions());
53 | return info;
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/utils/Exceptions.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.utils;
2 |
3 | /**
4 | * Description:
5 | *
6 | * 异常转化
7 | *
8 | * Author: javahuang
9 | * Create: 17/5/23 上午9:43
10 | */
11 | public class Exceptions {
12 |
13 | /**
14 | * 将 CheckedException 转换为 UncheckedException
15 | *
16 | * @param e CheckedException
17 | * @return UncheckedException
18 | */
19 | public static RuntimeException unchecked(Exception e) {
20 | if (e instanceof RuntimeException) {
21 | return (RuntimeException) e;
22 | } else {
23 | return new RuntimeException(e);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/utils/PasswordUtils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * PasswordUtil.java Create on 2012-11-19
3 | * Copyright(c) Gener-Tech Inc.
4 | * ALL Rights Reserved.
5 | */
6 | package me.hrps.aas.core.utils;
7 |
8 |
9 | import org.apache.commons.codec.DecoderException;
10 | import org.apache.commons.codec.binary.Hex;
11 |
12 | /**
13 | * Description:
14 | *
15 | *
16 | * Author: javahuang
17 | * Create: 17/5/17 下午2:58
18 | */
19 | public class PasswordUtils {
20 |
21 |
22 | public static byte[] getBytes(String str) {
23 | return str.getBytes();
24 | }
25 |
26 | public static String getEncodePassWord(String passWord, byte[] salt) {
27 | byte[] hashPassword = Digests.sha1(getBytes(passWord), salt, 1024);
28 | return encodeHex(hashPassword);
29 | }
30 |
31 | public static String encodeHex(byte[] bytes) {
32 | return Hex.encodeHexString(bytes);
33 | }
34 |
35 | /**
36 | * Hex解密算法
37 | */
38 | public static byte[] decodeHex(String input) {
39 | try {
40 | return Hex.decodeHex(input.toCharArray());
41 | } catch (DecoderException e) {
42 | throw Exceptions.unchecked(e);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/utils/Securitys.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.utils;
2 |
3 | import me.hrps.aas.core.shiro.domain.ShiroUser;
4 | import me.hrps.aas.web.sys.vo.MenuVO;
5 | import org.apache.shiro.SecurityUtils;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * Description:
11 | *
12 | *
13 | * Author: javahuang
14 | * Create: 2017/9/26 下午12:56
15 | */
16 | public class Securitys extends SecurityUtils {
17 |
18 | public static ShiroUser getUser() {
19 | if (isAuthenticatedOrRemembered()) {
20 | return (ShiroUser) getSubject().getPrincipal();
21 | }
22 | return new ShiroUser();
23 | }
24 |
25 | public static boolean isAuthenticatedOrRemembered() {
26 | return getSubject().isAuthenticated() || getSubject().isRemembered();
27 | }
28 |
29 | public static List getMenus() {
30 | return getUser().getMenus();
31 | }
32 |
33 | public static String getName() {
34 | return getUser().getName();
35 | }
36 |
37 | public static String getAccountId() {
38 | return getUser().getAccountId();
39 | }
40 |
41 | public static boolean isAdmin() {
42 | return getUser().isAdmin();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/core/utils/Utils.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.core.utils;
2 |
3 | import java.util.UUID;
4 |
5 | /**
6 | * Description:
7 | *
8 | * 系统常用的一些工具类
9 | *
10 | * Author: javahuang
11 | * Create: 17/9/26 下午1:40
12 | */
13 | public class Utils {
14 |
15 | /**
16 | * @return 返回UUID作为主键
17 | */
18 | public static String generateUUID() {
19 | return UUID.randomUUID().toString().replaceAll("-", "");
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/controller/AccountController.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.controller;
2 |
3 | import me.hrps.aas.web.sys.service.AccountService;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.web.bind.annotation.PostMapping;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RestController;
8 |
9 | import java.util.Map;
10 |
11 | /**
12 | * Description:
13 | *
14 | *
15 | * Author: javahuang
16 | * Create: 2017/9/26 下午12:53
17 | */
18 | @RestController
19 | @RequestMapping("/user")
20 | public class AccountController {
21 |
22 | @Autowired
23 | private AccountService accountService;
24 |
25 | @RequestMapping
26 | public Map user() {
27 | return accountService.getUser();
28 | }
29 |
30 | @PostMapping("/login")
31 | public void login(String username, String password) {
32 | accountService.login(username, password);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/domain/SysAccountRoleKey.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.domain;
2 |
3 | public class SysAccountRoleKey {
4 | private String roleId;
5 |
6 | private String accountId;
7 |
8 | public String getRoleId() {
9 | return roleId;
10 | }
11 |
12 | public void setRoleId(String roleId) {
13 | this.roleId = roleId == null ? null : roleId.trim();
14 | }
15 |
16 | public String getAccountId() {
17 | return accountId;
18 | }
19 |
20 | public void setAccountId(String accountId) {
21 | this.accountId = accountId == null ? null : accountId.trim();
22 | }
23 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/domain/SysPermission.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.domain;
2 |
3 | import java.util.Date;
4 |
5 | public class SysPermission {
6 | private String id;
7 |
8 | private String name;
9 |
10 | private String code;
11 |
12 | private String parentId;
13 |
14 | private Byte permissionLevel;
15 |
16 | private Byte sequence;
17 |
18 | private Byte isResource;
19 |
20 | private String remark;
21 |
22 | private Date createTime;
23 |
24 | private String createBy;
25 |
26 | private Date updateTime;
27 |
28 | private String updateBy;
29 |
30 | public String getId() {
31 | return id;
32 | }
33 |
34 | public void setId(String id) {
35 | this.id = id == null ? null : id.trim();
36 | }
37 |
38 | public String getName() {
39 | return name;
40 | }
41 |
42 | public void setName(String name) {
43 | this.name = name == null ? null : name.trim();
44 | }
45 |
46 | public String getCode() {
47 | return code;
48 | }
49 |
50 | public void setCode(String code) {
51 | this.code = code == null ? null : code.trim();
52 | }
53 |
54 | public String getParentId() {
55 | return parentId;
56 | }
57 |
58 | public void setParentId(String parentId) {
59 | this.parentId = parentId == null ? null : parentId.trim();
60 | }
61 |
62 | public Byte getPermissionLevel() {
63 | return permissionLevel;
64 | }
65 |
66 | public void setPermissionLevel(Byte permissionLevel) {
67 | this.permissionLevel = permissionLevel;
68 | }
69 |
70 | public Byte getSequence() {
71 | return sequence;
72 | }
73 |
74 | public void setSequence(Byte sequence) {
75 | this.sequence = sequence;
76 | }
77 |
78 | public Byte getIsResource() {
79 | return isResource;
80 | }
81 |
82 | public void setIsResource(Byte isResource) {
83 | this.isResource = isResource;
84 | }
85 |
86 | public String getRemark() {
87 | return remark;
88 | }
89 |
90 | public void setRemark(String remark) {
91 | this.remark = remark == null ? null : remark.trim();
92 | }
93 |
94 | public Date getCreateTime() {
95 | return createTime;
96 | }
97 |
98 | public void setCreateTime(Date createTime) {
99 | this.createTime = createTime;
100 | }
101 |
102 | public String getCreateBy() {
103 | return createBy;
104 | }
105 |
106 | public void setCreateBy(String createBy) {
107 | this.createBy = createBy == null ? null : createBy.trim();
108 | }
109 |
110 | public Date getUpdateTime() {
111 | return updateTime;
112 | }
113 |
114 | public void setUpdateTime(Date updateTime) {
115 | this.updateTime = updateTime;
116 | }
117 |
118 | public String getUpdateBy() {
119 | return updateBy;
120 | }
121 |
122 | public void setUpdateBy(String updateBy) {
123 | this.updateBy = updateBy == null ? null : updateBy.trim();
124 | }
125 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/domain/SysRole.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.domain;
2 |
3 | import java.util.Date;
4 |
5 | public class SysRole {
6 | private String id;
7 |
8 | private String name;
9 |
10 | private String reamrk;
11 |
12 | private Byte isValid;
13 |
14 | private String helpCode;
15 |
16 | private Date createTime;
17 |
18 | private String createBy;
19 |
20 | private Date updateTime;
21 |
22 | private String updateBy;
23 |
24 | public String getId() {
25 | return id;
26 | }
27 |
28 | public void setId(String id) {
29 | this.id = id == null ? null : id.trim();
30 | }
31 |
32 | public String getName() {
33 | return name;
34 | }
35 |
36 | public void setName(String name) {
37 | this.name = name == null ? null : name.trim();
38 | }
39 |
40 | public String getReamrk() {
41 | return reamrk;
42 | }
43 |
44 | public void setReamrk(String reamrk) {
45 | this.reamrk = reamrk == null ? null : reamrk.trim();
46 | }
47 |
48 | public Byte getIsValid() {
49 | return isValid;
50 | }
51 |
52 | public void setIsValid(Byte isValid) {
53 | this.isValid = isValid;
54 | }
55 |
56 | public String getHelpCode() {
57 | return helpCode;
58 | }
59 |
60 | public void setHelpCode(String helpCode) {
61 | this.helpCode = helpCode == null ? null : helpCode.trim();
62 | }
63 |
64 | public Date getCreateTime() {
65 | return createTime;
66 | }
67 |
68 | public void setCreateTime(Date createTime) {
69 | this.createTime = createTime;
70 | }
71 |
72 | public String getCreateBy() {
73 | return createBy;
74 | }
75 |
76 | public void setCreateBy(String createBy) {
77 | this.createBy = createBy == null ? null : createBy.trim();
78 | }
79 |
80 | public Date getUpdateTime() {
81 | return updateTime;
82 | }
83 |
84 | public void setUpdateTime(Date updateTime) {
85 | this.updateTime = updateTime;
86 | }
87 |
88 | public String getUpdateBy() {
89 | return updateBy;
90 | }
91 |
92 | public void setUpdateBy(String updateBy) {
93 | this.updateBy = updateBy == null ? null : updateBy.trim();
94 | }
95 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/domain/SysRolePermissionKey.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.domain;
2 |
3 | public class SysRolePermissionKey {
4 | private String roleId;
5 |
6 | private String permissionId;
7 |
8 | public String getRoleId() {
9 | return roleId;
10 | }
11 |
12 | public void setRoleId(String roleId) {
13 | this.roleId = roleId == null ? null : roleId.trim();
14 | }
15 |
16 | public String getPermissionId() {
17 | return permissionId;
18 | }
19 |
20 | public void setPermissionId(String permissionId) {
21 | this.permissionId = permissionId == null ? null : permissionId.trim();
22 | }
23 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/filter/AccountFilter.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.filter;
2 |
3 | import me.hrps.aas.core.mybatis.domain.PageFilter;
4 |
5 | /**
6 | * Description:
7 | *
8 | *
9 | * Author: javahuang
10 | * Create: 2017/9/26 下午12:59
11 | */
12 | public class AccountFilter extends PageFilter{
13 |
14 | private String searchId;
15 |
16 | public String getSearchId() {
17 | return searchId;
18 | }
19 |
20 | public void setSearchId(String searchId) {
21 | this.searchId = searchId;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/filter/RoleFilter.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.filter;
2 |
3 | import me.hrps.aas.core.mybatis.domain.PageFilter;
4 |
5 | /**
6 | * Description:
7 | *
8 | *
9 | * Author: javahuang
10 | * Create: 2017/9/26 下午1:55
11 | */
12 | public class RoleFilter extends PageFilter{
13 |
14 | private String searchId;
15 |
16 | public String getSearchId() {
17 | return searchId;
18 | }
19 |
20 | public void setSearchId(String searchId) {
21 | this.searchId = searchId;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/mapper/SysAccountMapper.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.mapper;
2 |
3 | import me.hrps.aas.web.sys.domain.SysAccount;
4 | import me.hrps.aas.web.sys.domain.SysAccountExample;
5 | import me.hrps.aas.web.sys.filter.AccountFilter;
6 | import org.apache.ibatis.annotations.Mapper;
7 | import org.apache.ibatis.annotations.Param;
8 |
9 | import java.util.List;
10 |
11 | @Mapper
12 | public interface SysAccountMapper {
13 | long countByExample(SysAccountExample example);
14 |
15 | int deleteByExample(SysAccountExample example);
16 |
17 | int deleteByPrimaryKey(String id);
18 |
19 | int insert(SysAccount record);
20 |
21 | int insertSelective(SysAccount record);
22 |
23 | List selectByExample(SysAccountExample example);
24 |
25 | SysAccount selectByPrimaryKey(String id);
26 |
27 | int updateByExampleSelective(@Param("record") SysAccount record, @Param("example") SysAccountExample example);
28 |
29 | int updateByExample(@Param("record") SysAccount record, @Param("example") SysAccountExample example);
30 |
31 | int updateByPrimaryKeySelective(SysAccount record);
32 |
33 | int updateByPrimaryKey(SysAccount record);
34 |
35 | List selectByFilter(AccountFilter filter);
36 |
37 | List selectRoleUsers(String roleName);
38 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/mapper/SysAccountRoleMapper.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.mapper;
2 |
3 | import me.hrps.aas.web.sys.domain.SysAccountRoleExample;
4 | import me.hrps.aas.web.sys.domain.SysAccountRoleKey;
5 | import org.apache.ibatis.annotations.Mapper;
6 | import org.apache.ibatis.annotations.Param;
7 |
8 | import java.util.List;
9 |
10 | @Mapper
11 | public interface SysAccountRoleMapper {
12 | long countByExample(SysAccountRoleExample example);
13 |
14 | int deleteByExample(SysAccountRoleExample example);
15 |
16 | int deleteByPrimaryKey(SysAccountRoleKey key);
17 |
18 | int insert(SysAccountRoleKey record);
19 |
20 | int insertSelective(SysAccountRoleKey record);
21 |
22 | List selectByExample(SysAccountRoleExample example);
23 |
24 | int updateByExampleSelective(@Param("record") SysAccountRoleKey record, @Param("example") SysAccountRoleExample example);
25 |
26 | int updateByExample(@Param("record") SysAccountRoleKey record, @Param("example") SysAccountRoleExample example);
27 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/mapper/SysMenuMapper.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.mapper;
2 |
3 | import me.hrps.aas.web.sys.domain.SysMenu;
4 | import me.hrps.aas.web.sys.domain.SysMenuExample;
5 | import org.apache.ibatis.annotations.Mapper;
6 | import org.apache.ibatis.annotations.Param;
7 |
8 | import java.util.List;
9 |
10 | @Mapper
11 | public interface SysMenuMapper {
12 | long countByExample(SysMenuExample example);
13 |
14 | int deleteByExample(SysMenuExample example);
15 |
16 | int deleteByPrimaryKey(String id);
17 |
18 | int insert(SysMenu record);
19 |
20 | int insertSelective(SysMenu record);
21 |
22 | List selectByExample(SysMenuExample example);
23 |
24 | SysMenu selectByPrimaryKey(String id);
25 |
26 | int updateByExampleSelective(@Param("record") SysMenu record, @Param("example") SysMenuExample example);
27 |
28 | int updateByExample(@Param("record") SysMenu record, @Param("example") SysMenuExample example);
29 |
30 | int updateByPrimaryKeySelective(SysMenu record);
31 |
32 | int updateByPrimaryKey(SysMenu record);
33 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/mapper/SysPermissionMapper.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.mapper;
2 |
3 | import me.hrps.aas.web.sys.domain.SysPermission;
4 | import me.hrps.aas.web.sys.domain.SysPermissionExample;
5 | import org.apache.ibatis.annotations.Mapper;
6 | import org.apache.ibatis.annotations.Param;
7 |
8 | import java.util.List;
9 |
10 | @Mapper
11 | public interface SysPermissionMapper {
12 | long countByExample(SysPermissionExample example);
13 |
14 | int deleteByExample(SysPermissionExample example);
15 |
16 | int deleteByPrimaryKey(String id);
17 |
18 | int insert(SysPermission record);
19 |
20 | int insertSelective(SysPermission record);
21 |
22 | List selectByExample(SysPermissionExample example);
23 |
24 | SysPermission selectByPrimaryKey(String id);
25 |
26 | int updateByExampleSelective(@Param("record") SysPermission record, @Param("example") SysPermissionExample example);
27 |
28 | int updateByExample(@Param("record") SysPermission record, @Param("example") SysPermissionExample example);
29 |
30 | int updateByPrimaryKeySelective(SysPermission record);
31 |
32 | int updateByPrimaryKey(SysPermission record);
33 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/mapper/SysRoleMapper.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.mapper;
2 |
3 | import me.hrps.aas.web.sys.domain.SysRole;
4 | import me.hrps.aas.web.sys.domain.SysRoleExample;
5 | import me.hrps.aas.web.sys.filter.RoleFilter;
6 | import org.apache.ibatis.annotations.Mapper;
7 | import org.apache.ibatis.annotations.Param;
8 |
9 | import java.util.List;
10 |
11 | @Mapper
12 | public interface SysRoleMapper {
13 | long countByExample(SysRoleExample example);
14 |
15 | int deleteByExample(SysRoleExample example);
16 |
17 | int deleteByPrimaryKey(String id);
18 |
19 | int insert(SysRole record);
20 |
21 | int insertSelective(SysRole record);
22 |
23 | List selectByExample(SysRoleExample example);
24 |
25 | SysRole selectByPrimaryKey(String id);
26 |
27 | int updateByExampleSelective(@Param("record") SysRole record, @Param("example") SysRoleExample example);
28 |
29 | int updateByExample(@Param("record") SysRole record, @Param("example") SysRoleExample example);
30 |
31 | int updateByPrimaryKeySelective(SysRole record);
32 |
33 | int updateByPrimaryKey(SysRole record);
34 |
35 | List> selectByFilter(RoleFilter roleFilter);
36 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/mapper/SysRolePermissionMapper.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.mapper;
2 |
3 | import me.hrps.aas.web.sys.domain.SysRolePermissionExample;
4 | import me.hrps.aas.web.sys.domain.SysRolePermissionKey;
5 | import org.apache.ibatis.annotations.Mapper;
6 | import org.apache.ibatis.annotations.Param;
7 |
8 | import java.util.List;
9 |
10 | @Mapper
11 | public interface SysRolePermissionMapper {
12 | long countByExample(SysRolePermissionExample example);
13 |
14 | int deleteByExample(SysRolePermissionExample example);
15 |
16 | int deleteByPrimaryKey(SysRolePermissionKey key);
17 |
18 | int insert(SysRolePermissionKey record);
19 |
20 | int insertSelective(SysRolePermissionKey record);
21 |
22 | List selectByExample(SysRolePermissionExample example);
23 |
24 | int updateByExampleSelective(@Param("record") SysRolePermissionKey record, @Param("example") SysRolePermissionExample example);
25 |
26 | int updateByExample(@Param("record") SysRolePermissionKey record, @Param("example") SysRolePermissionExample example);
27 | }
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/qo/TreeSortQO.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.qo;
2 |
3 | /**
4 | * Description:
5 | *
6 | *
7 | * Author: javahuang
8 | * Create: 2017/9/26 下午1:37
9 | */
10 | public class TreeSortQO {
11 |
12 | private String dropKey;
13 | private String dragKey;
14 | private Integer dropPosition;
15 |
16 | public String getDropKey() {
17 | return dropKey;
18 | }
19 |
20 | public void setDropKey(String dropKey) {
21 | this.dropKey = dropKey;
22 | }
23 |
24 | public String getDragKey() {
25 | return dragKey;
26 | }
27 |
28 | public void setDragKey(String dragKey) {
29 | this.dragKey = dragKey;
30 | }
31 |
32 | public Integer getDropPosition() {
33 | return dropPosition;
34 | }
35 |
36 | public void setDropPosition(Integer dropPosition) {
37 | this.dropPosition = dropPosition;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/vo/MenuPermissionTreeVO.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.vo;
2 |
3 | /**
4 | * Description:
5 | *
6 | * 用于 SysPermission 数据转换成 tree-select 能够识别的数据结构
7 | * 类型定义为 String,是因为 TreeNode 的参数类型都是 string
8 | *
9 | * Author: javahuang
10 | * Create: 2017/9/26 下午1:43
11 | */
12 | public class MenuPermissionTreeVO {
13 |
14 | private String value;
15 | private String label;
16 | private String parentId;
17 |
18 | public String getValue() {
19 | return value;
20 | }
21 |
22 | public void setValue(String value) {
23 | this.value = value;
24 | }
25 |
26 | public String getLabel() {
27 | return label;
28 | }
29 |
30 | public void setLabel(String label) {
31 | this.label = label;
32 | }
33 |
34 | public String getParentId() {
35 | return parentId;
36 | }
37 |
38 | public void setParentId(String parentId) {
39 | this.parentId = parentId;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/vo/MenuVO.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.vo;
2 |
3 | /**
4 | * Description:
5 | *
6 | *
7 | * Author: javahuang
8 | * Create: 17/9/18 下午10:14
9 | */
10 | public class MenuVO {
11 |
12 | private String id;
13 | private String bpid;
14 | private String mpid;
15 | private String name;
16 | private String icon;
17 | private String router;
18 | private Integer menuLevel;
19 |
20 | public String getId() {
21 | return id;
22 | }
23 |
24 | public void setId(String id) {
25 | this.id = id;
26 | }
27 |
28 | public String getBpid() {
29 | return bpid;
30 | }
31 |
32 | public void setBpid(String bpid) {
33 | this.bpid = bpid;
34 | }
35 |
36 | public String getMpid() {
37 | return mpid;
38 | }
39 |
40 | public void setMpid(String mpid) {
41 | this.mpid = mpid;
42 | }
43 |
44 | public String getName() {
45 | return name;
46 | }
47 |
48 | public void setName(String name) {
49 | this.name = name;
50 | }
51 |
52 | public String getIcon() {
53 | return icon;
54 | }
55 |
56 | public void setIcon(String icon) {
57 | this.icon = icon;
58 | }
59 |
60 | public String getRouter() {
61 | return router;
62 | }
63 |
64 | public void setRouter(String router) {
65 | this.router = router;
66 | }
67 |
68 | public Integer getMenuLevel() {
69 | return menuLevel;
70 | }
71 |
72 | public void setMenuLevel(Integer menuLevel) {
73 | this.menuLevel = menuLevel;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/vo/SysAccountVO.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.vo;
2 |
3 | import me.hrps.aas.web.sys.domain.SysAccount;
4 |
5 | /**
6 | * Description:
7 | *
8 | *
9 | * Author: javahuang
10 | * Create: 2017/9/26 下午1:05
11 | */
12 | public class SysAccountVO extends SysAccount{
13 |
14 | private String roleNames;
15 | private String roleIds;
16 |
17 | public String getRoleNames() {
18 | return roleNames;
19 | }
20 |
21 | public void setRoleNames(String roleNames) {
22 | this.roleNames = roleNames;
23 | }
24 |
25 | public String getRoleIds() {
26 | return roleIds;
27 | }
28 |
29 | public void setRoleIds(String roleIds) {
30 | this.roleIds = roleIds;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/vo/SysRoleVO.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.vo;
2 |
3 | import me.hrps.aas.web.sys.domain.SysRole;
4 |
5 | /**
6 | * Description:
7 | *
8 | * 添加角色对应的权限列表
9 | *
10 | * Author: javahuang
11 | * Create: 2017/9/26 下午2:02
12 | */
13 | public class SysRoleVO extends SysRole{
14 |
15 | private String permissionNames;
16 | private String permissionIds;
17 |
18 | public String getPermissionNames() {
19 | return permissionNames;
20 | }
21 |
22 | public void setPermissionNames(String permissionNames) {
23 | this.permissionNames = permissionNames;
24 | }
25 |
26 | public String getPermissionIds() {
27 | return permissionIds;
28 | }
29 |
30 | public void setPermissionIds(String permissionIds) {
31 | this.permissionIds = permissionIds;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/me/hrps/aas/web/sys/vo/UserVO.java:
--------------------------------------------------------------------------------
1 | package me.hrps.aas.web.sys.vo;
2 |
3 | import me.hrps.aas.core.utils.Securitys;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * Description:
9 | *
10 | *
11 | * Author: javahuang
12 | * Create: 2017/9/26 下午12:55
13 | */
14 | public class UserVO {
15 |
16 | private String name;
17 | private String accountId;
18 | private Boolean isAdmin;
19 | private List roles;
20 | private List permissions;
21 |
22 | public static UserVO buildUser() {
23 | if (!Securitys.isAuthenticatedOrRemembered()) {
24 | return null;
25 | }
26 | UserVO user = new UserVO();
27 | user.setName(Securitys.getName());
28 | user.setAccountId(Securitys.getAccountId());
29 | user.setAdmin(Securitys.isAdmin());
30 | return user;
31 | }
32 |
33 |
34 | public String getName() {
35 | return name;
36 | }
37 |
38 | public void setName(String name) {
39 | this.name = name;
40 | }
41 |
42 | public String getAccountId() {
43 | return accountId;
44 | }
45 |
46 | public void setAccountId(String accountId) {
47 | this.accountId = accountId;
48 | }
49 |
50 | public Boolean getAdmin() {
51 | return isAdmin;
52 | }
53 |
54 | public void setAdmin(Boolean admin) {
55 | isAdmin = admin;
56 | }
57 |
58 | public List getRoles() {
59 | return roles;
60 | }
61 |
62 | public void setRoles(List roles) {
63 | this.roles = roles;
64 | }
65 |
66 | public List getPermissions() {
67 | return permissions;
68 | }
69 |
70 | public void setPermissions(List permissions) {
71 | this.permissions = permissions;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | driver-class-name: com.mysql.jdbc.Driver
4 | druid:
5 | filters: wall,stat,slf4j
6 | poolPreparedStatements: true
7 | stat-view-servlet:
8 | login-username: hello
9 | login-password: hello
10 | mvc:
11 | static-path-pattern: /static/**
12 | profiles:
13 | active: development
14 | server:
15 | port: 7000
16 | session:
17 | timeout: 1800
18 | mybatis:
19 | mapper-locations: classpath:/mybatis/**/*Mapper.xml
20 | pagehelper:
21 | support-methods-arguments: true
22 | params: pageNum=page
23 |
24 | --- # 开发配置
25 | spring:
26 | profiles: development
27 | datasource:
28 | url: jdbc:mysql://localhost:3306/aas_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
29 | username: root
30 | password: 123456
31 | resources:
32 | chain:
33 | gzipped: true
--------------------------------------------------------------------------------
/src/main/resources/mybatisGeneratorConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
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 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/main/resources/security/ehcache-shiro.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------