Browse Source

(*)销售统计-销售人员分布 web代码暂交

master
wuqun 2 years ago
parent
commit
64d830229c
  1. 5
      web/client/src/sections/humanAffairs/actions/index.js
  2. 89
      web/client/src/sections/humanAffairs/actions/salesDistribution.js
  3. 2
      web/client/src/sections/humanAffairs/containers/index.js
  4. 42
      web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx
  5. 261
      web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js
  6. 339
      web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx
  7. 244
      web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js
  8. 6
      web/client/src/utils/webapi.js

5
web/client/src/sections/humanAffairs/actions/index.js

@ -2,8 +2,9 @@
import * as personnelFiles from './personnelFiles' import * as personnelFiles from './personnelFiles'
import * as employeeInformation from './employeeInformation' import * as employeeInformation from './employeeInformation'
import * as salesDistribution from './salesDistribution'
export default { export default {
...personnelFiles, ...personnelFiles,
...employeeInformation ...employeeInformation,
...salesDistribution
} }

89
web/client/src/sections/humanAffairs/actions/salesDistribution.js

@ -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: {},
});
}

2
web/client/src/sections/humanAffairs/containers/index.js

@ -9,7 +9,7 @@ import LeaveStatistics from './leaveStatistics';
import OvertimeStatistics from './overtimeStatistics'; import OvertimeStatistics from './overtimeStatistics';
//招聘 //招聘
import AppointmentRecords from './appointmentRecords'; import AppointmentRecords from './appointmentRecords';
import PersonnelDistribution from './personnelDistribution'; import PersonnelDistribution from './salersDistribution/personnelDistribution';
//培训 //培训
import ResourceRepository from './resourceRepository'; import ResourceRepository from './resourceRepository';
//绩效考核 //绩效考核

42
web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx

@ -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);

261
web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js

@ -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);

339
web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx

@ -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>&nbsp;&nbsp;
<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);

244
web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js

@ -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);

6
web/client/src/utils/webapi.js

@ -31,6 +31,12 @@ export const ApiTable = {
getAttendanceVacate: 'attendance/vacate',//请假统计 getAttendanceVacate: 'attendance/vacate',//请假统计
getAttendanceVacateType: 'attendance/vacate/type',//请假类型 getAttendanceVacateType: 'attendance/vacate/type',//请假类型
getAttendanceOvertime: 'attendance/overtime',//加班统计 getAttendanceOvertime: 'attendance/overtime',//加班统计
getSalesList: 'sales/member/list',
addSalesMember: 'sales/member/add',
editSalesMember: 'sales/member/modify',
delSalesMember: 'sales/member/del',
addSalesMemberBulk: 'add/sales/members/bulk'
}; };
export const RouteTable = { export const RouteTable = {
apiRoot: "/api/root", apiRoot: "/api/root",

Loading…
Cancel
Save