From 11f5acd056055cc74e8e4f9978844f88e5bdf4e6 Mon Sep 17 00:00:00 2001 From: smallchill Date: Sun, 31 Mar 2019 22:04:01 +0800 Subject: [PATCH] =?UTF-8?q?:zap:=20=E5=A2=9E=E5=8A=A0=E5=A4=9A=E7=BB=88?= =?UTF-8?q?=E7=AB=AF=E4=BB=A4=E7=89=8C=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/router.config.js | 10 ++ mock/client.js | 65 ++++++++ package.json | 3 +- src/actions/client.js | 36 +++++ src/defaultSettings.js | 2 + src/locales/en-US/menu.js | 1 + src/locales/zh-CN/menu.js | 1 + src/locales/zh-TW/menu.js | 1 + src/models/client.js | 87 ++++++++++ src/pages/System/Client/Client.js | 99 ++++++++++++ src/pages/System/Client/ClientAdd.js | 188 ++++++++++++++++++++++ src/pages/System/Client/ClientEdit.js | 219 ++++++++++++++++++++++++++ src/pages/System/Client/ClientView.js | 101 ++++++++++++ src/services/client.js | 25 +++ src/utils/request.js | 12 +- 15 files changed, 847 insertions(+), 3 deletions(-) create mode 100644 mock/client.js create mode 100644 src/actions/client.js create mode 100644 src/models/client.js create mode 100644 src/pages/System/Client/Client.js create mode 100644 src/pages/System/Client/ClientAdd.js create mode 100644 src/pages/System/Client/ClientEdit.js create mode 100644 src/pages/System/Client/ClientView.js create mode 100644 src/services/client.js diff --git a/config/router.config.js b/config/router.config.js index 8fc624d..2180332 100644 --- a/config/router.config.js +++ b/config/router.config.js @@ -166,6 +166,16 @@ export default [ { path: '/system/tenant/view/:id', component: './System/Tenant/TenantView' }, ], }, + { + path: '/system/client', + routes: [ + { path: '/system/client', redirect: '/system/client/list' }, + { path: '/system/client/list', component: './System/Client/Client' }, + { path: '/system/client/add', component: './System/Client/ClientAdd' }, + { path: '/system/client/edit/:id', component: './System/Client/ClientEdit' }, + { path: '/system/client/view/:id', component: './System/Client/ClientView' }, + ], + }, ], }, { diff --git a/mock/client.js b/mock/client.js new file mode 100644 index 0000000..21f82be --- /dev/null +++ b/mock/client.js @@ -0,0 +1,65 @@ +import { delay } from 'roadhog-api-doc'; + +function getFakeList(req, res) { + const json = { code: 200, success: true, msg: '操作成功' }; + const list = []; + list.push( + { + id: '1', + clientId: 'sword', + clientSecret: 'sword_secret', + scope: 'all', + authorizedGrantTypes: 'refresh_token,password,authorization_code', + webServerRedirectUri: 'https://sword.bladex.vip', + accessTokenValidity: '3600', + refreshTokenValidity: '36000', + }, + { + id: '2', + clientId: 'saber', + clientSecret: 'saber_secret', + scope: 'all', + authorizedGrantTypes: 'refresh_token,password,authorization_code', + webServerRedirectUri: 'https://saber.bladex.vip', + accessTokenValidity: '3600', + refreshTokenValidity: '36000', + } + ); + json.data = { + total: 10, + size: 10, + current: 2, + searchCount: true, + pages: 1, + records: list, + }; + return res.json(json); +} + +function getFakeDetail(req, res) { + const json = { code: 200, success: true, msg: '操作成功' }; + json.data = { + id: '1', + clientId: 'sword', + clientSecret: 'sword_secret', + scope: 'all', + authorizedGrantTypes: 'refresh_token,password,authorization_code', + webServerRedirectUri: 'https://sword.bladex.vip', + accessTokenValidity: '3600', + refreshTokenValidity: '36000', + }; + return res.json(json); +} + +function fakeSuccess(req, res) { + const json = { code: 200, success: true, msg: '操作成功' }; + return res.json(json); +} + +const proxy = { + 'GET /api/blade-system/client/list': getFakeList, + 'GET /api/blade-system/client/detail': getFakeDetail, + 'POST /api/blade-system/client/submit': fakeSuccess, + 'POST /api/blade-system/client/remove': fakeSuccess, +}; +export default delay(proxy, 500); diff --git a/package.json b/package.json index c8519b8..62fbe4f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "react-fittext": "^1.0.0", "react-media": "^1.9.2", "react-router-dom": "^4.3.1", - "roadhog-api-doc": "^1.1.2" + "roadhog-api-doc": "^1.1.2", + "js-base64": "^2.5.1" }, "devDependencies": { "@types/react": "^16.8.1", diff --git a/src/actions/client.js b/src/actions/client.js new file mode 100644 index 0000000..c8b55da --- /dev/null +++ b/src/actions/client.js @@ -0,0 +1,36 @@ +export const CLIENT_NAMESPACE = 'client'; + +export function CLIENT_LIST(payload) { + return { + type: `${CLIENT_NAMESPACE}/fetchList`, + payload, + }; +} + +export function CLIENT_DETAIL(id) { + return { + type: `${CLIENT_NAMESPACE}/fetchDetail`, + payload: { id }, + }; +} + +export function CLIENT_CLEAR_DETAIL() { + return { + type: `${CLIENT_NAMESPACE}/clearDetail`, + payload: {}, + }; +} + +export function CLIENT_SUBMIT(payload) { + return { + type: `${CLIENT_NAMESPACE}/submit`, + payload, + }; +} + +export function CLIENT_REMOVE(payload) { + return { + type: `${CLIENT_NAMESPACE}/remove`, + payload, + }; +} diff --git a/src/defaultSettings.js b/src/defaultSettings.js index 6714cbe..4d39b6e 100644 --- a/src/defaultSettings.js +++ b/src/defaultSettings.js @@ -1,5 +1,7 @@ module.exports = { title: 'Sword企业级开发平台', + clientId: 'sword', // 客户端id + clientSecret: 'sword_secret', // 客户端密钥 tenantMode: true, // 开启租户模式 navTheme: 'dark', // theme for nav menu primaryColor: '#1890FF', // primary color of ant design diff --git a/src/locales/en-US/menu.js b/src/locales/en-US/menu.js index 194b0b2..cc83d16 100644 --- a/src/locales/en-US/menu.js +++ b/src/locales/en-US/menu.js @@ -14,6 +14,7 @@ export default { 'menu.system.role': 'role', 'menu.system.param': 'parameter', 'menu.system.tenant': 'tenant', + 'menu.system.client': 'client', 'menu.monitor': 'monitor', 'menu.monitor.log': 'log', 'menu.monitor.log.log_usual': 'usual log', diff --git a/src/locales/zh-CN/menu.js b/src/locales/zh-CN/menu.js index 5e3f773..eafd4bb 100644 --- a/src/locales/zh-CN/menu.js +++ b/src/locales/zh-CN/menu.js @@ -14,6 +14,7 @@ export default { 'menu.system.role': '角色管理', 'menu.system.param': '参数管理', 'menu.system.tenant': '租户管理', + 'menu.system.client': '应用管理', 'menu.monitor': '系统监控', 'menu.monitor.log': '日志管理', 'menu.monitor.log.log_usual': '通用日志', diff --git a/src/locales/zh-TW/menu.js b/src/locales/zh-TW/menu.js index ff3c00b..3bd46e2 100644 --- a/src/locales/zh-TW/menu.js +++ b/src/locales/zh-TW/menu.js @@ -14,6 +14,7 @@ export default { 'menu.system.role': '角色管理', 'menu.system.param': '參數管理', 'menu.system.tenant': '租戶管理', + 'menu.system.client': '應用管理', 'menu.monitor': '系統監控', 'menu.monitor.log': '日志管理', 'menu.monitor.log.log_usual': '通用日志', diff --git a/src/models/client.js b/src/models/client.js new file mode 100644 index 0000000..e81ccbb --- /dev/null +++ b/src/models/client.js @@ -0,0 +1,87 @@ +import { message } from 'antd'; +import router from 'umi/router'; +import { CLIENT_NAMESPACE } from '../actions/client'; +import { list, submit, detail, remove } from '../services/client'; + +export default { + namespace: CLIENT_NAMESPACE, + state: { + data: { + list: [], + pagination: false, + }, + detail: {}, + }, + effects: { + *fetchList({ payload }, { call, put }) { + const response = yield call(list, payload); + if (response.success) { + yield put({ + type: 'saveList', + payload: { + list: response.data.records, + pagination: { + total: response.data.total, + current: response.data.current, + pageSize: response.data.size, + }, + }, + }); + } + }, + *fetchDetail({ payload }, { call, put }) { + const response = yield call(detail, payload); + if (response.success) { + yield put({ + type: 'saveDetail', + payload: { + detail: response.data, + }, + }); + } + }, + *clearDetail({ payload }, { put }) { + yield put({ + type: 'removeDetail', + payload: { payload }, + }); + }, + *submit({ payload }, { call }) { + const response = yield call(submit, payload); + if (response.success) { + message.success('提交成功'); + router.push('/system/client'); + } + }, + *remove({ payload }, { call }) { + const { + data: { keys }, + success, + } = payload; + const response = yield call(remove, { ids: keys }); + if (response.success) { + success(); + } + }, + }, + reducers: { + saveList(state, action) { + return { + ...state, + data: action.payload, + }; + }, + saveDetail(state, action) { + return { + ...state, + detail: action.payload.detail, + }; + }, + removeDetail(state) { + return { + ...state, + detail: {}, + }; + }, + }, +}; diff --git a/src/pages/System/Client/Client.js b/src/pages/System/Client/Client.js new file mode 100644 index 0000000..26bae1f --- /dev/null +++ b/src/pages/System/Client/Client.js @@ -0,0 +1,99 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'dva'; +import { Button, Col, Form, Input, Row } from 'antd'; +import Panel from '../../../components/Panel'; +import { CLIENT_LIST } from '../../../actions/client'; +import Grid from '../../../components/Sword/Grid'; + +const FormItem = Form.Item; + +@connect(({ client, loading }) => ({ + client, + loading: loading.models.client, +})) +@Form.create() +class Client extends PureComponent { + // ============ 查询 =============== + handleSearch = params => { + const { dispatch } = this.props; + dispatch(CLIENT_LIST(params)); + }; + + // ============ 查询表单 =============== + renderSearchForm = onReset => { + const { form } = this.props; + const { getFieldDecorator } = form; + + return ( + + + + {getFieldDecorator('clientId')()} + + + +
+ + +
+ +
+ ); + }; + + render() { + const code = 'client'; + + const { + form, + loading, + client: { data }, + } = this.props; + + const columns = [ + { + title: '客户端id', + dataIndex: 'clientId', + }, + { + title: '客户端密钥', + dataIndex: 'clientSecret', + }, + { + title: '授权范围', + dataIndex: 'scope', + }, + { + title: '授权类型', + dataIndex: 'authorizedGrantTypes', + }, + { + title: '回调地址', + dataIndex: 'webServerRedirectUri', + }, + { + title: '令牌过期秒数', + dataIndex: 'accessTokenValidity', + }, + ]; + + return ( + + + + ); + } +} +export default Client; diff --git a/src/pages/System/Client/ClientAdd.js b/src/pages/System/Client/ClientAdd.js new file mode 100644 index 0000000..72c103a --- /dev/null +++ b/src/pages/System/Client/ClientAdd.js @@ -0,0 +1,188 @@ +import React, { PureComponent } from 'react'; +import { Form, Input, Card, Button, Row, Col, InputNumber } from 'antd'; +import { connect } from 'dva'; +import Panel from '../../../components/Panel'; +import styles from '../../../layouts/Sword.less'; +import { CLIENT_SUBMIT } from '../../../actions/client'; + +const FormItem = Form.Item; + +@connect(({ loading }) => ({ + submitting: loading.effects['client/submit'], +})) +@Form.create() +class ClientAdd extends PureComponent { + handleSubmit = e => { + e.preventDefault(); + const { dispatch, form } = this.props; + form.validateFieldsAndScroll((err, values) => { + if (!err) { + dispatch(CLIENT_SUBMIT(values)); + } + }); + }; + + render() { + const { + form: { getFieldDecorator }, + submitting, + } = this.props; + + const formItemLayout = { + labelCol: { + span: 8, + }, + wrapperCol: { + span: 16, + }, + }; + + const formAllItemLayout = { + labelCol: { + span: 4, + }, + wrapperCol: { + span: 20, + }, + }; + + const action = ( + + ); + + return ( + +
+ + + + + {getFieldDecorator('clientId', { + rules: [ + { + required: true, + message: '请输入客户端id', + }, + ], + })()} + + + + + {getFieldDecorator('clientSecret', { + rules: [ + { + required: true, + message: '请输入客户端密钥', + }, + ], + })()} + + + + + + + {getFieldDecorator('authorizedGrantTypes', { + rules: [ + { + required: true, + message: '请输入授权类型', + }, + ], + initialValue: 'refresh_token,password,authorization_code', + })()} + + + + + {getFieldDecorator('scope', { + rules: [ + { + required: true, + message: '请输入授权范围', + }, + ], + initialValue: 'all', + })()} + + + + + + + {getFieldDecorator('accessTokenValidity', { + rules: [ + { + required: true, + message: '请输入令牌过期秒数', + }, + ], + initialValue: 3600, + })()} + + + + + {getFieldDecorator('refreshTokenValidity', { + rules: [ + { + required: true, + message: '请输入刷新令牌过期秒数', + }, + ], + initialValue: 604800, + })()} + + + + + + + {getFieldDecorator('webServerRedirectUri', { + rules: [ + { + required: true, + message: '请输入回调地址', + }, + ], + })()} + + + + + {getFieldDecorator('resourceIds', {})()} + + + + + + + {getFieldDecorator('authorities', {})()} + + + + + {getFieldDecorator('autoapprove', {})()} + + + + + + + {getFieldDecorator('additionalInformation', {})( + + )} + + + + +
+
+ ); + } +} + +export default ClientAdd; diff --git a/src/pages/System/Client/ClientEdit.js b/src/pages/System/Client/ClientEdit.js new file mode 100644 index 0000000..b099742 --- /dev/null +++ b/src/pages/System/Client/ClientEdit.js @@ -0,0 +1,219 @@ +import React, { PureComponent } from 'react'; +import { Form, Input, Card, Button, Row, Col, InputNumber } from 'antd'; +import { connect } from 'dva'; +import Panel from '../../../components/Panel'; +import styles from '../../../layouts/Sword.less'; +import { CLIENT_DETAIL, CLIENT_SUBMIT } from '../../../actions/client'; + +const FormItem = Form.Item; + +@connect(({ client, loading }) => ({ + client, + submitting: loading.effects['client/submit'], +})) +@Form.create() +class ClientEdit extends PureComponent { + componentWillMount() { + const { + dispatch, + match: { + params: { id }, + }, + } = this.props; + dispatch(CLIENT_DETAIL(id)); + } + + handleSubmit = e => { + e.preventDefault(); + const { + dispatch, + match: { + params: { id }, + }, + form, + } = this.props; + form.validateFieldsAndScroll((err, values) => { + if (!err) { + const params = { + id, + ...values, + }; + dispatch(CLIENT_SUBMIT(params)); + } + }); + }; + + render() { + const { + form: { getFieldDecorator }, + client: { detail }, + submitting, + } = this.props; + + const formItemLayout = { + labelCol: { + span: 8, + }, + wrapperCol: { + span: 16, + }, + }; + + const formAllItemLayout = { + labelCol: { + span: 4, + }, + wrapperCol: { + span: 20, + }, + }; + + const action = ( + + ); + + return ( + +
+ + + + + {getFieldDecorator('clientId', { + rules: [ + { + required: true, + message: '请输入客户端id', + }, + ], + initialValue: detail.clientId, + })()} + + + + + {getFieldDecorator('clientSecret', { + rules: [ + { + required: true, + message: '请输入客户端密钥', + }, + ], + initialValue: detail.clientSecret, + })()} + + + + + + + {getFieldDecorator('authorizedGrantTypes', { + rules: [ + { + required: true, + message: '请输入授权类型', + }, + ], + initialValue: detail.authorizedGrantTypes, + })()} + + + + + {getFieldDecorator('scope', { + rules: [ + { + required: true, + message: '请输入授权范围', + }, + ], + initialValue: detail.scope, + })()} + + + + + + + {getFieldDecorator('accessTokenValidity', { + rules: [ + { + required: true, + message: '请输入令牌过期秒数', + }, + ], + initialValue: detail.accessTokenValidity, + })()} + + + + + {getFieldDecorator('refreshTokenValidity', { + rules: [ + { + required: true, + message: '请输入刷新令牌过期秒数', + }, + ], + initialValue: detail.refreshTokenValidity, + })()} + + + + + + + {getFieldDecorator('webServerRedirectUri', { + rules: [ + { + required: true, + message: '请输入回调地址', + }, + ], + initialValue: detail.webServerRedirectUri, + })()} + + + + + {getFieldDecorator('resourceIds', { + initialValue: detail.resourceIds, + })()} + + + + + + + {getFieldDecorator('authorities', { + initialValue: detail.authorities, + })()} + + + + + {getFieldDecorator('autoapprove', { + initialValue: detail.autoapprove, + })()} + + + + + + + {getFieldDecorator('additionalInformation', { + initialValue: detail.additionalInformation, + })()} + + + + +
+
+ ); + } +} + +export default ClientEdit; diff --git a/src/pages/System/Client/ClientView.js b/src/pages/System/Client/ClientView.js new file mode 100644 index 0000000..c53bc66 --- /dev/null +++ b/src/pages/System/Client/ClientView.js @@ -0,0 +1,101 @@ +import React, { PureComponent } from 'react'; +import router from 'umi/router'; +import { Form, Card, Button } from 'antd'; +import { connect } from 'dva'; +import Panel from '../../../components/Panel'; +import styles from '../../../layouts/Sword.less'; +import { CLIENT_DETAIL } from '../../../actions/client'; + +const FormItem = Form.Item; + +@connect(({ client }) => ({ + client, +})) +@Form.create() +class ClientView extends PureComponent { + componentWillMount() { + const { + dispatch, + match: { + params: { id }, + }, + } = this.props; + dispatch(CLIENT_DETAIL(id)); + } + + handleEdit = () => { + const { + match: { + params: { id }, + }, + } = this.props; + router.push(`/system/client/edit/${id}`); + }; + + render() { + const { + client: { detail }, + } = this.props; + + const formItemLayout = { + labelCol: { + xs: { span: 24 }, + sm: { span: 7 }, + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 12 }, + md: { span: 10 }, + }, + }; + + const action = ( + + ); + + return ( + +
+ + + {detail.clientId} + + + {detail.clientSecret} + + + {detail.resourceIds} + + + {detail.scope} + + + {detail.authorizedGrantTypes} + + + {detail.accessTokenValidity} + + + {detail.refreshTokenValidity} + + + {detail.webServerRedirectUri} + + + {detail.authorities} + + + {detail.additionalInformation} + + + {detail.autoapprove} + + +
+
+ ); + } +} +export default ClientView; diff --git a/src/services/client.js b/src/services/client.js new file mode 100644 index 0000000..5f9ca3d --- /dev/null +++ b/src/services/client.js @@ -0,0 +1,25 @@ +import { stringify } from 'qs'; +import func from '../utils/Func'; +import request from '../utils/request'; + +export async function list(params) { + return request(`/api/blade-system/client/list?${stringify(params)}`); +} + +export async function submit(params) { + return request('/api/blade-system/client/submit', { + method: 'POST', + body: params, + }); +} + +export async function detail(params) { + return request(`/api/blade-system/client/detail?${stringify(params)}`); +} + +export async function remove(params) { + return request('/api/blade-system/client/remove', { + method: 'POST', + body: func.toFormData(params), + }); +} diff --git a/src/utils/request.js b/src/utils/request.js index 78df691..87ab3de 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -2,6 +2,8 @@ import fetch from 'dva/fetch'; import { notification } from 'antd'; import router from 'umi/router'; import hash from 'hash.js'; +import { Base64 } from 'js-base64'; +import { clientId, clientSecret } from '../defaultSettings'; import { getToken, removeAll } from './authority'; const codeMessage = { @@ -113,12 +115,18 @@ export default function request(url, option) { }; const newOptions = { ...defaultOptions, ...options }; - // 将Token放至请求头 + newOptions.headers = { + ...newOptions.headers, + // 客户端认证 + Authorization: `Basic ${Base64.encode(`${clientId}:${clientSecret}`)}`, + }; + const token = getToken(); if (token) { newOptions.headers = { ...newOptions.headers, - 'blade-auth': token, + // token鉴权 + 'Blade-Auth': token, }; }