巴林闲侠 2 years ago
parent
commit
48caac405a
  1. 41
      api/app/lib/controllers/member/index.js
  2. 336
      api/app/lib/controllers/salesDistribution/index.js
  3. 71
      api/app/lib/models/sales_distribution.js
  4. 24
      api/app/lib/routes/salesDistribution/index.js
  5. 8
      api/app/lib/utils/constant.js
  6. 20
      api/app/lib/utils/member.js
  7. 2
      api/app/lib/utils/xlsxDownload.js
  8. 11
      doc/scripts/0.0.4/schema/1.sales_distribution.sql
  9. 4
      doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql
  10. 2
      web/client/index.ejs
  11. 2
      web/client/index.html
  12. 5
      web/client/src/layout/components/header/contant.js
  13. 5
      web/client/src/sections/humanAffairs/actions/index.js
  14. 89
      web/client/src/sections/humanAffairs/actions/salesDistribution.js
  15. 27
      web/client/src/sections/humanAffairs/components/personnelModal.jsx
  16. 105
      web/client/src/sections/humanAffairs/containers/employeeInformation.jsx
  17. 2
      web/client/src/sections/humanAffairs/containers/import-members-modal.js
  18. 3
      web/client/src/sections/humanAffairs/containers/index.js
  19. 27
      web/client/src/sections/humanAffairs/containers/personnelFiles.jsx
  20. 58
      web/client/src/sections/humanAffairs/containers/personnelFilesDetail.jsx
  21. 272
      web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js
  22. 350
      web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx
  23. 263
      web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js
  24. 8
      web/client/src/sections/humanAffairs/nav-item.jsx
  25. 12
      web/client/src/sections/humanAffairs/routes.js
  26. 3
      web/client/src/utils/index.js
  27. 8
      web/client/src/utils/userAttribute.js
  28. 6
      web/client/src/utils/webapi.js

41
api/app/lib/controllers/member/index.js

