wuqun
2 years ago
8 changed files with 943 additions and 45 deletions
@ -0,0 +1,89 @@ |
|||
'use strict'; |
|||
|
|||
import { ApiTable, basicAction } from '$utils' |
|||
|
|||
export function addSalesMemberBulk(values) { |
|||
return dispatch => basicAction({ |
|||
type: 'post', |
|||
dispatch: dispatch, |
|||
actionType: 'SALES_MEMBER_BULK_ADD', |
|||
url: ApiTable.addSalesMemberBulk, |
|||
data: values, |
|||
msg: { option: '导入销售人员信息' }, |
|||
}); |
|||
} |
|||
|
|||
export function postSalesMember(data) {//添加/编辑
|
|||
let msg = '' |
|||
if (data) { |
|||
msg = data.msg |
|||
} |
|||
return (dispatch) => |
|||
basicAction({ |
|||
type: "post", |
|||
dispatch: dispatch, |
|||
data, |
|||
actionType: "POST_SALES_MEMBER", |
|||
url: `${ApiTable.addSalesMember}`, |
|||
msg: { option: msg }, //添加/编辑
|
|||
reducer: { name: "" }, |
|||
}); |
|||
} |
|||
|
|||
export function getSalesList(query) {//查询
|
|||
return (dispatch) => basicAction({ |
|||
type: "get", |
|||
dispatch: dispatch, |
|||
actionType: "GET_SALES_MENBER_LIST", |
|||
query: query, |
|||
url: `${ApiTable.getSalesList}`, |
|||
msg: { option: "查询销售人员列表" }, |
|||
reducer: { name: "SalesMemberList", params: { noClear: true } }, |
|||
}); |
|||
} |
|||
export function delSalesMember(data) {//删除
|
|||
let msg = '' |
|||
if (data) { |
|||
msg = data.msg |
|||
} |
|||
return (dispatch) => |
|||
basicAction({ |
|||
type: "del", |
|||
query: data, |
|||
dispatch: dispatch, |
|||
actionType: "DEL_SALES_MEMBER", |
|||
url: `${ApiTable.delSalesMember}`, |
|||
msg: { option: msg }, //删除
|
|||
reducer: {}, |
|||
}); |
|||
} |
|||
|
|||
// export function getMemberExport(query) {//导出员工信息
|
|||
// return (dispatch) => basicAction({
|
|||
// type: "get",
|
|||
// dispatch: dispatch,
|
|||
// actionType: "GET_MemberEXPORT",
|
|||
// query: query,
|
|||
// url: `${ApiTable.getMemberExport}`,
|
|||
// msg: { option: "导出员工信息" },
|
|||
// reducer: { name: "MemberExport", params: { noClear: true } },
|
|||
// });
|
|||
// }
|
|||
|
|||
|
|||
export function editSalesMember(data) {//更新
|
|||
let msg = '' |
|||
if (data) { |
|||
msg = data.msg |
|||
} |
|||
return (dispatch) => |
|||
basicAction({ |
|||
type: "put", |
|||
dispatch: dispatch, |
|||
data, |
|||
actionType: "PUT_SALES_MEMBER", |
|||
url: `${ApiTable.editSalesMember}`, |
|||
msg: { option: msg }, //更新
|
|||
reducer: {}, |
|||
}); |
|||
} |
@ -1,42 +0,0 @@ |
|||
import React, { useEffect, useState } from 'react'; |
|||
import { connect } from 'react-redux'; |
|||
import Empty from '../components/empty'; |
|||
import '../style.less' |
|||
|
|||
const PersonnelDistribution = (props) => { |
|||
const { dispatch, actions } = props |
|||
|
|||
return ( |
|||
<> |
|||
<div style={{ padding: '0px 12px' }}> |
|||
<div style={{ display: 'flex' }}> |
|||
<div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14 }}>招聘</div> |
|||
<div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14, margin: '0px 8px' }}>/</div> |
|||
<div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14 }}>销售统计</div> |
|||
<div style={{ color: '#033C9A', fontSize: 14, margin: '0px 8px' }}>/</div> |
|||
<div style={{ color: '#033C9A', fontSize: 14 }}>销售人员分布</div> |
|||
</div> |
|||
<div style={{ background: '#FFFFFF', boxShadow: '0px 0px 12px 2px rgba(220,222,224,0.2)', borderRadius: 2, padding: '20px 0px 20px 19px ', marginTop: 12 }}> |
|||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
|||
<div style={{ display: 'flex', alignItems: 'baseline' }}> |
|||
<div style={{ width: 0, height: 20, borderLeft: '3px solid #0F7EFB', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div> |
|||
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#033C9A', marginLeft: 8 }}>销售人员分布</div> |
|||
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>DISTRIBUTION OF SALES PERSONNEL</div> |
|||
</div> |
|||
</div> |
|||
<Empty /> |
|||
</div> |
|||
</div> |
|||
</> |
|||
) |
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { auth, global } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
}; |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(PersonnelDistribution); |
@ -0,0 +1,261 @@ |
|||
'use strict'; |
|||
import React, { useState, useEffect } from 'react'; |
|||
import { connect } from 'react-redux'; |
|||
import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui'; |
|||
import { IconUpload } from '@douyinfe/semi-icons'; |
|||
import cityData from '../../components/city.json'; |
|||
import XLSX from 'xlsx' |
|||
import { membersBulkAdd } from '../../actions/personnelFiles' |
|||
//下载模板和上传文件读取
|
|||
const ImportSalersModal = props => { |
|||
const { dispatch, actions, onCancel, rzMembers } = props |
|||
const { humanAffairs } = actions; |
|||
const [msg, setMsg] = useState('') |
|||
const [loading, setLoading] = useState('') |
|||
const [postData, setPostData] = useState([]) |
|||
const [allProvinces, setAllProvinces] = useState([]) |
|||
//初始化
|
|||
useEffect(() => { |
|||
let allProvinces = [];//所有省
|
|||
cityData.map(cd => { |
|||
allProvinces.push(cd.name); |
|||
}); |
|||
setAllProvinces(allProvinces); |
|||
}, []); |
|||
|
|||
const confirm = () => { |
|||
if (postData.length) { |
|||
setLoading(true) |
|||
dispatch(humanAffairs.addSalesMemberBulk(postData)).then(res => { |
|||
if (res.success) { |
|||
onCancel() |
|||
} |
|||
setLoading(false) |
|||
}) |
|||
} else { |
|||
Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 }) |
|||
} |
|||
} |
|||
|
|||
const dldCsvMb = () => { |
|||
//表头
|
|||
let head = "员工编号,姓名,销售区域(省/直辖市),销售区域(市)\n" |
|||
//数据
|
|||
//let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
|
|||
let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head; |
|||
//创建一个a标签
|
|||
let link = document.createElement("a"); |
|||
//为a标签设置属性
|
|||
link.setAttribute("href", templateCsv); |
|||
link.setAttribute("download", "人资系统销售人员信息导入模板.csv"); |
|||
//点击a标签
|
|||
link.click(); |
|||
} |
|||
const download = () => { |
|||
//dldTemplate();
|
|||
dldCsvMb(); |
|||
// let str = "";
|
|||
// rule.forEach((v, i) => {
|
|||
// str += `${v}\r\n`
|
|||
// })
|
|||
// dldText("填写说明.txt", str);
|
|||
} |
|||
|
|||
// const dldText = (filename, text) => {
|
|||
// var element = document.createElement('a');
|
|||
// element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
|||
// element.setAttribute('download', filename);
|
|||
|
|||
// element.style.display = 'none';
|
|||
// document.body.appendChild(element);
|
|||
|
|||
// element.click();
|
|||
// document.body.removeChild(element);
|
|||
// }
|
|||
const fileLimit = '.csv'; |
|||
|
|||
const getFileBlob = (url) => { |
|||
return new Promise((resolve, reject) => { |
|||
let request = new XMLHttpRequest() |
|||
request.open("GET", url, true) |
|||
request.responseType = "blob" |
|||
request.onreadystatechange = e => { |
|||
if (request.readyState == 4) { |
|||
if (request.status == 200) { |
|||
if (window.FileReader) { |
|||
let reader = new FileReader(); |
|||
reader.readAsBinaryString(request.response); |
|||
reader.onload = event => { |
|||
try { |
|||
const { result } = event.target; |
|||
// 以二进制流方式读取得到整份excel表格对象
|
|||
const workbook = XLSX.read(result, { |
|||
type: "binary", |
|||
cellDates: true,//设为true,将天数的时间戳转为时间格式
|
|||
codepage: 936//解决了乱码问题
|
|||
}); |
|||
let data = []; // 存储获取到的数据
|
|||
// 遍历每张工作表进行读取(这里默认只读取第一张表)
|
|||
for (const sheet in workbook.Sheets) { |
|||
if (workbook.Sheets.hasOwnProperty(sheet)) { |
|||
data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet])); |
|||
} |
|||
} |
|||
resolve(data);//导出数据
|
|||
} catch (e) { |
|||
reject("失败"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
request.send(); |
|||
}) |
|||
} |
|||
|
|||
const judgeProvinces = (provinces) => { |
|||
let noMark = 0; |
|||
provinces.split('、')?.map(p => { |
|||
if (allProvinces.indexOf(p) == -1) { |
|||
noMark++ |
|||
} |
|||
}) |
|||
return !noMark; |
|||
} |
|||
|
|||
const judgeCities = (provinces, cities) => { |
|||
let nowCities = []; |
|||
cityData.filter(cd => provinces.split('、').indexOf(cd.name) != -1).map(d => { |
|||
d.children?.map(c => { |
|||
nowCities.push(c.name) |
|||
}) |
|||
}) |
|||
let noMark = 0; |
|||
cities.split('、')?.map(p => { |
|||
if (nowCities.indexOf(p) == -1) { |
|||
noMark++ |
|||
} |
|||
}) |
|||
return !noMark; |
|||
} |
|||
|
|||
const judgeNull = (value) => { |
|||
return value ? String(value).trim().replace(/\s*/g, "") : null; |
|||
} |
|||
|
|||
return ( |
|||
<Modal |
|||
title="导入" visible={true} |
|||
onOk={confirm} |
|||
confirmLoading={loading} |
|||
onCancel={() => { |
|||
setMsg('') |
|||
setLoading(false) |
|||
setPostData([]) |
|||
onCancel() |
|||
}} |
|||
> |
|||
<div style={{ borderBottom: '1px solid #DCDEE0', margin: '0px -24px' }}></div> |
|||
<Form> |
|||
<Form.Upload |
|||
label={'销售人员信息'} labelPosition='left' |
|||
action={'/'} accept={fileLimit} |
|||
maxSize={200} limit={1} |
|||
onRemove={(currentFile, fileList, fileItem) => { |
|||
setMsg(''); |
|||
setPostData([]); |
|||
}} |
|||
customRequest={(data) => { |
|||
const { file, onSuccess, onError } = data |
|||
getFileBlob(file.url).then(res => { |
|||
const error = (msg) => { |
|||
setMsg(msg) |
|||
onError({ message: msg }) |
|||
} |
|||
if (res.length > 1000) { |
|||
error('一次性上传数据行数应小于1000行,请分批上传') |
|||
return |
|||
} |
|||
if (!res.length) { |
|||
error('请填写至少一行数据') |
|||
return |
|||
} |
|||
let postData = []; |
|||
const zmsz = /^[A-Za-z0-9]+$/;//字母数字组合
|
|||
for (let i = 0; i < res.length; i++) { |
|||
let d = res[i]; |
|||
let number = judgeNull(d['员工编号']); |
|||
let name = judgeNull(d['姓名']); |
|||
let provinces = judgeNull(d['销售区域(省/直辖市)']); |
|||
let cities = judgeNull(d['销售区域(市)']); |
|||
if (!number) {//人员编号不为空,唯一,字母和数字
|
|||
error(`第${i + 2}行人员编号为空,请填写`) |
|||
return |
|||
} |
|||
let rzExist = rzMembers.find(m => m.userCode == number); |
|||
if (!rzExist) { |
|||
error(`第${i + 2}行的人员编号无对应的员工信息`) |
|||
return |
|||
} |
|||
if (postData.some(p => p.number == number)) {//人员编号 唯一
|
|||
error(`第${i + 2}行人员编号重复,请更改后重新上传`) |
|||
return |
|||
} |
|||
if (!zmsz.test(number)) { |
|||
error(`第${i + 2}行人员编号错误,请填写字母和数字的组合`) |
|||
return |
|||
} |
|||
if (!name) {//姓名必填
|
|||
error(`第${i + 2}行姓名为空,请填写`) |
|||
return |
|||
} |
|||
if (!provinces) {//销售区域(省/直辖市)必填
|
|||
error(`第${i + 2}行销售区域(省/直辖市)为空,请填写`) |
|||
return |
|||
} |
|||
let pValid = judgeProvinces(provinces); |
|||
if (!pValid) { |
|||
error(`第${i + 2}行销售区域(省/直辖市)错误`) |
|||
return |
|||
} |
|||
let cValid = judgeCities(provinces, cities); |
|||
if (!cValid) { |
|||
error(`第${i + 2}行销售区域(市)错误`) |
|||
return |
|||
} |
|||
postData.push({//人员编号 待办todotodo
|
|||
pepUserId: rzExist.pepUserId, name, |
|||
provinces: provinces.split('、'), cities: cities.split('、'), |
|||
del: false |
|||
}) |
|||
} |
|||
setPostData(postData) |
|||
let msg = '文件解析完成,点击确定按钮上传保存!' |
|||
setMsg(msg) |
|||
onSuccess({ message: msg }) |
|||
}) |
|||
}}> |
|||
<Button icon={<IconUpload />} theme="light"> |
|||
请选择文件 |
|||
</Button> |
|||
</Form.Upload> |
|||
<span>{msg}</span> |
|||
<div style={{ color: '#ccc' }}>最大不超过200M,导入文件需与 |
|||
<span onClick={() => download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板</span> |
|||
一致</div> |
|||
</Form> |
|||
</Modal > |
|||
) |
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { auth, global, MemberList } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
rzMembers: MemberList.data?.rows || [], |
|||
} |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(ImportSalersModal); |
@ -0,0 +1,339 @@ |
|||
import React, { useEffect, useRef, useState } from 'react'; |
|||
import { connect } from 'react-redux'; |
|||
import moment from 'moment' |
|||
import { Select, Input, Button, Popconfirm, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui'; |
|||
import SalesMemberModal from './salesMemberModal' |
|||
import ImportSalersModal from './importSalersModal' |
|||
import { IconSearch } from '@douyinfe/semi-icons'; |
|||
import { SkeletonScreen } from "$components"; |
|||
import '../../style.less' |
|||
|
|||
const PersonnelDistribution = (props) => { |
|||
const { dispatch, actions } = props |
|||
const { humanAffairs } = actions; |
|||
const [keywordTarget, setKeywordTarget] = useState('dep'); |
|||
const [keyword, setKeyword] = useState('');//搜索内容 |
|||
const [limits, setLimits] = useState()//每页实际条数 |
|||
const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息 |
|||
const [modalV, setModalV] = useState(false); |
|||
const [dataToEdit, setDataToEdit] = useState(null); |
|||
const [tableData, setTableData] = useState([]); |
|||
const [importModalV, setImportModalV] = useState(false); |
|||
const page = useRef(query.page); |
|||
function seachValueChange(value) { |
|||
setKeyword(value) |
|||
} |
|||
|
|||
useEffect(() => { |
|||
dispatch(humanAffairs.getMemberList()) |
|||
getMemberSearchList() |
|||
}, []); |
|||
|
|||
useEffect(() => { |
|||
getMemberSearchList()//查询人员列表 |
|||
}, [query]) |
|||
|
|||
function getMemberSearchList() { |
|||
let kt = keywordTarget == 'place' ? '' : keywordTarget; |
|||
let k = keywordTarget == 'place' ? '' : keyword; |
|||
let placeSearch = keywordTarget == 'place' ? keyword : ''; |
|||
dispatch(humanAffairs.getSalesList({ keywordTarget: kt, keyword: k, placeSearch, ...query })).then(r => { |
|||
if (r.success) { |
|||
setTableData(r.payload?.data?.rows); |
|||
setLimits(r.payload?.data?.count) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
function handleRow(record, index) {// 给偶数行设置斑马纹 |
|||
if (index % 2 === 0) { |
|||
return { |
|||
style: { |
|||
background: '#FAFCFF', |
|||
} |
|||
}; |
|||
} else { |
|||
return {}; |
|||
} |
|||
} |
|||
|
|||
const closeAndFetch = () => { |
|||
setModalV(false) |
|||
getMemberSearchList(); |
|||
} |
|||
|
|||
const starHeader = (header) => { |
|||
return <div> |
|||
<img src="/assets/images/hrImg/V.png" style={{ width: 14, height: 14 }} /> {header} |
|||
</div> |
|||
} |
|||
|
|||
const getMultis = (arrStr) => {//默认展示2个 |
|||
return <div style={{ display: 'flex' }}> |
|||
{ |
|||
arrStr.map((ite, idx) => { |
|||
return ( |
|||
<div key={idx} style={{ display: 'flex' }}> |
|||
{idx < 2 ? |
|||
<div style={{ padding: '0px 4px 1px 4px', color: '#FFF', fontSize: 12, background: 'rgba(0,90,189,0.8)', borderRadius: 2, marginRight: 4 }}> |
|||
{ite} |
|||
</div> : '' |
|||
} |
|||
{ |
|||
arrStr.length > 2 && idx == 2 ? |
|||
<Tooltip content={arrStr.join(',')} trigger="click" style={{ lineHeight: 2 }}> |
|||
<div style={{ padding: '0px 4px 1px 4px ', color: 'rgba(0,90,189,0.8)', fontSize: 12, marginRight: 4, cursor: "pointer" }}> |
|||
+{arrStr.length - 2} |
|||
</div> |
|||
</Tooltip> |
|||
: '' |
|||
} |
|||
</div> |
|||
) |
|||
}) |
|||
} |
|||
</div> |
|||
} |
|||
|
|||
const columns = [{ |
|||
title: '序号', |
|||
dataIndex: 'id', |
|||
key: 'id', |
|||
width: '5%', |
|||
render: (text, record, index) => index + 1 |
|||
}, { |
|||
title: starHeader('姓名'), |
|||
dataIndex: 'name', |
|||
key: 'name', |
|||
width: '5%' |
|||
}, |
|||
{ |
|||
title: starHeader('部门名称'), |
|||
dataIndex: 'department', |
|||
key: 'department', |
|||
width: '15%', |
|||
render: (text, r, index) => { |
|||
let arrStr = text.map(t => t.name); |
|||
return getMultis(arrStr); |
|||
} |
|||
}, { |
|||
title: '销售区域(省/直辖市)', |
|||
dataIndex: 'provinces', |
|||
key: 'provinces', |
|||
width: '15%', |
|||
render: (text, record, index) => { |
|||
return getMultis(text); |
|||
} |
|||
}, { |
|||
title: '销售区域(市)', |
|||
dataIndex: 'cities', |
|||
key: 'cities', |
|||
width: '15%', |
|||
render: (text, record, index) => { |
|||
return getMultis(text); |
|||
} |
|||
}, { |
|||
title: starHeader('岗位'), |
|||
dataIndex: 'post', |
|||
key: 'post', |
|||
width: '10%', |
|||
}, { |
|||
title: starHeader('入职时间'), |
|||
dataIndex: 'hireDate', |
|||
key: 'hireDate', |
|||
width: '10%', |
|||
render: (text, record) => <span>{text || '-'}</span> |
|||
}, { |
|||
title: starHeader('转正时间'), |
|||
dataIndex: 'regularDate', |
|||
key: 'regularDate', |
|||
width: '10%', |
|||
render: (text, record) => <span>{text || '-'}</span> |
|||
}, { |
|||
title: starHeader('工龄'), |
|||
dataIndex: 'workYears', |
|||
key: 'workYears', |
|||
width: '5%', |
|||
render: (_, r, index) => { |
|||
return (r.hireDate ? <span style={{ color: '#1890FF' }}>{String(moment(new Date()).diff(r.hireDate, 'years', true)).substring(0, 3) + '年'}</span> : '-') |
|||
}, |
|||
}, { |
|||
title: '操作', |
|||
dataIndex: 'action', |
|||
width: '10%', |
|||
render: (text, record) => { |
|||
return <div> |
|||
<span style={{ color: '#1890FF', cursor: 'pointer' }} onClick={() => onEdit(record)}>编辑</span> |
|||
<Popconfirm |
|||
title='提示' content="确认删除该销售人员信息?" position='topLeft' |
|||
onConfirm={() => confirmDelete(record.pepUserId)} style={{ width: 330 }} |
|||
> <span style={{ color: '#1890FF', cursor: 'pointer' }}>删除</span></Popconfirm> |
|||
</div> |
|||
} |
|||
}]; |
|||
|
|||
const onEdit = (data) => { |
|||
setModalV(true); |
|||
setDataToEdit(data); |
|||
} |
|||
|
|||
const confirmDelete = (pepUserId) => { |
|||
dispatch(humanAffairs.delSalesMember({ pepUserId, msg: '删除销售人员信息' })).then(res => { |
|||
if (res.success) { |
|||
getMemberSearchList(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return (<div style={{ padding: '0px 12px' }}> |
|||
<div style={{ display: 'flex' }}> |
|||
<div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14 }}>招聘</div> |
|||
<div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14, margin: '0px 8px' }}>/</div> |
|||
<div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14 }}>销售统计</div> |
|||
<div style={{ color: '#033C9A', fontSize: 14, margin: '0px 8px' }}>/</div> |
|||
<div style={{ color: '#033C9A', fontSize: 14 }}>销售人员分布</div> |
|||
</div> |
|||
<div style={{ background: '#FFFFFF', boxShadow: '0px 0px 12px 2px rgba(220,222,224,0.2)', borderRadius: 2, padding: '20px 0px 20px 19px ', marginTop: 12 }}> |
|||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
|||
<div style={{ display: 'flex', alignItems: 'baseline' }}> |
|||
<div style={{ width: 0, height: 20, borderLeft: '3px solid #0F7EFB', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div> |
|||
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#033C9A', marginLeft: 8 }}>销售人员分布</div> |
|||
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>DISTRIBUTION OF SALES PERSONNEL</div> |
|||
</div> |
|||
</div> |
|||
<div style={{ margin: '18px 0px' }}> |
|||
<div style={{ display: 'flex', margin: '16px 0px', justifyContent: 'space-between' }}> |
|||
<div style={{ display: 'flex' }}> |
|||
<div style={{ padding: '6px 20px', background: '#0F7EFB', color: '#FFFFFF', fontSize: 14, cursor: "pointer", marginRight: 18 }} |
|||
onClick={() => { |
|||
setModalV(true); |
|||
setDataToEdit(null); |
|||
}}> |
|||
新增 |
|||
</div> |
|||
<div> |
|||
<Select value={keywordTarget} onChange={setKeywordTarget} style={{ width: 100 }} > |
|||
<Select.Option value='dep'>部门</Select.Option> |
|||
<Select.Option value='place'>地区</Select.Option> |
|||
</Select> |
|||
</div> |
|||
<div style={{ margin: '0px 18px' }}> |
|||
<Input suffix={<IconSearch />} |
|||
showClear |
|||
placeholder='请输入关键词搜索' |
|||
value={keyword} |
|||
style={{ width: 346 }} |
|||
onChange={seachValueChange}> |
|||
</Input> |
|||
</div> |
|||
<Button theme='solid' type='primary' style={{ width: 80, borderRadius: 2, height: 32, background: '#DBECFF', color: '#005ABD' }} |
|||
onClick={() => { |
|||
setQuery({ limit: 10, page: 0 }) |
|||
}}>查询</Button> |
|||
</div> |
|||
<div style={{ display: 'flex', marginRight: 20 }}> |
|||
<div style={{ padding: '6px 20px', background: '#0F7EFB', color: '#FFFFFF', fontSize: 14, cursor: "pointer" }} |
|||
onClick={() => { setImportModalV(true); }}> |
|||
导入 |
|||
</div> |
|||
<div style={{ padding: '6px 20px', background: '#00BA85', color: '#FFFFFF', fontSize: 14, marginLeft: 18 }}> |
|||
导出 |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div style={{ border: '1px solid #C7E1FF', background: '#F4F8FF', borderRadius: 2, height: 32, width: 669, padding: '8px 0px 7px 12px', display: 'flex', alignItems: 'center', color: '#0F7EFB', fontSize: 12 }}> |
|||
<img src="/assets/images/hrImg/!.png" alt="" style={{ width: 14, height: 14, marginRight: 8 }} /> |
|||
表格中带有认证标识" |
|||
<img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> |
|||
"信息的为系统基础数据,来源于项企PEP、钉钉等系统,其他数据均为导入或自定义数据 |
|||
</div> |
|||
|
|||
<div style={{ marginTop: 20 }}> |
|||
<Skeleton |
|||
// loading={loading} |
|||
loading={false} |
|||
active={true} |
|||
placeholder={SkeletonScreen()} |
|||
> |
|||
<Table |
|||
columns={columns} |
|||
dataSource={tableData} |
|||
bordered={false} |
|||
empty="暂无数据" |
|||
pagination={false} |
|||
onChange={({ sorter }) => { |
|||
if (sorter.key == 'userCode') { |
|||
if (sorter.sortOrder == 'descend') { |
|||
setOrder({ orderBy: 'code', orderDirection: 'DESC' }) |
|||
} else { |
|||
setOrder({ orderBy: 'code', orderDirection: 'ASC' }) |
|||
} |
|||
} else if (sorter.key == 'age') { |
|||
if (sorter.sortOrder == 'descend') { |
|||
setOrder({ orderBy: 'age', orderDirection: 'DESC' }) |
|||
} else { |
|||
setOrder({ orderBy: 'age', orderDirection: 'ASC' }) |
|||
} |
|||
} else { |
|||
if (sorter.sortOrder == 'descend') { |
|||
setOrder({ orderBy: 'hiredate', orderDirection: 'DESC' }) |
|||
} else { |
|||
setOrder({ orderBy: 'hiredate', orderDirection: 'ASC' }) |
|||
} |
|||
} |
|||
}} |
|||
onRow={handleRow} |
|||
scroll={scroll} |
|||
/> |
|||
</Skeleton> |
|||
<div style={{ |
|||
display: "flex", |
|||
justifyContent: "space-between", |
|||
padding: "20px 20px", |
|||
}}> |
|||
<div style={{ display: 'flex', }}> |
|||
<span style={{ lineHeight: "30px", fontSize: 13, color: 'rgba(0,90,189,0.8)' }}> |
|||
共{limits}条信息 |
|||
</span> |
|||
<Pagination |
|||
total={limits} |
|||
showSizeChanger |
|||
currentPage={query.page + 1} |
|||
pageSizeOpts={[10, 20, 30, 40]} |
|||
onChange={(currentPage, pageSize) => { |
|||
setQuery({ limit: pageSize, page: currentPage - 1 }); |
|||
page.current = currentPage - 1 |
|||
}} |
|||
/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{ |
|||
modalV ? <SalesMemberModal |
|||
dataToEdit={dataToEdit} getMultis={getMultis} |
|||
close={() => closeAndFetch()} |
|||
onCancel={() => setModalV(false)} /> : '' |
|||
} |
|||
{ |
|||
importModalV ? <ImportSalersModal |
|||
onCancel={() => { |
|||
setImportModalV(false); |
|||
getMemberSearchList(); |
|||
}} /> : '' |
|||
} |
|||
</div>) |
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { auth, global, SalesMemberList } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
salesMemberList: SalesMemberList.data |
|||
}; |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(PersonnelDistribution); |
@ -0,0 +1,244 @@ |
|||
import React, { useEffect, useRef, useState } from 'react'; |
|||
import moment from 'moment'; |
|||
import { connect } from "react-redux"; |
|||
import { Select, Modal, Form, Notification } from "@douyinfe/semi-ui"; |
|||
import cityData from '../../components/city.json'; |
|||
const SalesMemberModal = (props) => { |
|||
const { dispatch, actions, user, meetingList, onConfirm, getMultis, onCancel, close, rzMembers, dataToEdit } = props; |
|||
const { humanAffairs } = actions; |
|||
const form = useRef();//表单
|
|||
const [options, setOptions] = useState([]); |
|||
const [cityOptions, setCityOptions] = useState([]); |
|||
const [peoplePro, setPeoplePro] = useState({}); //人员信息
|
|||
//初始化
|
|||
useEffect(() => { |
|||
let optionItems = cityData.map(m => { |
|||
return <Select.Option value={m.name} key={m.code}> |
|||
{m.name} |
|||
</Select.Option> |
|||
}) |
|||
setOptions(optionItems); |
|||
if (dataToEdit) { |
|||
setPeoplePro(dataToEdit); |
|||
onChange(dataToEdit.provinces);//市options
|
|||
} |
|||
}, []); |
|||
|
|||
const onChange = (value) => { |
|||
let cityOptions = [], citiesRange = []; |
|||
cityData.filter(cd => value.indexOf(cd.name) !== -1).map(d => { |
|||
d.children?.map(c => { |
|||
cityOptions.push(<Select.Option value={c.name} key={c.code}> |
|||
{c.name} |
|||
</Select.Option>) |
|||
citiesRange.push(c.name) |
|||
}) |
|||
}) |
|||
setCityOptions(cityOptions) |
|||
|
|||
let citiesValue = form?.current?.getValues()?.cities || []; |
|||
if (citiesValue.length) { |
|||
let newCities = []; |
|||
citiesValue?.map(cv => { |
|||
if (citiesRange.indexOf(cv) != -1) { |
|||
newCities.push(cv); |
|||
} |
|||
}) |
|||
form.current.setValue('cities', newCities) |
|||
} |
|||
} |
|||
|
|||
function handleOk() { |
|||
form.current.validate().then((values) => { |
|||
if (peoplePro?.userCode) { |
|||
if (values.userCode == peoplePro.userCode) { |
|||
if (dataToEdit) { |
|||
dispatch(humanAffairs.editSalesMember({ pepUserId: peoplePro.pepUserId, msg: '编辑销售人员信息', ...values })).then((res) => { |
|||
if (res.success) { |
|||
close(); |
|||
} |
|||
}) |
|||
} else { |
|||
dispatch(humanAffairs.postSalesMember({ pepUserId: peoplePro.pepUserId, msg: '新增销售人员', ...values })).then((res) => { |
|||
if (res.success) { |
|||
close(); |
|||
} |
|||
}) |
|||
} |
|||
} else { |
|||
Notification.error({ |
|||
content: '你填写的员工编号无对应的人员信息', |
|||
duration: 2, |
|||
}) |
|||
} |
|||
} else { |
|||
Notification.error({ |
|||
content: '你填写的员工编号无对应的人员信息', |
|||
duration: 2, |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const memberSeach = (id) => {//搜索项企用户
|
|||
dispatch(humanAffairs.getMemberSearch({ code: id })).then((res) => {//搜索项企用户
|
|||
if (res.success) { |
|||
if (res.payload.data.length) { |
|||
let user = res.payload.data[0] |
|||
let exist = rzMembers.find(m => m.pepUserId == user.pepUserId);//人员档案里面需要有
|
|||
if (exist) { |
|||
let item = { |
|||
pepUserId: user.pepUserId, |
|||
name: exist.userName, |
|||
department: exist.departmrnt, |
|||
hireDate: exist.hiredate, |
|||
regularDate: exist.regularDate, |
|||
userCode: user.userCode, |
|||
post: '岗位todo' |
|||
} |
|||
setPeoplePro(item) |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const renderSimpleInfo = () => { |
|||
let arrStr = peoplePro?.department?.map(t => t.name) || []; |
|||
return <div> |
|||
{ |
|||
peoplePro?.name ? ( |
|||
<div style={{ width: 749, height: 95, background: '#F4F5FC', border: '1px solid rgba(0, 90, 189, 0.2)', marginTop: 8, padding: '20px 83px 20px 50px' }}> |
|||
<div style={{ display: 'flex' }}> |
|||
{renderPeopleItem('姓名:', peoplePro.name)} |
|||
<div style={{ display: 'flex', flexBasis: '50%' }}> |
|||
<div style={{ width: 16, height: 16, marginRight: 9 }}> |
|||
<img src="/assets/images/hrImg/department.png" alt="" style={{ width: '100%', height: '100%' }} /> |
|||
</div> |
|||
<div style={{ color: 'rgba(0,0,0,0.6)', fontSize: 12 }}> |
|||
所属部门: |
|||
</div> |
|||
{getMultis(arrStr)} |
|||
</div> |
|||
</div> |
|||
<div style={{ display: 'flex' }}> |
|||
{renderPeopleItem('入职时间:', peoplePro.hireDate)} |
|||
{renderPeopleItem('岗位:', peoplePro.post)} |
|||
</div> <div style={{ display: 'flex' }}> |
|||
{renderPeopleItem('转正时间:', peoplePro.regularDate)} |
|||
{renderPeopleItem('工龄:', peoplePro.hireDate ? String(moment(new Date()).diff(peoplePro.hireDate, 'years', true)).substring(0, 3) + '年' : '-')} |
|||
</div> |
|||
</div> |
|||
) : ('') |
|||
} |
|||
</div> |
|||
} |
|||
|
|||
const renderPeopleItem = (label, value) => { |
|||
return <div style={{ display: 'flex', flexBasis: '50%' }}> |
|||
<div style={{ width: 16, height: 16, marginRight: 9 }}> |
|||
<img src="/assets/images/hrImg/department.png" style={{ width: '100%', height: '100%' }} /> |
|||
</div> |
|||
<div style={{ color: 'rgba(0,0,0,0.6)', fontSize: 12 }}> |
|||
{label} |
|||
</div> |
|||
<div style={{ color: '#4A4A4A', fontSize: 12 }}> |
|||
{value} |
|||
</div> |
|||
</div> |
|||
} |
|||
|
|||
// const handleDeselect = (value) => {//删除
|
|||
// let ranges = cityData.find(td => td.name == value)?.children || []
|
|||
// if (ranges) {
|
|||
// let formList = form.current.getValues().cities;
|
|||
// }
|
|||
|
|||
// }
|
|||
|
|||
const onClear = () => { |
|||
form.current.setValue('cities', []) |
|||
} |
|||
|
|||
return ( |
|||
<Modal title={dataToEdit?.pepUserId ? '修改销售人员信息' : '新增销售人员'} |
|||
visible={true} |
|||
destroyOnClose |
|||
okText='保存' width={800} |
|||
onOk={handleOk} |
|||
onCancel={onCancel}> |
|||
<Form getFormApi={(formApi) => (form.current = formApi)} |
|||
labelPosition={'left'} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}> |
|||
<Form.Input |
|||
field="userCode" |
|||
label='人员编号' |
|||
initValue={dataToEdit?.userCode || ""} |
|||
placeholder="请输入人员编号" |
|||
showClear |
|||
style={{ width: '100%' }} |
|||
rules={[{ required: true, message: "请输入人员编号" }]} |
|||
addonAfter={<div style={{ margin: '0px 12px', color: '#005ABD', cursor: "pointer", fontSize: 14 }} onClick={() => { |
|||
let formList = form.current.getValues() |
|||
if (formList.userCode) { |
|||
memberSeach(formList.userCode) |
|||
} |
|||
}}> |
|||
搜索 |
|||
</div>} /> |
|||
|
|||
{peoplePro ? renderSimpleInfo() : ''} |
|||
<Form.Select |
|||
initValue={dataToEdit?.provinces || []} |
|||
label="销售区域(省/直辖市)" |
|||
field='provinces' |
|||
showClear |
|||
rules={[{ required: true, message: '请选择销售区域(省/直辖市)' }]} |
|||
placeholder='请选择销售区域(省/直辖市)' |
|||
multiple filter |
|||
style={{ width: '100%' }} |
|||
// optionFilterProp='children'
|
|||
// getPopupContainer={triggerNode => triggerNode.parentNode}
|
|||
// filterOption={(input, option) => option.props.children
|
|||
// .toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
|||
// value={selectedKeys || []}
|
|||
onClear={() => onClear()} |
|||
onChange={value => onChange(value)} |
|||
//onDeselect={value => handleDeselect(value)}
|
|||
maxTagCount={5} |
|||
> |
|||
{options} |
|||
</Form.Select> |
|||
<Form.Select |
|||
initValue={dataToEdit?.cities || []} |
|||
label="销售区域(市)" |
|||
field='cities' |
|||
showClear |
|||
placeholder='请选择销售区域(市)' |
|||
multiple filter |
|||
style={{ width: '100%' }} |
|||
// optionFilterProp='children'
|
|||
// getPopupContainer={triggerNode => triggerNode.parentNode}
|
|||
// filterOption={(input, option) => option.props.children
|
|||
// .toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
|||
// value={selectedKeys || []}
|
|||
//onDeselect={value => handleDeselect(value)}
|
|||
maxTagCount={5} |
|||
> |
|||
{cityOptions} |
|||
</Form.Select> |
|||
</Form> |
|||
</Modal> |
|||
) |
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { auth, global, MemberList } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
apiRoot: global.apiRoot, |
|||
rzMembers: MemberList.data?.rows || [], |
|||
}; |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(SalesMemberModal); |
Loading…
Reference in new issue