From ddb24e78372045196406c579914a0a6caf55cd12 Mon Sep 17 00:00:00 2001 From: wuqun Date: Fri, 18 Nov 2022 16:22:55 +0800 Subject: [PATCH 01/21] =?UTF-8?q?(*)=E6=B7=BB=E5=8A=A0=E9=94=80=E5=94=AE?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1-=E9=94=80=E5=94=AE=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E5=88=86=E5=B8=83=20=E6=A8=A1=E5=9D=97=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/.vscode/launch.json | 6 +-- .../src/layout/components/header/contant.js | 5 +++ .../sections/humanAffairs/containers/index.js | 3 +- .../containers/personnelDistribution.jsx | 42 +++++++++++++++++++ .../src/sections/humanAffairs/nav-item.jsx | 8 ++++ .../src/sections/humanAffairs/routes.js | 12 +++++- 6 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index bda745f..3a4a4a3 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -42,9 +42,9 @@ // 测试 "--clickHouseUrl http://10.8.30.161", - "--clickHousePepEmis pg_pepca", - "--clickHouseCamworkflow pg_camworkflow", - "--clickHouseHr pg_hrm", + "--clickHousePepEmis pepca8", + "--clickHouseCamworkflow camworkflow", + "--clickHouseHr hrm", ] }, { diff --git a/web/client/src/layout/components/header/contant.js b/web/client/src/layout/components/header/contant.js index b58ed2d..4aa17a0 100644 --- a/web/client/src/layout/components/header/contant.js +++ b/web/client/src/layout/components/header/contant.js @@ -60,6 +60,11 @@ const headerItems = [{ itemKey: "recruitRecord", text: "招聘记录", to: "/humanAffairs/recruit/recruitRecord/appointmentRecords" + }, { + fatherKey: "recruit", + itemKey: "salesStatistics", + text: "销售统计", + to: "/humanAffairs/recruit/salesStatistics/personnelDistribution" }] }, { itemKey: "employeeRelationship", diff --git a/web/client/src/sections/humanAffairs/containers/index.js b/web/client/src/sections/humanAffairs/containers/index.js index 58f7233..b9ddae3 100644 --- a/web/client/src/sections/humanAffairs/containers/index.js +++ b/web/client/src/sections/humanAffairs/containers/index.js @@ -9,6 +9,7 @@ import LeaveStatistics from './leaveStatistics'; import OvertimeStatistics from './overtimeStatistics'; //招聘 import AppointmentRecords from './appointmentRecords'; +import PersonnelDistribution from './personnelDistribution'; //培训 import ResourceRepository from './resourceRepository'; //绩效考核 @@ -32,7 +33,7 @@ import PersonnelFilesDetail from './personnelFilesDetail'; export { PersonnelFiles, EmployeeInformation, DeptArchives, AttendanceStatistics, LeaveStatistics, OvertimeStatistics, - AppointmentRecords, + AppointmentRecords, PersonnelDistribution, ResourceRepository, WeeklyManagement, SaleLog, PMLog, ProbationerKPI, RegularKPI, diff --git a/web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx b/web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx new file mode 100644 index 0000000..7072e89 --- /dev/null +++ b/web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx @@ -0,0 +1,42 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import Empty from '../components/empty'; +import '../style.less' + +const PersonnelDistribution = (props) => { + const { dispatch, actions } = props + + return ( + <> +
+
+
招聘
+
/
+
销售统计
+
/
+
销售人员分布
+
+
+
+
+
+
销售人员分布
+
DISTRIBUTION OF SALES PERSONNEL
+
+
+ +
+
+ + ) +} + +function mapStateToProps(state) { + const { auth, global } = state; + return { + user: auth.user, + actions: global.actions, + }; +} + +export default connect(mapStateToProps)(PersonnelDistribution); diff --git a/web/client/src/sections/humanAffairs/nav-item.jsx b/web/client/src/sections/humanAffairs/nav-item.jsx index 3696ea1..6576295 100644 --- a/web/client/src/sections/humanAffairs/nav-item.jsx +++ b/web/client/src/sections/humanAffairs/nav-item.jsx @@ -46,6 +46,14 @@ export function getNavItem(user, dispatch) { items: [{ itemKey: 'appointmentRecords', to: '/humanAffairs/recruit/recruitRecord/appointmentRecords', text: '任用记录' }] + }, { + itemKey: 'salesStatistics', + text: '销售统计', + icon: ,//wwwtodo + to: '/humanAffairs/recruit/salesStatistics/personnelDistribution', + items: [{ + itemKey: 'personnelDistribution', to: '/humanAffairs/recruit/salesStatistics/personnelDistribution', text: '销售人员分布' + }] }, { itemKey: 'trainFiles', text: '培训档案', diff --git a/web/client/src/sections/humanAffairs/routes.js b/web/client/src/sections/humanAffairs/routes.js index 9815cf7..91ffb0c 100644 --- a/web/client/src/sections/humanAffairs/routes.js +++ b/web/client/src/sections/humanAffairs/routes.js @@ -2,7 +2,7 @@ import { PersonnelFiles, EmployeeInformation, //人员档案 DeptArchives, //部门档案 AttendanceStatistics, LeaveStatistics, OvertimeStatistics, - AppointmentRecords, + AppointmentRecords, PersonnelDistribution, ResourceRepository, WeeklyManagement, SaleLog, PMLog, ProbationerKPI, RegularKPI, @@ -89,6 +89,16 @@ export default [{ component: AppointmentRecords, breadcrumb: '任用记录', }] + }, { + path: '/salesStatistics', + key: 'salesStatistics', + breadcrumb: '销售统计', + childRoutes: [{ + path: '/personnelDistribution', + key: 'personnelDistribution', + component: PersonnelDistribution, + breadcrumb: '销售人员分布', + }] }] }, { path: '/train', From 45d2d32e7253d9e850bd740ba317e18a9483bbb5 Mon Sep 17 00:00:00 2001 From: zhangminghua Date: Tue, 22 Nov 2022 16:32:19 +0800 Subject: [PATCH 02/21] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9B=B4=E6=96=B0click?= =?UTF-8?q?house=E9=A1=B9=E4=BC=81=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/.vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index 3a4a4a3..8ed60f2 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -17,7 +17,7 @@ "-f http://localhost:4700", // 研发 "-g postgres://postgres:123@10.8.30.166:5432/hr-dev", - "--redisHost 10.8.30.112", + "--redisHost localhost", "--redisPort 6379", // "--apiEmisUrl http://10.8.30.112:14000", // 测试 @@ -42,7 +42,7 @@ // 测试 "--clickHouseUrl http://10.8.30.161", - "--clickHousePepEmis pepca8", + "--clickHousePepEmis pepca_dev", "--clickHouseCamworkflow camworkflow", "--clickHouseHr hrm", ] From d7ed676ca98213ce4ed9fff38a7fa8f372d05106 Mon Sep 17 00:00:00 2001 From: zhangminghua Date: Tue, 22 Nov 2022 16:48:47 +0800 Subject: [PATCH 03/21] =?UTF-8?q?=E8=BF=98=E5=8E=9Fredishost=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/.vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index 8ed60f2..cfc15dc 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -17,7 +17,7 @@ "-f http://localhost:4700", // 研发 "-g postgres://postgres:123@10.8.30.166:5432/hr-dev", - "--redisHost localhost", + "--redisHost 10.8.30.112", "--redisPort 6379", // "--apiEmisUrl http://10.8.30.112:14000", // 测试 From 8c43edbe7074f1573691ce030d1a6a23ebc9d657 Mon Sep 17 00:00:00 2001 From: wuqun Date: Wed, 23 Nov 2022 13:44:35 +0800 Subject: [PATCH 04/21] =?UTF-8?q?(*)=E9=94=80=E5=94=AE=E7=BB=9F=E8=AE=A1-?= =?UTF-8?q?=E9=94=80=E5=94=AE=E4=BA=BA=E5=91=98=E5=88=86=E5=B8=83=20api?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=9A=82=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/salesDistribution/index.js | 333 ++++++++++++++++++ api/app/lib/models/sales_distribution.js | 62 ++++ api/app/lib/routes/salesDistribution/index.js | 24 ++ api/app/lib/utils/member.js | 3 + 4 files changed, 422 insertions(+) create mode 100644 api/app/lib/controllers/salesDistribution/index.js create mode 100644 api/app/lib/models/sales_distribution.js create mode 100644 api/app/lib/routes/salesDistribution/index.js diff --git a/api/app/lib/controllers/salesDistribution/index.js b/api/app/lib/controllers/salesDistribution/index.js new file mode 100644 index 0000000..9974264 --- /dev/null +++ b/api/app/lib/controllers/salesDistribution/index.js @@ -0,0 +1,333 @@ +'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 res = await models.SalesDistribution.findAndCountAll({ + where: { del: false }, + offset: Number(page) * Number(limit), + limit: Number(limit), + }) + + let rslt = [] + res.rows.map(d => { + let valid = false; + let info = members.find(m => m.pepUserId == d.dataValues.pepUserId); + if (info) { + if (placeSearch) { + let exist1 = d.dataValues.provinces.join(',').indexOf(placeSearch) != -1 + let exist2 = d.dataValues.cities.join(',').indexOf(placeSearch) != -1 + if (exist1 || exist2) { + valid = true; + } + } else { + valid = true; + } + } + if (valid) { + 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 } = ctx.request.body + + const existRes = await models.SalesDistribution.findOne({ + where: { pepUserId } + }) + + if (existRes && !existRes.del) { + throw '当前销售人员信息已存在' + } + + let storageData = { pepUserId, provinces, cities, 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 } = ctx.request.body + + const existRes = await models.SalesDistribution.findOne({ + where: { pepUserId } + }) + + if (!existRes) { + throw '当前销售人员信息不存在' + } + + let storageData = { pepUserId, provinces, cities, 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, del = false } = editArr[i]; + + let dataToUpdate = { + provinces, + cities, + 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, +} \ No newline at end of file diff --git a/api/app/lib/models/sales_distribution.js b/api/app/lib/models/sales_distribution.js new file mode 100644 index 0000000..6d7bf6c --- /dev/null +++ b/api/app/lib/models/sales_distribution.js @@ -0,0 +1,62 @@ +/* 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.ARRAY(DataTypes.STRING), + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "provinces", + autoIncrement: false + }, + cities: { + type: DataTypes.ARRAY(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 + } + }, { + tableName: "sales_distribution", + comment: "", + indexes: [] + }); + dc.models.SalesDistribution = SalesDistribution; + return SalesDistribution; +}; \ No newline at end of file diff --git a/api/app/lib/routes/salesDistribution/index.js b/api/app/lib/routes/salesDistribution/index.js new file mode 100644 index 0000000..3ddf054 --- /dev/null +++ b/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); +}; \ No newline at end of file diff --git a/api/app/lib/utils/member.js b/api/app/lib/utils/member.js index 479d521..f0ce54a 100644 --- a/api/app/lib/utils/member.js +++ b/api/app/lib/utils/member.js @@ -244,6 +244,7 @@ module.exports = function (app, opts) { hrMember.*, user.name AS userName, user.people_code AS userCode, + basicDataPost.name AS userPost, role.name AS roleName, role.id AS roleId, department.name AS depName, @@ -287,6 +288,8 @@ module.exports = function (app, opts) { ON ${pepEmis}.user_role.user = user.id LEFT JOIN ${pepEmis}.role AS 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 ON department_user.user = user.id LEFT JOIN ${pepEmis}.department AS department From ef8d29818107b8ccea9e3f7b056bdfbd8030a043 Mon Sep 17 00:00:00 2001 From: wuqun Date: Wed, 23 Nov 2022 13:45:21 +0800 Subject: [PATCH 05/21] =?UTF-8?q?(+)=E9=94=80=E5=94=AE=E7=BB=9F=E8=AE=A1-?= =?UTF-8?q?=E9=94=80=E5=94=AE=E4=BA=BA=E5=91=98=E5=88=86=E5=B8=83=20?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/scripts/0.0.4/schema/1.sales_distribution.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 doc/scripts/0.0.4/schema/1.sales_distribution.sql diff --git a/doc/scripts/0.0.4/schema/1.sales_distribution.sql b/doc/scripts/0.0.4/schema/1.sales_distribution.sql new file mode 100644 index 0000000..197851a --- /dev/null +++ b/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 varchar(255) [] NOT NULL, + cities varchar(255) [], + del boolean DEFAULT false NULL +); +CREATE UNIQUE INDEX sales_distribution_id_uindex ON sales_distribution (id); \ No newline at end of file From 64d830229c6916ca4af81576c1633b7efdb98a44 Mon Sep 17 00:00:00 2001 From: wuqun Date: Wed, 23 Nov 2022 13:46:56 +0800 Subject: [PATCH 06/21] =?UTF-8?q?(*)=E9=94=80=E5=94=AE=E7=BB=9F=E8=AE=A1-?= =?UTF-8?q?=E9=94=80=E5=94=AE=E4=BA=BA=E5=91=98=E5=88=86=E5=B8=83=20web?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=9A=82=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sections/humanAffairs/actions/index.js | 5 +- .../humanAffairs/actions/salesDistribution.js | 89 +++++ .../sections/humanAffairs/containers/index.js | 2 +- .../containers/personnelDistribution.jsx | 42 --- .../salersDistribution/importSalersModal.js | 261 ++++++++++++++ .../personnelDistribution.jsx | 339 ++++++++++++++++++ .../salersDistribution/salesMemberModal.js | 244 +++++++++++++ web/client/src/utils/webapi.js | 6 + 8 files changed, 943 insertions(+), 45 deletions(-) create mode 100644 web/client/src/sections/humanAffairs/actions/salesDistribution.js delete mode 100644 web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx create mode 100644 web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js create mode 100644 web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx create mode 100644 web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js diff --git a/web/client/src/sections/humanAffairs/actions/index.js b/web/client/src/sections/humanAffairs/actions/index.js index f2d429b..d69a9d8 100644 --- a/web/client/src/sections/humanAffairs/actions/index.js +++ b/web/client/src/sections/humanAffairs/actions/index.js @@ -2,8 +2,9 @@ import * as personnelFiles from './personnelFiles' import * as employeeInformation from './employeeInformation' - +import * as salesDistribution from './salesDistribution' export default { ...personnelFiles, - ...employeeInformation + ...employeeInformation, + ...salesDistribution } \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/actions/salesDistribution.js b/web/client/src/sections/humanAffairs/actions/salesDistribution.js new file mode 100644 index 0000000..6037d8c --- /dev/null +++ b/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: {}, + }); +} \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/index.js b/web/client/src/sections/humanAffairs/containers/index.js index b9ddae3..8a0f6c0 100644 --- a/web/client/src/sections/humanAffairs/containers/index.js +++ b/web/client/src/sections/humanAffairs/containers/index.js @@ -9,7 +9,7 @@ import LeaveStatistics from './leaveStatistics'; import OvertimeStatistics from './overtimeStatistics'; //招聘 import AppointmentRecords from './appointmentRecords'; -import PersonnelDistribution from './personnelDistribution'; +import PersonnelDistribution from './salersDistribution/personnelDistribution'; //培训 import ResourceRepository from './resourceRepository'; //绩效考核 diff --git a/web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx b/web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx deleted file mode 100644 index 7072e89..0000000 --- a/web/client/src/sections/humanAffairs/containers/personnelDistribution.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import Empty from '../components/empty'; -import '../style.less' - -const PersonnelDistribution = (props) => { - const { dispatch, actions } = props - - return ( - <> -
-
-
招聘
-
/
-
销售统计
-
/
-
销售人员分布
-
-
-
-
-
-
销售人员分布
-
DISTRIBUTION OF SALES PERSONNEL
-
-
- -
-
- - ) -} - -function mapStateToProps(state) { - const { auth, global } = state; - return { - user: auth.user, - actions: global.actions, - }; -} - -export default connect(mapStateToProps)(PersonnelDistribution); diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js b/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js new file mode 100644 index 0000000..d83b636 --- /dev/null +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js @@ -0,0 +1,261 @@ +'use strict'; +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui'; +import { IconUpload } from '@douyinfe/semi-icons'; +import cityData from '../../components/city.json'; +import XLSX from 'xlsx' +import { membersBulkAdd } from '../../actions/personnelFiles' +//下载模板和上传文件读取 +const ImportSalersModal = props => { + const { dispatch, actions, onCancel, rzMembers } = props + const { humanAffairs } = actions; + const [msg, setMsg] = useState('') + const [loading, setLoading] = useState('') + const [postData, setPostData] = useState([]) + const [allProvinces, setAllProvinces] = useState([]) + //初始化 + useEffect(() => { + let allProvinces = [];//所有省 + cityData.map(cd => { + allProvinces.push(cd.name); + }); + setAllProvinces(allProvinces); + }, []); + + const confirm = () => { + if (postData.length) { + setLoading(true) + dispatch(humanAffairs.addSalesMemberBulk(postData)).then(res => { + if (res.success) { + onCancel() + } + setLoading(false) + }) + } else { + Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 }) + } + } + + const dldCsvMb = () => { + //表头 + let head = "员工编号,姓名,销售区域(省/直辖市),销售区域(市)\n" + //数据 + //let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5 + let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head; + //创建一个a标签 + let link = document.createElement("a"); + //为a标签设置属性 + link.setAttribute("href", templateCsv); + link.setAttribute("download", "人资系统销售人员信息导入模板.csv"); + //点击a标签 + link.click(); + } + const download = () => { + //dldTemplate(); + dldCsvMb(); + // let str = ""; + // rule.forEach((v, i) => { + // str += `${v}\r\n` + // }) + // dldText("填写说明.txt", str); + } + + // const dldText = (filename, text) => { + // var element = document.createElement('a'); + // element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + // element.setAttribute('download', filename); + + // element.style.display = 'none'; + // document.body.appendChild(element); + + // element.click(); + // document.body.removeChild(element); + // } + const fileLimit = '.csv'; + + const getFileBlob = (url) => { + return new Promise((resolve, reject) => { + let request = new XMLHttpRequest() + request.open("GET", url, true) + request.responseType = "blob" + request.onreadystatechange = e => { + if (request.readyState == 4) { + if (request.status == 200) { + if (window.FileReader) { + let reader = new FileReader(); + reader.readAsBinaryString(request.response); + reader.onload = event => { + try { + const { result } = event.target; + // 以二进制流方式读取得到整份excel表格对象 + const workbook = XLSX.read(result, { + type: "binary", + cellDates: true,//设为true,将天数的时间戳转为时间格式 + codepage: 936//解决了乱码问题 + }); + let data = []; // 存储获取到的数据 + // 遍历每张工作表进行读取(这里默认只读取第一张表) + for (const sheet in workbook.Sheets) { + if (workbook.Sheets.hasOwnProperty(sheet)) { + data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet])); + } + } + resolve(data);//导出数据 + } catch (e) { + reject("失败"); + } + } + } + } + } + } + request.send(); + }) + } + + const judgeProvinces = (provinces) => { + let noMark = 0; + provinces.split('、')?.map(p => { + if (allProvinces.indexOf(p) == -1) { + noMark++ + } + }) + return !noMark; + } + + const judgeCities = (provinces, cities) => { + let nowCities = []; + cityData.filter(cd => provinces.split('、').indexOf(cd.name) != -1).map(d => { + d.children?.map(c => { + nowCities.push(c.name) + }) + }) + let noMark = 0; + cities.split('、')?.map(p => { + if (nowCities.indexOf(p) == -1) { + noMark++ + } + }) + return !noMark; + } + + const judgeNull = (value) => { + return value ? String(value).trim().replace(/\s*/g, "") : null; + } + + return ( + { + setMsg('') + setLoading(false) + setPostData([]) + onCancel() + }} + > +
+
+ { + setMsg(''); + setPostData([]); + }} + customRequest={(data) => { + const { file, onSuccess, onError } = data + getFileBlob(file.url).then(res => { + const error = (msg) => { + setMsg(msg) + onError({ message: msg }) + } + if (res.length > 1000) { + error('一次性上传数据行数应小于1000行,请分批上传') + return + } + if (!res.length) { + error('请填写至少一行数据') + return + } + let postData = []; + const zmsz = /^[A-Za-z0-9]+$/;//字母数字组合 + for (let i = 0; i < res.length; i++) { + let d = res[i]; + let number = judgeNull(d['员工编号']); + let name = judgeNull(d['姓名']); + let provinces = judgeNull(d['销售区域(省/直辖市)']); + let cities = judgeNull(d['销售区域(市)']); + if (!number) {//人员编号不为空,唯一,字母和数字 + error(`第${i + 2}行人员编号为空,请填写`) + return + } + let rzExist = rzMembers.find(m => m.userCode == number); + if (!rzExist) { + error(`第${i + 2}行的人员编号无对应的员工信息`) + return + } + if (postData.some(p => p.number == number)) {//人员编号 唯一 + error(`第${i + 2}行人员编号重复,请更改后重新上传`) + return + } + if (!zmsz.test(number)) { + error(`第${i + 2}行人员编号错误,请填写字母和数字的组合`) + return + } + if (!name) {//姓名必填 + error(`第${i + 2}行姓名为空,请填写`) + return + } + if (!provinces) {//销售区域(省/直辖市)必填 + error(`第${i + 2}行销售区域(省/直辖市)为空,请填写`) + return + } + let pValid = judgeProvinces(provinces); + if (!pValid) { + error(`第${i + 2}行销售区域(省/直辖市)错误`) + return + } + let cValid = judgeCities(provinces, cities); + if (!cValid) { + error(`第${i + 2}行销售区域(市)错误`) + return + } + postData.push({//人员编号 待办todotodo + pepUserId: rzExist.pepUserId, name, + provinces: provinces.split('、'), cities: cities.split('、'), + del: false + }) + } + setPostData(postData) + let msg = '文件解析完成,点击确定按钮上传保存!' + setMsg(msg) + onSuccess({ message: msg }) + }) + }}> + + + {msg} +
最大不超过200M,导入文件需与 + download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板 + 一致
+
+
+ ) +} + +function mapStateToProps(state) { + const { auth, global, MemberList } = state; + return { + user: auth.user, + actions: global.actions, + rzMembers: MemberList.data?.rows || [], + } +} + +export default connect(mapStateToProps)(ImportSalersModal); \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx b/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx new file mode 100644 index 0000000..6f11520 --- /dev/null +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx @@ -0,0 +1,339 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { connect } from 'react-redux'; +import moment from 'moment' +import { Select, Input, Button, Popconfirm, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui'; +import SalesMemberModal from './salesMemberModal' +import ImportSalersModal from './importSalersModal' +import { IconSearch } from '@douyinfe/semi-icons'; +import { SkeletonScreen } from "$components"; +import '../../style.less' + +const PersonnelDistribution = (props) => { + const { dispatch, actions } = props + const { humanAffairs } = actions; + const [keywordTarget, setKeywordTarget] = useState('dep'); + const [keyword, setKeyword] = useState('');//搜索内容 + const [limits, setLimits] = useState()//每页实际条数 + const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息 + const [modalV, setModalV] = useState(false); + const [dataToEdit, setDataToEdit] = useState(null); + const [tableData, setTableData] = useState([]); + const [importModalV, setImportModalV] = useState(false); + const page = useRef(query.page); + function seachValueChange(value) { + setKeyword(value) + } + + useEffect(() => { + dispatch(humanAffairs.getMemberList()) + getMemberSearchList() + }, []); + + useEffect(() => { + getMemberSearchList()//查询人员列表 + }, [query]) + + function getMemberSearchList() { + let kt = keywordTarget == 'place' ? '' : keywordTarget; + let k = keywordTarget == 'place' ? '' : keyword; + let placeSearch = keywordTarget == 'place' ? keyword : ''; + dispatch(humanAffairs.getSalesList({ keywordTarget: kt, keyword: k, placeSearch, ...query })).then(r => { + if (r.success) { + setTableData(r.payload?.data?.rows); + setLimits(r.payload?.data?.count) + } + }) + } + + function handleRow(record, index) {// 给偶数行设置斑马纹 + if (index % 2 === 0) { + return { + style: { + background: '#FAFCFF', + } + }; + } else { + return {}; + } + } + + const closeAndFetch = () => { + setModalV(false) + getMemberSearchList(); + } + + const starHeader = (header) => { + return
+ {header} +
+ } + + const getMultis = (arrStr) => {//默认展示2个 + return
+ { + arrStr.map((ite, idx) => { + return ( +
+ {idx < 2 ? +
+ {ite} +
: '' + } + { + arrStr.length > 2 && idx == 2 ? + +
+ +{arrStr.length - 2} +
+
+ : '' + } +
+ ) + }) + } +
+ } + + const columns = [{ + title: '序号', + dataIndex: 'id', + key: 'id', + width: '5%', + render: (text, record, index) => index + 1 + }, { + title: starHeader('姓名'), + dataIndex: 'name', + key: 'name', + width: '5%' + }, + { + title: starHeader('部门名称'), + dataIndex: 'department', + key: 'department', + width: '15%', + render: (text, r, index) => { + let arrStr = text.map(t => t.name); + return getMultis(arrStr); + } + }, { + title: '销售区域(省/直辖市)', + dataIndex: 'provinces', + key: 'provinces', + width: '15%', + render: (text, record, index) => { + return getMultis(text); + } + }, { + title: '销售区域(市)', + dataIndex: 'cities', + key: 'cities', + width: '15%', + render: (text, record, index) => { + return getMultis(text); + } + }, { + title: starHeader('岗位'), + dataIndex: 'post', + key: 'post', + width: '10%', + }, { + title: starHeader('入职时间'), + dataIndex: 'hireDate', + key: 'hireDate', + width: '10%', + render: (text, record) => {text || '-'} + }, { + title: starHeader('转正时间'), + dataIndex: 'regularDate', + key: 'regularDate', + width: '10%', + render: (text, record) => {text || '-'} + }, { + title: starHeader('工龄'), + dataIndex: 'workYears', + key: 'workYears', + width: '5%', + render: (_, r, index) => { + return (r.hireDate ? {String(moment(new Date()).diff(r.hireDate, 'years', true)).substring(0, 3) + '年'} : '-') + }, + }, { + title: '操作', + dataIndex: 'action', + width: '10%', + render: (text, record) => { + return
+ onEdit(record)}>编辑   + confirmDelete(record.pepUserId)} style={{ width: 330 }} + > 删除 +
+ } + }]; + + const onEdit = (data) => { + setModalV(true); + setDataToEdit(data); + } + + const confirmDelete = (pepUserId) => { + dispatch(humanAffairs.delSalesMember({ pepUserId, msg: '删除销售人员信息' })).then(res => { + if (res.success) { + getMemberSearchList(); + } + }); + } + + return (
+
+
招聘
+
/
+
销售统计
+
/
+
销售人员分布
+
+
+
+
+
+
销售人员分布
+
DISTRIBUTION OF SALES PERSONNEL
+
+
+
+
+
+
{ + setModalV(true); + setDataToEdit(null); + }}> + 新增 +
+
+ +
+
+ } + showClear + placeholder='请输入关键词搜索' + value={keyword} + style={{ width: 346 }} + onChange={seachValueChange}> + +
+ +
+
+
{ setImportModalV(true); }}> + 导入 +
+
+ 导出 +
+
+
+ +
+ + 表格中带有认证标识" + + "信息的为系统基础数据,来源于项企PEP、钉钉等系统,其他数据均为导入或自定义数据 +
+ +
+ + { + 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} + /> + +
+
+ + 共{limits}条信息 + + { + setQuery({ limit: pageSize, page: currentPage - 1 }); + page.current = currentPage - 1 + }} + /> +
+
+ + + + { + modalV ? closeAndFetch()} + onCancel={() => setModalV(false)} /> : '' + } + { + importModalV ? { + setImportModalV(false); + getMemberSearchList(); + }} /> : '' + } + ) +} + +function mapStateToProps(state) { + const { auth, global, SalesMemberList } = state; + return { + user: auth.user, + actions: global.actions, + salesMemberList: SalesMemberList.data + }; +} + +export default connect(mapStateToProps)(PersonnelDistribution); \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js b/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js new file mode 100644 index 0000000..a886338 --- /dev/null +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js @@ -0,0 +1,244 @@ +import React, { useEffect, useRef, useState } from 'react'; +import moment from 'moment'; +import { connect } from "react-redux"; +import { Select, Modal, Form, Notification } from "@douyinfe/semi-ui"; +import cityData from '../../components/city.json'; +const SalesMemberModal = (props) => { + const { dispatch, actions, user, meetingList, onConfirm, getMultis, onCancel, close, rzMembers, dataToEdit } = props; + const { humanAffairs } = actions; + const form = useRef();//表单 + const [options, setOptions] = useState([]); + const [cityOptions, setCityOptions] = useState([]); + const [peoplePro, setPeoplePro] = useState({}); //人员信息 + //初始化 + useEffect(() => { + let optionItems = cityData.map(m => { + return + {m.name} + + }) + setOptions(optionItems); + if (dataToEdit) { + setPeoplePro(dataToEdit); + onChange(dataToEdit.provinces);//市options + } + }, []); + + const onChange = (value) => { + let cityOptions = [], citiesRange = []; + cityData.filter(cd => value.indexOf(cd.name) !== -1).map(d => { + d.children?.map(c => { + cityOptions.push( + {c.name} + ) + citiesRange.push(c.name) + }) + }) + setCityOptions(cityOptions) + + let citiesValue = form?.current?.getValues()?.cities || []; + if (citiesValue.length) { + let newCities = []; + citiesValue?.map(cv => { + if (citiesRange.indexOf(cv) != -1) { + newCities.push(cv); + } + }) + form.current.setValue('cities', newCities) + } + } + + function handleOk() { + form.current.validate().then((values) => { + if (peoplePro?.userCode) { + if (values.userCode == peoplePro.userCode) { + if (dataToEdit) { + dispatch(humanAffairs.editSalesMember({ pepUserId: peoplePro.pepUserId, msg: '编辑销售人员信息', ...values })).then((res) => { + if (res.success) { + close(); + } + }) + } else { + dispatch(humanAffairs.postSalesMember({ pepUserId: peoplePro.pepUserId, msg: '新增销售人员', ...values })).then((res) => { + if (res.success) { + close(); + } + }) + } + } else { + Notification.error({ + content: '你填写的员工编号无对应的人员信息', + duration: 2, + }) + } + } else { + Notification.error({ + content: '你填写的员工编号无对应的人员信息', + duration: 2, + }) + } + }) + } + + const memberSeach = (id) => {//搜索项企用户 + dispatch(humanAffairs.getMemberSearch({ code: id })).then((res) => {//搜索项企用户 + if (res.success) { + if (res.payload.data.length) { + let user = res.payload.data[0] + let exist = rzMembers.find(m => m.pepUserId == user.pepUserId);//人员档案里面需要有 + if (exist) { + let item = { + pepUserId: user.pepUserId, + name: exist.userName, + department: exist.departmrnt, + hireDate: exist.hiredate, + regularDate: exist.regularDate, + userCode: user.userCode, + post: '岗位todo' + } + setPeoplePro(item) + } + } + } + }) + } + + const renderSimpleInfo = () => { + let arrStr = peoplePro?.department?.map(t => t.name) || []; + return
+ { + peoplePro?.name ? ( +
+
+ {renderPeopleItem('姓名:', peoplePro.name)} +
+
+ +
+
+ 所属部门: +
+ {getMultis(arrStr)} +
+
+
+ {renderPeopleItem('入职时间:', peoplePro.hireDate)} + {renderPeopleItem('岗位:', peoplePro.post)} +
+ {renderPeopleItem('转正时间:', peoplePro.regularDate)} + {renderPeopleItem('工龄:', peoplePro.hireDate ? String(moment(new Date()).diff(peoplePro.hireDate, 'years', true)).substring(0, 3) + '年' : '-')} +
+
+ ) : ('') + } +
+ } + + const renderPeopleItem = (label, value) => { + return
+
+ +
+
+ {label} +
+
+ {value} +
+
+ } + + // const handleDeselect = (value) => {//删除 + // let ranges = cityData.find(td => td.name == value)?.children || [] + // if (ranges) { + // let formList = form.current.getValues().cities; + // } + + // } + + const onClear = () => { + form.current.setValue('cities', []) + } + + return ( + +
(form.current = formApi)} + labelPosition={'left'} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}> + { + let formList = form.current.getValues() + if (formList.userCode) { + memberSeach(formList.userCode) + } + }}> + 搜索 + } /> + + {peoplePro ? renderSimpleInfo() : ''} + triggerNode.parentNode} + // filterOption={(input, option) => option.props.children + // .toLowerCase().indexOf(input.toLowerCase()) >= 0} + // value={selectedKeys || []} + onClear={() => onClear()} + onChange={value => onChange(value)} + //onDeselect={value => handleDeselect(value)} + maxTagCount={5} + > + {options} + + triggerNode.parentNode} + // filterOption={(input, option) => option.props.children + // .toLowerCase().indexOf(input.toLowerCase()) >= 0} + // value={selectedKeys || []} + //onDeselect={value => handleDeselect(value)} + maxTagCount={5} + > + {cityOptions} + + +
+ ) +} + +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); \ No newline at end of file diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index ccfc4d2..b2b85cb 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -31,6 +31,12 @@ export const ApiTable = { getAttendanceVacate: 'attendance/vacate',//请假统计 getAttendanceVacateType: 'attendance/vacate/type',//请假类型 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 = { apiRoot: "/api/root", From fa5c9986266cfa36ee951b896c3ea491bf309f04 Mon Sep 17 00:00:00 2001 From: wuqun Date: Wed, 23 Nov 2022 14:06:40 +0800 Subject: [PATCH 07/21] =?UTF-8?q?(*)=E5=91=98=E5=B7=A5=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=88=97=E8=A1=A8=20=E5=8A=A0"=E7=BB=A9=E7=82=B9"=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../humanAffairs/containers/employeeInformation.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx b/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx index f0691ad..13e28f0 100644 --- a/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx +++ b/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx @@ -34,6 +34,7 @@ const employeeInformation = (props) => { { name: "姓名", value: "userName" }, { name: "所属部门", value: "departmrnt" }, { name: "职位", value: "roleName" }, + { name: "绩点", value: "point" }, { name: "证件号", value: "idNumber" }, { name: "性别", value: "gender" }, { name: "籍贯", value: "nativePlace" }, @@ -77,7 +78,7 @@ const employeeInformation = (props) => { localStorage.getItem(EMPLOYEEINFORMATION) == null ? localStorage.setItem( EMPLOYEEINFORMATION, - JSON.stringify(['userName', 'departmrnt', 'roleName', 'hiredate', 'age', 'phoneNumber', 'marital', 'politicsStatus', 'educationBackground', + JSON.stringify(['userName', 'departmrnt', 'roleName', 'point', 'hiredate', 'age', 'phoneNumber', 'marital', 'politicsStatus', 'educationBackground', 'graduatedFrom', 'employmentLife', 'occupationalHistory']) ) : ""; @@ -235,6 +236,12 @@ const employeeInformation = (props) => { } ); }, + }, { + title: '绩点', + width: 100, + dataIndex: "point", + key: "point", + render: (_, r, index) => , }, { title: '证件号', width: 180, From f268eaf2191e66fbd75b36117ff77d73ac08f41a Mon Sep 17 00:00:00 2001 From: zhangminghua Date: Wed, 23 Nov 2022 14:49:23 +0800 Subject: [PATCH 08/21] =?UTF-8?q?=E5=91=98=E5=B7=A5=E5=8D=A1=E7=89=87?= =?UTF-8?q?=EF=BC=8C=E8=81=8C=E4=BD=8D=E6=8D=A2=E4=B8=BA=E5=B2=97=E4=BD=8D?= =?UTF-8?q?=E3=80=82=E5=85=B3=E9=94=AE=E5=AD=97=E6=90=9C=E7=B4=A2=E8=81=8C?= =?UTF-8?q?=E4=BD=8D=E6=9B=BF=E6=8D=A2=E4=B8=BA=E5=B2=97=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/lib/controllers/member/index.js | 8 ++++-- api/app/lib/utils/member.js | 12 ++++----- .../components/personnelModal.jsx | 27 ++----------------- .../containers/employeeInformation.jsx | 2 +- .../containers/personnelFiles.jsx | 27 ++----------------- 5 files changed, 16 insertions(+), 60 deletions(-) diff --git a/api/app/lib/controllers/member/index.js b/api/app/lib/controllers/member/index.js index 37d5b84..1593234 100644 --- a/api/app/lib/controllers/member/index.js +++ b/api/app/lib/controllers/member/index.js @@ -103,7 +103,8 @@ async function searchPepMember (ctx) { const userRes = await clickHouse.pepEmis.query(` SELECT user.id AS pepUserId, - user.people_code AS userCode, + user.people_code AS userCode, + basicdata_post.name AS userPost, user.name AS userName, role.name AS roleName, role.id AS roleId, @@ -118,7 +119,9 @@ async function searchPepMember (ctx) { LEFT JOIN department_user ON department_user.user = user.id LEFT JOIN department - ON department.id = department_user.department + ON department.id = department_user.department + LEFT JOIN basicdata_post + ON basicdata_post.id = user.post WHERE user.delete = '0' ${whereOption.length ? `AND ${whereOption.join(' OR ')}` : ''} @@ -154,6 +157,7 @@ async function searchPepMember (ctx) { id: u.roleId, name: u.roleName }] : [], + userPost:u.userPost }) } }) diff --git a/api/app/lib/utils/member.js b/api/app/lib/utils/member.js index f0ce54a..3f4c7a2 100644 --- a/api/app/lib/utils/member.js +++ b/api/app/lib/utils/member.js @@ -200,13 +200,11 @@ module.exports = function (app, opts) { `: ''} WHERE member.del = '0' - ${keywordTarget == 'role' && keyword ? ` - AND user.id IN ( - SELECT user_role.user - FROM ${pepEmis}.user_role AS user_role - INNER JOIN ${pepEmis}.role AS role - ON role.id = user_role.role - AND role.name LIKE '%${keyword}%' + ${keywordTarget == 'post' && keyword ? ` + AND user.post IN ( + SELECT basicDataPost.id + FROM ${pepEmis}.basicdata_post AS basicDataPost + where basicDataPost.name LIKE '%${keyword}%' ) ` : ''} ${keywordTarget == 'dep' && keyword ? ` diff --git a/web/client/src/sections/humanAffairs/components/personnelModal.jsx b/web/client/src/sections/humanAffairs/components/personnelModal.jsx index 425d835..30727fc 100644 --- a/web/client/src/sections/humanAffairs/components/personnelModal.jsx +++ b/web/client/src/sections/humanAffairs/components/personnelModal.jsx @@ -240,33 +240,10 @@ function pushModal (props) {
- 职位: + 岗位:
- { - peoplePro.role.map((ite, idx) => { - let roleArr = [] - for (let i = 0; i < peoplePro.role.length; i++) { - roleArr.push(peoplePro.role[i].name) - } - return ( -
- {idx == 0 ? - (ite.name) : ('') - } - { - peoplePro.role.length > 1 && idx == 1 ? ( - -
- +{peoplePro.role.length - 1} -
-
- ) : ('') - } -
- ) - }) - } + {peoplePro.userPost || ''}
diff --git a/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx b/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx index 13e28f0..553c264 100644 --- a/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx +++ b/web/client/src/sections/humanAffairs/containers/employeeInformation.jsx @@ -492,7 +492,7 @@ const employeeInformation = (props) => { style={{ width: 200 }} initValue={"name"} > - 职位 + 岗位 部门 编号 姓名 diff --git a/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx b/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx index d3420f4..7dd5c49 100644 --- a/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx +++ b/web/client/src/sections/humanAffairs/containers/personnelFiles.jsx @@ -87,7 +87,7 @@ const Rest = (props) => {