🎉 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
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
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,9 +1,9 @@
<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/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%20Boot-2.2.5.RELEASE-blue.svg" alt="Downloads">
<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.6.RELEASE-blue.svg" alt="Downloads">
<a target="_blank" href="https://bladex.vip">
<img src="https://img.shields.io/badge/Author-Small%20Chill-ff69b4.svg" alt="Downloads">
</a>
@ -64,6 +64,7 @@ SpringBlade
* Saber-基于Vue[https://saber.bladex.vip](https://saber.bladex.vip)
* Sword-基于React[https://sword.bladex.vip](https://sword.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开发手册)

View File

@ -125,6 +125,17 @@ export default [
{ 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',
routes: [

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "sword",
"version": "2.6.2",
"version": "2.7.0",
"description": "An out-of-box UI solution for enterprise applications",
"private": true,
"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>
))}
{renderLeftButton ? renderLeftButton() : null}
{renderRightButton ? (
<div style={{ float: 'right', marginRight: '20px' }}>{renderRightButton()}</div>
) : null}
{renderRightButton ? <div style={{ float: 'right' }}>{renderRightButton()}</div> : null}
</div>
</div>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import { accountLogin } from '../services/user';
import { dynamicRoutes, dynamicButtons } from '../services/menu';
import {
setAuthority,
setAccessToken,
setToken,
setCurrentUser,
setRoutes,
@ -101,6 +102,7 @@ export default {
} = payload;
const token = `${tokenType} ${accessToken}`;
setToken(token);
setAccessToken(accessToken);
setAuthority(authority);
setCurrentUser({ avatar, account, name: userName, authority });
} 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 { tree as roles } from '../services/role';
import { tree as depts } from '../services/dept';
import { select as posts } from '../services/post';
import { getCurrentUser } from '../utils/authority';
export default {
@ -20,6 +21,7 @@ export default {
init: {
roleTree: [],
deptTree: [],
postList: [],
tenantList: [],
},
detail: {},
@ -59,13 +61,20 @@ export default {
*fetchInit({ payload }, { call, put }) {
const responseRole = yield call(roles, payload);
const responseDept = yield call(depts, payload);
const responsePost = yield call(posts, 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({
type: 'saveInit',
payload: {
roleTree: responseRole.data,
deptTree: responseDept.data,
postList: responsePost.data,
tenantList: responseTenant.data,
},
});
@ -74,12 +83,14 @@ export default {
*fetchChangeInit({ payload }, { call, put }) {
const responseRole = yield call(roles, 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({
type: 'saveChangeInit',
payload: {
roleTree: responseRole.data,
deptTree: responseDept.data,
postList: responsePost.data,
},
});
}
@ -174,6 +185,7 @@ export default {
const newState = state;
newState.init.roleTree = action.payload.roleTree;
newState.init.deptTree = action.payload.deptTree;
newState.init.postList = action.payload.postList;
return {
...newState,
};

View File

@ -14,14 +14,14 @@ class Workplace extends PureComponent {
<Row gutter={24}>
<Col span={24}>
<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/Spring%20Cloud-Hoxton.SR2-blue.svg"
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"
src="https://img.shields.io/badge/Spring%20Boot-2.2.6.RELEASE-blue.svg"
alt="Downloads"
/>
<a href="https://bladex.vip">
@ -147,7 +147,7 @@ class Workplace extends PureComponent {
<div>
2.接3个月以内工期的reactvuespringbootspringcloudapp小程序等软件定制服务
</div>
<div>3.有意向请联系唯一指定QQ:85088620</div>
<div>3.有意向请联系唯一指定QQ:1272154962</div>
</Panel>
</Collapse>
</Card>
@ -206,7 +206,19 @@ class Workplace extends PureComponent {
</Row>
<Row gutter={24}>
<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">
<div>1.升级SpringBoot 2.2.5.RELEASE</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 Login from '../../components/Login';
import styles from './Login.less';
import { tenantMode } from '../../defaultSettings';
import { tenantMode, captchaMode } from '../../defaultSettings';
const { Tab, TenantId, UserName, Password, Captcha, Submit } = Login;
@ -117,7 +117,7 @@ class LoginPage extends Component {
]}
onPressEnter={() => this.loginForm.validateFields(this.handleSubmit)}
/>
<Captcha name="code" mode="image" />
{captchaMode ? <Captcha name="code" mode="image" /> : null}
</Tab>
<div>
<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 { 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 Grid from '../../../components/Sword/Grid';
import { USER_INIT, USER_LIST, USER_ROLE_GRANT } from '../../../actions/user';
import { resetPassword } from '../../../services/user';
import { tenantMode } from '../../../defaultSettings';
import { getAccessToken, getToken } from '../../../utils/authority';
const FormItem = Form.Item;
const { TreeNode } = Tree;
const { Dragger } = Upload;
@connect(({ user, loading }) => ({
user,
@ -18,9 +31,12 @@ const { TreeNode } = Tree;
class User extends PureComponent {
state = {
visible: false,
excelVisible: false,
confirmLoading: false,
selectedRows: [],
checkedTreeKeys: [],
params: {},
onReset: () => {},
};
componentWillMount() {
@ -41,6 +57,7 @@ class User extends PureComponent {
// ============ 查询 ===============
handleSearch = params => {
this.setState({ params });
const { dispatch } = this.props;
dispatch(USER_LIST(params));
};
@ -133,6 +150,10 @@ class User extends PureComponent {
const { form } = this.props;
const { getFieldDecorator } = form;
this.setState({
onReset,
});
return (
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<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() {
const code = 'user';
const { visible, confirmLoading, checkedTreeKeys } = this.state;
const { visible, excelVisible, confirmLoading, checkedTreeKeys } = this.state;
const {
form,
@ -173,6 +264,26 @@ class User extends PureComponent {
},
} = 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 = [
{
title: '租户ID',
@ -220,6 +331,7 @@ class User extends PureComponent {
onSearch={this.handleSearch}
onSelectRow={this.onSelectRow}
renderSearchForm={this.renderSearchForm}
renderRightButton={this.renderRightButton}
btnCallBack={this.handleBtnCallBack}
loading={loading}
data={data}
@ -239,6 +351,33 @@ class User extends PureComponent {
{this.renderTreeNodes(roleTree)}
</Tree>
</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>
);
}

View File

@ -35,6 +35,7 @@ class UserAdd extends PureComponent {
...values,
roleId: func.join(values.roleId),
deptId: func.join(values.deptId),
postId: func.join(values.postId),
birthday: func.format(values.birthday),
};
dispatch(USER_SUBMIT(params));
@ -45,7 +46,7 @@ class UserAdd extends PureComponent {
handleChange = value => {
const { dispatch, form } = this.props;
form.resetFields(['roleId', 'deptId']);
form.resetFields(['roleId', 'deptId', 'postId']);
dispatch(USER_CHANGE_INIT({ tenantId: value }));
};
@ -53,7 +54,7 @@ class UserAdd extends PureComponent {
const {
form: { getFieldDecorator },
user: {
init: { roleTree, deptTree, tenantList },
init: { roleTree, deptTree, postList, tenantList },
},
submitting,
} = this.props;
@ -230,6 +231,41 @@ class UserAdd extends PureComponent {
</FormItem>
</Col>
</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}>
<Col span={10}>
<FormItem {...formItemLayout} label="手机号码">

View File

@ -5,7 +5,7 @@ import { connect } from 'dva';
import Panel from '../../../components/Panel';
import func from '../../../utils/Func';
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';
const FormItem = Form.Item;
@ -23,8 +23,12 @@ class UserEdit extends PureComponent {
params: { id },
},
} = this.props;
dispatch(USER_DETAIL(id));
dispatch(USER_INIT());
dispatch(USER_DETAIL(id)).then(()=>{
const {
user: { detail },
} = this.props;
dispatch(USER_CHANGE_INIT({ tenantId: detail.tenantId }));
});
}
handleSubmit = e => {
@ -43,6 +47,7 @@ class UserEdit extends PureComponent {
...values,
roleId: func.join(values.roleId),
deptId: func.join(values.deptId),
postId: func.join(values.postId),
birthday: func.format(values.birthday),
};
dispatch(USER_UPDATE(params));
@ -52,7 +57,7 @@ class UserEdit extends PureComponent {
handleChange = value => {
const { dispatch, form } = this.props;
form.resetFields(['roleId', 'deptId']);
form.resetFields(['roleId', 'deptId', 'postId']);
dispatch(USER_CHANGE_INIT({ tenantId: value }));
};
@ -61,7 +66,7 @@ class UserEdit extends PureComponent {
form: { getFieldDecorator },
user: {
detail,
init: { roleTree, deptTree, tenantList },
init: { roleTree, deptTree, postList, tenantList },
},
submitting,
} = this.props;
@ -218,6 +223,44 @@ class UserEdit extends PureComponent {
</FormItem>
</Col>
</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}>
<Col span={10}>
<FormItem {...formItemLayout} label="手机号码">

View File

@ -106,6 +106,18 @@ class UserView extends PureComponent {
</FormItem>
</Col>
</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}>
<Col span={10}>
<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 func from '../utils/Func';
import { getCaptchaKey } from '../utils/authority';
import { captchaMode } from '../defaultSettings';
// =====================用户===========================
export async function accountLogin(params) {
const values = params;
values.grantType = 'captcha';
values.grantType = captchaMode ? 'captcha' : 'password';
values.scope = 'all';
return request('/api/blade-auth/token', {
headers: {

View File

@ -34,6 +34,14 @@ export function setToken(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() {
return JSON.parse(localStorage.getItem('sword-routes')) || [];
}