28 changed files with 1658 additions and 174 deletions
@ -0,0 +1,336 @@ |
|||||
|
'use strict'; |
||||
|
const moment = require('moment') |
||||
|
const fs = require('fs'); |
||||
|
|
||||
|
async function salesList(ctx) { |
||||
|
try { |
||||
|
const { memberList, packageUserData } = ctx.app.fs.utils |
||||
|
const { |
||||
|
keywordTarget, keyword, limit, page, state, |
||||
|
hiredateStart, hiredateEnd, marital, native, workPlace, |
||||
|
orderBy, orderDirection, placeSearch |
||||
|
} = ctx.query |
||||
|
|
||||
|
const userRes = await memberList({ |
||||
|
keywordTarget, keyword, limit: '', page: '', state, |
||||
|
hiredateStart, hiredateEnd, marital, native, workPlace, |
||||
|
orderBy, orderDirection, |
||||
|
nowAttendanceTime: true |
||||
|
}) |
||||
|
|
||||
|
let { packageUser: members } = await packageUserData(userRes, { |
||||
|
state: true, |
||||
|
}) |
||||
|
|
||||
|
const { models } = ctx.fs.dc; |
||||
|
|
||||
|
let mIds = members.map(m => m.pepUserId); |
||||
|
|
||||
|
|
||||
|
let where = { |
||||
|
del: false, |
||||
|
pepUserId: { $in: mIds } |
||||
|
} |
||||
|
if (placeSearch) { |
||||
|
where.$or = [{ |
||||
|
provinces: { $like: `%${placeSearch}%` } |
||||
|
}, { |
||||
|
cities: { $like: `%${placeSearch}%` } |
||||
|
}] |
||||
|
} |
||||
|
let res = await models.SalesDistribution.findAndCountAll({ |
||||
|
where: where, |
||||
|
offset: Number(page) * Number(limit), |
||||
|
limit: Number(limit), |
||||
|
order: [['id', 'DESC']] |
||||
|
}) |
||||
|
|
||||
|
let rslt = [] |
||||
|
res.rows.map(d => { |
||||
|
let info = members.find(m => m.pepUserId == d.dataValues.pepUserId); |
||||
|
let item = { |
||||
|
name: info.userName, |
||||
|
userCode: info.userCode, |
||||
|
post: info.userPost, |
||||
|
department: info.departmrnt, |
||||
|
hireDate: info.hiredate,//入职时间
|
||||
|
regularDate: info.regularDate,//转正时间
|
||||
|
...d.dataValues |
||||
|
} |
||||
|
rslt.push(item); |
||||
|
}) |
||||
|
ctx.status = 200; |
||||
|
ctx.body = { |
||||
|
count: res.count, |
||||
|
rows: rslt |
||||
|
}; |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = { |
||||
|
message: typeof error == 'string' ? error : undefined |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function add(ctx) { |
||||
|
try { |
||||
|
const { models } = ctx.fs.dc; |
||||
|
const { pepUserId, provinces, cities, businessLines } = ctx.request.body |
||||
|
|
||||
|
const existRes = await models.SalesDistribution.findOne({ |
||||
|
where: { pepUserId } |
||||
|
}) |
||||
|
|
||||
|
if (existRes && !existRes.del) { |
||||
|
throw '当前销售人员信息已存在' |
||||
|
} |
||||
|
|
||||
|
let storageData = { pepUserId, provinces, cities, businessLines, del: false } |
||||
|
if (existRes && existRes.del) { |
||||
|
await models.SalesDistribution.update(storageData, { |
||||
|
where: { pepUserId } |
||||
|
}) |
||||
|
} else { |
||||
|
await models.SalesDistribution.create(storageData) |
||||
|
} |
||||
|
ctx.status = 204; |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = { |
||||
|
message: typeof error == 'string' ? error : undefined |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function edit(ctx) { |
||||
|
try { |
||||
|
const { models } = ctx.fs.dc; |
||||
|
const { pepUserId, provinces, cities, businessLines } = ctx.request.body |
||||
|
|
||||
|
const existRes = await models.SalesDistribution.findOne({ |
||||
|
where: { pepUserId } |
||||
|
}) |
||||
|
|
||||
|
if (!existRes) { |
||||
|
throw '当前销售人员信息不存在' |
||||
|
} |
||||
|
|
||||
|
let storageData = { pepUserId, provinces, cities, businessLines, del: false } |
||||
|
|
||||
|
await models.SalesDistribution.update(storageData, { |
||||
|
where: { |
||||
|
pepUserId: pepUserId |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
ctx.status = 204; |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = { |
||||
|
message: typeof error == 'string' ? error : undefined |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function del(ctx) { |
||||
|
try { |
||||
|
const { models } = ctx.fs.dc; |
||||
|
const { pepUserId } = ctx.query |
||||
|
|
||||
|
await models.SalesDistribution.update({ |
||||
|
del: true, |
||||
|
}, { |
||||
|
where: { |
||||
|
pepUserId, |
||||
|
} |
||||
|
}) |
||||
|
ctx.status = 204; |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = { |
||||
|
message: typeof error == 'string' ? error : undefined |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// async function exportData(ctx) {
|
||||
|
// try {
|
||||
|
// const { models } = ctx.fs.dc;
|
||||
|
// const { clickHouse, opts: { qiniu } } = ctx.app.fs
|
||||
|
// const { simpleExcelDown, memberList, packageUserData } = ctx.app.fs.utils
|
||||
|
// const {
|
||||
|
// keywordTarget, keyword, limit, page, state, keys = '',
|
||||
|
// hiredateStart, hiredateEnd, marital, native, workPlace,
|
||||
|
// orderBy, orderDirection,
|
||||
|
// } = ctx.query
|
||||
|
|
||||
|
// const userRes = await memberList({
|
||||
|
// keywordTarget, keyword, limit, page, state,
|
||||
|
// hiredateStart, hiredateEnd, marital, native, workPlace,
|
||||
|
// orderBy, orderDirection,
|
||||
|
// nowAttendanceTime: true
|
||||
|
// })
|
||||
|
|
||||
|
// const tableAttributes = models['Member'].tableAttributes
|
||||
|
// const optionKeys = keys.split(',')
|
||||
|
|
||||
|
// let { packageUser: exportD, pepUserIds } = await packageUserData(userRes)
|
||||
|
|
||||
|
// let preHeader = [{
|
||||
|
// title: '员工编号',
|
||||
|
// key: 'userCode',
|
||||
|
// }, {
|
||||
|
// title: '姓名',
|
||||
|
// key: 'userName',
|
||||
|
// }]
|
||||
|
// let header = [].concat(preHeader)
|
||||
|
// for (let k in tableAttributes) {
|
||||
|
// const comment = tableAttributes[k].comment
|
||||
|
// if (k != 'id' && k != 'pepUserId' && comment) {
|
||||
|
// if ([].includes(k)) {
|
||||
|
// // 截住不想导出的字段
|
||||
|
// continue
|
||||
|
// }
|
||||
|
// header.push({
|
||||
|
// title: comment || '-',
|
||||
|
// key: k,
|
||||
|
// // index: tableAttributes[k].index,
|
||||
|
// })
|
||||
|
// }
|
||||
|
// }
|
||||
|
|
||||
|
// if (optionKeys.includes('overtimeStatistic')) {
|
||||
|
// header = header.concat([{
|
||||
|
// title: '累计加班次数',
|
||||
|
// key: 'overTimeCount',
|
||||
|
// }, {
|
||||
|
// title: '累计加班总时长 / h',
|
||||
|
// key: 'overTimeDuration',
|
||||
|
// },])
|
||||
|
// }
|
||||
|
// if (optionKeys.includes('vacateStatistic')) {
|
||||
|
// header = header.concat([{
|
||||
|
// title: '累计请假次数',
|
||||
|
// key: 'vacateCount',
|
||||
|
// }, {
|
||||
|
// title: '累计请假总时长 / h',
|
||||
|
// key: 'vacateDuration',
|
||||
|
// },])
|
||||
|
// }
|
||||
|
|
||||
|
// // 查询累计加班次数及总时长
|
||||
|
// const statisticOvertimeRes = await clickHouse.hr.query(`
|
||||
|
// SELECT
|
||||
|
// pep_user_id AS pepUserId,
|
||||
|
// count(id) AS count,
|
||||
|
// sum(duration) AS duration
|
||||
|
// FROM
|
||||
|
// overtime
|
||||
|
// WHERE pep_user_id IN (${pepUserIds.join(',')})
|
||||
|
// GROUP BY pep_user_id
|
||||
|
// `).toPromise()
|
||||
|
|
||||
|
// const statisticVacateRes = await clickHouse.hr.query(`
|
||||
|
// SELECT
|
||||
|
// pep_user_id AS pepUserId,
|
||||
|
// count(id) AS count,
|
||||
|
// sum(duration) AS duration
|
||||
|
// FROM
|
||||
|
// vacate
|
||||
|
// WHERE pep_user_id IN (${pepUserIds.join(',')})
|
||||
|
// GROUP BY pep_user_id
|
||||
|
// `).toPromise()
|
||||
|
|
||||
|
// exportD.forEach(d => {
|
||||
|
// d.departmrnt = d.departmrnt.map(dep => dep.name).join('、')
|
||||
|
// d.role = d.role.map(r => r.name).join('、')
|
||||
|
|
||||
|
// d.idPhoto ? d.idPhoto = qiniu.domain + '/' + d.idPhoto : ''
|
||||
|
// d.vitae ? d.vitae = qiniu.domain + '/' + d.vitae : ''
|
||||
|
|
||||
|
// const corOverTime = statisticOvertimeRes.find(so => so.pepUserId == d.pepUserId)
|
||||
|
// d.overTimeCount = corOverTime ? corOverTime.count : 0
|
||||
|
// d.overTimeDuration = corOverTime ? (corOverTime.duration / 3600).toFixed(1) : 0
|
||||
|
// const corVacate = statisticVacateRes.find(so => so.pepUserId == d.pepUserId)
|
||||
|
// d.vacateCount = corVacate ? corVacate.count : 0
|
||||
|
// d.vacateDuration = corVacate ? (corVacate.duration / 3600).toFixed(1) : 0
|
||||
|
// })
|
||||
|
|
||||
|
// const fileName = `人员信息_${moment().format('YYYYMMDDHHmmss')}` + '.csv'
|
||||
|
// const filePath = await simpleExcelDown({ data: exportD, header, fileName: fileName })
|
||||
|
// const fileData = fs.readFileSync(filePath);
|
||||
|
|
||||
|
// ctx.status = 200;
|
||||
|
// ctx.set('Content-Type', 'application/x-xls');
|
||||
|
// ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName));
|
||||
|
// ctx.body = fileData;
|
||||
|
// } catch (error) {
|
||||
|
// ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
|
||||
|
// ctx.status = 400;
|
||||
|
// ctx.body = {
|
||||
|
// message: typeof error == 'string' ? error : undefined
|
||||
|
// }
|
||||
|
// }
|
||||
|
// }
|
||||
|
|
||||
|
async function addSalesMemberBulk(ctx) { |
||||
|
let errorMsg = { message: '导入销售人员信息失败' }; |
||||
|
const transaction = await ctx.fs.dc.orm.transaction(); |
||||
|
try { |
||||
|
const models = ctx.fs.dc.models; |
||||
|
const data = ctx.request.body; |
||||
|
let addArr = []; |
||||
|
let editArr = []; |
||||
|
let list = await models.SalesDistribution.findAll({ |
||||
|
attributes: ['pepUserId'] |
||||
|
}); |
||||
|
data.map(d => { |
||||
|
let exist = list.find(m => m.pepUserId == d.pepUserId);//项企的人员编号字段还没有
|
||||
|
if (exist) { |
||||
|
editArr.push(d); |
||||
|
} else { |
||||
|
addArr.push(d); |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
//处理新增的
|
||||
|
if (addArr.length) { |
||||
|
await models.SalesDistribution.bulkCreate(addArr); |
||||
|
} |
||||
|
|
||||
|
//处理编辑的
|
||||
|
if (editArr.length) { |
||||
|
for (let i in editArr) { |
||||
|
let { pepUserId, provinces, cities, businessLines, del = false } = editArr[i]; |
||||
|
|
||||
|
let dataToUpdate = { |
||||
|
provinces, |
||||
|
cities, |
||||
|
businessLines, |
||||
|
del |
||||
|
} |
||||
|
await models.SalesDistribution.update(dataToUpdate, { where: { pepUserId: pepUserId } }); |
||||
|
} |
||||
|
} |
||||
|
await transaction.commit(); |
||||
|
ctx.status = 204; |
||||
|
} catch (error) { |
||||
|
await transaction.rollback(); |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = errorMsg; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
salesList, |
||||
|
add, |
||||
|
edit, |
||||
|
del, |
||||
|
//exportData,
|
||||
|
addSalesMemberBulk, |
||||
|
} |
@ -0,0 +1,71 @@ |
|||||
|
/* eslint-disable*/ |
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = dc => { |
||||
|
const DataTypes = dc.ORM; |
||||
|
const sequelize = dc.orm; |
||||
|
const SalesDistribution = sequelize.define("salesDistribution", { |
||||
|
id: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
allowNull: false, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: true, |
||||
|
field: "id", |
||||
|
autoIncrement: true, |
||||
|
unique: "sales_distribution_id_uindex" |
||||
|
}, |
||||
|
pepUserId: { |
||||
|
type: DataTypes.INTEGER, |
||||
|
allowNull: false, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "pep_user_id", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
provinces: { |
||||
|
type: DataTypes.STRING, |
||||
|
allowNull: false, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "provinces", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
cities: { |
||||
|
type: DataTypes.STRING, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "cities", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
del: { |
||||
|
type: DataTypes.BOOLEAN, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "del", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
businessLines: { |
||||
|
type: DataTypes.STRING, |
||||
|
allowNull: true, |
||||
|
defaultValue: null, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "business_lines", |
||||
|
autoIncrement: false |
||||
|
} |
||||
|
}, { |
||||
|
tableName: "sales_distribution", |
||||
|
comment: "", |
||||
|
indexes: [] |
||||
|
}); |
||||
|
dc.models.SalesDistribution = SalesDistribution; |
||||
|
return SalesDistribution; |
||||
|
}; |
@ -0,0 +1,24 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const salesDistribution = require('../../controllers/salesDistribution'); |
||||
|
|
||||
|
module.exports = function (app, router, opts) { |
||||
|
|
||||
|
app.fs.api.logAttr['GET/sales/member/list'] = { content: '查询销售人员列表', visible: true }; |
||||
|
router.get('/sales/member/list', salesDistribution.salesList); |
||||
|
|
||||
|
app.fs.api.logAttr['POST/sales/member/add'] = { content: '添加销售人员信息', visible: true }; |
||||
|
router.post('/sales/member/add', salesDistribution.add); |
||||
|
|
||||
|
app.fs.api.logAttr['PUT/sales/member/modify'] = { content: '编辑销售人员信息', visible: true }; |
||||
|
router.put('/sales/member/modify', salesDistribution.edit); |
||||
|
|
||||
|
app.fs.api.logAttr['DEL/sales/member/del'] = { content: '删除销售人员信息', visible: true }; |
||||
|
router.del('/sales/member/del', salesDistribution.del); |
||||
|
|
||||
|
app.fs.api.logAttr['POST/add/sales/members/bulk'] = { content: '导入销售人员信息', visible: true }; |
||||
|
router.post('/add/sales/members/bulk', salesDistribution.addSalesMemberBulk); |
||||
|
|
||||
|
// app.fs.api.logAttr['GET/sales/members/export'] = { content: '导出销售人员信息', visible: true };
|
||||
|
// router.get('/sales/members/export', salesDistribution.exportData);
|
||||
|
}; |
@ -0,0 +1,11 @@ |
|||||
|
|
||||
|
|
||||
|
CREATE TABLE sales_distribution |
||||
|
( |
||||
|
id serial PRIMARY KEY NOT NULL, |
||||
|
pep_user_id int NOT NULL, |
||||
|
provinces text NOT NULL, |
||||
|
cities text, |
||||
|
del boolean DEFAULT false NULL |
||||
|
); |
||||
|
CREATE UNIQUE INDEX sales_distribution_id_uindex ON sales_distribution (id); |
@ -0,0 +1,4 @@ |
|||||
|
|
||||
|
|
||||
|
alter table sales_distribution |
||||
|
add "business_lines" text; |
@ -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: {}, |
||||
|
}); |
||||
|
} |
@ -0,0 +1,272 @@ |
|||||
|
'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' |
||||
|
//下载模板和上传文件读取
|
||||
|
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) => { |
||||
|
if (!cities) {//可以不填
|
||||
|
return true; |
||||
|
} |
||||
|
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} width={620} |
||||
|
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['销售区域(市)']); |
||||
|
let businessLines = 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 |
||||
|
} |
||||
|
//todo 业务线判断
|
||||
|
postData.push({ |
||||
|
pepUserId: rzExist.pepUserId, name, number, |
||||
|
provinces: provinces || '', cities: cities || '', businessLines: businessLines || '', |
||||
|
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> |
||||
|
<div style={{ marginTop: 20, border: '1px solid #C7E1FF', background: '#F4F8FF', borderRadius: 2, padding: '8px 0px 7px 12px', alignItems: 'center', color: '#0F7EFB', fontSize: 12 }}> |
||||
|
<div>填写要求:</div> |
||||
|
<div>员工编号:必填,唯一,数字和字母的组合;</div> |
||||
|
<div>姓名:必填,若与员工编号对应的项企用户姓名不同,将以项企数据为准;</div> |
||||
|
<div>销售区域(省/直辖市):必填,省或直辖市顿号隔开,如“北京市、江西省、江苏省”;</div> |
||||
|
<div>销售区域(市):非必填,归属所填省的地级市顿号隔开,如“南昌市、镇江市”。</div> |
||||
|
</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,350 @@ |
|||||
|
import React, { useEffect, useRef, useState, useMemo } 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.length ? |
||||
|
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: 60, |
||||
|
render: (text, record, index) => index + 1 |
||||
|
}, { |
||||
|
title: starHeader('姓名'), |
||||
|
dataIndex: 'name', |
||||
|
key: 'name', |
||||
|
width: 80 |
||||
|
}, |
||||
|
{ |
||||
|
title: starHeader('部门名称'), |
||||
|
dataIndex: 'department', |
||||
|
key: 'department', |
||||
|
width: 200, |
||||
|
render: (text, r, index) => { |
||||
|
let arrStr = text.map(t => t.name); |
||||
|
return getMultis(arrStr); |
||||
|
} |
||||
|
}, { |
||||
|
title: '销售区域(省/直辖市)', |
||||
|
dataIndex: 'provinces', |
||||
|
key: 'provinces', |
||||
|
width: 160, |
||||
|
render: (text, record, index) => { |
||||
|
return getMultis(text?.split('、') || []); |
||||
|
} |
||||
|
}, { |
||||
|
title: '销售区域(市)', |
||||
|
dataIndex: 'cities', |
||||
|
key: 'cities', |
||||
|
width: 160, |
||||
|
render: (text, record, index) => { |
||||
|
return text ? getMultis(text?.split('、') || []) : '-'; |
||||
|
} |
||||
|
}, { |
||||
|
title: '业务线', |
||||
|
dataIndex: 'businessLines', |
||||
|
key: 'businessLines', |
||||
|
width: 120, |
||||
|
render: (text, record, index) => { |
||||
|
return text ? getMultis(text?.split('、') || []) : '-'; |
||||
|
} |
||||
|
}, { |
||||
|
title: starHeader('岗位'), |
||||
|
dataIndex: 'post', |
||||
|
key: 'post', |
||||
|
width: 120, |
||||
|
render: (text, record) => <span>{text || '-'}</span> |
||||
|
}, { |
||||
|
title: starHeader('入职时间'), |
||||
|
dataIndex: 'hireDate', |
||||
|
key: 'hireDate', |
||||
|
width: 120, |
||||
|
render: (text, record) => <span>{text || '-'}</span> |
||||
|
}, { |
||||
|
title: starHeader('转正时间'), |
||||
|
dataIndex: 'regularDate', |
||||
|
key: 'regularDate', |
||||
|
width: 120, |
||||
|
render: (text, record) => <span>{text || '-'}</span> |
||||
|
}, { |
||||
|
title: starHeader('工龄'), |
||||
|
dataIndex: 'workYears', |
||||
|
key: 'workYears', |
||||
|
width: 120, |
||||
|
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: 120, |
||||
|
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(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
const scroll = useMemo(() => ({}), []); |
||||
|
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></div> |
||||
|
<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,263 @@ |
|||||
|
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 businessLines = ['市政', '地灾', '水利', '智慧城市', '工地', '环保', '安防', '产品投标', '交通', '矿山', '产品线'] |
||||
|
const SalesMemberModal = (props) => { |
||||
|
const { dispatch, actions, user, meetingList, onConfirm, getMultis, onCancel, close, rzMembers, dataToEdit } = props; |
||||
|
const { humanAffairs } = actions; |
||||
|
const form = useRef();//表单
|
||||
|
const [lineOptions, setLineOptions] = useState([]); |
||||
|
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); |
||||
|
|
||||
|
let lineOptions = businessLines.map((l, index) => { |
||||
|
return <Select.Option value={l} key={index}> |
||||
|
{l} |
||||
|
</Select.Option> |
||||
|
}) |
||||
|
setLineOptions(lineOptions); |
||||
|
|
||||
|
if (dataToEdit) { |
||||
|
setPeoplePro(dataToEdit); |
||||
|
onChange(dataToEdit.provinces?.split('、') || []);//市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 == 'noValid') { |
||||
|
Notification.error({ |
||||
|
content: '你填写的员工编号无对应的人员信息', |
||||
|
duration: 2, |
||||
|
}) |
||||
|
} else if (peoplePro?.userCode) { |
||||
|
if (values.userCode == peoplePro.userCode) { |
||||
|
values.provinces = values.provinces.join('、') |
||||
|
values.cities = values.cities.join('、') |
||||
|
values.businessLines = values.businessLines.join('、') |
||||
|
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.userCode == id);//人员档案里面需要有
|
||||
|
if (exist) { |
||||
|
let item = { |
||||
|
pepUserId: exist.pepUserId, |
||||
|
name: exist.userName, |
||||
|
department: exist.departmrnt, |
||||
|
hireDate: exist.hiredate, |
||||
|
regularDate: exist.regularDate, |
||||
|
userCode: exist.userCode, |
||||
|
post: exist.userPost//岗位
|
||||
|
} |
||||
|
setPeoplePro(item) |
||||
|
} else { |
||||
|
setPeoplePro('noValid') |
||||
|
} |
||||
|
// } else {
|
||||
|
// setPeoplePro('noValid')
|
||||
|
// }
|
||||
|
// }
|
||||
|
// })
|
||||
|
} |
||||
|
|
||||
|
const renderSimpleInfo = () => { |
||||
|
let arrStr = peoplePro?.department?.map(t => t.name) || []; |
||||
|
return <div> |
||||
|
{ |
||||
|
peoplePro == 'noValid' ? |
||||
|
<div style={{ background: '#F4F5FC', border: '1px solid rgba(0, 90, 189, 0.2)', marginTop: 8, padding: '10px 0px', textAlign: 'center', color: '#4A4A4A' }}> |
||||
|
没有符合条件的结果 |
||||
|
</div> : |
||||
|
peoplePro?.name ? ( |
||||
|
<div style={{ 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 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: "请输入人员编号" }]} |
||||
|
onChange={() => setPeoplePro({})} |
||||
|
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?.split('、') || []} |
||||
|
label="销售区域(省/直辖市)" |
||||
|
field='provinces' |
||||
|
showClear |
||||
|
rules={[{ required: true, message: '请选择销售区域(省/直辖市)' }]} |
||||
|
placeholder='请选择销售区域(省/直辖市)' |
||||
|
multiple filter |
||||
|
style={{ width: '100%' }} |
||||
|
onClear={() => onClear()} |
||||
|
onChange={value => onChange(value)} |
||||
|
maxTagCount={5} |
||||
|
> |
||||
|
{options} |
||||
|
</Form.Select> |
||||
|
<Form.Select |
||||
|
initValue={dataToEdit?.cities ? dataToEdit?.cities?.split('、') : []} |
||||
|
label="销售区域(市)" |
||||
|
field='cities' |
||||
|
showClear |
||||
|
placeholder='请选择销售区域(市)' |
||||
|
multiple filter |
||||
|
style={{ width: '100%' }} |
||||
|
maxTagCount={5} |
||||
|
> |
||||
|
{cityOptions} |
||||
|
</Form.Select> |
||||
|
<Form.Select |
||||
|
initValue={dataToEdit?.businessLines ? dataToEdit?.businessLines?.split('、') : []} |
||||
|
label="业务线" |
||||
|
field='businessLines' |
||||
|
showClear |
||||
|
placeholder='请选择业务线' |
||||
|
multiple filter |
||||
|
style={{ width: '100%' }} |
||||
|
maxTagCount={5} |
||||
|
> |
||||
|
{lineOptions} |
||||
|
</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); |
@ -0,0 +1,8 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
export const UserAttribute = { |
||||
|
jobDataSource: ['普通员工', '中层', '高层'], |
||||
|
activeStatusDataSource: ['在职', '离职', '特殊状态-特殊账号'], |
||||
|
organizationDataSource: ['江西飞尚科技有限公司', '江西飞尚工程质量检测有限公司', |
||||
|
'江西飞尚科技有限公司江苏分公司', '江西汇派科技有限公司'], |
||||
|
}; |
Loading…
Reference in new issue