🎉 2.7.0.RELEASE,内核全面升级,增加岗位管理,用户导入导出

This commit is contained in:
smallchill 2020-04-24 18:02:54 +08:00
parent 471c659f38
commit 50cbbe6524
29 changed files with 1004 additions and 30 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 bladex.vip Copyright (c) 2020 BladeX (https://bladex.vip)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,9 +1,9 @@
<p align="center"> <p align="center">
<img src="https://img.shields.io/badge/Release-V2.6.2-green.svg" alt="Downloads"> <img src="https://img.shields.io/badge/Release-V2.7.0-green.svg" alt="Downloads">
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status"> <img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status">
<img src="https://img.shields.io/badge/license-Apache%202-blue.svg" alt="Build Status"> <img src="https://img.shields.io/badge/license-Apache%202-blue.svg" alt="Build Status">
<img src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR2-blue.svg" alt="Coverage Status"> <img src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR3-blue.svg" alt="Coverage Status">
<img src="https://img.shields.io/badge/Spring%20Boot-2.2.5.RELEASE-blue.svg" alt="Downloads"> <img src="https://img.shields.io/badge/Spring%20Boot-2.2.6.RELEASE-blue.svg" alt="Downloads">
<a target="_blank" href="https://bladex.vip"> <a target="_blank" href="https://bladex.vip">
<img src="https://img.shields.io/badge/Author-Small%20Chill-ff69b4.svg" alt="Downloads"> <img src="https://img.shields.io/badge/Author-Small%20Chill-ff69b4.svg" alt="Downloads">
</a> </a>
@ -64,6 +64,7 @@ SpringBlade
* Saber-基于Vue[https://saber.bladex.vip](https://saber.bladex.vip) * Saber-基于Vue[https://saber.bladex.vip](https://saber.bladex.vip)
* Sword-基于React[https://sword.bladex.vip](https://sword.bladex.vip) * Sword-基于React[https://sword.bladex.vip](https://sword.bladex.vip)
* Archer-全能代码生成系统:[https://archer.bladex.vip](https://archer.bladex.vip) * Archer-全能代码生成系统:[https://archer.bladex.vip](https://archer.bladex.vip)
* Caster-数据大屏展示系统:[https://data.avuejs.com](https://data.avuejs.com)
## 技术文档 ## 技术文档
* [开发手册一览](https://gitee.com/smallc/SpringBlade/wikis/SpringBlade开发手册) * [开发手册一览](https://gitee.com/smallc/SpringBlade/wikis/SpringBlade开发手册)

View File

@ -125,6 +125,17 @@ export default [
{ path: '/system/dept/view/:id', component: './System/Dept/DeptView' }, { path: '/system/dept/view/:id', component: './System/Dept/DeptView' },
], ],
}, },
{
path: '/system/post',
routes: [
{ path: '/system/post', redirect: '/system/post/list' },
{ path: '/system/post/list', component: './System/Post/Post' },
{ path: '/system/post/add', component: './System/Post/PostAdd' },
{ path: '/system/post/add/:id', component: './System/Post/PostAdd' },
{ path: '/system/post/edit/:id', component: './System/Post/PostEdit' },
{ path: '/system/post/view/:id', component: './System/Post/PostView' },
],
},
{ {
path: '/system/role', path: '/system/role',
routes: [ routes: [

View File

@ -46,6 +46,7 @@ function getFakeDetail(req, res) {
id: '1', id: '1',
tenantId: '000000', tenantId: '000000',
account: 'admin', account: 'admin',
code: 'admin',
name: '超级管理员', name: '超级管理员',
realName: '管理员', realName: '管理员',
phone: '13888888888', phone: '13888888888',
@ -54,6 +55,8 @@ function getFakeDetail(req, res) {
roleName: '超级管理员', roleName: '超级管理员',
deptId: 1, deptId: 1,
deptName: '刀锋科技', deptName: '刀锋科技',
postId: 1,
postName: '首席执行官',
sex: 1, sex: 1,
sexName: '男', sexName: '男',
birthday: '2018-12-31 23:33:33', birthday: '2018-12-31 23:33:33',
@ -61,7 +64,6 @@ function getFakeDetail(req, res) {
}; };
return res.json(json); return res.json(json);
} }
function fakeSuccess(req, res) { function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' }; const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json); return res.json(json);

View File

@ -1,6 +1,6 @@
{ {
"name": "sword", "name": "sword",
"version": "2.6.2", "version": "2.7.0",
"description": "An out-of-box UI solution for enterprise applications", "description": "An out-of-box UI solution for enterprise applications",
"private": true, "private": true,
"scripts": { "scripts": {

43
src/actions/post.js Normal file
View File

@ -0,0 +1,43 @@
export const POST_NAMESPACE = 'post';
export function POST_INIT() {
return {
type: `${POST_NAMESPACE}/fetchInit`,
payload: { code: 'post_category' },
};
}
export function POST_LIST(payload) {
return {
type: `${POST_NAMESPACE}/fetchList`,
payload,
};
}
export function POST_DETAIL(id) {
return {
type: `${POST_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function POST_CLEAR_DETAIL() {
return {
type: `${POST_NAMESPACE}/clearDetail`,
payload: {},
};
}
export function POST_SUBMIT(payload) {
return {
type: `${POST_NAMESPACE}/submit`,
payload,
};
}
export function POST_REMOVE(payload) {
return {
type: `${POST_NAMESPACE}/remove`,
payload,
};
}

View File

@ -30,9 +30,7 @@ export default class ToolBar extends PureComponent {
</Button> </Button>
))} ))}
{renderLeftButton ? renderLeftButton() : null} {renderLeftButton ? renderLeftButton() : null}
{renderRightButton ? ( {renderRightButton ? <div style={{ float: 'right' }}>{renderRightButton()}</div> : null}
<div style={{ float: 'right', marginRight: '20px' }}>{renderRightButton()}</div>
) : null}
</div> </div>
</div> </div>
); );

View File

@ -3,6 +3,7 @@ module.exports = {
clientId: 'sword', // 客户端id clientId: 'sword', // 客户端id
clientSecret: 'sword_secret', // 客户端密钥 clientSecret: 'sword_secret', // 客户端密钥
tenantMode: true, // 开启租户模式 tenantMode: true, // 开启租户模式
captchaMode: true, // 开启验证码模式
navTheme: 'dark', // theme for nav menu navTheme: 'dark', // theme for nav menu
primaryColor: '#1890FF', // primary color of ant design primaryColor: '#1890FF', // primary color of ant design
layout: 'sidemenu', // nav menu position: sidemenu or topmenu layout: 'sidemenu', // nav menu position: sidemenu or topmenu

View File

@ -8,7 +8,7 @@ const FooterView = () => (
<GlobalFooter <GlobalFooter
copyright={ copyright={
<Fragment> <Fragment>
Copyright <Icon type="copyright" /> 2019 SpringBlade{' '} Copyright <Icon type="copyright" /> 2020 SpringBlade{' '}
<a <a
key="github" key="github"
title="git" title="git"

View File

@ -27,7 +27,7 @@ const links = [
const copyright = ( const copyright = (
<Fragment> <Fragment>
Copyright <Icon type="copyright" /> 2019 SpringBlade{' '} Copyright <Icon type="copyright" /> 2020 SpringBlade{' '}
<a <a
key="github" key="github"
title="git" title="git"

View File

@ -9,6 +9,7 @@ export default {
'menu.system': 'system', 'menu.system': 'system',
'menu.system.user': 'user', 'menu.system.user': 'user',
'menu.system.dept': 'department', 'menu.system.dept': 'department',
'menu.system.post': 'post',
'menu.system.dict': 'dictionary', 'menu.system.dict': 'dictionary',
'menu.system.menu': 'menu', 'menu.system.menu': 'menu',
'menu.system.role': 'role', 'menu.system.role': 'role',

View File

@ -9,6 +9,7 @@ export default {
'menu.system': '系统管理', 'menu.system': '系统管理',
'menu.system.user': '用户管理', 'menu.system.user': '用户管理',
'menu.system.dept': '部门管理', 'menu.system.dept': '部门管理',
'menu.system.post': '岗位管理',
'menu.system.dict': '字典管理', 'menu.system.dict': '字典管理',
'menu.system.menu': '菜单管理', 'menu.system.menu': '菜单管理',
'menu.system.role': '角色管理', 'menu.system.role': '角色管理',

View File

@ -9,6 +9,7 @@ export default {
'menu.system': '系統管理', 'menu.system': '系統管理',
'menu.system.user': '用戶管理', 'menu.system.user': '用戶管理',
'menu.system.dept': '部門管理', 'menu.system.dept': '部門管理',
'menu.system.post': '崗位管理',
'menu.system.dict': '字典管理', 'menu.system.dict': '字典管理',
'menu.system.menu': '菜單管理', 'menu.system.menu': '菜單管理',
'menu.system.role': '角色管理', 'menu.system.role': '角色管理',

View File

@ -5,6 +5,7 @@ import { accountLogin } from '../services/user';
import { dynamicRoutes, dynamicButtons } from '../services/menu'; import { dynamicRoutes, dynamicButtons } from '../services/menu';
import { import {
setAuthority, setAuthority,
setAccessToken,
setToken, setToken,
setCurrentUser, setCurrentUser,
setRoutes, setRoutes,
@ -101,6 +102,7 @@ export default {
} = payload; } = payload;
const token = `${tokenType} ${accessToken}`; const token = `${tokenType} ${accessToken}`;
setToken(token); setToken(token);
setAccessToken(accessToken);
setAuthority(authority); setAuthority(authority);
setCurrentUser({ avatar, account, name: userName, authority }); setCurrentUser({ avatar, account, name: userName, authority });
} else { } else {

108
src/models/post.js Normal file
View File

@ -0,0 +1,108 @@
import { message } from 'antd';
import router from 'umi/router';
import { POST_NAMESPACE } from '../actions/post';
import { dict } from '../services/dict';
import { list, submit, detail, remove } from '../services/post';
export default {
namespace: POST_NAMESPACE,
state: {
data: {
list: [],
pagination: false,
},
init: {
category: [],
},
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,
},
},
});
}
},
*fetchInit({ payload }, { call, put }) {
const response = yield call(dict, payload);
if (response.success) {
yield put({
type: 'saveInit',
payload: {
category: response.data,
},
});
}
},
*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/post');
}
},
*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,
};
},
saveInit(state, action) {
return {
...state,
init: action.payload,
};
},
saveDetail(state, action) {
return {
...state,
detail: action.payload.detail,
};
},
removeDetail(state) {
return {
...state,
detail: {},
};
},
},
};

View File

@ -5,6 +5,7 @@ import { query as queryUsers, list, submit, update, detail, remove, grant } from
import { select as tenants } from '../services/tenant'; import { select as tenants } from '../services/tenant';
import { tree as roles } from '../services/role'; import { tree as roles } from '../services/role';
import { tree as depts } from '../services/dept'; import { tree as depts } from '../services/dept';
import { select as posts } from '../services/post';
import { getCurrentUser } from '../utils/authority'; import { getCurrentUser } from '../utils/authority';
export default { export default {
@ -20,6 +21,7 @@ export default {
init: { init: {
roleTree: [], roleTree: [],
deptTree: [], deptTree: [],
postList: [],
tenantList: [], tenantList: [],
}, },
detail: {}, detail: {},
@ -59,13 +61,20 @@ export default {
*fetchInit({ payload }, { call, put }) { *fetchInit({ payload }, { call, put }) {
const responseRole = yield call(roles, payload); const responseRole = yield call(roles, payload);
const responseDept = yield call(depts, payload); const responseDept = yield call(depts, payload);
const responsePost = yield call(posts, payload);
const responseTenant = yield call(tenants, payload); const responseTenant = yield call(tenants, payload);
if (responseRole.success && responseDept.success && responseTenant.success) { if (
responseRole.success &&
responseDept.success &&
responsePost.success &&
responseTenant.success
) {
yield put({ yield put({
type: 'saveInit', type: 'saveInit',
payload: { payload: {
roleTree: responseRole.data, roleTree: responseRole.data,
deptTree: responseDept.data, deptTree: responseDept.data,
postList: responsePost.data,
tenantList: responseTenant.data, tenantList: responseTenant.data,
}, },
}); });
@ -74,12 +83,14 @@ export default {
*fetchChangeInit({ payload }, { call, put }) { *fetchChangeInit({ payload }, { call, put }) {
const responseRole = yield call(roles, payload); const responseRole = yield call(roles, payload);
const responseDept = yield call(depts, payload); const responseDept = yield call(depts, payload);
if (responseRole.success && responseDept.success) { const responsePost = yield call(posts, payload);
if (responseRole.success && responseDept.success && responsePost.success) {
yield put({ yield put({
type: 'saveChangeInit', type: 'saveChangeInit',
payload: { payload: {
roleTree: responseRole.data, roleTree: responseRole.data,
deptTree: responseDept.data, deptTree: responseDept.data,
postList: responsePost.data,
}, },
}); });
} }
@ -174,6 +185,7 @@ export default {
const newState = state; const newState = state;
newState.init.roleTree = action.payload.roleTree; newState.init.roleTree = action.payload.roleTree;
newState.init.deptTree = action.payload.deptTree; newState.init.deptTree = action.payload.deptTree;
newState.init.postList = action.payload.postList;
return { return {
...newState, ...newState,
}; };

View File

@ -14,14 +14,14 @@ class Workplace extends PureComponent {
<Row gutter={24}> <Row gutter={24}>
<Col span={24}> <Col span={24}>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<img src="https://img.shields.io/badge/Release-V2.6.2-green.svg" alt="Downloads" /> <img src="https://img.shields.io/badge/Release-V2.7.0-green.svg" alt="Downloads" />
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status" /> <img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status" />
<img <img
src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR2-blue.svg" src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR3-blue.svg"
alt="Coverage Status" alt="Coverage Status"
/> />
<img <img
src="https://img.shields.io/badge/Spring%20Boot-2.2.5.RELEASE-blue.svg" src="https://img.shields.io/badge/Spring%20Boot-2.2.6.RELEASE-blue.svg"
alt="Downloads" alt="Downloads"
/> />
<a href="https://bladex.vip"> <a href="https://bladex.vip">
@ -147,7 +147,7 @@ class Workplace extends PureComponent {
<div> <div>
2.接3个月以内工期的reactvuespringbootspringcloudapp小程序等软件定制服务 2.接3个月以内工期的reactvuespringbootspringcloudapp小程序等软件定制服务
</div> </div>
<div>3.有意向请联系唯一指定QQ:85088620</div> <div>3.有意向请联系唯一指定QQ:1272154962</div>
</Panel> </Panel>
</Collapse> </Collapse>
</Card> </Card>
@ -206,7 +206,19 @@ class Workplace extends PureComponent {
</Row> </Row>
<Row gutter={24}> <Row gutter={24}>
<Card className={styles.card} bordered={false}> <Card className={styles.card} bordered={false}>
<Collapse bordered={false} defaultActiveKey={['16']}> <Collapse bordered={false} defaultActiveKey={['17']}>
<Panel header="2.7.0发布 内核全面升级,增加岗位管理,用户导入导出" key="17">
<div>1.升级至 SpringCloud Hoxton.SR3</div>
<div>2.升级至 SpringBoot 2.2.6.RELEASE</div>
<div>3.升级至 Avue 2.5.0</div>
<div>4.升级Saber内核采用最新版本API优化交互体验</div>
<div>5.新增岗位管理模块</div>
<div>6.新增用户导入导出模块</div>
<div>7.数据库主键统一改成bigint并采用snowflake算法</div>
<div>8.优化INode类主键跟随修改为Long类型</div>
<div>9.优化鉴权逻辑支持header以及parameter两种方式</div>
<div>10.优化代码生成模板以支持最新版API</div>
</Panel>
<Panel header="2.6.1发布 增加登陆验证码支持seata1.0" key="16"> <Panel header="2.6.1发布 增加登陆验证码支持seata1.0" key="16">
<div>1.升级SpringBoot 2.2.5.RELEASE</div> <div>1.升级SpringBoot 2.2.5.RELEASE</div>
<div>2.升级SpringCloud Hoxton.SR2</div> <div>2.升级SpringCloud Hoxton.SR2</div>

View File

@ -4,7 +4,7 @@ import { formatMessage, FormattedMessage } from 'umi/locale';
import { Checkbox, Alert } from 'antd'; import { Checkbox, Alert } from 'antd';
import Login from '../../components/Login'; import Login from '../../components/Login';
import styles from './Login.less'; import styles from './Login.less';
import { tenantMode } from '../../defaultSettings'; import { tenantMode, captchaMode } from '../../defaultSettings';
const { Tab, TenantId, UserName, Password, Captcha, Submit } = Login; const { Tab, TenantId, UserName, Password, Captcha, Submit } = Login;
@ -117,7 +117,7 @@ class LoginPage extends Component {
]} ]}
onPressEnter={() => this.loginForm.validateFields(this.handleSubmit)} onPressEnter={() => this.loginForm.validateFields(this.handleSubmit)}
/> />
<Captcha name="code" mode="image" /> {captchaMode ? <Captcha name="code" mode="image" /> : null}
</Tab> </Tab>
<div> <div>
<Checkbox checked={autoLogin} onChange={this.changeAutoLogin}> <Checkbox checked={autoLogin} onChange={this.changeAutoLogin}>

View File

@ -0,0 +1,102 @@
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { Button, Col, Form, Input, Row, Tag } from 'antd';
import Panel from '../../../components/Panel';
import { POST_LIST } from '../../../actions/post';
import Grid from '../../../components/Sword/Grid';
const FormItem = Form.Item;
@connect(({ post, loading }) => ({
post,
loading: loading.models.post,
}))
@Form.create()
class Post extends PureComponent {
// ============ 查询 ===============
handleSearch = params => {
const { dispatch } = this.props;
dispatch(POST_LIST(params));
};
// ============ 查询表单 ===============
renderSearchForm = onReset => {
const { form } = this.props;
const { getFieldDecorator } = form;
return (
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={6} sm={24}>
<FormItem label="查询名称">
{getFieldDecorator('name')(<Input placeholder="查询名称" />)}
</FormItem>
</Col>
<Col>
<div style={{ float: 'right' }}>
<Button type="primary" htmlType="submit">
查询
</Button>
<Button style={{ marginLeft: 8 }} onClick={onReset}>
重置
</Button>
</div>
</Col>
</Row>
);
};
render() {
const code = 'post';
const {
form,
loading,
post: { data },
} = this.props;
const columns = [
{
title: '租户ID',
dataIndex: 'tenantId',
},
{
title: '岗位类型',
dataIndex: 'categoryName',
render: categoryName => (
<span>
<Tag color="geekblue" key={categoryName}>
{categoryName}
</Tag>
</span>
),
},
{
title: '岗位编号',
dataIndex: 'postCode',
},
{
title: '岗位名称',
dataIndex: 'postName',
},
{
title: '岗位排序',
dataIndex: 'sort',
},
];
return (
<Panel>
<Grid
code={code}
form={form}
onSearch={this.handleSearch}
renderSearchForm={this.renderSearchForm}
loading={loading}
data={data}
columns={columns}
/>
</Panel>
);
}
}
export default Post;

View File

@ -0,0 +1,152 @@
import React, { PureComponent } from 'react';
import { Form, Input, Card, Button, InputNumber, Col, Row, Select } from 'antd';
import { connect } from 'dva';
import Panel from '../../../components/Panel';
import styles from '../../../layouts/Sword.less';
import { POST_INIT, POST_SUBMIT } from '../../../actions/post';
const FormItem = Form.Item;
const { TextArea } = Input;
@connect(({ post, loading }) => ({
post,
submitting: loading.effects['post/submit'],
}))
@Form.create()
class PostAdd extends PureComponent {
componentWillMount() {
const { dispatch } = this.props;
dispatch(POST_INIT());
}
handleSubmit = e => {
e.preventDefault();
const { dispatch, form } = this.props;
form.validateFieldsAndScroll((err, values) => {
if (!err) {
dispatch(POST_SUBMIT(values));
}
});
};
render() {
const {
form: { getFieldDecorator },
post: { init },
submitting,
} = this.props;
const { category } = init;
const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
const formAllItemLayout = {
labelCol: {
span: 4,
},
wrapperCol: {
span: 20,
},
};
const action = (
<Button type="primary" onClick={this.handleSubmit} loading={submitting}>
提交
</Button>
);
return (
<Panel title="新增" back="/system/post" action={action}>
<Form hideRequiredMark style={{ marginTop: 8 }}>
<Card title="基本信息" className={styles.card} bordered={false}>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="岗位类型">
{getFieldDecorator('category', {
rules: [
{
required: true,
message: '请选择岗位类型',
},
],
})(
<Select placeholder="请选择机构类型">
{category.map(d => (
<Select.Option key={d.dictKey} value={d.dictKey}>
{d.dictValue}
</Select.Option>
))}
</Select>
)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} label="岗位编号">
{getFieldDecorator('postCode', {
rules: [
{
required: true,
message: '请输入岗位编号',
},
],
})(<Input placeholder="请输入岗位编号" />)}
</FormItem>
</Col>
</Row>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="岗位名称">
{getFieldDecorator('postName', {
rules: [
{
required: true,
message: '请输入岗位名称',
},
],
})(<Input placeholder="请输入岗位名称" />)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} className={styles.inputItem} label="岗位排序">
{getFieldDecorator('sort', {
rules: [
{
required: true,
message: '请输入岗位排序',
},
],
})(<InputNumber placeholder="请输入岗位排序" />)}
</FormItem>
</Col>
</Row>
</Card>
<Card title="其他信息" className={styles.card} bordered={false}>
<Row gutter={24}>
<Col span={20}>
<FormItem {...formAllItemLayout} label="岗位描述">
{getFieldDecorator('remark', {
rules: [
{
required: true,
message: '请输入岗位描述',
},
],
})(<TextArea rows={4} placeholder="请输入岗位描述" />)}
</FormItem>
</Col>
</Row>
</Card>
</Form>
</Panel>
);
}
}
export default PostAdd;

View File

@ -0,0 +1,174 @@
import React, { PureComponent } from 'react';
import { Form, Input, Card, Button, InputNumber, Row, Col, Select } from 'antd';
import { connect } from 'dva';
import Panel from '../../../components/Panel';
import styles from '../../../layouts/Sword.less';
import { POST_DETAIL, POST_INIT, POST_SUBMIT } from '../../../actions/post';
const FormItem = Form.Item;
const { TextArea } = Input;
@connect(({ post, loading }) => ({
post,
submitting: loading.effects['post/submit'],
}))
@Form.create()
class PostEdit extends PureComponent {
componentWillMount() {
const {
dispatch,
match: {
params: { id },
},
} = this.props;
dispatch(POST_DETAIL(id));
dispatch(POST_INIT());
}
handleSubmit = e => {
e.preventDefault();
const {
dispatch,
match: {
params: { id },
},
form,
} = this.props;
form.validateFieldsAndScroll((err, values) => {
if (!err) {
const params = {
id,
...values,
};
console.log(params);
dispatch(POST_SUBMIT(params));
}
});
};
render() {
const {
form: { getFieldDecorator },
post: { detail, init },
submitting,
} = this.props;
const { category } = init;
const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
const formAllItemLayout = {
labelCol: {
span: 4,
},
wrapperCol: {
span: 20,
},
};
const action = (
<Button type="primary" onClick={this.handleSubmit} loading={submitting}>
提交
</Button>
);
return (
<Panel title="修改" back="/system/post" action={action}>
<Form hideRequiredMark style={{ marginTop: 8 }}>
<Card title="基本信息" className={styles.card} bordered={false}>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="岗位类型">
{getFieldDecorator('category', {
rules: [
{
required: true,
message: '请选择岗位类型',
},
],
initialValue: detail.category,
})(
<Select placeholder="请选择岗位类型">
{category.map(d => (
<Select.Option key={d.dictKey} value={d.dictKey}>
{d.dictValue}
</Select.Option>
))}
</Select>
)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} label="岗位编号">
{getFieldDecorator('postCode', {
rules: [
{
required: true,
message: '请输入岗位编号',
},
],
initialValue: detail.postCode,
})(<Input placeholder="请输入岗位编号" />)}
</FormItem>
</Col>
</Row>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="岗位名称">
{getFieldDecorator('postName', {
rules: [
{
required: true,
message: '请输入岗位名称',
},
],
initialValue: detail.postName,
})(<Input placeholder="请输入岗位名称" />)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} className={styles.inputItem} label="岗位排序">
{getFieldDecorator('sort', {
rules: [
{
required: true,
message: '请输入岗位排序',
},
],
initialValue: detail.sort,
})(<InputNumber placeholder="请输入岗位排序" />)}
</FormItem>
</Col>
</Row>
</Card>
<Card title="其他信息" className={styles.card} bordered={false}>
<Row gutter={24}>
<Col span={20}>
<FormItem {...formAllItemLayout} label="岗位描述">
{getFieldDecorator('remark', {
rules: [
{
required: true,
message: '请输入岗位描述',
},
],
initialValue: detail.remark,
})(<TextArea rows={4} placeholder="请输入岗位描述" />)}
</FormItem>
</Col>
</Row>
</Card>
</Form>
</Panel>
);
}
}
export default PostEdit;

View File

@ -0,0 +1,85 @@
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 { POST_DETAIL } from '../../../actions/post';
const FormItem = Form.Item;
@connect(({ post }) => ({
post,
}))
@Form.create()
class PostView extends PureComponent {
componentWillMount() {
const {
dispatch,
match: {
params: { id },
},
} = this.props;
dispatch(POST_DETAIL(id));
}
handleEdit = () => {
const {
match: {
params: { id },
},
} = this.props;
router.push(`/system/post/edit/${id}`);
};
render() {
const {
post: { detail },
} = this.props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 7 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
md: { span: 10 },
},
};
const action = (
<Button type="primary" onClick={this.handleEdit}>
修改
</Button>
);
return (
<Panel title="查看" back="/system/post" action={action}>
<Form hideRequiredMark style={{ marginTop: 8 }}>
<Card title="基本信息" className={styles.card} bordered={false}>
<FormItem {...formItemLayout} label="岗位类型">
<span>{detail.categoryName}</span>
</FormItem>
<FormItem {...formItemLayout} label="岗位编号">
<span>{detail.postCode}</span>
</FormItem>
<FormItem {...formItemLayout} label="岗位名称">
<span>{detail.postName}</span>
</FormItem>
<FormItem {...formItemLayout} label="岗位排序">
<span>{detail.sort}</span>
</FormItem>
</Card>
<Card title="其他信息" className={styles.card} bordered={false}>
<FormItem {...formItemLayout} label="岗位描述">
<span>{detail.remark}</span>
</FormItem>
</Card>
</Form>
</Panel>
);
}
}
export default PostView;

View File

@ -1,14 +1,27 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { connect } from 'dva'; import { connect } from 'dva';
import { Button, Col, Form, Input, message, Modal, Row, Tree } from 'antd'; import {
Upload,
Icon,
Button,
Col,
Form,
Input,
message,
Modal,
Row,
Tree,
} from 'antd';
import Panel from '../../../components/Panel'; import Panel from '../../../components/Panel';
import Grid from '../../../components/Sword/Grid'; import Grid from '../../../components/Sword/Grid';
import { USER_INIT, USER_LIST, USER_ROLE_GRANT } from '../../../actions/user'; import { USER_INIT, USER_LIST, USER_ROLE_GRANT } from '../../../actions/user';
import { resetPassword } from '../../../services/user'; import { resetPassword } from '../../../services/user';
import { tenantMode } from '../../../defaultSettings'; import { tenantMode } from '../../../defaultSettings';
import { getAccessToken, getToken } from '../../../utils/authority';
const FormItem = Form.Item; const FormItem = Form.Item;
const { TreeNode } = Tree; const { TreeNode } = Tree;
const { Dragger } = Upload;
@connect(({ user, loading }) => ({ @connect(({ user, loading }) => ({
user, user,
@ -18,9 +31,12 @@ const { TreeNode } = Tree;
class User extends PureComponent { class User extends PureComponent {
state = { state = {
visible: false, visible: false,
excelVisible: false,
confirmLoading: false, confirmLoading: false,
selectedRows: [], selectedRows: [],
checkedTreeKeys: [], checkedTreeKeys: [],
params: {},
onReset: () => {},
}; };
componentWillMount() { componentWillMount() {
@ -41,6 +57,7 @@ class User extends PureComponent {
// ============ 查询 =============== // ============ 查询 ===============
handleSearch = params => { handleSearch = params => {
this.setState({ params });
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(USER_LIST(params)); dispatch(USER_LIST(params));
}; };
@ -133,6 +150,10 @@ class User extends PureComponent {
const { form } = this.props; const { form } = this.props;
const { getFieldDecorator } = form; const { getFieldDecorator } = form;
this.setState({
onReset,
});
return ( return (
<Row gutter={{ md: 8, lg: 24, xl: 48 }}> <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={6} sm={24}> <Col md={6} sm={24}>
@ -159,10 +180,80 @@ class User extends PureComponent {
); );
}; };
onClickReset = () => {
const { onReset } = this.state;
onReset();
};
handleImport = () => {
this.setState({
excelVisible: true,
});
};
handleExcelImport = () =>
this.setState({
excelVisible: false,
});
handleExcelCancel = () =>
this.setState({
excelVisible: false,
});
handleExport = () => {
const { params } = this.state;
Modal.confirm({
title: '用户导出确认',
content: '是否导出用户数据?',
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk() {
const account = params.account || '';
const realName = params.realName || '';
window.open(
`/api/blade-user/export-user?blade-auth=${getAccessToken()}&account=${account}&realName=${realName}`
);
},
onCancel() {},
});
};
handleTemplate = () => {
window.open(`/api/blade-user/export-template?blade-auth=${getAccessToken()}`);
};
onUpload = info => {
const { status } = info.file;
if (status !== 'uploading') {
window.console.log(info.file, info.fileList);
}
if (status === 'done') {
message.success(`${info.file.name} 数据导入成功!`);
this.handleExcelCancel();
this.onClickReset();
} else if (status === 'error') {
message.error(`${info.file.response.msg}`);
}
};
renderRightButton = () => (
<div>
<Button icon="vertical-align-bottom" onClick={this.handleImport}>
导入
</Button>
<Button icon="vertical-align-top" onClick={this.handleExport} style={{ marginRight: 0 }}>
导出
</Button>
</div>
);
render() { render() {
const code = 'user'; const code = 'user';
const { visible, confirmLoading, checkedTreeKeys } = this.state; const { visible, excelVisible, confirmLoading, checkedTreeKeys } = this.state;
const { const {
form, form,
@ -173,6 +264,26 @@ class User extends PureComponent {
}, },
} = this.props; } = this.props;
const uploadProps = {
name: 'file',
headers: {
'Blade-Auth': getToken(),
},
action: "/api/blade-user/import-user",
};
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
md: { span: 16 },
},
};
const columns = [ const columns = [
{ {
title: '租户ID', title: '租户ID',
@ -220,6 +331,7 @@ class User extends PureComponent {
onSearch={this.handleSearch} onSearch={this.handleSearch}
onSelectRow={this.onSelectRow} onSelectRow={this.onSelectRow}
renderSearchForm={this.renderSearchForm} renderSearchForm={this.renderSearchForm}
renderRightButton={this.renderRightButton}
btnCallBack={this.handleBtnCallBack} btnCallBack={this.handleBtnCallBack}
loading={loading} loading={loading}
data={data} data={data}
@ -239,6 +351,33 @@ class User extends PureComponent {
{this.renderTreeNodes(roleTree)} {this.renderTreeNodes(roleTree)}
</Tree> </Tree>
</Modal> </Modal>
<Modal
title="用户数据导入"
width={500}
visible={excelVisible}
confirmLoading={confirmLoading}
onOk={this.handleExcelImport}
onCancel={this.handleExcelCancel}
okText="确认"
cancelText="取消"
>
<Form style={{ marginTop: 8 }} hideRequiredMark>
<FormItem {...formItemLayout} label="模板上传">
<Dragger {...uploadProps} onChange={this.onUpload}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">将文件拖到此处或点击上传</p>
<p className="ant-upload-hint">请上传 .xls,.xlsx 格式的文件</p>
</Dragger>
</FormItem>
<FormItem {...formItemLayout} label="模板下载">
<Button type="primary" icon="download" size="small" onClick={this.handleTemplate}>
点击下载
</Button>
</FormItem>
</Form>
</Modal>
</Panel> </Panel>
); );
} }

View File

@ -35,6 +35,7 @@ class UserAdd extends PureComponent {
...values, ...values,
roleId: func.join(values.roleId), roleId: func.join(values.roleId),
deptId: func.join(values.deptId), deptId: func.join(values.deptId),
postId: func.join(values.postId),
birthday: func.format(values.birthday), birthday: func.format(values.birthday),
}; };
dispatch(USER_SUBMIT(params)); dispatch(USER_SUBMIT(params));
@ -45,7 +46,7 @@ class UserAdd extends PureComponent {
handleChange = value => { handleChange = value => {
const { dispatch, form } = this.props; const { dispatch, form } = this.props;
form.resetFields(['roleId', 'deptId']); form.resetFields(['roleId', 'deptId', 'postId']);
dispatch(USER_CHANGE_INIT({ tenantId: value })); dispatch(USER_CHANGE_INIT({ tenantId: value }));
}; };
@ -53,7 +54,7 @@ class UserAdd extends PureComponent {
const { const {
form: { getFieldDecorator }, form: { getFieldDecorator },
user: { user: {
init: { roleTree, deptTree, tenantList }, init: { roleTree, deptTree, postList, tenantList },
}, },
submitting, submitting,
} = this.props; } = this.props;
@ -230,6 +231,41 @@ class UserAdd extends PureComponent {
</FormItem> </FormItem>
</Col> </Col>
</Row> </Row>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="用户编号">
{getFieldDecorator('code', {})(<Input placeholder="请输入用户编号" />)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} label="所属岗位">
{getFieldDecorator('postId', {
rules: [
{
required: true,
message: '请选择所属岗位',
},
],
})(
<Select
mode="multiple"
showSearch
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
allowClear
placeholder="请选择所属岗位"
>
{postList.map(d => (
<Select.Option key={d.id} value={d.id}>
{d.postName}
</Select.Option>
))}
</Select>
)}
</FormItem>
</Col>
</Row>
<Row gutter={24}> <Row gutter={24}>
<Col span={10}> <Col span={10}>
<FormItem {...formItemLayout} label="手机号码"> <FormItem {...formItemLayout} label="手机号码">

View File

@ -5,7 +5,7 @@ import { connect } from 'dva';
import Panel from '../../../components/Panel'; import Panel from '../../../components/Panel';
import func from '../../../utils/Func'; import func from '../../../utils/Func';
import styles from '../../../layouts/Sword.less'; import styles from '../../../layouts/Sword.less';
import { USER_CHANGE_INIT, USER_DETAIL, USER_INIT, USER_UPDATE } from '../../../actions/user'; import { USER_CHANGE_INIT, USER_DETAIL, USER_UPDATE } from '../../../actions/user';
import { tenantMode } from '../../../defaultSettings'; import { tenantMode } from '../../../defaultSettings';
const FormItem = Form.Item; const FormItem = Form.Item;
@ -23,8 +23,12 @@ class UserEdit extends PureComponent {
params: { id }, params: { id },
}, },
} = this.props; } = this.props;
dispatch(USER_DETAIL(id)); dispatch(USER_DETAIL(id)).then(()=>{
dispatch(USER_INIT()); const {
user: { detail },
} = this.props;
dispatch(USER_CHANGE_INIT({ tenantId: detail.tenantId }));
});
} }
handleSubmit = e => { handleSubmit = e => {
@ -43,6 +47,7 @@ class UserEdit extends PureComponent {
...values, ...values,
roleId: func.join(values.roleId), roleId: func.join(values.roleId),
deptId: func.join(values.deptId), deptId: func.join(values.deptId),
postId: func.join(values.postId),
birthday: func.format(values.birthday), birthday: func.format(values.birthday),
}; };
dispatch(USER_UPDATE(params)); dispatch(USER_UPDATE(params));
@ -52,7 +57,7 @@ class UserEdit extends PureComponent {
handleChange = value => { handleChange = value => {
const { dispatch, form } = this.props; const { dispatch, form } = this.props;
form.resetFields(['roleId', 'deptId']); form.resetFields(['roleId', 'deptId', 'postId']);
dispatch(USER_CHANGE_INIT({ tenantId: value })); dispatch(USER_CHANGE_INIT({ tenantId: value }));
}; };
@ -61,7 +66,7 @@ class UserEdit extends PureComponent {
form: { getFieldDecorator }, form: { getFieldDecorator },
user: { user: {
detail, detail,
init: { roleTree, deptTree, tenantList }, init: { roleTree, deptTree, postList, tenantList },
}, },
submitting, submitting,
} = this.props; } = this.props;
@ -218,6 +223,44 @@ class UserEdit extends PureComponent {
</FormItem> </FormItem>
</Col> </Col>
</Row> </Row>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="用户编号">
{getFieldDecorator('code', {
initialValue: detail.code
})(<Input placeholder="请输入用户编号" />)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} label="所属岗位">
{getFieldDecorator('postId', {
rules: [
{
required: true,
message: '请选择所属岗位',
},
],
initialValue: func.split(detail.postId),
})(
<Select
mode="multiple"
showSearch
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
allowClear
placeholder="请选择所属岗位"
>
{postList.map(d => (
<Select.Option key={d.id} value={d.id}>
{d.postName}
</Select.Option>
))}
</Select>
)}
</FormItem>
</Col>
</Row>
<Row gutter={24}> <Row gutter={24}>
<Col span={10}> <Col span={10}>
<FormItem {...formItemLayout} label="手机号码"> <FormItem {...formItemLayout} label="手机号码">

View File

@ -106,6 +106,18 @@ class UserView extends PureComponent {
</FormItem> </FormItem>
</Col> </Col>
</Row> </Row>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="用户编号">
<span>{detail.code}</span>
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} label="所属岗位">
<span>{detail.postName}</span>
</FormItem>
</Col>
</Row>
<Row gutter={24}> <Row gutter={24}>
<Col span={10}> <Col span={10}>
<FormItem {...formItemLayout} label="手机号码"> <FormItem {...formItemLayout} label="手机号码">

29
src/services/post.js Normal file
View File

@ -0,0 +1,29 @@
import { stringify } from 'qs';
import func from '../utils/Func';
import request from '../utils/request';
export async function list(params) {
return request(`/api/blade-system/post/list?${stringify(params)}`);
}
export async function select(params) {
return request(`/api/blade-system/post/select?${stringify(params)}`);
}
export async function submit(params) {
return request('/api/blade-system/post/submit', {
method: 'POST',
body: params,
});
}
export async function detail(params) {
return request(`/api/blade-system/post/detail?${stringify(params)}`);
}
export async function remove(params) {
return request('/api/blade-system/post/remove', {
method: 'POST',
body: func.toFormData(params),
});
}

View File

@ -2,12 +2,13 @@ import { stringify } from 'qs';
import request from '../utils/request'; import request from '../utils/request';
import func from '../utils/Func'; import func from '../utils/Func';
import { getCaptchaKey } from '../utils/authority'; import { getCaptchaKey } from '../utils/authority';
import { captchaMode } from '../defaultSettings';
// =====================用户=========================== // =====================用户===========================
export async function accountLogin(params) { export async function accountLogin(params) {
const values = params; const values = params;
values.grantType = 'captcha'; values.grantType = captchaMode ? 'captcha' : 'password';
values.scope = 'all'; values.scope = 'all';
return request('/api/blade-auth/token', { return request('/api/blade-auth/token', {
headers: { headers: {

View File

@ -34,6 +34,14 @@ export function setToken(token) {
localStorage.setItem('sword-token', token); localStorage.setItem('sword-token', token);
} }
export function getAccessToken() {
return localStorage.getItem('sword-access-token') || '';
}
export function setAccessToken(accessToken) {
localStorage.setItem('sword-access-token', accessToken);
}
export function getRoutes() { export function getRoutes() {
return JSON.parse(localStorage.getItem('sword-routes')) || []; return JSON.parse(localStorage.getItem('sword-routes')) || [];
} }