新增工资单模板

This commit is contained in:
MustangDeng 2022-04-13 16:56:31 +08:00
parent 49ea369fcd
commit 7f08c6ae5c
12 changed files with 566 additions and 49 deletions

View File

@ -46,7 +46,14 @@ export const exportPayroll = params => {
//工资单-工资单模板列表
export const getPayrollTemplateList = params => {
return WeaTools.callApi('/api/bs/hrmsalary/salaryBill/template/list', 'POST', params);
return fetch('/api/bs/hrmsalary/salaryBill/template/list', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then(res => res.json())
}
//工资单-获取薪资账套下拉列表
@ -77,7 +84,14 @@ export const changePayrollDefaultUse = params => {
//工资单-新建工资单
export const savePayroll = params => {
return WeaTools.callApi('/api/bs/hrmsalary/salaryBill/template/save', 'POST', params);
return fetch('/api/bs/hrmsalary/salaryBill/template/save', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then(res => res.json())
}
//工资单-编辑工资单

View File

@ -28,7 +28,7 @@ export default class ModalStep1 extends React.Component {
const dragger = {
name: 'file',
multiple: false,
action: "/api/doc/upload/uploadFile", //上传地址
action: "handleCancel", //上传地址
onChange: (info) => {
const { status } = info.file;
if (status !== 'uploading') {

View File

@ -0,0 +1,91 @@
import React from 'react'
import { Upload, Icon, Modal } from 'antd';
import "./index.less"
function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
function beforeUpload(file) {
const isJPG = file.type === 'image/jpeg' || file.type==="image/png";
if (!isJPG) {
message.error('只允许上传jpg、png类型的图片!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('图片大小限制2MB!');
}
return isJPG && isLt2M;
}
export default class BackgroundUpload extends React.Component {
constructor(props) {
super(props)
this.state = {
showOperate: false,
visible: false
}
}
// 上传完成监听
handleChange = (info) => {
if (info.file.status === 'done') {
this.props.onChange && this.props.onChange(info.file.response.data.acclink)
getBase64(info.file.originFileObj, imageUrl => this.setState({ imageUrl }));
}
}
// 删除
handleDelete = () => {
this.setState({
imageUrl: null,
})
this.props.onChange && this.props.onChagne("");
}
// 预览
handlePreview = () => {
this.setState({visible: true})
}
render() {
const props = {
action: '/api/doc/upload/uploadFile',
multiple: false,
listType: "picture-card",
showUploadList: false,
onChange: this.handleChange.bind(this)
};
const imageUrl = this.state.imageUrl;
return (
<div className="uploadPictureWrapper">
{
imageUrl ?
<div className="previewWrapper" onMouseEnter={() => {this.setState({showOperate: true})}} onMouseLeave={() => {this.setState({showOperate: false})}}>
<img src={imageUrl} alt="" className="previewImg"/>
{
this.state.showOperate && <div className="operateWrapper">
<i className="icon-coms-Supervise operateIcon" onClick={() => {this.handlePreview()}}/>
<i className="icon-coms-Delete operateIcon" onClick={() => {this.handleDelete()}}/>
</div>
}
</div>
:
<Upload {...props}>
<Icon type="plus" className="avatar-uploader-trigger" />
</Upload>
}
<Modal visible={this.state.visible} width={600} footer={null} onCancel={() => {this.setState({visible: false})}} >
<div style={{width: "100%", textAlign: "center"}}>
<img src={imageUrl}/>
</div>
</Modal>
</div>
);
}
}

View File

@ -0,0 +1,31 @@
.uploadPictureWrapper {
.previewWrapper {
position: relative;
width: 96px;
height: 96px;
border: 1px dashed #d9d9d9;
.previewImg {
width: 100%;
height: 100%;
}
.operateWrapper {
position: absolute;
width: 96px;
height: 90px;
top: 0;
left: 0;
line-height: 96px;
z-index: 100;
color: #fff;
background-color: rgba(0, 0, 0, 0.3);
text-align: center;
.operateIcon {
margin-right: 20px;
cursor: pointer;
}
.operateIcon:last-child {
margin-right: 0px;
}
}
}
}

View File

@ -2,7 +2,7 @@ import React from 'react';
import { inject, observer } from 'mobx-react';
import { toJS } from 'mobx';
import { Button, Table, DatePicker } from 'antd';
import { Button, Table, DatePicker, message } from 'antd';
import moment from 'moment'
import { WeaTop, WeaTab, WeaRightMenu, WeaRangePicker, WeaTable,WeaDatePicker, WeaHelpfulTip, WeaSelect, WeaInputSearch, WeaSlideModal } from 'ecCom';
@ -17,10 +17,12 @@ import ItemMangeFormModal from '../dataAcquisition/attendance/itemMangeFormModal
import BaseInformForm from './stepForm/baseInformForm'
import ShowSettingForm from './stepForm/showSettingForm'
import SlideModalTitle from "../../components/slideModalTitle"
import TemplateSettingList from './templateSettingList'
import { notNull } from '../../util/validate';
const { MonthPicker } = DatePicker;
@inject('baseTableStore')
@inject('payrollStore')
@observer
export default class Payroll extends React.Component {
constructor(props) {
@ -73,10 +75,25 @@ export default class Payroll extends React.Component {
})
}
// 工资单模板-新建表单变化监听
handleBaseInfoChange(request) {
const { payrollStore: {setTemplateBaseData}} = this.props;
setTemplateBaseData(request);
}
// 新建保存
handleSave() {
const { payrollStore } = this.props;
const { fetchSavePayroll } = payrollStore
fetchSavePayroll().then(() => {
this.setState({currentStep: 0, stepSlideVisible: false})
})
}
render() {
const { baseTableStore } = this.props;
const { loading, hasRight, form, condition, tableStore, showSearchAd, getTableDatas, doSearch, setShowSearchAd } = baseTableStore;
const { payrollStore } = this.props;
const { loading, hasRight, form, condition, tableStore, showSearchAd, getTableDatas, doSearch, setShowSearchAd } = payrollStore;
const { currentStep, selectedTab } = this.state
if (!hasRight && !loading) { // 无权限处理
return renderNoright();
@ -152,7 +169,24 @@ export default class Payroll extends React.Component {
"显示设置"
]
const validateStep1 = () => {
const { payrollStore: {templateBaseData}} = this.props;
if(!notNull(templateBaseData.name)) {
message.warning("工资单模板名称不能为空")
return false
}
if(!notNull(templateBaseData.salarySob)) {
message.warning("薪资账套不能为空")
return false;
}
return true;
}
const nextStep = () => {
if(!validateStep1()) {
return
}
this.setState({
currentStep: this.state.currentStep + 1
})
@ -186,7 +220,8 @@ export default class Payroll extends React.Component {
}
{
this.state.selectedKey == 1 && <WeaTable columns={tempateColumns} dataSource={dataSource}/>
this.state.selectedKey == 1 &&
<TemplateSettingList />
}
</WeaTop>
@ -206,7 +241,7 @@ export default class Payroll extends React.Component {
{
currentStep == 1 && <div style={{display: "inline-block"}}>
<Button type="default" style={{marginRight: "10px"}}>上一步</Button>
<Button type="primary">保存</Button>
<Button type="primary" onClick={() => {this.handleSave()}}>保存</Button>
<Button type="default" style={{marginLeft: "10px"}}>预览</Button>
</div>
}
@ -216,7 +251,7 @@ export default class Payroll extends React.Component {
content={
<div>
{
currentStep == 0 && <BaseInformForm />
currentStep == 0 && <BaseInformForm onChange={(request) => {this.handleBaseInfoChange(request)}}/>
}
{
currentStep == 1 && <ShowSettingForm />

View File

@ -1,31 +1,69 @@
import React from 'react'
import { Row, Col, Switch } from 'antd'
import { WeaInput, WeaSelect } from 'ecCom'
import { inject, observer } from 'mobx-react';
import RequiredLabelTip from '../../../components/requiredLabelTip';
import "./index.less"
@inject('payrollStore')
@observer
export default class BaseInformForm extends React.Component {
constructor(props) {
super(props)
this.state = {
inited: false,
options: [],
request: {}
}
}
componentWillMount() {
const { payrollStore} = this.props;
const { getPayrollBaseForm} = payrollStore
getPayrollBaseForm(this.props.id).then(data => {
this.setState({
inited: true,
options: data.salarySobOptions,
request: data.templateBaseData
})
})
}
hanldeChange(params) {
let request = {...this.state.request, ...params};
this.setState({
request
})
this.props.onChange && this.props.onChange(request)
}
render() {
const { request } = this.state;
const { salarySob, salarySobOption, name,
description, emailStatus, sendEmail,
sendEmailOptions, msgStatus } = request;
return (
<div className="baseInformForm">
<div className="formItemWrapper">
<div className="itemTitle">基础信息</div>
<div className="formWrapper">
<Row className="formItem">
<Col span={8}>薪资账套</Col>
<Col span={8}>薪资账套<RequiredLabelTip /></Col>
<Col span={16}>
<WeaSelect style={{width: "200px"}}/>
{
this.state.inited && <WeaSelect options={this.state.options} value={salarySob} style={{width: "200px"}} onChange={(value) => {this.hanldeChange({salarySob: value})}}/>
}
</Col>
</Row>
<Row className="formItem">
<Col span={8}>工资单模板名称</Col>
<Col span={8}>工资单模板名称<RequiredLabelTip /></Col>
<Col span={16}>
<WeaInput />
<WeaInput value={name} onChange={(value) => this.hanldeChange({name: value})}/>
</Col>
</Row>
<Row className="formItem">
<Col span={8}>备注</Col>
<Col span={16}>
<WeaInput />
<WeaInput value={description} onChange={(value) => this.hanldeChange({description: value})}/>
</Col>
</Row>
</div>
@ -38,7 +76,7 @@ export default class BaseInformForm extends React.Component {
<Row>
<Col span={8}>邮件</Col>
<Col span={16}>
<Switch />
<Switch value={emailStatus} onChange={(value) => {this.hanldeChange({emailStatus: value})}}/>
</Col>
</Row>
</Col>
@ -46,7 +84,7 @@ export default class BaseInformForm extends React.Component {
<Row>
<Col span={8}>发送地址</Col>
<Col span={16}>
<WeaSelect style={{width: '200px'}}/>
<WeaSelect style={{width: '200px'}} value={sendEmail} onChange={(value) => {this.hanldeChange({sendEmail: value})}}/>
</Col>
</Row>
</Col>
@ -56,7 +94,7 @@ export default class BaseInformForm extends React.Component {
<Row>
<Col span={8}>消息中心</Col>
<Col span={16}>
<Switch />
<Switch checked={msgStatus} onChange={(value)=>{this.hanldeChange({msgStatus: value})}}/>
</Col>
</Row>
</Col>

View File

@ -32,6 +32,9 @@
line-height: 40px;
}
}
.themeFormalStr {
margin-right: 10px;
}
.settingItemWrapper {
margin-top: 10px;
.itemTitle {

View File

@ -1,15 +1,65 @@
import React from 'react'
import { Row, Col, Upload, Icon, Radio, Switch } from 'antd'
import { WeaInput } from 'ecCom'
import { inject, observer } from 'mobx-react';
import BackgroundUpload from '../components/backgroundUpload'
const Dragger = Upload.Dragger;
@inject('payrollStore')
@observer
export default class ShowSettingForm extends React.Component {
componentWillMount() {
const { payrollStore } = this.props;
const { initShowSettingForm } = payrollStore
initShowSettingForm(this.props.id)
}
// form 字段变化时的回调
handleChange(params) {
const { payrollStore: {salaryTemplateShowSet, setSalaryTemplateShowSet}} = this.props;
let request= {...salaryTemplateShowSet, ...params};
setSalaryTemplateShowSet(request);
// this.props.onChange && this.props.onChange(request);
}
// 工资单主题 插入变量
handleThemeNameCllck(param) {
const { payrollStore } = this.props;
const { salaryTemplateShowSet, setSalaryTemplateShowSet } = payrollStore;
let request= {...salaryTemplateShowSet};
request.theme = (request.theme ? request.theme : "") + param;
setSalaryTemplateShowSet(request);
}
render() {
const { payrollStore } = this.props;
const { salaryTemplateShowSet } = payrollStore;
const { salaryItemSet } = payrollStore
const { theme,
background,
textContent,
textContentPosition,
salaryItemNullStatus,
salaryItemZeroStatus
} = salaryTemplateShowSet
const dropProps = {
name: 'file',
action: 'http://www.mocky.io/v2/5e0085b82f0000780013b4c7',
action: '/api/doc/upload/uploadFile',
onChange(info) {
const { status } = info.file;
if (status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
onDrop(e) {
console.log('Dropped files', e.dataTransfer.files);
},
};
return (
<div className="showSettingForm">
@ -21,51 +71,56 @@ export default class ShowSettingForm extends React.Component {
<Row>
<Col span={8}>工资单主题</Col>
<Col span={16}>
<WeaInput style={{width: "200px"}}/>
<WeaInput style={{width: "200px"}} value={theme} onChange={(value) => {this.handleChange({theme:value})}}/>
</Col>
</Row>
</Col>
<Col span={12}>
插入变量
<span>公司名称</span>
<span>薪资所属月</span>
<a onClick={() => {this.handleThemeNameCllck("${companyName}")}} className="themeFormalStr">公司名称</a>
<a onClick={() => {this.handleThemeNameCllck("${salaryMonth}")}} className="themeFormalStr">薪资所属月</a>
</Col>
</Row>
<Row className="formItem">
<Col span={4}>工资单主题</Col>
<Col span={20}>
<Dragger {...dropProps} style={{width: "100px"}}>
<BackgroundUpload onChnage={(value) => {this.handleChange({textContentPosition: value})}}/>
{/* <Dragger {...dropProps} style={{width: "100px"}}>
<div style={{ padding: '55px 0' }}>
<Icon type="plus" />
</div>
</Dragger>
</Dragger> */}
</Col>
</Row>
<Row className="formItem">
<Col span={4}>文本内容</Col>
<Col span={20}>
<WeaInput />
<Radio.Group>
<Radio value={1}>薪资项目前</Radio>
<Radio value={2}>薪资项目后</Radio>
<WeaInput value={textContent} onChange={(value) => {this.handleChange({textContent: value})}}/>
</Col>
</Row>
<Row className="formItem">
<Col span={4}>文本内容位置</Col>
<Col span={20}>
<Radio.Group value={textContentPosition} onChange={(e) => {this.handleChange({textContentPosition: e.target.value})}}>
<Radio value={"1"}>薪资项目前</Radio>
<Radio value={"2"}>薪资项目后</Radio>
</Radio.Group>
</Col>
</Row>
<Row className="formItem">
<Col span={6}>薪资项为空时不显示</Col>
<Col span={18}>
<Switch />
<Switch checked={salaryItemNullStatus} onChange={(value) => {this.handleChange({salaryItemNullStatus: value})}}/>
</Col>
</Row>
<Row className="formItem">
<Col span={6}>薪资项为0时不显示</Col>
<Col span={18}>
<Switch />
<Switch checked={salaryItemZeroStatus} onChange={(value) => {this.handleChange({salaryItemZeroStatus: value})}}/>
</Col>
</Row>
</div>
@ -74,16 +129,18 @@ export default class ShowSettingForm extends React.Component {
<div className="settingItemWrapper">
<div className="itemTitle">薪资项目设置</div>
<div className="itemContent">
<div className="configItemWrapper">
<div className="configTitle">员工信息</div>
<div className="configContent"><span className="editItem">个税扣缴义务人</span> <span className="editItem"></span> <span></span> </div>
</div>
<div className="configItemWrapper">
<div className="configTitle">月度固定薪酬</div>
<div className="configContent"><span className="editItem">基本工资</span> <span className="editItem"></span> <span className="editItem"></span> </div>
</div>
{
salaryItemSet.map(group => (
<div className="configItemWrapper">
<div className="configTitle">{group.groupName}</div>
<div className="configContent">
{group.items.map(item => (
<span className="editItem">{item.name}</span>
))}
</div>
</div>
))
}
</div>
</div>
</div>

View File

@ -0,0 +1,29 @@
import React from 'react'
import { inject, observer } from 'mobx-react';
import { WeaTableNew } from 'comsMobx';
const WeaTable = WeaTableNew.WeaTable;
@inject('payrollStore')
@observer
export default class TemplateSettingList extends React.Component {
componentWillMount() {
const { payrollStore } = this.props;
const { getPayrollTemplateList } = payrollStore;
getPayrollTemplateList();
}
render() {
const { payrollStore } = this.props;
const { templateStore } = payrollStore;
return (
<div>
<WeaTable // table内部做了loading加载处理页面就不需要再加了
comsWeaTableStore={templateStore} // table store
hasOrder={true} // 是否启用排序
needScroll={true} // 是否启用table内部列表滚动将自适应到父级高度
// getColumns={this.getColumns}
// onOperatesClick={this.onOperatesClick.bind(this)}
/>
</div>
)
}
}

View File

@ -13,6 +13,7 @@ import { SalaryItemStore } from './salaryItem'
import { LedgerStore } from './ledger'
import { ArchivesStore } from './archives'
import { salaryFileStore } from './salaryFile';
import { payrollStore } from './payroll';
module.exports = {
baseFormStore: new BaseFormStore(),
@ -28,6 +29,7 @@ module.exports = {
salaryItemStore: new SalaryItemStore(),
ledgerStore: new LedgerStore(),
archivesStore: new ArchivesStore(),
salaryFileStore: new salaryFileStore()
salaryFileStore: new salaryFileStore(),
payrollStore: new payrollStore()
};

View File

@ -0,0 +1,211 @@
import { observable, action, toJS } from 'mobx';
import { message } from 'antd';
import { WeaForm, WeaTableNew } from 'comsMobx';
import * as API from '../apis/payroll'; // 引入API接口文件
import { notNull } from '../util/validate';
const { TableStore } = WeaTableNew;
export class payrollStore {
@observable tableStore = new TableStore(); // new table
@observable form = new WeaForm(); // nrew 一个form
@observable condition = []; // 存储后台得到的form数据
@observable hasRight = true; // 判断用户是有权限查看当前页面: 没有权限渲染无权限页面,有权限渲染数据
@observable showSearchAd = false; // 高级搜索面板显示
@observable loading = true; // 数据加载状态
// **** 模板页面 ****
@observable templateStore = new TableStore(); // 模板设置列表
// 基础设置表单
@observable templateBaseData = {} // 基础信息表单数据
@observable salarySobOptions = [] // 账套列表
// 显示设置表单
@observable salaryTemplateShowSet = {} // 显示设置基础表单
@observable salaryItemSet = [] // 显示设置薪资项
// 基础信息表单数据
@action
setTemplateBaseData = (templateBaseData) => this.templateBaseData = templateBaseData
// 显示设置基础表单
@action
setSalaryTemplateShowSet = (salaryTemplateShowSet) => this.salaryTemplateShowSet = salaryTemplateShowSet
// 初始化操作
@action
doInit = () => {
// this.getCondition();
// this.getTableDatas();
}
// 获得高级搜索表单数据
@action
getCondition = () => {
API.getCondition().then(action(res => {
if (res.api_status) { // 接口请求成功/失败处理
this.condition = res.condition;
this.form.initFormFields(res.condition); // 渲染高级搜索form表单
} else {
message.error(res.msg || '接口调用失败!')
}
}));
}
// 渲染table数据
@action
getTableDatas = (params) => {
this.loading = true;
const formParams = this.form.getFormParams() || {};
params = params || formParams;
API.getTableDatas(params).then(action(res => {
if (res.api_status) { // 接口请求成功/失败处理
this.tableStore.getDatas(res.datas); // table 请求数据
this.hasRight = res.hasRight;
} else {
message.error(res.msg || '接口调用失败!')
}
this.loading = false;
}));
}
@action
setShowSearchAd = bool => this.showSearchAd = bool;
// 高级搜索 - 搜索
@action doSearch = () => {
this.getTableDatas();
this.showSearchAd = false;
}
// 工资单模板-工资单模板列表
@action
getPayrollTemplateList = () => {
let params = {}
API.getPayrollTemplateList(params).then(res => {
if(res.status) {
this.templateStore.getDatas(res.data.datas);
} else {
message.error(res.errormsg || "获取失败");
}
})
}
// 工资单模板-获取工资单模板基础设置表单
@action
getPayrollBaseForm = (id = "") => {
let params = {
id
}
return new Promise((resolve, reject) => {
API.getPayrollBaseForm(params).then(res => {
if(res.status) {
let response = res.data.salaryTemplateBaseSet
let templateBaseData = response.data
templateBaseData.salarySob = templateBaseData.salarySob !== undefined ? templateBaseData.salarySob : null;
this.templateBaseData = templateBaseData // 基础信息表单数据
this.salarySobOptions = response.salarySobOptions ?
response.salarySobOptions.map(item => {
let result = {}
result.showname = item.name;
result.key = item.id + "";
result.selected = false;
return result;
}) : []
resolve({
templateBaseData: this.templateBaseData,
salarySobOptions: this.salarySobOptions
});
} else {
message.errro(res.errormsg || "获取失败");
reject()
}
})
})
}
// 工资单模板-获取工资单模板显示设置表单
@action
getPayrollShowForm = (id = "") => {
let params = {
id
}
API.getPayrollShowForm(params).then(res => {
if(res.status) {
this.salaryTemplateShowSet = res.data.salaryTemplateShowSet.data
} else {
message.error(res.errormsg || "获取失败")
}
})
}
// 工资单模板-获取薪资项目设置
@action
getPayrollItemList = (salarySobId = "") => {
let params = {
salarySobId
}
API.getPayrollItemList(params).then(res => {
if(res.status) {
this.salaryItemSet = res.data
} else {
message.error(res.errormsg || "获取失败")
}
})
}
// 初始化显示设置表单
@action
initShowSettingForm = (id = "") => {
this.getPayrollShowForm(id);
if(id == "") {
this.getPayrollItemList(this.templateBaseData.salarySob)
}
}
// 校验显示设置表单
validateSalaryTemplateShowSet = () => {
if(!notNull(this.salaryTemplateShowSet.theme)) {
message.warning("工资单主题不能为空");
return false;
}
return true;
}
// 拼装保存参数
convertParams = () => {
let params = {...this.templateBaseData, ...this.salaryTemplateShowSet}
params.salarySobId = params.salarySob
params.emailStatus = params.emailStatus ? params.emailStatus : false;
params.msgStatus = params.msgStatus ? params.msgStatus : false
params.salaryItemNullStatus = params.salaryItemNullStatus ? params.salaryItemNullStatus : false;
params.salaryItemZeroStatus = params.salaryItemZeroStatus ? params.salaryItemZeroStatus : false;
params.salaryItemSetting = this.salaryItemSet
return params
}
// 工资单模板-新建工资单模板
@action
fetchSavePayroll = () => {
if(!(this.validateSalaryTemplateShowSet())) {
return false
}
let params = this.convertParams()
return new Promise((resolve, reject) => {
API.savePayroll(params).then(res => {
if(res.status) {
message.success("保存成功");
this.getPayrollTemplateList();
resolve();
} else {
message.error(res.errormsg || "保存失败")
reject()
}
})
})
}
}

View File

@ -1,12 +1,18 @@
/**
* 判断是否为空
* @param {*} name
* @returns false 为空 true 不为空
*/
export const notNull = (name) => {
if(typeof(name) == "string") {
name = name.trim()
}
if(name !== undefined) {
name = name.toString()
}
if(!name || name == "") {
return false
if(typeof(name) == "string") {
if(name == "") {
return false;
}
} else if(name === null || name === undefined) {
return false;
}
return true
}