@ -104,6 +104,7 @@ async function searchPepMember (ctx) {
SELECT SELECT
user.id AS pepUserId, user.id AS pepUserId,
user.people_code AS userCode, user.people_code AS userCode,
basicdata_post.name AS userPost,
user.name AS userName, user.name AS userName,
role.name AS roleName, role.name AS roleName,
role.id AS roleId, role.id AS roleId,
@ -115,6 +116,8 @@ async function searchPepMember (ctx) {
ON user_role.user = user.id ON user_role.user = user.id
LEFT JOIN role LEFT JOIN role
ON role.id = user_role.role ON role.id = user_role.role
LEFT JOIN basicdata_post
ON basicdata_post.id = user.post
LEFT JOIN department_user LEFT JOIN department_user
ON department_user.user = user.id ON department_user.user = user.id
LEFT JOIN department LEFT JOIN department
@ -146,6 +149,7 @@ async function searchPepMember (ctx) {
pepUserId: u.pepUserId, pepUserId: u.pepUserId,
name: u.userName, name: u.userName,
userCode: u.userCode, userCode: u.userCode,
userPost: u.userPost,
departmrnt: u.depId ? [{ departmrnt: u.depId ? [{
id: u.depId, id: u.depId,
name: u.depName name: u.depName
@ -153,7 +157,7 @@ async function searchPepMember (ctx) {
role: u.roleId ? [{ role: u.roleId ? [{
id: u.roleId, id: u.roleId,
name: u.roleName name: u.roleName
}] : [], }] : []
}) })
} }
}) })
@ -181,7 +185,13 @@ async function del (ctx) {
pepUserId, pepUserId,
} }
}) })
await models.SalesDistribution.update({//顺便把销售人员删了
del: true,
}, {
where: {
pepUserId,
}
})
ctx.status = 204; ctx.status = 204;
} catch (error) { } catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
@ -482,7 +492,7 @@ async function exportData (ctx) {
try { try {
const { models } = ctx.fs.dc; const { models } = ctx.fs.dc;
const { clickHouse, opts: { qiniu } } = ctx.app.fs const { clickHouse, opts: { qiniu } } = ctx.app.fs
const { simpleExcelDown, memberList, packageUserData } = ctx.app.fs.utils const { simpleExcelDown, memberList, packageUserData, UserAttribute } = ctx.app.fs.utils
const { const {
keywordTarget, keyword, limit, page, state, keys = '', keywordTarget, keyword, limit, page, state, keys = '',
hiredateStart, hiredateEnd, marital, native, workPlace, hiredateStart, hiredateEnd, marital, native, workPlace,
@ -507,6 +517,27 @@ async function exportData (ctx) {
}, { }, {
title: '姓名', title: '姓名',
key: 'userName', key: 'userName',
}, {
title: '所属部门',
key: 'departmrnt',
}, {
title: '职位',
key: 'userJob',
}, {
title: '岗位',
key: 'userPost',
}, {
title: '在职状态',
key: 'userActiveStatus',
}, {
title: '绩点',
key: 'point',
}, {
title: '归属机构',
key: 'userOrganization',
}, {
title: '技术职级等级',
key: 'technicalGrade',
}] }]
let header = [].concat(preHeader) let header = [].concat(preHeader)
for (let k in tableAttributes) { for (let k in tableAttributes) {
@ -570,6 +601,10 @@ async function exportData (ctx) {
d.departmrnt = d.departmrnt.map(dep => dep.name).join('、') d.departmrnt = d.departmrnt.map(dep => dep.name).join('、')
d.role = d.role.map(r => r.name).join('、') d.role = d.role.map(r => r.name).join('、')
d.userJob = d.userJob? UserAttribute.jobDataSource[d.userJob - 1] : '';
d.userActiveStatus = d.userActiveStatus? UserAttribute.activeStatusDataSource[d.userActiveStatus - 1] : '';
d.userOrganization = d.userOrganization? UserAttribute.organizationDataSource[d.userOrganization - 1] : '';
d.idPhoto ? d.idPhoto = qiniu.domain + '/' + d.idPhoto : '' d.idPhoto ? d.idPhoto = qiniu.domain + '/' + d.idPhoto : ''
d.vitae ? d.vitae = qiniu.domain + '/' + d.vitae : '' d.vitae ? d.vitae = qiniu.domain + '/' + d.vitae : ''

336
api/app/lib/controllers/salesDistribution/index.js

@ -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,
}

71
api/app/lib/models/sales_distribution.js

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

24
api/app/lib/routes/salesDistribution/index.js

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

8
api/app/lib/utils/constant.js

@ -12,9 +12,15 @@ module.exports = function (app, opts) {
'发放加班补偿': '折算', '发放加班补偿': '折算',
'调休': '调休' '调休': '调休'
} }
const UserAttribute = {
jobDataSource: ['普通员工', '中层', '高层'],
activeStatusDataSource: ['在职', '离职', '特殊状态-特殊账号'],
organizationDataSource: ['江西飞尚科技有限公司', '江西飞尚工程质量检测有限公司',
'江西飞尚科技有限公司江苏分公司', '江西汇派科技有限公司'],
};
return { return {
dayType, dayType,
overtimeType, overtimeType,
UserAttribute
} }
} }

20
api/app/lib/utils/member.js

@ -200,13 +200,11 @@ module.exports = function (app, opts) {
`: ''} `: ''}
WHERE WHERE
member.del = '0' member.del = '0'
${keywordTarget == 'role' && keyword ? ` ${keywordTarget == 'post' && keyword ? `
AND user.id IN ( AND user.post IN (
SELECT user_role.user SELECT basicDataPost.id
FROM ${pepEmis}.user_role AS user_role FROM ${pepEmis}.basicdata_post AS basicDataPost
INNER JOIN ${pepEmis}.role AS role where basicDataPost.name LIKE '%${keyword}%'
ON role.id = user_role.role
AND role.name LIKE '%${keyword}%'
) )
` : ''} ` : ''}
${keywordTarget == 'dep' && keyword ? ` ${keywordTarget == 'dep' && keyword ? `
@ -244,10 +242,14 @@ module.exports = function (app, opts) {
hrMember.*, hrMember.*,
user.name AS userName, user.name AS userName,
user.people_code AS userCode, user.people_code AS userCode,
basicDataPost.name AS userPost,
role.name AS roleName, role.name AS roleName,
role.id AS roleId, role.id AS roleId,
department.name AS depName, department.name AS depName,
department.id AS depId department.id AS depId,
user.job AS userJob,
user.active_status AS userActiveStatus,
user.organization AS userOrganization
FROM ( FROM (
SELECT SELECT
${orderBy == 'overtimeTakeRestSum' ${orderBy == 'overtimeTakeRestSum'
@ -287,6 +289,8 @@ module.exports = function (app, opts) {
ON ${pepEmis}.user_role.user = user.id ON ${pepEmis}.user_role.user = user.id
LEFT JOIN ${pepEmis}.role AS role LEFT JOIN ${pepEmis}.role AS role
ON ${pepEmis}.role.id = user_role.role ON ${pepEmis}.role.id = user_role.role
LEFT JOIN ${pepEmis}.basicdata_post AS basicDataPost
ON ${pepEmis}.basicdata_post.id = user.post
LEFT JOIN ${pepEmis}.department_user AS department_user LEFT JOIN ${pepEmis}.department_user AS department_user
ON department_user.user = user.id ON department_user.user = user.id
LEFT JOIN ${pepEmis}.department AS department LEFT JOIN ${pepEmis}.department AS department

2
api/app/lib/utils/xlsxDownload.js

@ -59,7 +59,7 @@ module.exports = function (app, opts) {
indexCell.style = headerStyle indexCell.style = headerStyle
for (let h of header) { for (let h of header) {
const cell = row.addCell(); const cell = row.addCell();
cell.value = data[i][h.key] || h.defaultValue || ''; cell.value = data[i][h.key] || h.defaultValue || '-';
cell.style = style cell.style = style
} }
} }

11
doc/scripts/0.0.4/schema/1.sales_distribution.sql

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

4
doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql

@ -0,0 +1,4 @@
alter table sales_distribution
add "business_lines" text;

2
web/client/index.ejs

@ -9,7 +9,7 @@
<!-- <link rel="shortcut icon" href="/assets/images/favicon.ico"> --> <!-- <link rel="shortcut icon" href="/assets/images/favicon.ico"> -->
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_20231_12.7f8fd1546294d369f0a85f7d68afb538.es5.js"></script> <script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_20231_13.3484caadabc6f822796451f94a83fc64.es5.js"></script>
</head> </head>

2
web/client/index.html

@ -7,7 +7,7 @@
<!-- <link rel="shortcut icon" href="/assets/images/favicon.ico"> --> <!-- <link rel="shortcut icon" href="/assets/images/favicon.ico"> -->
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_20231_12.7f8fd1546294d369f0a85f7d68afb538.es5.js"></script> <script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_20231_13.3484caadabc6f822796451f94a83fc64.es5.js"></script>
</head> </head>
<body> <body>

5
web/client/src/layout/components/header/contant.js

@ -60,6 +60,11 @@ const headerItems = [{
itemKey: "recruitRecord", itemKey: "recruitRecord",
text: "招聘记录", text: "招聘记录",
to: "/humanAffairs/recruit/recruitRecord/appointmentRecords" to: "/humanAffairs/recruit/recruitRecord/appointmentRecords"
}, {
fatherKey: "recruit",
itemKey: "salesStatistics",
text: "销售统计",
to: "/humanAffairs/recruit/salesStatistics/personnelDistribution"
}] }]
}, { }, {
itemKey: "employeeRelationship", itemKey: "employeeRelationship",

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

27
web/client/src/sections/humanAffairs/components/personnelModal.jsx

@ -240,33 +240,10 @@ function pushModal (props) {
<img src="/assets/images/hrImg/position.png" alt="" style={{ width: '100%', height: '100%' }} /> <img src="/assets/images/hrImg/position.png" alt="" style={{ width: '100%', height: '100%' }} />
</div> </div>
<div style={{ color: 'rgba(0,0,0,0.6)', fontSize: 12 }}> <div style={{ color: 'rgba(0,0,0,0.6)', fontSize: 12 }}>
</div> </div>
<div style={{ color: '#4A4A4A', fontSize: 12, display: 'flex', alignItems: 'center' }}> <div style={{ color: '#4A4A4A', fontSize: 12, display: 'flex', alignItems: 'center' }}>
{ {peoplePro.userPost || '-'}
peoplePro.role.map((ite, idx) => {
let roleArr = []
for (let i = 0; i < peoplePro.role.length; i++) {
roleArr.push(peoplePro.role[i].name)
}
return (
<div key={idx} style={{ display: 'flex', alignItems: 'center' }}>
{idx == 0 ?
(ite.name) : ('')
}
{
peoplePro.role.length > 1 && idx == 1 ? (
<Tooltip content={roleArr.join(',')} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ color: 'rgba(0,90,189,0.8)', fontSize: 12, marginRight: 4, cursor: "pointer", }}>
+{peoplePro.role.length - 1}
</div>
</Tooltip>
) : ('')
}
</div>
)
})
}
</div> </div>
</div> </div>
</div> </div>

105
web/client/src/sections/humanAffairs/containers/employeeInformation.jsx

@ -7,6 +7,7 @@ import '../style.less'
import { Setup } from "$components"; import { Setup } from "$components";
import moment from 'moment' import moment from 'moment'
import { set } from 'nprogress'; import { set } from 'nprogress';
import { UserAttribute } from '$utils'
const employeeInformation = (props) => { const employeeInformation = (props) => {
const { dispatch, actions, history, user, loading, socket, xqMembers } = props const { dispatch, actions, history, user, loading, socket, xqMembers } = props
@ -33,7 +34,12 @@ const employeeInformation = (props) => {
list: [ list: [
{ name: "姓名", value: "userName" }, { name: "姓名", value: "userName" },
{ name: "所属部门", value: "departmrnt" }, { name: "所属部门", value: "departmrnt" },
{ name: "职位", value: "roleName" }, { name: "职位", value: "userJob" },
{ name: "岗位", value: "userPost" },
{ name: "在职状态", value: "userActiveStatus" },
{ name: "绩点", value: "point" },
{ name: "所属机构", value: "userOrganization" },
{ name: "技术职级等级", value: "technicalGrade" },
{ name: "证件号", value: "idNumber" }, { name: "证件号", value: "idNumber" },
{ name: "性别", value: "gender" }, { name: "性别", value: "gender" },
{ name: "籍贯", value: "nativePlace" }, { name: "籍贯", value: "nativePlace" },
@ -77,7 +83,8 @@ const employeeInformation = (props) => {
localStorage.getItem(EMPLOYEEINFORMATION) == null localStorage.getItem(EMPLOYEEINFORMATION) == null
? localStorage.setItem( ? localStorage.setItem(
EMPLOYEEINFORMATION, EMPLOYEEINFORMATION,
JSON.stringify(['userName', 'departmrnt', 'roleName', 'hiredate', 'age', 'phoneNumber', 'marital', 'politicsStatus', 'educationBackground', JSON.stringify(['userName', 'departmrnt', 'userJob', 'userPost', 'userActiveStatus', 'point', 'userOrganization', 'technicalGrade',
'hiredate', 'age', 'phoneNumber', 'marital', 'politicsStatus', 'educationBackground',
'graduatedFrom', 'employmentLife', 'occupationalHistory']) 'graduatedFrom', 'employmentLife', 'occupationalHistory'])
) )
: ""; : "";
@ -203,38 +210,60 @@ const employeeInformation = (props) => {
<img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> 职位 <img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> 职位
</div> </div>
), ),
width: 150, width: 100,
dataIndex: "roleName", dataIndex: "userJob",
key: "roleName", key: "userJob",
render: (_, r, index) => { render: (_, r, index) => {
return ( return r.userJob ? UserAttribute.jobDataSource[r.userJob - 1] : '-';
<div style={{ display: 'flex', alignItems: 'center' }}> },
{ }, {
r.role.map((ite, idx) => { title: (
let roleArr = [] <div>
for (let i = 0; i < r.role.length; i++) { <img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> 岗位
roleArr.push(r.role[i].name)
}
return (
<div key={idx} style={{ display: 'flex', alignItems: 'center' }}>
{idx == 0 ?
(ite.name) : ('')
}
{
r.role.length > 1 && idx == 1 ? (
<Tooltip content={roleArr.join(',')} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ color: 'rgba(0,90,189,0.8)', fontSize: 12, marginRight: 4, cursor: "pointer", }}>
+{r.role.length - 1}
</div> </div>
</Tooltip> ),
) : ('') width: 120,
} dataIndex: "userPost",
key: "userPost",
render: (_, r, index) => {
return r.userPost || '-';
},
}, {
title: (
<div>
<img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> 在职状态
</div> </div>
) ),
}) width: 110,
} dataIndex: "userActiveStatus",
</div>); key: "userActiveStatus",
render: (_, r, index) => {
return r.userActiveStatus ? UserAttribute.activeStatusDataSource[r.userActiveStatus - 1] : '-';
}, },
}, {
title: '绩点',
width: 100,
dataIndex: "point",
key: "point",
render: (_, r, index) => <span>-</span>,
}, {
title: (
<div>
<img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> 所属机构
</div>
),
width: 150,
dataIndex: "userOrganization",
key: "userOrganization",
render: (_, r, index) => {
return r.userOrganization ? UserAttribute.organizationDataSource[r.userOrganization - 1] : '-';
},
}, {
title: '技术职级等级',
width: 150,
dataIndex: "technicalGrade",
key: "technicalGrade",
render: (_, r, index) => <span>-</span>,
}, { }, {
title: '证件号', title: '证件号',
width: 180, width: 180,
@ -427,13 +456,13 @@ const employeeInformation = (props) => {
}, },
}, },
]; ];
for (let i = 0; i < arr.length; i++) { let newColumns = columns;
let colum = column.filter((item) => { for (let i = 0; i < column.length; i++) {
return item.key === arr[i]; if (arr.indexOf(column[i].key) > -1) {
}); newColumns.push(column[i]);
columns.splice(i + 2, 0, colum[0]); }
} }
setSetupp(columns); setSetupp(newColumns);
} }
function handleRow(record, index) {// function handleRow(record, index) {//
// //
@ -485,7 +514,7 @@ const employeeInformation = (props) => {
style={{ width: 200 }} style={{ width: 200 }}
initValue={"name"} initValue={"name"}
> >
<Form.Select.Option value='role'></Form.Select.Option> <Form.Select.Option value='post'></Form.Select.Option>
<Form.Select.Option value='dep'>部门</Form.Select.Option> <Form.Select.Option value='dep'>部门</Form.Select.Option>
<Form.Select.Option value='number'>编号</Form.Select.Option> <Form.Select.Option value='number'>编号</Form.Select.Option>
<Form.Select.Option value='name'>姓名</Form.Select.Option> <Form.Select.Option value='name'>姓名</Form.Select.Option>
@ -668,7 +697,7 @@ const employeeInformation = (props) => {
<Setup <Setup
tableType={EMPLOYEEINFORMATION} tableType={EMPLOYEEINFORMATION}
tableList={tableList} tableList={tableList}
length={25} length={30}
close={() => { close={() => {
setSetup(false); setSetup(false);
attribute(); attribute();

2
web/client/src/sections/humanAffairs/containers/import-members-modal.js

@ -343,7 +343,7 @@ const ImportMembersModal = props => {
error(`${i + 2}行工作经验(年)错误,请填写非负数`) error(`${i + 2}行工作经验(年)错误,请填写非负数`)
return return
} }
postData.push({//人员编号 待办todotodo postData.push({
pepUserId: xqExist.pepUserId, name, idNumber, gender, birthday, nativePlace, marital, pepUserId: xqExist.pepUserId, name, idNumber, gender, birthday, nativePlace, marital,
politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate, politicsStatus, phoneNumber, workPlace, graduatedFrom, educationBackground, specialty, graduationDate,
hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory, hiredate, turnProbationPeriod, regularDate, dimissionDate, experienceYear, occupationalHistory,

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

@ -9,6 +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 './salersDistribution/personnelDistribution';
//培训 //培训
import ResourceRepository from './resourceRepository'; import ResourceRepository from './resourceRepository';
//绩效考核 //绩效考核
@ -32,7 +33,7 @@ import PersonnelFilesDetail from './personnelFilesDetail';
export { export {
PersonnelFiles, EmployeeInformation, DeptArchives, PersonnelFiles, EmployeeInformation, DeptArchives,
AttendanceStatistics, LeaveStatistics, OvertimeStatistics, AttendanceStatistics, LeaveStatistics, OvertimeStatistics,
AppointmentRecords, AppointmentRecords, PersonnelDistribution,
ResourceRepository, ResourceRepository,
WeeklyManagement, SaleLog, PMLog, WeeklyManagement, SaleLog, PMLog,
ProbationerKPI, RegularKPI, ProbationerKPI, RegularKPI,

27
web/client/src/sections/humanAffairs/containers/personnelFiles.jsx

@ -87,7 +87,7 @@ const Rest = (props) => {
<div style={{ display: 'flex', marginTop: 16, marginBottom: 17 }}> <div style={{ display: 'flex', marginTop: 16, marginBottom: 17 }}>
<div> <div>
<Select value={keywordTarget} onChange={setKeywordTarget} placeholder='请选择搜索类型' style={{ width: 200 }} > <Select value={keywordTarget} onChange={setKeywordTarget} placeholder='请选择搜索类型' style={{ width: 200 }} >
<Select.Option value='role'></Select.Option> <Select.Option value='post'></Select.Option>
<Select.Option value='dep'>部门</Select.Option> <Select.Option value='dep'>部门</Select.Option>
<Select.Option value='number'>编号</Select.Option> <Select.Option value='number'>编号</Select.Option>
<Select.Option value='name'>姓名</Select.Option> <Select.Option value='name'>姓名</Select.Option>
@ -232,30 +232,7 @@ const Rest = (props) => {
<img src="/assets/images/hrImg/post.png" alt="" style={{ width: '100%', height: '100%' }} /> <img src="/assets/images/hrImg/post.png" alt="" style={{ width: '100%', height: '100%' }} />
</div> </div>
<div style={{ fontSize: 14, color: '#282828', marginLeft: 12, marginRight: 9, display: 'flex', }}> <div style={{ fontSize: 14, color: '#282828', marginLeft: 12, marginRight: 9, display: 'flex', }}>
{ {item.userPost || '暂无'}
item.role.map((ite, idx) => {
let roleArr = []
for (let i = 0; i < item.role.length; i++) {
roleArr.push(item.role[i].name)
}
return (
<div key={idx} style={{ display: 'flex', alignItems: 'center' }}>
{idx == 0 ?
(ite.name.substring(0, 6)) : ('')
}
{
item.role.length > 1 && idx == 1 ? (
<Tooltip content={roleArr.join(',')} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ color: 'rgba(0,90,189,0.8)', fontSize: 12, marginRight: 4, cursor: "pointer", }}>
+{item.role.length - 1}
</div>
</Tooltip>
) : ('')
}
</div>
)
})
}
</div> </div>
</div> </div>
<div style={{ color: 'rgba(0,0,0,0.65)', fontSize: 12 }}> <div style={{ color: 'rgba(0,0,0,0.65)', fontSize: 12 }}>

58
web/client/src/sections/humanAffairs/containers/personnelFilesDetail.jsx

@ -7,6 +7,7 @@ import * as echarts from 'echarts';
import DeleteModal from '../components/deleteModal'; import DeleteModal from '../components/deleteModal';
import PersonnelModal from '../components/personnelModal'; import PersonnelModal from '../components/personnelModal';
import moment from 'moment' import moment from 'moment'
import { UserAttribute } from '$utils'
import '../style.less' import '../style.less'
@ -610,7 +611,7 @@ const Rest = (props) => {
职务信息 职务信息
</div> </div>
<div style={{ marginTop: 13, display: 'flex' }}> <div style={{ marginTop: 13, display: 'flex' }}>
<div style={{ display: 'flex', width: '14.715%' }}> <div style={{ display: 'flex', width: '15.715%' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}>
员工编号 员工编号
</div> </div>
@ -618,7 +619,7 @@ const Rest = (props) => {
{pepObj.userCode || '暂无'} {pepObj.userCode || '暂无'}
</div> </div>
</div> </div>
<div style={{ display: 'flex', width: '19.072%' }}> <div style={{ display: 'flex', width: '17.072%' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}>
入职时间 入职时间
</div> </div>
@ -626,7 +627,7 @@ const Rest = (props) => {
{pepObj.hiredate || '暂无'} {pepObj.hiredate || '暂无'}
</div> </div>
</div> </div>
<div style={{ display: 'flex', width: '21.395%' }}> <div style={{ display: 'flex', width: '32.395%' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 84 }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 84 }}>
转试用期时间 转试用期时间
</div> </div>
@ -644,43 +645,38 @@ const Rest = (props) => {
</div> </div>
</div> </div>
<div style={{ marginTop: 9, display: 'flex' }}> <div style={{ marginTop: 9, display: 'flex' }}>
<div style={{ display: 'flex', width: '33.787%' }}> <div style={{ display: 'flex', width: '15.715%' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}>
员工职位 员工职位
</div> </div>
<div style={{ color: '#005ABD', fontSize: 13, display: 'flex', alignItems: 'center' }}> <div style={{ color: '#005ABD', fontSize: 13 }}>
{ {
pepObj?.role?.map((item, index) => { pepObj.userJob? UserAttribute.jobDataSource[pepObj.userJob - 1] : '暂无'
let roleArr = []
for (let i = 0; i < pepObj.role.length; i++) {
roleArr.push(pepObj.role[i].name)
}
return (
<div key={index} style={{ display: 'flex', alignItems: 'center' }}>
{index < 2 ?
(item.name) : ('')
} }
</div>
</div>
<div style={{ display: 'flex', width: '17.072%'}}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}>
员工岗位
</div>
<div style={{ color: '#4A4A4A', fontSize: 13 }}>
{ {
pepObj.role.length > 1 && index == 0 ? pepObj.userPost || '暂无'
(',') : ('')
} }
{
pepObj.role.length > 2 && index == 2 ? (
<Tooltip content={roleArr.join(',')} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ color: 'rgba(0,90,189,0.8)', fontSize: 12, marginRight: 4, cursor: "pointer", }}>
+{pepObj.role.length - 2}
</div> </div>
</Tooltip>
) : ('')
}
</div> </div>
) <div style={{ display: 'flex', width: '32.395%'}}>
}) <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 84, textAlign: 'end' }}>
所属机构
</div>
<div style={{ color: '#4A4A4A', fontSize: 13 }}>
{
pepObj.userOrganization? UserAttribute.organizationDataSource[pepObj.userOrganization - 1] : '暂无'
} }
</div> </div>
</div> </div>
<div style={{ display: 'flex', width: '66.213%' }}> <div style={{ display: 'flex', width: '' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 84, textAlign: 'end' }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}>
所属部门 所属部门
</div> </div>
{ {
@ -712,7 +708,7 @@ const Rest = (props) => {
</div> </div>
</div> </div>
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
<div style={{ marginTop: 9, display: 'flex', width: '14.715%' }}> <div style={{ marginTop: 9, display: 'flex', width: '15.715%' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}>
工作经验 工作经验
</div> </div>
@ -720,7 +716,7 @@ const Rest = (props) => {
{pepObj.experienceYear ? pepObj.experienceYear + '年' : '暂无'} {pepObj.experienceYear ? pepObj.experienceYear + '年' : '暂无'}
</div> </div>
</div> </div>
<div style={{ marginTop: 9, display: 'flex', width: '19.072%' }}> <div style={{ marginTop: 9, display: 'flex', width: '17.072%' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 60 }}>
入职年限 入职年限
</div> </div>
@ -728,7 +724,7 @@ const Rest = (props) => {
{pepObj.hiredate ? String(moment(new Date()).diff(pepObj.hiredate, 'years',true)).substring(0,3) + '年' : '暂无'} {pepObj.hiredate ? String(moment(new Date()).diff(pepObj.hiredate, 'years',true)).substring(0,3) + '年' : '暂无'}
</div> </div>
</div> </div>
<div style={{ marginTop: 9, display: 'flex', width: '39.4%' }}> <div style={{ marginTop: 9, display: 'flex', width: '32.395%' }}>
<div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 84, textAlign: 'end' }}> <div style={{ color: 'rgba(0, 0, 0,0.6)', fontSize: 12, width: 84, textAlign: 'end' }}>
试用期 试用期
</div> </div>

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

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

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

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

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

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

8
web/client/src/sections/humanAffairs/nav-item.jsx

@ -46,6 +46,14 @@ export function getNavItem(user, dispatch) {
items: [{ items: [{
itemKey: 'appointmentRecords', to: '/humanAffairs/recruit/recruitRecord/appointmentRecords', text: '任用记录' itemKey: 'appointmentRecords', to: '/humanAffairs/recruit/recruitRecord/appointmentRecords', text: '任用记录'
}] }]
}, {
itemKey: 'salesStatistics',
text: '销售统计',
icon: <iconpark-icon style={{ width: 20, height: 20 }} name="iconxiaoshou"></iconpark-icon>,//wwwtodo
to: '/humanAffairs/recruit/salesStatistics/personnelDistribution',
items: [{
itemKey: 'personnelDistribution', to: '/humanAffairs/recruit/salesStatistics/personnelDistribution', text: '销售人员分布'
}]
}, { }, {
itemKey: 'trainFiles', itemKey: 'trainFiles',
text: '培训档案', text: '培训档案',

12
web/client/src/sections/humanAffairs/routes.js

@ -2,7 +2,7 @@ import {
PersonnelFiles, EmployeeInformation, //人员档案 PersonnelFiles, EmployeeInformation, //人员档案
DeptArchives, //部门档案 DeptArchives, //部门档案
AttendanceStatistics, LeaveStatistics, OvertimeStatistics, AttendanceStatistics, LeaveStatistics, OvertimeStatistics,
AppointmentRecords, AppointmentRecords, PersonnelDistribution,
ResourceRepository, ResourceRepository,
WeeklyManagement, SaleLog, PMLog, WeeklyManagement, SaleLog, PMLog,
ProbationerKPI, RegularKPI, ProbationerKPI, RegularKPI,
@ -89,6 +89,16 @@ export default [{
component: AppointmentRecords, component: AppointmentRecords,
breadcrumb: '任用记录', breadcrumb: '任用记录',
}] }]
}, {
path: '/salesStatistics',
key: 'salesStatistics',
breadcrumb: '销售统计',
childRoutes: [{
path: '/personnelDistribution',
key: 'personnelDistribution',
component: PersonnelDistribution,
breadcrumb: '销售人员分布',
}]
}] }]
}, { }, {
path: '/train', path: '/train',

3
web/client/src/utils/index.js

@ -6,7 +6,7 @@ import {
AxyRequest, EmisRequest, AxyRequest, EmisRequest,
basicAction, RouteRequest basicAction, RouteRequest
} from './webapi' } from './webapi'
import { UserAttribute } from './userAttribute';
export { export {
isAuthorized, isAuthorized,
AuthorizationCode, AuthorizationCode,
@ -17,4 +17,5 @@ export {
EmisRequest, EmisRequest,
basicAction, basicAction,
RouteRequest, RouteRequest,
UserAttribute
} }

8
web/client/src/utils/userAttribute.js

@ -0,0 +1,8 @@
'use strict';
export const UserAttribute = {
jobDataSource: ['普通员工', '中层', '高层'],
activeStatusDataSource: ['在职', '离职', '特殊状态-特殊账号'],
organizationDataSource: ['江西飞尚科技有限公司', '江西飞尚工程质量检测有限公司',
'江西飞尚科技有限公司江苏分公司', '江西汇派科技有限公司'],
};

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