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 (
+
+
+
+ );
+ }
+}
+
+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 (
+
+
+
+ );
+ }
+}
+
+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 (
+
+
+
+ );
+ }
+}
+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,
};
}