Merge remote-tracking branch 'origin/master'
commit
b384179610
|
@ -1,136 +0,0 @@
|
||||||
English | [简体中文](./README.zh-CN.md) | [Русский](./README.ru-RU.md)
|
|
||||||
|
|
||||||
<h1 align="center">Ant Design Pro</h1>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
An out-of-box UI solution for enterprise applications as a React boilerplate.
|
|
||||||
|
|
||||||
[![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
|
|
||||||
[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
|
|
||||||
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
|
|
||||||
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
|
|
||||||
[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
[![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
|
|
||||||
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
- Preview: http://preview.pro.ant.design
|
|
||||||
- Home Page: http://pro.ant.design
|
|
||||||
- Documentation: http://pro.ant.design/docs/getting-started
|
|
||||||
- ChangeLog: http://pro.ant.design/docs/changelog
|
|
||||||
- FAQ: http://pro.ant.design/docs/faq
|
|
||||||
- Mirror Site in China: http://ant-design-pro.gitee.io
|
|
||||||
|
|
||||||
## 2.0 Released Now! 🎉🎉🎉
|
|
||||||
[Announcing Ant Design Pro 2.0.0](https://medium.com/ant-design/beautiful-and-powerful-ant-design-pro-2-0-release-51358da5af95)
|
|
||||||
|
|
||||||
## Translation Recruitment :loudspeaker:
|
|
||||||
|
|
||||||
We need your help: https://github.com/ant-design/ant-design-pro/issues/120
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
|
|
||||||
- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
|
|
||||||
- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
|
|
||||||
- :iphone: **Responsive**: Designed for variable screen sizes
|
|
||||||
- :art: **Theming**: Customizable theme with simple config
|
|
||||||
- :globe_with_meridians: **International**: Built-in i18n solution
|
|
||||||
- :gear: **Best Practices**: Solid workflow to make your code healthy
|
|
||||||
- :1234: **Mock development**: Easy to use mock development solution
|
|
||||||
- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests
|
|
||||||
|
|
||||||
## Templates
|
|
||||||
|
|
||||||
```
|
|
||||||
- Dashboard
|
|
||||||
- Analytic
|
|
||||||
- Monitor
|
|
||||||
- Workspace
|
|
||||||
- Form
|
|
||||||
- Basic Form
|
|
||||||
- Step Form
|
|
||||||
- Advanced From
|
|
||||||
- List
|
|
||||||
- Standard Table
|
|
||||||
- Standard List
|
|
||||||
- Card List
|
|
||||||
- Search List (Project/Applications/Article)
|
|
||||||
- Profile
|
|
||||||
- Simple Profile
|
|
||||||
- Advanced Profile
|
|
||||||
- Account
|
|
||||||
- Account Center
|
|
||||||
- Account Settings
|
|
||||||
- Result
|
|
||||||
- Success
|
|
||||||
- Failed
|
|
||||||
- Exception
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
- 500
|
|
||||||
- User
|
|
||||||
- Login
|
|
||||||
- Register
|
|
||||||
- Register Result
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Use bash
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
|
|
||||||
$ cd ant-design-pro
|
|
||||||
$ npm install
|
|
||||||
$ npm start # visit http://localhost:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use by docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# preview
|
|
||||||
$ docker pull antdesign/ant-design-pro
|
|
||||||
$ docker run -p 80:80 antdesign/ant-design-pro
|
|
||||||
# open http://localhost
|
|
||||||
|
|
||||||
# dev
|
|
||||||
$ npm run docker:dev
|
|
||||||
|
|
||||||
# build
|
|
||||||
$ npm run docker:build
|
|
||||||
|
|
||||||
|
|
||||||
# production dev
|
|
||||||
$ npm run docker-prod:dev
|
|
||||||
|
|
||||||
# production build
|
|
||||||
$ npm run docker-prod:build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Gitpod
|
|
||||||
|
|
||||||
Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately.
|
|
||||||
|
|
||||||
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ant-design/ant-design-pro)
|
|
||||||
|
|
||||||
More instructions at [documentation](http://pro.ant.design/docs/getting-started).
|
|
||||||
|
|
||||||
## Browsers support
|
|
||||||
|
|
||||||
Modern browsers and IE11.
|
|
||||||
|
|
||||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
|
||||||
| --------- | --------- | --------- | --------- | --------- |
|
|
||||||
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Any type of contribution is welcome, here are some examples of how you may contribute to this project:
|
|
||||||
|
|
||||||
- Use Ant Design Pro in your daily work.
|
|
||||||
- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
|
|
||||||
- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
|
|
|
@ -3,3 +3,32 @@
|
||||||
> 采用 antd pro 快速开发
|
> 采用 antd pro 快速开发
|
||||||
|
|
||||||
> TODO
|
> TODO
|
||||||
|
|
||||||
|
## 命名规范
|
||||||
|
|
||||||
|
#### 1.文件夹命名
|
||||||
|
|
||||||
|
文件夹命名全部小写,单词之间以中划线隔离 例如: node-modules
|
||||||
|
|
||||||
|
|
||||||
|
#### 2.文件命名
|
||||||
|
|
||||||
|
文件以小写开头,以驼峰格式连接单词 例如: dashBoard.js
|
||||||
|
component目录下的文件 以大写开头
|
||||||
|
route目录下的文件以大写开头
|
||||||
|
model目录下的文件大写开头
|
||||||
|
|
||||||
|
|
||||||
|
#### 3.标点符号
|
||||||
|
|
||||||
|
对于字符串统一用单引号 例如: 'hello world'
|
||||||
|
|
||||||
|
#### 4.语法规范
|
||||||
|
|
||||||
|
JS语法规范遵守ES6规范
|
||||||
|
http://www.tuicool.com/articles/YrQ7j2a
|
||||||
|
|
||||||
|
#### 5.注释
|
||||||
|
|
||||||
|
1.route目录下的文件需要加上文件头部注释(写清楚文件是什么功能)
|
||||||
|
2.component文件需要加上头部注释 (写清楚改控件的用处)
|
|
@ -1,103 +0,0 @@
|
||||||
[English](./README.md) | [简体中文](./README.zh-CN.md) | Русский
|
|
||||||
|
|
||||||
<h1 align="center">Ant Design Pro</h1>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
UI-решение "из коробки" для корпоративных приложений как React boilerplate
|
|
||||||
|
|
||||||
[![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
|
|
||||||
[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
|
|
||||||
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
|
|
||||||
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
|
|
||||||
[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
[![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
|
|
||||||
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
- Демо: http://preview.pro.ant.design
|
|
||||||
- Домашняя страница: http://pro.ant.design
|
|
||||||
- Документация: http://pro.ant.design/docs/getting-started
|
|
||||||
- История изменений: http://pro.ant.design/docs/changelog
|
|
||||||
- FAQ: http://pro.ant.design/docs/faq
|
|
||||||
- Китайское зеркало сайта: http://ant-design-pro.gitee.io
|
|
||||||
|
|
||||||
## Поиск переводчиков :loudspeaker:
|
|
||||||
|
|
||||||
Нам нужна ваша помощь: https://github.com/ant-design/ant-design-pro/issues/120
|
|
||||||
|
|
||||||
## Возможности
|
|
||||||
|
|
||||||
- :gem: **Аккуратный дизайн**: Посмотрите [спецификацию Ant Design](http://ant.design/)
|
|
||||||
- :triangular_ruler: **Общие шаблоны**: Стандартные шаблоны для корпоративных приложений
|
|
||||||
- :rocket: **Разработка, как искусство**: Новейший стек технологий React/umi/dva/antd
|
|
||||||
- :iphone: **Отзывчивая верстка**: Создан для экранов разных размеров
|
|
||||||
- :art: **Темизация**: Возможность изменения темы с помощью конфигурации
|
|
||||||
- :globe_with_meridians: **Мультиязычность**: Встроенное i18n решение
|
|
||||||
- :gear: **Лучшие практики**: Надежные процессы для хорошего кода
|
|
||||||
- :1234: **Разработка по шаблону**: Простое в использовании решение для разработки
|
|
||||||
- :white_check_mark: **UI тесты**: Разрабатывайте безопасно с юнит и e2e тестами
|
|
||||||
|
|
||||||
## Шаблоны
|
|
||||||
|
|
||||||
```
|
|
||||||
- Dashboard
|
|
||||||
- Analytic
|
|
||||||
- Monitor
|
|
||||||
- Workspace
|
|
||||||
- Form
|
|
||||||
- Basic Form
|
|
||||||
- Step Form
|
|
||||||
- Advanced From
|
|
||||||
- List
|
|
||||||
- Standard Table
|
|
||||||
- Standard List
|
|
||||||
- Card List
|
|
||||||
- Search List (Project/Applications/Article)
|
|
||||||
- Profile
|
|
||||||
- Simple Profile
|
|
||||||
- Advanced Profile
|
|
||||||
- Account
|
|
||||||
- Account Center
|
|
||||||
- Account Settings
|
|
||||||
- Result
|
|
||||||
- Success
|
|
||||||
- Failed
|
|
||||||
- Exception
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
- 500
|
|
||||||
- User
|
|
||||||
- Login
|
|
||||||
- Register
|
|
||||||
- Register Result
|
|
||||||
```
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
|
|
||||||
$ cd ant-design-pro
|
|
||||||
$ npm install
|
|
||||||
$ npm start # visit http://localhost:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
Больше информации в [документации](http://pro.ant.design/docs/getting-started).
|
|
||||||
|
|
||||||
## Совместимость
|
|
||||||
|
|
||||||
Современные браузеры и IE11.
|
|
||||||
|
|
||||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
|
||||||
| --------- | --------- | --------- | --------- | --------- |
|
|
||||||
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
|
|
||||||
|
|
||||||
## Распространение
|
|
||||||
|
|
||||||
Любые варианты распространения приветствуются! Вот несколько примеров того, как вы можете помочь распространению проекта:
|
|
||||||
|
|
||||||
- Использовать Ant Design Pro в ежедневной работе.
|
|
||||||
- Создавать [задачи](http://github.com/ant-design/ant-design-pro/issues) заводить баги или отвечать на вопросы.
|
|
||||||
- Делать [pull-реквесты](http://github.com/ant-design/ant-design-pro/pulls) для совершенствования нашего кода.
|
|
|
@ -1,121 +0,0 @@
|
||||||
[English](./README.md) | 简体中文 | [Русский](./README.ru-RU.md)
|
|
||||||
|
|
||||||
<h1 align="center">Ant Design Pro</h1>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
开箱即用的中台前端/设计解决方案。
|
|
||||||
|
|
||||||
[![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
|
|
||||||
[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
|
|
||||||
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
|
|
||||||
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
|
|
||||||
[![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
|
|
||||||
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
- 预览:http://preview.pro.ant.design
|
|
||||||
- 首页:http://pro.ant.design/index-cn
|
|
||||||
- 使用文档:http://pro.ant.design/docs/getting-started-cn
|
|
||||||
- 更新日志: http://pro.ant.design/docs/changelog-cn
|
|
||||||
- 常见问题:http://pro.ant.design/docs/faq-cn
|
|
||||||
- 国内镜像:http://ant-design-pro.gitee.io
|
|
||||||
|
|
||||||
## 特性
|
|
||||||
|
|
||||||
- :gem: **优雅美观**:基于 Ant Design 体系精心设计
|
|
||||||
- :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景
|
|
||||||
- :rocket: **最新技术栈**:使用 React/umi/dva/antd 等前端前沿技术开发
|
|
||||||
- :iphone: **响应式**:针对不同屏幕大小设计
|
|
||||||
- :art: **主题**:可配置的主题满足多样化的品牌诉求
|
|
||||||
- :globe_with_meridians: **国际化**:内建业界通用的国际化方案
|
|
||||||
- :gear: **最佳实践**:良好的工程实践助您持续产出高质量代码
|
|
||||||
- :1234: **Mock 数据**:实用的本地数据调试方案
|
|
||||||
- :white_check_mark: **UI 测试**:自动化测试保障前端产品质量
|
|
||||||
|
|
||||||
## 模板
|
|
||||||
|
|
||||||
```
|
|
||||||
- Dashboard
|
|
||||||
- 分析页
|
|
||||||
- 监控页
|
|
||||||
- 工作台
|
|
||||||
- 表单页
|
|
||||||
- 基础表单页
|
|
||||||
- 分步表单页
|
|
||||||
- 高级表单页
|
|
||||||
- 列表页
|
|
||||||
- 查询表格
|
|
||||||
- 标准列表
|
|
||||||
- 卡片列表
|
|
||||||
- 搜索列表(项目/应用/文章)
|
|
||||||
- 详情页
|
|
||||||
- 基础详情页
|
|
||||||
- 高级详情页
|
|
||||||
- 用户
|
|
||||||
- 用户中心页
|
|
||||||
- 用户设置页
|
|
||||||
- 结果
|
|
||||||
- 成功页
|
|
||||||
- 失败页
|
|
||||||
- 异常
|
|
||||||
- 403 无权限
|
|
||||||
- 404 找不到
|
|
||||||
- 500 服务器出错
|
|
||||||
- 帐户
|
|
||||||
- 登录
|
|
||||||
- 注册
|
|
||||||
- 注册成功
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用
|
|
||||||
|
|
||||||
### 使用命令行
|
|
||||||
```bash
|
|
||||||
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
|
|
||||||
$ cd ant-design-pro
|
|
||||||
$ npm install
|
|
||||||
$ npm start # 访问 http://localhost:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用 docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# preview
|
|
||||||
$ docker pull antdesign/ant-design-pro
|
|
||||||
$ docker run -p 80:80 antdesign/ant-design-pro
|
|
||||||
# open http://localhost
|
|
||||||
|
|
||||||
# dev
|
|
||||||
$ npm run docker:dev
|
|
||||||
|
|
||||||
# build
|
|
||||||
$ npm run docker:build
|
|
||||||
|
|
||||||
|
|
||||||
# production dev
|
|
||||||
$ npm run docker-prod:dev
|
|
||||||
|
|
||||||
// production build
|
|
||||||
$ npm run docker-prod:build
|
|
||||||
```
|
|
||||||
|
|
||||||
更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)。
|
|
||||||
|
|
||||||
## 支持环境
|
|
||||||
|
|
||||||
现代浏览器及 IE11。
|
|
||||||
|
|
||||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
|
||||||
| --------- | --------- | --------- | --------- | --------- |
|
|
||||||
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
|
|
||||||
|
|
||||||
## 参与贡献
|
|
||||||
|
|
||||||
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley::
|
|
||||||
|
|
||||||
- 在你的公司或个人项目中使用 Ant Design Pro。
|
|
||||||
- 通过 [Issue](http://github.com/ant-design/ant-design-pro/issues) 报告 bug 或进行咨询。
|
|
||||||
- 提交 [Pull Request](http://github.com/ant-design/ant-design-pro/pulls) 改进 Pro 的代码。
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
// 校验必须是英文或者数字
|
||||||
|
export function checkTypeWithEnglishAndNumbers (rule, value, callback, text) {
|
||||||
|
let char = /^[a-zA-Z0-9]+$/
|
||||||
|
if (char.test(value)) {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
callback(text)
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,10 +49,10 @@ function getDictionaryTree(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'GET /admin-api/admins/admin/menu_resource_tree': getAdminMenu,
|
'GET /admin-api/admins/admin/menu_resource_tree': getAdminMenuAll,
|
||||||
'GET /admin-api/admins/admin/url_resource_list': getAdminUrls,
|
'GET /admin-api/admins/admin/url_resource_list': getAdminUrls,
|
||||||
'GET /admin-api/admins/resource/tree': getResourceTree,
|
'GET /admin-api/admins/resource/tree': getResourceTree,
|
||||||
'GET /admin-api/admins/role/page': getQueryRole,
|
'GET /admin-api/admins/role/page': getQueryRole,
|
||||||
'GET /admin-api/admins/admin/page': getQueryRole,
|
// 'GET /admin-api/admins/admin/page': getQueryRole,
|
||||||
'GET /admin-api/admins/data_dict/tree': getDictionaryTree,
|
'GET /admin-api/admins/data_dict/tree': getDictionaryTree,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default class DictionaryText extends PureComponent {
|
||||||
componentDidMount() {}
|
componentDidMount() {}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
debugger;
|
||||||
const { dicKey, dicValue } = this.props;
|
const { dicKey, dicValue } = this.props;
|
||||||
return (
|
return (
|
||||||
<DictionaryContext.Consumer>
|
<DictionaryContext.Consumer>
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import React, {PureComponent} from "react";
|
||||||
|
import {Select} from "antd";
|
||||||
|
|
||||||
|
const Option = Select.Option;
|
||||||
|
|
||||||
|
export default class ProductAttrSelectFormItem extends PureComponent {
|
||||||
|
|
||||||
|
handleSelectAttr = (value, option) => {
|
||||||
|
// console.log(value);
|
||||||
|
// console.log(option);
|
||||||
|
// debugger;
|
||||||
|
const { dispatch, index } = this.props;
|
||||||
|
// let attrIndex = option.key.substring(option.key.indexOf('option-attr-') + 'option-attr-'.length, option.key.lastIndexOf('-'));
|
||||||
|
// console.log('attrIndex: ' + attrIndex);
|
||||||
|
// debugger;
|
||||||
|
dispatch({
|
||||||
|
type: 'productSpuAddOrUpdate/selectAttr',
|
||||||
|
payload: {
|
||||||
|
attrIndex: index,
|
||||||
|
attr: {
|
||||||
|
id: option.props.value,
|
||||||
|
name: option.props.children,
|
||||||
|
values: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectAttrValue = (values, options) => {
|
||||||
|
let attrValues = [];
|
||||||
|
const { dispatch, index } = this.props;
|
||||||
|
// debugger;
|
||||||
|
// console.log('x' + this.children[0]);
|
||||||
|
// let firstOption = this.children[0];
|
||||||
|
// let attrIndex = firstOption.key.substring(firstOption.key.indexOf('option-attr-value-') + 'option-attr-value-'.length, firstOption.key.lastIndexOf('-'));
|
||||||
|
for (let i in options) {
|
||||||
|
let option = options[i];
|
||||||
|
attrValues.push({
|
||||||
|
id: parseInt(option.props.value),
|
||||||
|
name: option.props.children,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dispatch({
|
||||||
|
type: 'productSpuAddOrUpdate/selectAttrValues',
|
||||||
|
payload: {
|
||||||
|
attrIndex: index,
|
||||||
|
attrValues: attrValues,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// debugger;
|
||||||
|
|
||||||
|
// console.log(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {attr, allAttrTree, selectedAttrIds, index} = this.props;
|
||||||
|
// console.log('i: ' + i);
|
||||||
|
// 1. 规格
|
||||||
|
let attrOptions = [];
|
||||||
|
// allAttrTree.unshift(attr);
|
||||||
|
// debugger;
|
||||||
|
for (let j in allAttrTree) {
|
||||||
|
let allAttr = allAttrTree[j];
|
||||||
|
if (selectedAttrIds.has(allAttr.id) && allAttr.id !== attr.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
attrOptions.push(<Option key={`option-attr-${index}-${allAttr.id}`} value={allAttr.id}>{allAttr.name}</Option>);
|
||||||
|
}
|
||||||
|
// 2. 规格值
|
||||||
|
let attrValueOptions = [];
|
||||||
|
// debugger;
|
||||||
|
if (attr.id) {
|
||||||
|
// 2.1 先找到规格值的数组
|
||||||
|
let attrValues = [];
|
||||||
|
for (let j in allAttrTree) {
|
||||||
|
let allAttr = allAttrTree[j];
|
||||||
|
if (attr.id === allAttr.id) {
|
||||||
|
attrValues = allAttr.values;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2.2 生成规格值的 HTML
|
||||||
|
for (let j in attrValues) {
|
||||||
|
let attrValue = attrValues[j];
|
||||||
|
attrValueOptions.push(<Option key={`option-attr-value-${index}-${attrValue.id}`}
|
||||||
|
value={attrValue.id + ''}>{attrValue.name}</Option>); // + '' 的原因是,多选必须是字符串
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. 拼装最终,添加到 attrTreeHTML 中
|
||||||
|
return <div key={`div-attr-${index}`}>
|
||||||
|
<Select key={`select-attr-${index}`} style={{width: 120}} placeholder='请选择规格' onChange={this.handleSelectAttr}>
|
||||||
|
{attrOptions}
|
||||||
|
</Select>
|
||||||
|
<Select key={`select-attr-value-${index}`} mode={"tags"} style={{width: 260}} placeholder='请选择规格值'
|
||||||
|
onChange={this.handleSelectAttrValue}>
|
||||||
|
{attrValueOptions}
|
||||||
|
</Select>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import React, {PureComponent} from "react";
|
||||||
|
import {InputNumber, Select, Table} from "antd";
|
||||||
|
import Input from "antd/es/input";
|
||||||
|
|
||||||
|
const Option = Select.Option;
|
||||||
|
|
||||||
|
class SkuInputNumber extends PureComponent {
|
||||||
|
|
||||||
|
handleChange = value => {
|
||||||
|
// debugger;
|
||||||
|
const { dispatch, index, dataIndex } = this.props;
|
||||||
|
if (dataIndex === 'price') {
|
||||||
|
dispatch({
|
||||||
|
type: 'productSpuAddOrUpdate/inputSkuPrice',
|
||||||
|
payload: {
|
||||||
|
index: index,
|
||||||
|
price: value
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (dataIndex === 'quantity') {
|
||||||
|
dispatch({
|
||||||
|
type: 'productSpuAddOrUpdate/inputSkuQuantity',
|
||||||
|
payload: {
|
||||||
|
index: index,
|
||||||
|
quantity: value
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <InputNumber placeholder="请输入" onChange={this.handleChange} />
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ProductSkuAddOrUpdateTable extends PureComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let that = this;
|
||||||
|
// debugger;
|
||||||
|
// console.log('ProductSkuAddOrUpdateTable');
|
||||||
|
const {attrTree, skus, dispatch} = this.props;
|
||||||
|
let columns = [];
|
||||||
|
for (let i in attrTree) {
|
||||||
|
let attr = attrTree[i];
|
||||||
|
columns.push({
|
||||||
|
title: attr.name,
|
||||||
|
dataIndex: 'attrs[i]',
|
||||||
|
render(value, record) {
|
||||||
|
return record.attrs[i].name;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
columns.push({
|
||||||
|
title: '价格',
|
||||||
|
dataIndex: 'price',
|
||||||
|
render(value, record, index) {
|
||||||
|
let props = {
|
||||||
|
record: record,
|
||||||
|
index: index,
|
||||||
|
dispatch: dispatch,
|
||||||
|
dataIndex: 'price'
|
||||||
|
};
|
||||||
|
return <SkuInputNumber {...props} />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
columns.push({
|
||||||
|
title: '库存',
|
||||||
|
dataIndex: 'quantity',
|
||||||
|
render(value, record, index) {
|
||||||
|
let props = {
|
||||||
|
record: record,
|
||||||
|
index: index,
|
||||||
|
dispatch: dispatch,
|
||||||
|
dataIndex: 'quantity'
|
||||||
|
};
|
||||||
|
return <SkuInputNumber {...props} />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return <Table columns={columns} dataSource={skus} rowKey="index" />;
|
||||||
|
// return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -44,4 +44,9 @@ export default {
|
||||||
'menu.account.settings': '个人设置',
|
'menu.account.settings': '个人设置',
|
||||||
'menu.account.trigger': '触发报错',
|
'menu.account.trigger': '触发报错',
|
||||||
'menu.account.logout': '退出登录',
|
'menu.account.logout': '退出登录',
|
||||||
|
// 商品相关
|
||||||
|
'menu.product': '商品管理',
|
||||||
|
'menu.product.product-spu-list': '商品管理',
|
||||||
|
'menu.product.product-spu-add': '商品添加',
|
||||||
|
'menu.product.product-category-list': '商品分类',
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,13 +76,13 @@ export default {
|
||||||
},
|
},
|
||||||
*query({ payload }, { call, put }) {
|
*query({ payload }, { call, put }) {
|
||||||
const response = yield call(queryAdmin, payload);
|
const response = yield call(queryAdmin, payload);
|
||||||
message.info('查询成功!');
|
|
||||||
const { count, admins } = response.data;
|
const { count, admins } = response.data;
|
||||||
yield put({
|
yield put({
|
||||||
type: 'querySuccess',
|
type: 'querySuccess',
|
||||||
payload: {
|
payload: {
|
||||||
list: admins,
|
list: admins,
|
||||||
count,
|
count,
|
||||||
|
pageNo: payload.pageNo + 1
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { productCategoryTree, productCategoryAdd, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product';
|
import { productCategoryTree, productSpuAdd, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespace: 'productSpuAddOrUpdate',
|
namespace: 'productSpuAddOrUpdate',
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
list: [],
|
list: [],
|
||||||
attrTree: [{
|
attrTree: [
|
||||||
|
|
||||||
}
|
|
||||||
// {
|
// {
|
||||||
// id: //
|
// id: //
|
||||||
// name: //
|
// name: //
|
||||||
|
@ -17,6 +15,16 @@ export default {
|
||||||
// name: //
|
// name: //
|
||||||
// }]
|
// }]
|
||||||
// }
|
// }
|
||||||
|
],
|
||||||
|
skus: [
|
||||||
|
// {
|
||||||
|
// attrs: [{
|
||||||
|
// id: // 规格值编号
|
||||||
|
// name: // 规格值名
|
||||||
|
// }],
|
||||||
|
// price: // 价格
|
||||||
|
// quantity: // 数量
|
||||||
|
// }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -65,7 +73,7 @@ export default {
|
||||||
*addAttr({ payload }, { call, put }) {
|
*addAttr({ payload }, { call, put }) {
|
||||||
// const { queryParams } = payload;
|
// const { queryParams } = payload;
|
||||||
// const response = yield call(productCategoryTree, queryParams);
|
// const response = yield call(productCategoryTree, queryParams);
|
||||||
message.info('调试:添加规格成功!');
|
// message.info('调试:添加规格成功!');
|
||||||
yield put({
|
yield put({
|
||||||
type: 'addAttrSuccess',
|
type: 'addAttrSuccess',
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -73,17 +81,137 @@ export default {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
*selectAttr({ payload }, { call, put }) {
|
||||||
|
// const { queryParams } = payload;
|
||||||
|
// const response = yield call(productCategoryTree, queryParams);
|
||||||
|
// message.info('调试:选择规格成功!');
|
||||||
|
yield put({
|
||||||
|
type: 'selectAttrSuccess',
|
||||||
|
payload: payload,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
*selectAttrValues({ payload }, { call, put }) {
|
||||||
|
// const { queryParams } = payload;
|
||||||
|
// const response = yield call(productCategoryTree, queryParams);
|
||||||
|
// message.info('调试:选择规格值成功!');
|
||||||
|
yield put({
|
||||||
|
type: 'selectAttrValueSuccess',
|
||||||
|
payload: payload,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
*inputSkuPrice({ payload }, { call, put }) {
|
||||||
|
// debugger;
|
||||||
|
yield put({
|
||||||
|
type: 'inputSkuPriceSuccess',
|
||||||
|
payload: payload,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
*inputSkuQuantity({ payload }, { call, put }) {
|
||||||
|
// debugger;
|
||||||
|
yield put({
|
||||||
|
type: 'inputSkuQuantitySuccess',
|
||||||
|
payload: payload,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
*add({ payload }, { call, put }) {
|
||||||
|
const { callback, body } = payload;
|
||||||
|
const response = yield call(productSpuAdd, body);
|
||||||
|
if (callback) {
|
||||||
|
callback(response);
|
||||||
|
}
|
||||||
|
// yield put({
|
||||||
|
// type: 'tree',
|
||||||
|
// payload: {},
|
||||||
|
// });
|
||||||
|
alert('添加成功!后续改成跳转到手机站的详情');
|
||||||
|
},
|
||||||
|
*update({ payload }, { call, put }) {
|
||||||
|
const { callback, body } = payload;
|
||||||
|
const response = yield call(productSpuAdd, body);
|
||||||
|
if (callback) {
|
||||||
|
callback(response);
|
||||||
|
}
|
||||||
|
// yield put({
|
||||||
|
// type: 'tree',
|
||||||
|
// payload: {},
|
||||||
|
// });
|
||||||
|
alert('修改成功!后续改成跳转到手机站的详情');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
reducers: {
|
reducers: {
|
||||||
addAttrSuccess(state, {payload}) {
|
addAttrSuccess(state, {payload}) {
|
||||||
// debugger;
|
// debugger;
|
||||||
console.log(state.attrTree);
|
// console.log(state.attrTree);
|
||||||
state.attrTree.push(payload.attrAdd);
|
state.attrTree.push(payload.attrAdd);
|
||||||
return {
|
return {
|
||||||
...state
|
...state
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectAttrSuccess(state, {payload}) {
|
||||||
|
// debugger;
|
||||||
|
// console.log(state.attrTree);
|
||||||
|
state.attrTree[payload.attrIndex] = payload.attr;
|
||||||
|
return {
|
||||||
|
...state
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectAttrValueSuccess(state, {payload}) {
|
||||||
|
// debugger;
|
||||||
|
// console.log(state);
|
||||||
|
state.attrTree[payload.attrIndex].values = payload.attrValues;
|
||||||
|
// 生成 skus 值
|
||||||
|
let skus = [];
|
||||||
|
let skuSize = 1;
|
||||||
|
for (let i in state.attrTree) { // 先计算 sku 数量
|
||||||
|
let attr = state.attrTree[i];
|
||||||
|
skuSize = skuSize * attr.values.length;
|
||||||
|
}
|
||||||
|
// console.log('skuSize: ' + skuSize);
|
||||||
|
for (let i = 0; i < skuSize; i++) { // 初始化 sku 格子
|
||||||
|
skus.push({
|
||||||
|
attrs: [],
|
||||||
|
price: undefined,
|
||||||
|
quantity: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (let i = 0; i < state.attrTree.length; i++) { // 初始化 sku 格子里的 attrs
|
||||||
|
for (let j = 0; j < skuSize; j++) {
|
||||||
|
// let values = state.attrTree[i].values;
|
||||||
|
// let attr = values[j % values.length];
|
||||||
|
// skus[i].attrs.push({
|
||||||
|
// id: attr.id,
|
||||||
|
// name: attr.name,
|
||||||
|
// });
|
||||||
|
let values = state.attrTree[i].values;
|
||||||
|
let attr = values[j % values.length];
|
||||||
|
skus[j].attrs.push({
|
||||||
|
id: attr.id,
|
||||||
|
name: attr.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.skus = skus;
|
||||||
|
// debugger;
|
||||||
|
// console.l og('skus: ' + skus);
|
||||||
|
return {
|
||||||
|
...state
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputSkuPriceSuccess(state, {payload}) {
|
||||||
|
// debugger;
|
||||||
|
state.skus[payload.index].price = payload.price;
|
||||||
|
return {
|
||||||
|
...state
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputSkuQuantitySuccess(state, {payload}) {
|
||||||
|
// debugger;
|
||||||
|
state.skus[payload.index].quantity = payload.quantity;
|
||||||
|
return {
|
||||||
|
...state
|
||||||
|
}
|
||||||
|
},
|
||||||
treeSuccess(state, { payload }) {
|
treeSuccess(state, { payload }) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { productSpuPage, productCategoryAdd, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product';
|
import { productSpuPage, productCategoryAdd, productCategoryUpdate, productCategoryUpdateStatus, productCategoryDelete } from '../../services/product';
|
||||||
|
import {routerRedux} from "dva/router";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespace: 'productSpuList',
|
namespace: 'productSpuList',
|
||||||
|
@ -9,46 +10,50 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
effects: {
|
effects: {
|
||||||
*add({ payload }, { call, put }) {
|
// *add({ payload }, { call, put }) {
|
||||||
const { callback, body } = payload;
|
// const { callback, body } = payload;
|
||||||
const response = yield call(productCategoryAdd, body);
|
// const response = yield call(productCategoryAdd, body);
|
||||||
if (callback) {
|
// if (callback) {
|
||||||
callback(response);
|
// callback(response);
|
||||||
}
|
// }
|
||||||
yield put({
|
// yield put({
|
||||||
type: 'tree',
|
// type: 'tree',
|
||||||
payload: {},
|
// payload: {},
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
*update({ payload }, { call, put }) {
|
// *update({ payload }, { call, put }) {
|
||||||
const { callback, body } = payload;
|
// const { callback, body } = payload;
|
||||||
const response = yield call(productCategoryUpdate, body);
|
// const response = yield call(productCategoryUpdate, body);
|
||||||
if (callback) {
|
// if (callback) {
|
||||||
callback(response);
|
// callback(response);
|
||||||
}
|
// }
|
||||||
yield put({
|
// yield put({
|
||||||
type: 'tree',
|
// type: 'tree',
|
||||||
payload: {},
|
// payload: {},
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
*updateStatus({ payload }, { call, put }) {
|
// *updateStatus({ payload }, { call, put }) {
|
||||||
const { callback, body } = payload;
|
// const { callback, body } = payload;
|
||||||
const response = yield call(productCategoryUpdateStatus, body);
|
// const response = yield call(productCategoryUpdateStatus, body);
|
||||||
if (callback) {
|
// if (callback) {
|
||||||
callback(response);
|
// callback(response);
|
||||||
}
|
// }
|
||||||
yield put({
|
// yield put({
|
||||||
type: 'tree',
|
// type: 'tree',
|
||||||
payload: {},
|
// payload: {},
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
*delete({ payload }, { call, put }) {
|
// *delete({ payload }, { call, put }) {
|
||||||
const response = yield call(productCategoryDelete, payload);
|
// const response = yield call(productCategoryDelete, payload);
|
||||||
message.info('删除成功!');
|
// message.info('删除成功!');
|
||||||
yield put({
|
// yield put({
|
||||||
type: 'tree',
|
// type: 'tree',
|
||||||
payload: {},
|
// payload: {},
|
||||||
});
|
// });
|
||||||
|
// },
|
||||||
|
*redirectToAdd({ payload }, { call, put }) {
|
||||||
|
// const { callback, body } = payload;
|
||||||
|
yield put(routerRedux.replace('/product/product-spu-add'));
|
||||||
},
|
},
|
||||||
*page({ payload }, { call, put }) {
|
*page({ payload }, { call, put }) {
|
||||||
const { queryParams } = payload;
|
const { queryParams } = payload;
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
import React, { PureComponent, Fragment } from 'react';
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
import { connect } from 'dva';
|
import { connect } from 'dva';
|
||||||
import { Card, Form, Input, Button, Modal, message, Table, Divider, Tree, Spin } from 'antd';
|
import {Card, Form, Input, Button, Modal, message, Table, Divider, Tree, Spin, Row, Col, Select, Icon} from 'antd';
|
||||||
|
import { checkTypeWithEnglishAndNumbers } from '../../../helpers/validator'
|
||||||
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
|
|
||||||
import styles from './AdminList.less';
|
import styles from './AdminList.less';
|
||||||
|
import moment from "moment";
|
||||||
|
import Pagination from "antd/es/pagination";
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const { TreeNode } = Tree;
|
const { TreeNode } = Tree;
|
||||||
|
@ -31,31 +34,36 @@ const CreateForm = Form.create()(props => {
|
||||||
width: 200,
|
width: 200,
|
||||||
};
|
};
|
||||||
|
|
||||||
const title = modalType === 'add' ? '添加一个 Resource' : '更新一个 Resource';
|
const title = modalType === 'add' ? '新建管理员' : '更新管理员';
|
||||||
const okText = modalType === 'add' ? '添加' : '更新';
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
title={title}
|
title={title}
|
||||||
visible={modalVisible}
|
visible={modalVisible}
|
||||||
onOk={okHandle}
|
onOk={okHandle}
|
||||||
okText={okText}
|
okText='保存'
|
||||||
onCancel={() => handleModalVisible()}
|
onCancel={() => handleModalVisible()}
|
||||||
>
|
>
|
||||||
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="名称">
|
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="用户名">
|
||||||
{form.getFieldDecorator('username', {
|
{form.getFieldDecorator('username', {
|
||||||
rules: [{ required: true, message: '请输入名称!', min: 2 }],
|
rules: [{ required: true, message: '请输入用户名!'},
|
||||||
|
{max: 16, min:6, message: '长度为6-16位'},
|
||||||
|
{ validator: (rule, value, callback) => checkTypeWithEnglishAndNumbers(rule, value, callback, '数字以及字母')}
|
||||||
|
],
|
||||||
initialValue: initValues.username,
|
initialValue: initValues.username,
|
||||||
})(<Input placeholder="请输入" />)}
|
})(<Input placeholder="请输入" />)}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="昵称">
|
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="昵称">
|
||||||
{form.getFieldDecorator('nickname', {
|
{form.getFieldDecorator('nickname', {
|
||||||
rules: [{ required: true, message: '请输入昵称!', min: 2 }],
|
rules: [{ required: true, message: '请输入昵称!'},
|
||||||
|
{max: 10, message: '姓名最大长度为10'}],
|
||||||
initialValue: initValues.nickname,
|
initialValue: initValues.nickname,
|
||||||
})(<Input placeholder="请输入" />)}
|
})(<Input placeholder="请输入" />)}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="密码">
|
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="密码">
|
||||||
{form.getFieldDecorator('password', {
|
{form.getFieldDecorator('password', {
|
||||||
|
rules: [{ required: modalType === 'add', message: '请填写密码'}, // 添加时,必须输入密码
|
||||||
|
{max: 16, min: 6, message: '长度为6-18位'}],
|
||||||
initialValue: initValues.password,
|
initialValue: initValues.password,
|
||||||
})(<Input placeholder="请输入" type="password" />)}
|
})(<Input placeholder="请输入" type="password" />)}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -304,9 +312,49 @@ class ResourceList extends PureComponent {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onPageChange = (page = {}) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
// debugger;
|
||||||
|
dispatch({
|
||||||
|
type: 'adminList/query',
|
||||||
|
payload: {
|
||||||
|
pageNo: page - 1,
|
||||||
|
pageSize: 10,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSimpleForm() { // TODO 芋艿,搜索功能未完成
|
||||||
|
const {
|
||||||
|
form: { getFieldDecorator },
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<Form onSubmit={this.handleSearch} layout="inline">
|
||||||
|
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
|
||||||
|
<Col md={8} sm={24}>
|
||||||
|
<FormItem label="昵称">
|
||||||
|
{getFieldDecorator('name')(<Input placeholder="请输入" />)}
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col md={8} sm={24}>
|
||||||
|
<span className={styles.submitButtons}>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
查询
|
||||||
|
</Button>
|
||||||
|
<Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { list, data } = this.props;
|
let that = this;
|
||||||
const { roleList, roleCheckedKeys, roleAssignLoading } = data;
|
const { list, data } = this.props;
|
||||||
|
const { count, pageNo, pageSize, roleList, roleCheckedKeys, roleAssignLoading } = data;
|
||||||
const {
|
const {
|
||||||
modalVisible,
|
modalVisible,
|
||||||
modalType,
|
modalType,
|
||||||
|
@ -323,15 +371,9 @@ class ResourceList extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
|
||||||
title: 'id',
|
|
||||||
dataIndex: 'id',
|
|
||||||
render: text => <strong>{text}</strong>,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username'
|
||||||
render: text => <a>{text}</a>,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '昵称',
|
title: '昵称',
|
||||||
|
@ -341,17 +383,22 @@ class ResourceList extends PureComponent {
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
render(val) {
|
render(val) {
|
||||||
return <span>{status[val]}</span>;
|
return <span>{status[val]}</span>; // TODO 芋艿,此处要改
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
render: val => <span>{moment(val).format('YYYY-MM-DD HH:mm')}</span>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 300,
|
width: 360,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
const statusText = record.status === 1 ? '确认禁用' : '取消禁用';
|
const statusText = record.status === 1 ? '禁用' : '禁用';
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<a onClick={() => this.handleModalVisible(true, 'update', record)}>更新</a>
|
<a onClick={() => this.handleModalVisible(true, 'update', record)}>编辑</a>
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
<a onClick={() => this.handleRoleAssign(record)}>角色分配</a>
|
<a onClick={() => this.handleRoleAssign(record)}>角色分配</a>
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
|
@ -369,16 +416,17 @@ class ResourceList extends PureComponent {
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeaderWrapper title="查询表格">
|
<PageHeaderWrapper>
|
||||||
<Card bordered={false}>
|
<Card bordered={false}>
|
||||||
<div className={styles.tableList}>
|
<div className={styles.tableList}>
|
||||||
|
<div className={styles.tableListForm}>{that.renderSimpleForm()}</div>
|
||||||
<div className={styles.tableListOperator}>
|
<div className={styles.tableListOperator}>
|
||||||
<Button
|
<Button
|
||||||
icon="plus"
|
icon="plus"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => this.handleModalVisible(true, 'add', {})}
|
onClick={() => this.handleModalVisible(true, 'add', {})}
|
||||||
>
|
>
|
||||||
新建
|
新建管理员
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -387,6 +435,12 @@ class ResourceList extends PureComponent {
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={list}
|
dataSource={list}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
pagination={{
|
||||||
|
current: pageNo,
|
||||||
|
pageSize: pageSize,
|
||||||
|
total: count,
|
||||||
|
onChange: this.onPageChange
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<CreateForm {...parentMethods} modalVisible={modalVisible} />
|
<CreateForm {...parentMethods} modalVisible={modalVisible} />
|
||||||
|
|
|
@ -13,3 +13,35 @@
|
||||||
.tableDelete {
|
.tableDelete {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tableListForm {
|
||||||
|
:global {
|
||||||
|
.ant-form-item {
|
||||||
|
display: flex;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
> .ant-form-item-label {
|
||||||
|
width: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
.ant-form-item-control {
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-form-item-control-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.submitButtons {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @screen-lg) {
|
||||||
|
.tableListForm :global(.ant-form-item) {
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import React, { PureComponent, Fragment } from 'react';
|
import React, {PureComponent, Fragment, Component} from 'react';
|
||||||
import { connect } from 'dva';
|
import { connect } from 'dva';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {Card, Form, Input, Radio, Button, Table, Select} from 'antd';
|
import {Card, Form, Input, Radio, Button, Table, Select} from 'antd';
|
||||||
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
|
|
||||||
import styles from './ProductSpuAddOrUpdate.less';
|
import styles from './ProductSpuAddOrUpdate.less';
|
||||||
|
import ProductAttrSelectFormItem from "../../components/Product/ProductAttrSelectFormItem";
|
||||||
|
import ProductSkuAddOrUpdateTable from "../../components/Product/ProductSkuAddOrUpdateTable";
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const RadioGroup = Radio.Group;
|
const RadioGroup = Radio.Group;
|
||||||
|
@ -16,12 +18,15 @@ const Option = Select.Option;
|
||||||
@connect(({ productSpuList, productAttrList, productSpuAddOrUpdate, loading }) => ({
|
@connect(({ productSpuList, productAttrList, productSpuAddOrUpdate, loading }) => ({
|
||||||
// list: productSpuList.list.spus,
|
// list: productSpuList.list.spus,
|
||||||
// loading: loading.models.productSpuList,
|
// loading: loading.models.productSpuList,
|
||||||
|
productAttrList,
|
||||||
|
productSpuAddOrUpdate,
|
||||||
allAttrTree: productAttrList.tree,
|
allAttrTree: productAttrList.tree,
|
||||||
attrTree: productSpuAddOrUpdate.attrTree
|
attrTree: productSpuAddOrUpdate.attrTree,
|
||||||
|
skus: productSpuAddOrUpdate.skus,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@Form.create()
|
@Form.create()
|
||||||
class ProductSpuAddOrUpdate extends PureComponent {
|
class ProductSpuAddOrUpdate extends Component {
|
||||||
state = {
|
state = {
|
||||||
modalVisible: false,
|
modalVisible: false,
|
||||||
modalType: 'add', //add update
|
modalType: 'add', //add update
|
||||||
|
@ -42,18 +47,18 @@ class ProductSpuAddOrUpdate extends PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
// handleSubmit = e => {
|
||||||
const { dispatch, form } = this.props;
|
// const { dispatch, form } = this.props;
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
form.validateFieldsAndScroll((err, values) => {
|
// form.validateFieldsAndScroll((err, values) => {
|
||||||
if (!err) {
|
// if (!err) {
|
||||||
dispatch({
|
// dispatch({
|
||||||
type: 'form/submitRegularForm',
|
// type: 'form/submitRegularForm',
|
||||||
payload: values,
|
// payload: values,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
handleAddAttr = e => {
|
handleAddAttr = e => {
|
||||||
// alert('你猜');
|
// alert('你猜');
|
||||||
|
@ -65,50 +70,171 @@ class ProductSpuAddOrUpdate extends PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSubmit = e => {
|
||||||
|
debugger;
|
||||||
|
e.preventDefault();
|
||||||
|
const { skus, dispatch } = this.props;
|
||||||
|
// 生成 skuStr 格式
|
||||||
|
let skuStr = []; // 因为提交是字符串格式
|
||||||
|
for (let i in skus) {
|
||||||
|
let sku = skus[i];
|
||||||
|
if (!sku.price || !sku.quantity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let newAttr = {
|
||||||
|
attrs: [],
|
||||||
|
price: sku.price,
|
||||||
|
quantity: sku.quantity,
|
||||||
|
}
|
||||||
|
for (let j in sku.attrs) {
|
||||||
|
newAttr.attrs.push(sku.attrs[j].id);
|
||||||
|
}
|
||||||
|
skuStr.push(newAttr);
|
||||||
|
}
|
||||||
|
if (skuStr.length === 0) {
|
||||||
|
alert('请设置商品规格!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.form.validateFields((err, values) => {
|
||||||
|
if (!err) {
|
||||||
|
dispatch({
|
||||||
|
type: 'productSpuAddOrUpdate/add',
|
||||||
|
payload: {
|
||||||
|
body: {
|
||||||
|
...values,
|
||||||
|
skuStr: JSON.stringify(skuStr)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// console.log(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSelectAttr = (value, option) => {
|
||||||
|
// // console.log(value);
|
||||||
|
// // console.log(option);
|
||||||
|
// // debugger;
|
||||||
|
// const { dispatch } = this.props;
|
||||||
|
// let attrIndex = option.key.substring(option.key.indexOf('option-attr-') + 'option-attr-'.length, option.key.lastIndexOf('-'));
|
||||||
|
// // console.log('attrIndex: ' + attrIndex);
|
||||||
|
// // debugger;
|
||||||
|
// dispatch({
|
||||||
|
// type: 'productSpuAddOrUpdate/selectAttr',
|
||||||
|
// payload: {
|
||||||
|
// attrIndex: attrIndex,
|
||||||
|
// attr: {
|
||||||
|
// id: option.props.value,
|
||||||
|
// name: option.props.children,
|
||||||
|
// values: []
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// handleSelectAttrValue = (values, options) => {
|
||||||
|
// let attrValues = [];
|
||||||
|
// const { dispatch } = this.props;
|
||||||
|
// debugger;
|
||||||
|
// // console.log('x' + this.children[0]);
|
||||||
|
// let firstOption = this.children[0];
|
||||||
|
// // let attrIndex = firstOption.key.substring(firstOption.key.indexOf('option-attr-value-') + 'option-attr-value-'.length, firstOption.key.lastIndexOf('-'));
|
||||||
|
// let attrIndex = 0;
|
||||||
|
// for (let i in options) {
|
||||||
|
// let option = options[i];
|
||||||
|
// attrValues.push({
|
||||||
|
// id: parseInt(option.props.value),
|
||||||
|
// name: option.props.children,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// dispatch({
|
||||||
|
// type: 'productSpuAddOrUpdate/selectAttrValues',
|
||||||
|
// payload: {
|
||||||
|
// attrIndex: attrIndex,
|
||||||
|
// attrValues: attrValues,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// // debugger;
|
||||||
|
//
|
||||||
|
// // console.log(value);
|
||||||
|
// }
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// debugger;
|
// debugger;
|
||||||
const { form, data, attrTree } = this.props;
|
const { form, skus, attrTree, allAttrTree, dispatch } = this.props;
|
||||||
|
// const that = this;
|
||||||
// 规格明细
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '颜色',
|
|
||||||
dataIndex: 'price'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '价格',
|
|
||||||
dataIndex: 'price',
|
|
||||||
render(val) {
|
|
||||||
return <span>{status[val]}</span>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '库存',
|
|
||||||
dataIndex: 'quantity',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 添加规格
|
// 添加规格
|
||||||
// debugger;
|
// debugger;
|
||||||
let attrTreeHTML = [];
|
let attrTreeHTML = [];
|
||||||
if (attrTree && attrTree.length > 0) {
|
if (attrTree && attrTree.length > 0) {
|
||||||
|
// 已选择的的规格集合
|
||||||
|
let selectedAttrIds = new Set();
|
||||||
for (let i in attrTree) {
|
for (let i in attrTree) {
|
||||||
let attr = attrTree[i];
|
let attr = attrTree[i];
|
||||||
attr = <div>
|
selectedAttrIds.add(attr.id);
|
||||||
<Select defaultValue="lucy" style={{ width: 120 }}>
|
}
|
||||||
{
|
// 创建每个规格下拉框的 HTML
|
||||||
|
for (let i in attrTree) {
|
||||||
}
|
let attr = attrTree[i];
|
||||||
<Option value="jack">Jack</Option>
|
let itemProps = {
|
||||||
<Option value="lucy">Lucy</Option>
|
attr: attr,
|
||||||
<Option value="disabled" disabled>Disabled</Option>
|
allAttrTree: allAttrTree,
|
||||||
<Option value="Yiminghe">yiminghe</Option>
|
dispatch: dispatch,
|
||||||
</Select>
|
selectedAttrIds: selectedAttrIds,
|
||||||
</div>;
|
index: i // 位置。不然无法正确修改 Model 指定位置的数据
|
||||||
attrTreeHTML.push(attr);
|
};
|
||||||
// debugger;
|
attrTreeHTML.push(<ProductAttrSelectFormItem {...itemProps} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if (attrTree && attrTree.length > 0) {
|
||||||
|
// for (let i in attrTree) {
|
||||||
|
// let attr = attrTree[i];
|
||||||
|
// // console.log('i: ' + i);
|
||||||
|
// // 1. 规格
|
||||||
|
// let attrOptions = [];
|
||||||
|
// for (let j in allAttrTree) {
|
||||||
|
// let attr = allAttrTree[j];
|
||||||
|
// attrOptions.push(<Option key={`option-attr-${i}-${attr.id}`} value={attr.id}>{attr.name}</Option>);
|
||||||
|
// }
|
||||||
|
// // 2. 规格值
|
||||||
|
// let attrValueOptions = [];
|
||||||
|
// // debugger;
|
||||||
|
// if (attr.id) {
|
||||||
|
// // 2.1 先招到规格值的数组
|
||||||
|
// let attrValues = [];
|
||||||
|
// for (let j in allAttrTree) {
|
||||||
|
// let allAttr = allAttrTree[j];
|
||||||
|
// if (attr.id === allAttr.id) {
|
||||||
|
// attrValues = allAttr.values;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // 2.2 生成规格值的 HTML
|
||||||
|
// for (let j in attrValues) {
|
||||||
|
// let attrValue = attrValues[j];
|
||||||
|
// attrValueOptions.push(<Option key={`option-attr-value-${i}-${attrValue.id}`} value={attrValue.id + ''}>{attrValue.name}</Option>); // + '' 的原因是,多选必须是字符串
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // 3. 拼装最终,添加到 attrTreeHTML 中
|
||||||
|
// attr = <div key={`div-attr-${i}`}>
|
||||||
|
// <Select key={`select-attr-${i}`} style={{ width: 120 }} placeholder='请选择规格' onChange={that.handleSelectAttr}>
|
||||||
|
// {attrOptions}
|
||||||
|
// </Select>
|
||||||
|
// <Select key={`select-attr-value-${i}`} mode={"tags"} style={{ width: 260 }} placeholder='请选择规格值' onChange={that.handleSelectAttrValue}>
|
||||||
|
// {attrValueOptions}
|
||||||
|
// </Select>
|
||||||
|
// </div>;
|
||||||
|
// attrTreeHTML.push(attr);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// 规格明细
|
||||||
|
let productSkuProps = {
|
||||||
|
attrTree: attrTree,
|
||||||
|
skus: skus,
|
||||||
|
dispatch: dispatch,
|
||||||
|
};
|
||||||
|
// console.log(productSkuProps);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeaderWrapper title="">
|
<PageHeaderWrapper title="">
|
||||||
|
@ -163,13 +289,12 @@ class ProductSpuAddOrUpdate extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/*<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="规格明细">*/}
|
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="规格明细">
|
||||||
{/*{form.getFieldDecorator('visible', {*/}
|
{/*<Table defaultExpandAllRows={true} columns={columns} rowKey="id" />*/}
|
||||||
{/*initialValue: 1, // TODO 修改*/}
|
<ProductSkuAddOrUpdateTable {...productSkuProps} />
|
||||||
{/*})(*/}
|
|
||||||
{/*<Table defaultExpandAllRows={true} columns={columns} rowKey="id" />*/}
|
<Button type="primary" htmlType="submit" style={{ marginLeft: 8 }} onSubmit={this.handleSubmit}>保存</Button>
|
||||||
{/*)}*/}
|
</FormItem>
|
||||||
{/*</FormItem>*/}
|
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
</PageHeaderWrapper>
|
</PageHeaderWrapper>
|
||||||
|
|
|
@ -77,52 +77,13 @@ class ProductSpuList extends PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModalVisible = (flag, modalType, initValues) => {
|
redirectToAdd = () => {
|
||||||
this.setState({
|
const { dispatch } = this.props;
|
||||||
modalVisible: !!flag,
|
dispatch({
|
||||||
initValues: initValues || {},
|
type: 'productSpuList/redirectToAdd',
|
||||||
modalType: modalType || 'add',
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAdd = ({ fields, modalType, initValues }) => {
|
|
||||||
const { dispatch, data } = this.props;
|
|
||||||
const queryParams = {
|
|
||||||
pageNo: data.pageNo,
|
|
||||||
pageSize: data.pageSize,
|
|
||||||
};
|
|
||||||
if (modalType === 'add') {
|
|
||||||
dispatch({
|
|
||||||
type: 'roleList/add',
|
|
||||||
payload: {
|
|
||||||
body: {
|
|
||||||
...fields,
|
|
||||||
},
|
|
||||||
queryParams,
|
|
||||||
callback: () => {
|
|
||||||
message.success('添加成功');
|
|
||||||
this.handleModalVisible();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dispatch({
|
|
||||||
type: 'roleList/update',
|
|
||||||
payload: {
|
|
||||||
body: {
|
|
||||||
...initValues,
|
|
||||||
...fields,
|
|
||||||
},
|
|
||||||
queryParams,
|
|
||||||
callback: () => {
|
|
||||||
message.success('更新成功');
|
|
||||||
this.handleModalVisible();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// debugger;
|
// debugger;
|
||||||
const { list, data } = this.props;
|
const { list, data } = this.props;
|
||||||
|
@ -199,7 +160,7 @@ class ProductSpuList extends PureComponent {
|
||||||
<Button
|
<Button
|
||||||
icon="plus"
|
icon="plus"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => this.handleModalVisible(true, 'add', {})}
|
onClick={this.redirectToAdd}
|
||||||
>
|
>
|
||||||
发布商品
|
发布商品
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -44,6 +44,13 @@ export async function productSpuPage(params) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function productSpuAdd(params) {
|
||||||
|
return request(`/product-api/admins/spu/add?${stringify(params)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// product attr + attr value
|
// product attr + attr value
|
||||||
|
|
||||||
export async function productAttrTree(params) {
|
export async function productAttrTree(params) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package cn.iocoder.mall.admin.application.config;
|
package cn.iocoder.mall.admin.application.config;
|
||||||
|
|
||||||
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
||||||
|
import cn.iocoder.mall.admin.sdk.interceptor.AdminAccessLogInterceptor;
|
||||||
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
|
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -18,16 +19,17 @@ public class MVCConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AdminSecurityInterceptor adminSecurityInterceptor;
|
private AdminSecurityInterceptor adminSecurityInterceptor;
|
||||||
|
@Autowired
|
||||||
|
private AdminAccessLogInterceptor adminAccessLogInterceptor;
|
||||||
//
|
//
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
// registry.addInterceptor(securityInterceptor).addPathPatterns("/user/**", "/admin/**"); // 只拦截我们定义的接口
|
// registry.addInterceptor(securityInterceptor).addPathPatterns("/user/**", "/admin/**"); // 只拦截我们定义的接口
|
||||||
|
registry.addInterceptor(adminAccessLogInterceptor).addPathPatterns("/admins/**");
|
||||||
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**")
|
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**")
|
||||||
.excludePathPatterns("/admins/passport/login"); // 排除登陆接口
|
.excludePathPatterns("/admins/passport/login"); // 排除登陆接口
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
// 解决 swagger-ui.html 的访问,参考自 https://stackoverflow.com/questions/43545540/swagger-ui-no-mapping-found-for-http-request 解决
|
// 解决 swagger-ui.html 的访问,参考自 https://stackoverflow.com/questions/43545540/swagger-ui-no-mapping-found-for-http-request 解决
|
||||||
|
|
|
@ -42,6 +42,8 @@ public class AdminController {
|
||||||
|
|
||||||
// =========== 当前管理员相关的资源 API ===========
|
// =========== 当前管理员相关的资源 API ===========
|
||||||
|
|
||||||
|
// TODO 功能:当前管理员
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
@GetMapping("/menu_resource_tree")
|
@GetMapping("/menu_resource_tree")
|
||||||
@ApiOperation(value = "获得当前登陆的管理员拥有的菜单权限", notes = "以树结构返回")
|
@ApiOperation(value = "获得当前登陆的管理员拥有的菜单权限", notes = "以树结构返回")
|
||||||
|
@ -120,7 +122,7 @@ public class AdminController {
|
||||||
public CommonResult<Boolean> update(@RequestParam("id") Integer id,
|
public CommonResult<Boolean> update(@RequestParam("id") Integer id,
|
||||||
@RequestParam("username") String username,
|
@RequestParam("username") String username,
|
||||||
@RequestParam("nickname") String nickname,
|
@RequestParam("nickname") String nickname,
|
||||||
@RequestParam("password") String password) {
|
@RequestParam(value = "password", required = false) String password) {
|
||||||
AdminUpdateDTO adminUpdateDTO = new AdminUpdateDTO().setId(id).setUsername(username).setNickname(nickname).setPassword(password);
|
AdminUpdateDTO adminUpdateDTO = new AdminUpdateDTO().setId(id).setUsername(username).setNickname(nickname).setPassword(password);
|
||||||
return adminService.updateAdmin(AdminSecurityContextHolder.getContext().getAdminId(), adminUpdateDTO);
|
return adminService.updateAdmin(AdminSecurityContextHolder.getContext().getAdminId(), adminUpdateDTO);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,17 @@ package cn.iocoder.mall.admin.application.controller.admins;
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
import cn.iocoder.mall.admin.api.OAuth2Service;
|
import cn.iocoder.mall.admin.api.OAuth2Service;
|
||||||
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
|
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
|
||||||
import cn.iocoder.mall.admin.application.convert.AdminConvert;
|
|
||||||
import cn.iocoder.mall.admin.application.convert.PassportConvert;
|
import cn.iocoder.mall.admin.application.convert.PassportConvert;
|
||||||
import cn.iocoder.mall.admin.application.vo.AdminInfoVO;
|
|
||||||
import cn.iocoder.mall.admin.application.vo.PassportLoginVO;
|
import cn.iocoder.mall.admin.application.vo.PassportLoginVO;
|
||||||
import cn.iocoder.mall.admin.sdk.context.AdminSecurityContextHolder;
|
|
||||||
import com.alibaba.dubbo.config.annotation.Reference;
|
import com.alibaba.dubbo.config.annotation.Reference;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiImplicitParam;
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
import io.swagger.annotations.ApiImplicitParams;
|
import io.swagger.annotations.ApiImplicitParams;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("admins/passport")
|
@RequestMapping("admins/passport")
|
||||||
|
@ -35,10 +35,8 @@ public class PassportController {
|
||||||
return PassportConvert.INSTANCE.convert(result);
|
return PassportConvert.INSTANCE.convert(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 艿艿:后续继续完善
|
// TODO 功能 logout
|
||||||
@GetMapping("/info")
|
|
||||||
public CommonResult<AdminInfoVO> info() {
|
// TODO 功能 refresh_token
|
||||||
return CommonResult.success(AdminConvert.INSTANCE.convert(AdminSecurityContextHolder.getContext()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package cn.iocoder.mall.admin.sdk.interceptor;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.util.HttpUtil;
|
||||||
|
import cn.iocoder.mall.admin.api.AdminAccessLogService;
|
||||||
|
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO;
|
||||||
|
import com.alibaba.dubbo.config.annotation.Reference;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问日志拦截器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AdminAccessLogInterceptor extends HandlerInterceptorAdapter {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始时间
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Date> START_TIME = new ThreadLocal<>();
|
||||||
|
/**
|
||||||
|
* 管理员编号
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Integer> ADMIN_ID = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@Reference
|
||||||
|
private AdminAccessLogService adminAccessLogService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
// 记录当前时间
|
||||||
|
START_TIME.set(new Date());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||||
|
AdminAccessLogAddDTO accessLog = new AdminAccessLogAddDTO();
|
||||||
|
try {
|
||||||
|
accessLog.setAdminId(ADMIN_ID.get());
|
||||||
|
if (accessLog.getAdminId() == null) {
|
||||||
|
accessLog.setAdminId(AdminAccessLogAddDTO.ADMIN_ID_NULL);
|
||||||
|
}
|
||||||
|
accessLog.setUri(request.getRequestURI()); // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
||||||
|
accessLog.setQueryString(HttpUtil.buildQueryString(request));
|
||||||
|
accessLog.setMethod(request.getMethod());
|
||||||
|
accessLog.setUserAgent(HttpUtil.getUserAgent(request));
|
||||||
|
accessLog.setIp(HttpUtil.getIp(request));
|
||||||
|
accessLog.setStartTime(START_TIME.get());
|
||||||
|
accessLog.setResponseTime((int) (System.currentTimeMillis() - accessLog.getStartTime().getTime()));// 默认响应时间设为0
|
||||||
|
adminAccessLogService.addAdminAccessLog(accessLog);
|
||||||
|
// TODO 提升:暂时不考虑 ELK 的方案。而是基于 MySQL 存储。如果访问日志比较多,需要定期归档。
|
||||||
|
} catch (Throwable th) {
|
||||||
|
logger.error("[afterCompletion][插入管理员访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
|
||||||
|
} finally {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setAdminId(Integer adminId) {
|
||||||
|
ADMIN_ID.set(adminId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
START_TIME.remove();
|
||||||
|
ADMIN_ID.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -39,6 +39,13 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
|
||||||
// 添加到 AdminSecurityContext
|
// 添加到 AdminSecurityContext
|
||||||
AdminSecurityContext context = new AdminSecurityContext(authentication.getAdminId(), authentication.getRoleIds());
|
AdminSecurityContext context = new AdminSecurityContext(authentication.getAdminId(), authentication.getRoleIds());
|
||||||
AdminSecurityContextHolder.setContext(context);
|
AdminSecurityContextHolder.setContext(context);
|
||||||
|
// 同时也记录管理员编号到 AdminAccessLogInterceptor 中。因为:
|
||||||
|
// AdminAccessLogInterceptor 需要在 AdminSecurityInterceptor 之前执行,这样记录的访问日志才健全
|
||||||
|
// AdminSecurityInterceptor 执行后,会移除 AdminSecurityContext 信息,这就导致 AdminAccessLogInterceptor 无法获得管理员编号
|
||||||
|
// 因此,这里需要进行记录
|
||||||
|
if (authentication.getAdminId() != null) {
|
||||||
|
AdminAccessLogInterceptor.setAdminId(authentication.getAdminId());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String url = request.getRequestURI();
|
String url = request.getRequestURI();
|
||||||
if (!url.equals("/admin/passport/login")) { // TODO 临时写死。非登陆接口,必须已经认证身份,不允许匿名访问
|
if (!url.equals("/admin/passport/login")) { // TODO 临时写死。非登陆接口,必须已经认证身份,不允许匿名访问
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package cn.iocoder.mall.admin.api;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
|
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员访问日志 Service 接口
|
||||||
|
*/
|
||||||
|
public interface AdminAccessLogService {
|
||||||
|
|
||||||
|
CommonResult<Boolean> addAdminAccessLog(AdminAccessLogAddDTO adminAccessLogAddDTO);
|
||||||
|
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ import cn.iocoder.mall.admin.api.dto.AdminUpdateDTO;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员 Service 接口
|
||||||
|
*/
|
||||||
public interface AdminService {
|
public interface AdminService {
|
||||||
|
|
||||||
CommonResult<AdminPageBO> getAdminPage(AdminPageDTO adminPageDTO);
|
CommonResult<AdminPageBO> getAdminPage(AdminPageDTO adminPageDTO);
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
package cn.iocoder.mall.admin.api.dto;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员访问日志添加 DTO
|
||||||
|
*/
|
||||||
|
public class AdminAccessLogAddDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员编号 - 空
|
||||||
|
*/
|
||||||
|
public static final Integer ADMIN_ID_NULL = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员编号.
|
||||||
|
*
|
||||||
|
* 当管理员为空时,该值为0
|
||||||
|
*/
|
||||||
|
@NotNull(message = "管理员编号不能为空")
|
||||||
|
private Integer adminId;
|
||||||
|
/**
|
||||||
|
* 访问地址
|
||||||
|
*/
|
||||||
|
@NotNull(message = "访问地址不能为空")
|
||||||
|
private String uri;
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请求参数不能为空")
|
||||||
|
private String queryString;
|
||||||
|
/**
|
||||||
|
* http 方法
|
||||||
|
*/
|
||||||
|
@NotNull(message = "http 请求方法不能为空")
|
||||||
|
private String method;
|
||||||
|
/**
|
||||||
|
* User Agent
|
||||||
|
*/
|
||||||
|
@NotNull(message = "User-Agent 不能为空")
|
||||||
|
private String userAgent;
|
||||||
|
/**
|
||||||
|
* ip
|
||||||
|
*/
|
||||||
|
@NotNull(message = "ip 不能为空")
|
||||||
|
private String ip;
|
||||||
|
/**
|
||||||
|
* 请求时间
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请求时间不能为空")
|
||||||
|
private Date startTime;
|
||||||
|
/**
|
||||||
|
* 响应时长 -- 毫秒级
|
||||||
|
*/
|
||||||
|
@NotNull(message = "响应时长不能为空")
|
||||||
|
private Integer responseTime;
|
||||||
|
|
||||||
|
public Integer getAdminId() {
|
||||||
|
return adminId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setAdminId(Integer adminId) {
|
||||||
|
this.adminId = adminId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setUri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setQueryString(String queryString) {
|
||||||
|
this.queryString = queryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAgent() {
|
||||||
|
return userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setUserAgent(String userAgent) {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setStartTime(Date startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getResponseTime() {
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogAddDTO setResponseTime(Integer responseTime) {
|
||||||
|
this.responseTime = responseTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cn.iocoder.mall.admin.convert;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO;
|
||||||
|
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mappings;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AdminAccessLogConvert {
|
||||||
|
|
||||||
|
AdminAccessLogConvert INSTANCE = Mappers.getMapper(AdminAccessLogConvert.class);
|
||||||
|
|
||||||
|
@Mappings({})
|
||||||
|
AdminAccessLogDO convert(AdminAccessLogAddDTO adminAccessLogAddDTO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package cn.iocoder.mall.admin.dao;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface AdminAccessLogMapper {
|
||||||
|
|
||||||
|
void insert(AdminAccessLogDO entity);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package cn.iocoder.mall.admin.dataobject;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.dataobject.BaseDO;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员访问日志 DO
|
||||||
|
*/
|
||||||
|
public class AdminAccessLogDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
/**
|
||||||
|
* 管理员编号.
|
||||||
|
*
|
||||||
|
* 当管理员为空时,该值为0
|
||||||
|
*/
|
||||||
|
private Integer adminId;
|
||||||
|
/**
|
||||||
|
* 访问地址
|
||||||
|
*/
|
||||||
|
private String uri;
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
private String queryString;
|
||||||
|
/**
|
||||||
|
* http 方法
|
||||||
|
*/
|
||||||
|
private String method;
|
||||||
|
/**
|
||||||
|
* userAgent
|
||||||
|
*/
|
||||||
|
private String userAgent;
|
||||||
|
/**
|
||||||
|
* ip
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
/**
|
||||||
|
* 请求时间
|
||||||
|
*/
|
||||||
|
private Date startTime;
|
||||||
|
/**
|
||||||
|
* 响应时长 -- 毫秒级
|
||||||
|
*/
|
||||||
|
private Integer responseTime;
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setId(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getAdminId() {
|
||||||
|
return adminId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setAdminId(Integer adminId) {
|
||||||
|
this.adminId = adminId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setUri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setQueryString(String queryString) {
|
||||||
|
this.queryString = queryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAgent() {
|
||||||
|
return userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setUserAgent(String userAgent) {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setStartTime(Date startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getResponseTime() {
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdminAccessLogDO setResponseTime(Integer responseTime) {
|
||||||
|
this.responseTime = responseTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package cn.iocoder.mall.admin.service;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.util.StringUtil;
|
||||||
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
|
import cn.iocoder.mall.admin.api.AdminAccessLogService;
|
||||||
|
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO;
|
||||||
|
import cn.iocoder.mall.admin.convert.AdminAccessLogConvert;
|
||||||
|
import cn.iocoder.mall.admin.dao.AdminAccessLogMapper;
|
||||||
|
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
|
||||||
|
public class AdminAccessLogServiceImpl implements AdminAccessLogService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求参数最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer QUERY_STRING_MAX_LENGTH = 4096;
|
||||||
|
/**
|
||||||
|
* 请求地址最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer URI_MAX_LENGTH = 4096;
|
||||||
|
/**
|
||||||
|
* User-Agent 最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer USER_AGENT_MAX_LENGTH = 1024;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AdminAccessLogMapper adminAccessLogMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<Boolean> addAdminAccessLog(AdminAccessLogAddDTO adminAccessLogAddDTO) {
|
||||||
|
// 创建 AdminAccessLogDO
|
||||||
|
AdminAccessLogDO accessLog = AdminAccessLogConvert.INSTANCE.convert(adminAccessLogAddDTO);
|
||||||
|
accessLog.setCreateTime(new Date());
|
||||||
|
// 截取最大长度
|
||||||
|
if (accessLog.getUri().length() > URI_MAX_LENGTH) {
|
||||||
|
accessLog.setUri(StringUtil.substring(accessLog.getUri(), URI_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
if (accessLog.getQueryString().length() > QUERY_STRING_MAX_LENGTH) {
|
||||||
|
accessLog.setQueryString(StringUtil.substring(accessLog.getQueryString(), QUERY_STRING_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
if (accessLog.getUserAgent().length() > USER_AGENT_MAX_LENGTH) {
|
||||||
|
accessLog.setUserAgent(StringUtil.substring(accessLog.getUserAgent(), USER_AGENT_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
// 插入
|
||||||
|
adminAccessLogMapper.insert(accessLog);
|
||||||
|
// 返回成功
|
||||||
|
return CommonResult.success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -77,7 +77,6 @@ public class OAuth2ServiceImpl implements OAuth2Service {
|
||||||
}
|
}
|
||||||
// 获得管理员拥有的角色
|
// 获得管理员拥有的角色
|
||||||
List<AdminRoleDO> adminRoleDOs = adminService.getAdminRoles(accessTokenDO.getAdminId());
|
List<AdminRoleDO> adminRoleDOs = adminService.getAdminRoles(accessTokenDO.getAdminId());
|
||||||
// TODO 芋艿,有个 bug ,要排除掉已经失效的角色
|
|
||||||
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO, adminRoleDOs));
|
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO, adminRoleDOs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="cn.iocoder.mall.admin.dao.AdminAccessLogMapper">
|
||||||
|
|
||||||
|
<!--<sql id="FIELDS">-->
|
||||||
|
<!--id, username, nickname, password, status,-->
|
||||||
|
<!--create_time-->
|
||||||
|
<!--</sql>-->
|
||||||
|
|
||||||
|
<insert id="insert" parameterType="AdminAccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||||
|
INSERT INTO admin_access_log (
|
||||||
|
admin_id, uri, query_string, method, user_agent,
|
||||||
|
ip, start_time, response_time, create_time
|
||||||
|
) VALUES (
|
||||||
|
#{adminId}, #{uri}, #{queryString}, #{method}, #{userAgent},
|
||||||
|
#{ip}, #{startTime}, #{responseTime}, #{createTime}
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
</mapper>
|
|
@ -1,11 +1,49 @@
|
||||||
package cn.iocoder.common.framework.util;
|
package cn.iocoder.common.framework.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
public class HttpUtil {
|
public class HttpUtil {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard Servlet 2.3+ spec request attributes for include URI and paths.
|
||||||
|
* <p>If included via a RequestDispatcher, the current resource will see the
|
||||||
|
* originating request. Its own URI and paths are exposed as request attributes.
|
||||||
|
*/
|
||||||
|
public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
|
||||||
|
public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
|
||||||
|
// public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
|
||||||
|
// public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";
|
||||||
|
// public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
|
||||||
|
// * <p>If forwarded to via a RequestDispatcher, the current resource will see its
|
||||||
|
// * own URI and paths. The originating URI and paths are exposed as request attributes.
|
||||||
|
// */
|
||||||
|
// public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";
|
||||||
|
// public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";
|
||||||
|
// public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";
|
||||||
|
// public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";
|
||||||
|
// public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default character encoding to use when <code>request.getCharacterEncoding</code>
|
||||||
|
* returns <code>null</code>, according to the Servlet spec.
|
||||||
|
*
|
||||||
|
* @see javax.servlet.ServletRequest#getCharacterEncoding
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
|
||||||
|
|
||||||
public static String obtainAccess(HttpServletRequest request) {
|
public static String obtainAccess(HttpServletRequest request) {
|
||||||
String authorization = request.getHeader("Authorization");
|
String authorization = request.getHeader("Authorization");
|
||||||
if (!StringUtils.hasText(authorization)) {
|
if (!StringUtils.hasText(authorization)) {
|
||||||
|
@ -39,4 +77,243 @@ public class HttpUtil {
|
||||||
return request.getRemoteAddr();
|
return request.getRemoteAddr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param request 请求
|
||||||
|
* @return ua
|
||||||
|
*/
|
||||||
|
public static String getUserAgent(HttpServletRequest request) {
|
||||||
|
String ua = request.getHeader("User-Agent");
|
||||||
|
return ua != null ? ua : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据request拼接queryString
|
||||||
|
*
|
||||||
|
* @return queryString
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static String buildQueryString(HttpServletRequest request) {
|
||||||
|
Enumeration<String> es = request.getParameterNames();
|
||||||
|
if (!es.hasMoreElements()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String parameterName, parameterValue;
|
||||||
|
StringBuilder params = new StringBuilder();
|
||||||
|
while (es.hasMoreElements()) {
|
||||||
|
parameterName = es.nextElement();
|
||||||
|
parameterValue = request.getParameter(parameterName);
|
||||||
|
params.append(parameterName).append("=").append(parameterValue).append("&");
|
||||||
|
}
|
||||||
|
return params.deleteCharAt(params.length() - 1).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the path within the web application for the given request.
|
||||||
|
* Detects include request URL if called within a RequestDispatcher include.
|
||||||
|
* <p/>
|
||||||
|
* For example, for a request to URL
|
||||||
|
* <p/>
|
||||||
|
* <code>http://www.somehost.com/myapp/my/url.jsp</code>,
|
||||||
|
* <p/>
|
||||||
|
* for an application deployed to <code>/mayapp</code> (the application's context path), this method would return
|
||||||
|
* <p/>
|
||||||
|
* <code>/my/url.jsp</code>.
|
||||||
|
*
|
||||||
|
* 该方法,是从 Shiro 源码中,扣出来。add by 芋艿
|
||||||
|
*
|
||||||
|
* @param request current HTTP request
|
||||||
|
* @return the path within the web application
|
||||||
|
*/
|
||||||
|
public static String getPathWithinApplication(HttpServletRequest request) {
|
||||||
|
String contextPath = getContextPath(request);
|
||||||
|
String requestUri = getRequestUri(request);
|
||||||
|
if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
|
||||||
|
// Normal case: URI contains context path.
|
||||||
|
String path = requestUri.substring(contextPath.length());
|
||||||
|
return (StringUtils.hasText(path) ? path : "/");
|
||||||
|
} else {
|
||||||
|
// Special case: rather unusual.
|
||||||
|
return requestUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the request URI for the given request, detecting an include request
|
||||||
|
* URL if called within a RequestDispatcher include.
|
||||||
|
* <p>As the value returned by <code>request.getRequestURI()</code> is <i>not</i>
|
||||||
|
* decoded by the servlet container, this method will decode it.
|
||||||
|
* <p>The URI that the web container resolves <i>should</i> be correct, but some
|
||||||
|
* containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
|
||||||
|
* in the URI. This method cuts off such incorrect appendices.
|
||||||
|
*
|
||||||
|
* @param request current HTTP request
|
||||||
|
* @return the request URI
|
||||||
|
*/
|
||||||
|
public static String getRequestUri(HttpServletRequest request) {
|
||||||
|
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
|
||||||
|
if (uri == null) {
|
||||||
|
uri = request.getRequestURI();
|
||||||
|
}
|
||||||
|
return normalize(decodeAndCleanUriString(request, uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a relative URI path that may have relative values ("/./",
|
||||||
|
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
|
||||||
|
* useful only for normalizing application-generated paths. It does not
|
||||||
|
* try to perform security checks for malicious input.
|
||||||
|
* Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
|
||||||
|
* Tomcat trunk, r939305
|
||||||
|
*
|
||||||
|
* @param path Relative path to be normalized
|
||||||
|
* @return normalized path
|
||||||
|
*/
|
||||||
|
public static String normalize(String path) {
|
||||||
|
return normalize(path, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a relative URI path that may have relative values ("/./",
|
||||||
|
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
|
||||||
|
* useful only for normalizing application-generated paths. It does not
|
||||||
|
* try to perform security checks for malicious input.
|
||||||
|
* Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
|
||||||
|
* Tomcat trunk, r939305
|
||||||
|
*
|
||||||
|
* @param path Relative path to be normalized
|
||||||
|
* @param replaceBackSlash Should '\\' be replaced with '/'
|
||||||
|
* @return normalized path
|
||||||
|
*/
|
||||||
|
private static String normalize(String path, boolean replaceBackSlash) {
|
||||||
|
|
||||||
|
if (path == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Create a place for the normalized path
|
||||||
|
String normalized = path;
|
||||||
|
|
||||||
|
if (replaceBackSlash && normalized.indexOf('\\') >= 0)
|
||||||
|
normalized = normalized.replace('\\', '/');
|
||||||
|
|
||||||
|
if (normalized.equals("/."))
|
||||||
|
return "/";
|
||||||
|
|
||||||
|
// Add a leading "/" if necessary
|
||||||
|
if (!normalized.startsWith("/"))
|
||||||
|
normalized = "/" + normalized;
|
||||||
|
|
||||||
|
// Resolve occurrences of "//" in the normalized path
|
||||||
|
while (true) {
|
||||||
|
int index = normalized.indexOf("//");
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
normalized = normalized.substring(0, index) +
|
||||||
|
normalized.substring(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve occurrences of "/./" in the normalized path
|
||||||
|
while (true) {
|
||||||
|
int index = normalized.indexOf("/./");
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
normalized = normalized.substring(0, index) +
|
||||||
|
normalized.substring(index + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve occurrences of "/../" in the normalized path
|
||||||
|
while (true) {
|
||||||
|
int index = normalized.indexOf("/../");
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
if (index == 0)
|
||||||
|
return (null); // Trying to go outside our context
|
||||||
|
int index2 = normalized.lastIndexOf('/', index - 1);
|
||||||
|
normalized = normalized.substring(0, index2) +
|
||||||
|
normalized.substring(index + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the normalized path that we have completed
|
||||||
|
return (normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the supplied URI string and strips any extraneous portion after a ';'.
|
||||||
|
*
|
||||||
|
* @param request the incoming HttpServletRequest
|
||||||
|
* @param uri the application's URI string
|
||||||
|
* @return the supplied URI string stripped of any extraneous portion after a ';'.
|
||||||
|
*/
|
||||||
|
private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
|
||||||
|
uri = decodeRequestString(request, uri);
|
||||||
|
int semicolonIndex = uri.indexOf(';');
|
||||||
|
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the context path for the given request, detecting an include request
|
||||||
|
* URL if called within a RequestDispatcher include.
|
||||||
|
* <p>As the value returned by <code>request.getContextPath()</code> is <i>not</i>
|
||||||
|
* decoded by the servlet container, this method will decode it.
|
||||||
|
*
|
||||||
|
* @param request current HTTP request
|
||||||
|
* @return the context path
|
||||||
|
*/
|
||||||
|
public static String getContextPath(HttpServletRequest request) {
|
||||||
|
String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
|
||||||
|
if (contextPath == null) {
|
||||||
|
contextPath = request.getContextPath();
|
||||||
|
}
|
||||||
|
if ("/".equals(contextPath)) {
|
||||||
|
// Invalid case, but happens for includes on Jetty: silently adapt it.
|
||||||
|
contextPath = "";
|
||||||
|
}
|
||||||
|
return decodeRequestString(request, contextPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the given source string with a URLDecoder. The encoding will be taken
|
||||||
|
* from the request, falling back to the default "ISO-8859-1".
|
||||||
|
* <p>The default implementation uses <code>URLDecoder.decode(input, enc)</code>.
|
||||||
|
*
|
||||||
|
* @param request current HTTP request
|
||||||
|
* @param source the String to decode
|
||||||
|
* @return the decoded String
|
||||||
|
* @see #DEFAULT_CHARACTER_ENCODING
|
||||||
|
* @see javax.servlet.ServletRequest#getCharacterEncoding
|
||||||
|
* @see java.net.URLDecoder#decode(String, String)
|
||||||
|
* @see java.net.URLDecoder#decode(String)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"deprecation"})
|
||||||
|
public static String decodeRequestString(HttpServletRequest request, String source) {
|
||||||
|
String enc = determineEncoding(request);
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(source, enc);
|
||||||
|
} catch (UnsupportedEncodingException ex) {
|
||||||
|
if (logger.isWarnEnabled()) {
|
||||||
|
logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
|
||||||
|
"': falling back to platform default encoding; exception message: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
return URLDecoder.decode(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the encoding for the given request.
|
||||||
|
* Can be overridden in subclasses.
|
||||||
|
* <p>The default implementation checks the request's
|
||||||
|
* {@link ServletRequest#getCharacterEncoding() character encoding}, and if that
|
||||||
|
* <code>null</code>, falls back to the {@link #DEFAULT_CHARACTER_ENCODING}.
|
||||||
|
*
|
||||||
|
* @param request current HTTP request
|
||||||
|
* @return the encoding for the request (never <code>null</code>)
|
||||||
|
* @see javax.servlet.ServletRequest#getCharacterEncoding()
|
||||||
|
*/
|
||||||
|
protected static String determineEncoding(HttpServletRequest request) {
|
||||||
|
String enc = request.getCharacterEncoding();
|
||||||
|
if (enc == null) {
|
||||||
|
enc = DEFAULT_CHARACTER_ENCODING;
|
||||||
|
}
|
||||||
|
return enc;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -27,4 +27,8 @@ public class StringUtil {
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String substring(String str, int start) {
|
||||||
|
return org.apache.commons.lang3.StringUtils.substring(str, start);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -52,7 +52,7 @@ public class AdminsProductSpuController {
|
||||||
@RequestParam("sellPoint") String sellPoint,
|
@RequestParam("sellPoint") String sellPoint,
|
||||||
@RequestParam("description") String description,
|
@RequestParam("description") String description,
|
||||||
@RequestParam("cid") Integer cid,
|
@RequestParam("cid") Integer cid,
|
||||||
@RequestParam("picURLs") List<String> picUrls,
|
@RequestParam("picUrls") List<String> picUrls,
|
||||||
@RequestParam("visible") Boolean visible,
|
@RequestParam("visible") Boolean visible,
|
||||||
@RequestParam("skuStr") String skuStr) { // TODO 芋艿,因为考虑不使用 json 接受参数,所以这里手动转。
|
@RequestParam("skuStr") String skuStr) { // TODO 芋艿,因为考虑不使用 json 接受参数,所以这里手动转。
|
||||||
// 创建 ProductSpuAddDTO 对象
|
// 创建 ProductSpuAddDTO 对象
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.mall.user.application.config;
|
||||||
|
|
||||||
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
||||||
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
|
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
|
||||||
|
import cn.iocoder.mall.user.sdk.interceptor.UserAccessLogInterceptor;
|
||||||
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
|
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -18,13 +19,18 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
public class MVCConfiguration implements WebMvcConfigurer {
|
public class MVCConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserSecurityInterceptor securityInterceptor;
|
private UserSecurityInterceptor userSecurityInterceptor;
|
||||||
|
@Autowired
|
||||||
|
private UserAccessLogInterceptor userAccessLogInterceptor;
|
||||||
@Autowired
|
@Autowired
|
||||||
private AdminSecurityInterceptor adminSecurityInterceptor;
|
private AdminSecurityInterceptor adminSecurityInterceptor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(securityInterceptor).addPathPatterns("/users/**"); // 只拦截我们定义的接口
|
// 用户
|
||||||
|
registry.addInterceptor(userAccessLogInterceptor).addPathPatterns("/users/**");
|
||||||
|
registry.addInterceptor(userSecurityInterceptor).addPathPatterns("/users/**"); // 只拦截我们定义的接口
|
||||||
|
// 管理员
|
||||||
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**"); // 只拦截我们定义的接口
|
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**"); // 只拦截我们定义的接口
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package cn.iocoder.mall.user.sdk.interceptor;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.util.HttpUtil;
|
||||||
|
import cn.iocoder.mall.user.service.api.UserAccessLogService;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
import com.alibaba.dubbo.config.annotation.Reference;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问日志拦截器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class UserAccessLogInterceptor extends HandlerInterceptorAdapter {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始时间
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Date> START_TIME = new ThreadLocal<>();
|
||||||
|
/**
|
||||||
|
* 管理员编号
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Integer> USER_ID = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@Reference
|
||||||
|
private UserAccessLogService userAccessLogService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
// 记录当前时间
|
||||||
|
START_TIME.set(new Date());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||||
|
UserAccessLogAddDTO accessLog = new UserAccessLogAddDTO();
|
||||||
|
try {
|
||||||
|
accessLog.setUserId(USER_ID.get());
|
||||||
|
if (accessLog.getUserId() == null) {
|
||||||
|
accessLog.setUserId(UserAccessLogAddDTO.USER_ID_NULL);
|
||||||
|
}
|
||||||
|
accessLog.setUri(request.getRequestURI()); // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
||||||
|
accessLog.setQueryString(HttpUtil.buildQueryString(request));
|
||||||
|
accessLog.setMethod(request.getMethod());
|
||||||
|
accessLog.setUserAgent(HttpUtil.getUserAgent(request));
|
||||||
|
accessLog.setIp(HttpUtil.getIp(request));
|
||||||
|
accessLog.setStartTime(START_TIME.get());
|
||||||
|
accessLog.setResponseTime((int) (System.currentTimeMillis() - accessLog.getStartTime().getTime()));// 默认响应时间设为0
|
||||||
|
userAccessLogService.addUserAccessLog(accessLog);
|
||||||
|
// TODO 提升:暂时不考虑 ELK 的方案。而是基于 MySQL 存储。如果访问日志比较多,需要定期归档。
|
||||||
|
} catch (Throwable th) {
|
||||||
|
logger.error("[afterCompletion][插入管理员访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
|
||||||
|
} finally {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUserId(Integer userId) {
|
||||||
|
USER_ID.set(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
START_TIME.remove();
|
||||||
|
USER_ID.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -40,6 +40,13 @@ public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
|
||||||
// 添加到 SecurityContext
|
// 添加到 SecurityContext
|
||||||
UserSecurityContext context = new UserSecurityContext(authentication.getUserId());
|
UserSecurityContext context = new UserSecurityContext(authentication.getUserId());
|
||||||
UserSecurityContextHolder.setContext(context);
|
UserSecurityContextHolder.setContext(context);
|
||||||
|
// 同时也记录管理员编号到 AdminAccessLogInterceptor 中。因为:
|
||||||
|
// AdminAccessLogInterceptor 需要在 AdminSecurityInterceptor 之前执行,这样记录的访问日志才健全
|
||||||
|
// AdminSecurityInterceptor 执行后,会移除 AdminSecurityContext 信息,这就导致 AdminAccessLogInterceptor 无法获得管理员编号
|
||||||
|
// 因此,这里需要进行记录
|
||||||
|
if (authentication.getUserId() != null) {
|
||||||
|
UserAccessLogInterceptor.setUserId(authentication.getUserId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 校验是否需要已授权
|
// 校验是否需要已授权
|
||||||
HandlerMethod method = (HandlerMethod) handler;
|
HandlerMethod method = (HandlerMethod) handler;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package cn.iocoder.mall.user.service.api;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
|
||||||
|
public interface UserAccessLogService {
|
||||||
|
|
||||||
|
CommonResult<Boolean> addUserAccessLog(UserAccessLogAddDTO userAccessLogAddDTO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package cn.iocoder.mall.user.service.api.dto;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户访问日志添加 DTO
|
||||||
|
*/
|
||||||
|
public class UserAccessLogAddDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号 - 空
|
||||||
|
*/
|
||||||
|
public static final Integer USER_ID_NULL = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号.
|
||||||
|
*
|
||||||
|
* 当用户为空时,该值为0
|
||||||
|
*/
|
||||||
|
@NotNull(message = "用户编号不能为空")
|
||||||
|
private Integer userId;
|
||||||
|
/**
|
||||||
|
* 访问地址
|
||||||
|
*/
|
||||||
|
@NotNull(message = "访问地址不能为空")
|
||||||
|
private String uri;
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请求参数不能为空")
|
||||||
|
private String queryString;
|
||||||
|
/**
|
||||||
|
* http 方法
|
||||||
|
*/
|
||||||
|
@NotNull(message = "http 请求方法不能为空")
|
||||||
|
private String method;
|
||||||
|
/**
|
||||||
|
* User Agent
|
||||||
|
*/
|
||||||
|
@NotNull(message = "User-Agent 不能为空")
|
||||||
|
private String userAgent;
|
||||||
|
/**
|
||||||
|
* ip
|
||||||
|
*/
|
||||||
|
@NotNull(message = "ip 不能为空")
|
||||||
|
private String ip;
|
||||||
|
/**
|
||||||
|
* 请求时间
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请求时间不能为空")
|
||||||
|
private Date startTime;
|
||||||
|
/**
|
||||||
|
* 响应时长 -- 毫秒级
|
||||||
|
*/
|
||||||
|
@NotNull(message = "响应时长不能为空")
|
||||||
|
private Integer responseTime;
|
||||||
|
|
||||||
|
public Integer getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setUserId(Integer userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setUri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setQueryString(String queryString) {
|
||||||
|
this.queryString = queryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAgent() {
|
||||||
|
return userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setUserAgent(String userAgent) {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setStartTime(Date startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getResponseTime() {
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setResponseTime(Integer responseTime) {
|
||||||
|
this.responseTime = responseTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cn.iocoder.mall.user.convert;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.user.dataobject.UserAccessLogDO;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mappings;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserAccessLogConvert {
|
||||||
|
|
||||||
|
UserAccessLogConvert INSTANCE = Mappers.getMapper(UserAccessLogConvert.class);
|
||||||
|
|
||||||
|
@Mappings({})
|
||||||
|
UserAccessLogDO convert(UserAccessLogAddDTO adminAccessLogAddDTO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package cn.iocoder.mall.user.dao;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.user.dataobject.UserAccessLogDO;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserAccessLogMapper {
|
||||||
|
|
||||||
|
void insert(UserAccessLogDO entity);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package cn.iocoder.mall.user.dataobject;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.dataobject.BaseDO;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户访问日志 DO
|
||||||
|
*/
|
||||||
|
public class UserAccessLogDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
/**
|
||||||
|
* 用户编号.
|
||||||
|
*
|
||||||
|
* 当用户编号为空时,该值为0
|
||||||
|
*/
|
||||||
|
private Integer userId;
|
||||||
|
/**
|
||||||
|
* 访问地址
|
||||||
|
*/
|
||||||
|
private String uri;
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
private String queryString;
|
||||||
|
/**
|
||||||
|
* http 方法
|
||||||
|
*/
|
||||||
|
private String method;
|
||||||
|
/**
|
||||||
|
* userAgent
|
||||||
|
*/
|
||||||
|
private String userAgent;
|
||||||
|
/**
|
||||||
|
* ip
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
/**
|
||||||
|
* 请求时间
|
||||||
|
*/
|
||||||
|
private Date startTime;
|
||||||
|
/**
|
||||||
|
* 响应时长 -- 毫秒级
|
||||||
|
*/
|
||||||
|
private Integer responseTime;
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setId(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setUserId(Integer userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setUri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setQueryString(String queryString) {
|
||||||
|
this.queryString = queryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAgent() {
|
||||||
|
return userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setUserAgent(String userAgent) {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setStartTime(Date startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getResponseTime() {
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setResponseTime(Integer responseTime) {
|
||||||
|
this.responseTime = responseTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package cn.iocoder.mall.user.service;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.util.StringUtil;
|
||||||
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
|
import cn.iocoder.mall.user.convert.UserAccessLogConvert;
|
||||||
|
import cn.iocoder.mall.user.dao.UserAccessLogMapper;
|
||||||
|
import cn.iocoder.mall.user.dataobject.UserAccessLogDO;
|
||||||
|
import cn.iocoder.mall.user.service.api.UserAccessLogService;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
|
||||||
|
public class UserAccessLogServiceImpl implements UserAccessLogService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求参数最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer QUERY_STRING_MAX_LENGTH = 4096;
|
||||||
|
/**
|
||||||
|
* 请求地址最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer URI_MAX_LENGTH = 4096;
|
||||||
|
/**
|
||||||
|
* User-Agent 最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer USER_AGENT_MAX_LENGTH = 1024;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserAccessLogMapper userAccessLogMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<Boolean> addUserAccessLog(UserAccessLogAddDTO userAccessLogAddDTO) {
|
||||||
|
// 创建 UserAccessLogDO
|
||||||
|
UserAccessLogDO accessLog = UserAccessLogConvert.INSTANCE.convert(userAccessLogAddDTO);
|
||||||
|
accessLog.setCreateTime(new Date());
|
||||||
|
// 截取最大长度
|
||||||
|
if (accessLog.getUri().length() > URI_MAX_LENGTH) {
|
||||||
|
accessLog.setUri(StringUtil.substring(accessLog.getUri(), URI_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
if (accessLog.getQueryString().length() > QUERY_STRING_MAX_LENGTH) {
|
||||||
|
accessLog.setQueryString(StringUtil.substring(accessLog.getQueryString(), QUERY_STRING_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
if (accessLog.getUserAgent().length() > USER_AGENT_MAX_LENGTH) {
|
||||||
|
accessLog.setUserAgent(StringUtil.substring(accessLog.getUserAgent(), USER_AGENT_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
// 插入
|
||||||
|
userAccessLogMapper.insert(accessLog);
|
||||||
|
// 返回成功
|
||||||
|
return CommonResult.success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="cn.iocoder.mall.user.dao.UserAccessLogMapper">
|
||||||
|
|
||||||
|
<!--<sql id="FIELDS">-->
|
||||||
|
<!--id, username, nickname, password, status,-->
|
||||||
|
<!--create_time-->
|
||||||
|
<!--</sql>-->
|
||||||
|
|
||||||
|
<insert id="insert" parameterType="UserAccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||||
|
INSERT INTO user_access_log (
|
||||||
|
user_id, uri, query_string, method, user_agent,
|
||||||
|
ip, start_time, response_time, create_time
|
||||||
|
) VALUES (
|
||||||
|
#{userId}, #{uri}, #{queryString}, #{method}, #{userAgent},
|
||||||
|
#{ip}, #{startTime}, #{responseTime}, #{createTime}
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
</mapper>
|
Loading…
Reference in New Issue