28 changed files with 1658 additions and 174 deletions
			
			
		@ -0,0 +1,336 @@ | 
				
			|||||
 | 
					'use strict'; | 
				
			||||
 | 
					const moment = require('moment') | 
				
			||||
 | 
					const fs = require('fs'); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					async function salesList(ctx) { | 
				
			||||
 | 
					   try { | 
				
			||||
 | 
					      const { memberList, packageUserData } = ctx.app.fs.utils | 
				
			||||
 | 
					      const { | 
				
			||||
 | 
					         keywordTarget, keyword, limit, page, state, | 
				
			||||
 | 
					         hiredateStart, hiredateEnd, marital, native, workPlace, | 
				
			||||
 | 
					         orderBy, orderDirection, placeSearch | 
				
			||||
 | 
					      } = ctx.query | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      const userRes = await memberList({ | 
				
			||||
 | 
					         keywordTarget, keyword, limit: '', page: '', state, | 
				
			||||
 | 
					         hiredateStart, hiredateEnd, marital, native, workPlace, | 
				
			||||
 | 
					         orderBy, orderDirection, | 
				
			||||
 | 
					         nowAttendanceTime: true | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      let { packageUser: members } = await packageUserData(userRes, { | 
				
			||||
 | 
					         state: true, | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      const { models } = ctx.fs.dc; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      let mIds = members.map(m => m.pepUserId); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      let where = { | 
				
			||||
 | 
					         del: false, | 
				
			||||
 | 
					         pepUserId: { $in: mIds } | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      if (placeSearch) { | 
				
			||||
 | 
					         where.$or = [{ | 
				
			||||
 | 
					            provinces: { $like: `%${placeSearch}%` } | 
				
			||||
 | 
					         }, { | 
				
			||||
 | 
					            cities: { $like: `%${placeSearch}%` } | 
				
			||||
 | 
					         }] | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      let res = await models.SalesDistribution.findAndCountAll({ | 
				
			||||
 | 
					         where: where, | 
				
			||||
 | 
					         offset: Number(page) * Number(limit), | 
				
			||||
 | 
					         limit: Number(limit), | 
				
			||||
 | 
					         order: [['id', 'DESC']] | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      let rslt = [] | 
				
			||||
 | 
					      res.rows.map(d => { | 
				
			||||
 | 
					         let info = members.find(m => m.pepUserId == d.dataValues.pepUserId); | 
				
			||||
 | 
					         let item = { | 
				
			||||
 | 
					            name: info.userName, | 
				
			||||
 | 
					            userCode: info.userCode, | 
				
			||||
 | 
					            post: info.userPost, | 
				
			||||
 | 
					            department: info.departmrnt, | 
				
			||||
 | 
					            hireDate: info.hiredate,//入职时间
 | 
				
			||||
 | 
					            regularDate: info.regularDate,//转正时间
 | 
				
			||||
 | 
					            ...d.dataValues | 
				
			||||
 | 
					         } | 
				
			||||
 | 
					         rslt.push(item); | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					      ctx.status = 200; | 
				
			||||
 | 
					      ctx.body = { | 
				
			||||
 | 
					         count: res.count, | 
				
			||||
 | 
					         rows: rslt | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					   } catch (error) { | 
				
			||||
 | 
					      ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); | 
				
			||||
 | 
					      ctx.status = 400; | 
				
			||||
 | 
					      ctx.body = { | 
				
			||||
 | 
					         message: typeof error == 'string' ? error : undefined | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					   } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					async function add(ctx) { | 
				
			||||
 | 
					   try { | 
				
			||||
 | 
					      const { models } = ctx.fs.dc; | 
				
			||||
 | 
					      const { pepUserId, provinces, cities, businessLines } = ctx.request.body | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      const existRes = await models.SalesDistribution.findOne({ | 
				
			||||
 | 
					         where: { pepUserId } | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      if (existRes && !existRes.del) { | 
				
			||||
 | 
					         throw '当前销售人员信息已存在' | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      let storageData = { pepUserId, provinces, cities, businessLines, del: false } | 
				
			||||
 | 
					      if (existRes && existRes.del) { | 
				
			||||
 | 
					         await models.SalesDistribution.update(storageData, { | 
				
			||||
 | 
					            where: { pepUserId } | 
				
			||||
 | 
					         }) | 
				
			||||
 | 
					      } else { | 
				
			||||
 | 
					         await models.SalesDistribution.create(storageData) | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      ctx.status = 204; | 
				
			||||
 | 
					   } catch (error) { | 
				
			||||
 | 
					      ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); | 
				
			||||
 | 
					      ctx.status = 400; | 
				
			||||
 | 
					      ctx.body = { | 
				
			||||
 | 
					         message: typeof error == 'string' ? error : undefined | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					   } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					async function edit(ctx) { | 
				
			||||
 | 
					   try { | 
				
			||||
 | 
					      const { models } = ctx.fs.dc; | 
				
			||||
 | 
					      const { pepUserId, provinces, cities, businessLines } = ctx.request.body | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      const existRes = await models.SalesDistribution.findOne({ | 
				
			||||
 | 
					         where: { pepUserId } | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      if (!existRes) { | 
				
			||||
 | 
					         throw '当前销售人员信息不存在' | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      let storageData = { pepUserId, provinces, cities, businessLines, del: false } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      await models.SalesDistribution.update(storageData, { | 
				
			||||
 | 
					         where: { | 
				
			||||
 | 
					            pepUserId: pepUserId | 
				
			||||
 | 
					         } | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      ctx.status = 204; | 
				
			||||
 | 
					   } catch (error) { | 
				
			||||
 | 
					      ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); | 
				
			||||
 | 
					      ctx.status = 400; | 
				
			||||
 | 
					      ctx.body = { | 
				
			||||
 | 
					         message: typeof error == 'string' ? error : undefined | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					   } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					async function del(ctx) { | 
				
			||||
 | 
					   try { | 
				
			||||
 | 
					      const { models } = ctx.fs.dc; | 
				
			||||
 | 
					      const { pepUserId } = ctx.query | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      await models.SalesDistribution.update({ | 
				
			||||
 | 
					         del: true, | 
				
			||||
 | 
					      }, { | 
				
			||||
 | 
					         where: { | 
				
			||||
 | 
					            pepUserId, | 
				
			||||
 | 
					         } | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					      ctx.status = 204; | 
				
			||||
 | 
					   } catch (error) { | 
				
			||||
 | 
					      ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); | 
				
			||||
 | 
					      ctx.status = 400; | 
				
			||||
 | 
					      ctx.body = { | 
				
			||||
 | 
					         message: typeof error == 'string' ? error : undefined | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					   } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// async function exportData(ctx) {
 | 
				
			||||
 | 
					//    try {
 | 
				
			||||
 | 
					//       const { models } = ctx.fs.dc;
 | 
				
			||||
 | 
					//       const { clickHouse, opts: { qiniu } } = ctx.app.fs
 | 
				
			||||
 | 
					//       const { simpleExcelDown, memberList, packageUserData } = ctx.app.fs.utils
 | 
				
			||||
 | 
					//       const {
 | 
				
			||||
 | 
					//          keywordTarget, keyword, limit, page, state, keys = '',
 | 
				
			||||
 | 
					//          hiredateStart, hiredateEnd, marital, native, workPlace,
 | 
				
			||||
 | 
					//          orderBy, orderDirection,
 | 
				
			||||
 | 
					//       } = ctx.query
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       const userRes = await memberList({
 | 
				
			||||
 | 
					//          keywordTarget, keyword, limit, page, state,
 | 
				
			||||
 | 
					//          hiredateStart, hiredateEnd, marital, native, workPlace,
 | 
				
			||||
 | 
					//          orderBy, orderDirection,
 | 
				
			||||
 | 
					//          nowAttendanceTime: true
 | 
				
			||||
 | 
					//       })
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       const tableAttributes = models['Member'].tableAttributes
 | 
				
			||||
 | 
					//       const optionKeys = keys.split(',')
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       let { packageUser: exportD, pepUserIds } = await packageUserData(userRes)
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       let preHeader = [{
 | 
				
			||||
 | 
					//          title: '员工编号',
 | 
				
			||||
 | 
					//          key: 'userCode',
 | 
				
			||||
 | 
					//       }, {
 | 
				
			||||
 | 
					//          title: '姓名',
 | 
				
			||||
 | 
					//          key: 'userName',
 | 
				
			||||
 | 
					//       }]
 | 
				
			||||
 | 
					//       let header = [].concat(preHeader)
 | 
				
			||||
 | 
					//       for (let k in tableAttributes) {
 | 
				
			||||
 | 
					//          const comment = tableAttributes[k].comment
 | 
				
			||||
 | 
					//          if (k != 'id' && k != 'pepUserId' && comment) {
 | 
				
			||||
 | 
					//             if ([].includes(k)) {
 | 
				
			||||
 | 
					//                // 截住不想导出的字段
 | 
				
			||||
 | 
					//                continue
 | 
				
			||||
 | 
					//             }
 | 
				
			||||
 | 
					//             header.push({
 | 
				
			||||
 | 
					//                title: comment || '-',
 | 
				
			||||
 | 
					//                key: k,
 | 
				
			||||
 | 
					//                // index: tableAttributes[k].index,
 | 
				
			||||
 | 
					//             })
 | 
				
			||||
 | 
					//          }
 | 
				
			||||
 | 
					//       }
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       if (optionKeys.includes('overtimeStatistic')) {
 | 
				
			||||
 | 
					//          header = header.concat([{
 | 
				
			||||
 | 
					//             title: '累计加班次数',
 | 
				
			||||
 | 
					//             key: 'overTimeCount',
 | 
				
			||||
 | 
					//          }, {
 | 
				
			||||
 | 
					//             title: '累计加班总时长 / h',
 | 
				
			||||
 | 
					//             key: 'overTimeDuration',
 | 
				
			||||
 | 
					//          },])
 | 
				
			||||
 | 
					//       }
 | 
				
			||||
 | 
					//       if (optionKeys.includes('vacateStatistic')) {
 | 
				
			||||
 | 
					//          header = header.concat([{
 | 
				
			||||
 | 
					//             title: '累计请假次数',
 | 
				
			||||
 | 
					//             key: 'vacateCount',
 | 
				
			||||
 | 
					//          }, {
 | 
				
			||||
 | 
					//             title: '累计请假总时长 / h',
 | 
				
			||||
 | 
					//             key: 'vacateDuration',
 | 
				
			||||
 | 
					//          },])
 | 
				
			||||
 | 
					//       }
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       // 查询累计加班次数及总时长
 | 
				
			||||
 | 
					//       const statisticOvertimeRes = await clickHouse.hr.query(`
 | 
				
			||||
 | 
					//             SELECT 
 | 
				
			||||
 | 
					//                pep_user_id AS pepUserId,
 | 
				
			||||
 | 
					//                count(id) AS count,  
 | 
				
			||||
 | 
					//                sum(duration) AS duration
 | 
				
			||||
 | 
					//             FROM 
 | 
				
			||||
 | 
					//                overtime
 | 
				
			||||
 | 
					//             WHERE pep_user_id IN (${pepUserIds.join(',')})
 | 
				
			||||
 | 
					//             GROUP BY pep_user_id
 | 
				
			||||
 | 
					//          `).toPromise()
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       const statisticVacateRes = await clickHouse.hr.query(`
 | 
				
			||||
 | 
					//             SELECT 
 | 
				
			||||
 | 
					//                pep_user_id AS pepUserId,
 | 
				
			||||
 | 
					//                count(id) AS count,
 | 
				
			||||
 | 
					//                sum(duration) AS duration
 | 
				
			||||
 | 
					//             FROM 
 | 
				
			||||
 | 
					//                vacate
 | 
				
			||||
 | 
					//             WHERE pep_user_id IN (${pepUserIds.join(',')})
 | 
				
			||||
 | 
					//             GROUP BY pep_user_id
 | 
				
			||||
 | 
					//          `).toPromise()
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       exportD.forEach(d => {
 | 
				
			||||
 | 
					//          d.departmrnt = d.departmrnt.map(dep => dep.name).join('、')
 | 
				
			||||
 | 
					//          d.role = d.role.map(r => r.name).join('、')
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//          d.idPhoto ? d.idPhoto = qiniu.domain + '/' + d.idPhoto : ''
 | 
				
			||||
 | 
					//          d.vitae ? d.vitae = qiniu.domain + '/' + d.vitae : ''
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//          const corOverTime = statisticOvertimeRes.find(so => so.pepUserId == d.pepUserId)
 | 
				
			||||
 | 
					//          d.overTimeCount = corOverTime ? corOverTime.count : 0
 | 
				
			||||
 | 
					//          d.overTimeDuration = corOverTime ? (corOverTime.duration / 3600).toFixed(1) : 0
 | 
				
			||||
 | 
					//          const corVacate = statisticVacateRes.find(so => so.pepUserId == d.pepUserId)
 | 
				
			||||
 | 
					//          d.vacateCount = corVacate ? corVacate.count : 0
 | 
				
			||||
 | 
					//          d.vacateDuration = corVacate ? (corVacate.duration / 3600).toFixed(1) : 0
 | 
				
			||||
 | 
					//       })
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       const fileName = `人员信息_${moment().format('YYYYMMDDHHmmss')}` + '.csv'
 | 
				
			||||
 | 
					//       const filePath = await simpleExcelDown({ data: exportD, header, fileName: fileName })
 | 
				
			||||
 | 
					//       const fileData = fs.readFileSync(filePath);
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					//       ctx.status = 200;
 | 
				
			||||
 | 
					//       ctx.set('Content-Type', 'application/x-xls');
 | 
				
			||||
 | 
					//       ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName));
 | 
				
			||||
 | 
					//       ctx.body = fileData;
 | 
				
			||||
 | 
					//    } catch (error) {
 | 
				
			||||
 | 
					//       ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
 | 
				
			||||
 | 
					//       ctx.status = 400;
 | 
				
			||||
 | 
					//       ctx.body = {
 | 
				
			||||
 | 
					//          message: typeof error == 'string' ? error : undefined
 | 
				
			||||
 | 
					//       }
 | 
				
			||||
 | 
					//    }
 | 
				
			||||
 | 
					// }
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					async function addSalesMemberBulk(ctx) { | 
				
			||||
 | 
					   let errorMsg = { message: '导入销售人员信息失败' }; | 
				
			||||
 | 
					   const transaction = await ctx.fs.dc.orm.transaction(); | 
				
			||||
 | 
					   try { | 
				
			||||
 | 
					      const models = ctx.fs.dc.models; | 
				
			||||
 | 
					      const data = ctx.request.body; | 
				
			||||
 | 
					      let addArr = []; | 
				
			||||
 | 
					      let editArr = []; | 
				
			||||
 | 
					      let list = await models.SalesDistribution.findAll({ | 
				
			||||
 | 
					         attributes: ['pepUserId'] | 
				
			||||
 | 
					      }); | 
				
			||||
 | 
					      data.map(d => { | 
				
			||||
 | 
					         let exist = list.find(m => m.pepUserId == d.pepUserId);//项企的人员编号字段还没有
 | 
				
			||||
 | 
					         if (exist) { | 
				
			||||
 | 
					            editArr.push(d); | 
				
			||||
 | 
					         } else { | 
				
			||||
 | 
					            addArr.push(d); | 
				
			||||
 | 
					         } | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      //处理新增的
 | 
				
			||||
 | 
					      if (addArr.length) { | 
				
			||||
 | 
					         await models.SalesDistribution.bulkCreate(addArr); | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      //处理编辑的
 | 
				
			||||
 | 
					      if (editArr.length) { | 
				
			||||
 | 
					         for (let i in editArr) { | 
				
			||||
 | 
					            let { pepUserId, provinces, cities, businessLines, del = false } = editArr[i]; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					            let dataToUpdate = { | 
				
			||||
 | 
					               provinces, | 
				
			||||
 | 
					               cities, | 
				
			||||
 | 
					               businessLines, | 
				
			||||
 | 
					               del | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            await models.SalesDistribution.update(dataToUpdate, { where: { pepUserId: pepUserId } }); | 
				
			||||
 | 
					         } | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      await transaction.commit(); | 
				
			||||
 | 
					      ctx.status = 204; | 
				
			||||
 | 
					   } catch (error) { | 
				
			||||
 | 
					      await transaction.rollback(); | 
				
			||||
 | 
					      ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); | 
				
			||||
 | 
					      ctx.status = 400; | 
				
			||||
 | 
					      ctx.body = errorMsg; | 
				
			||||
 | 
					   } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					module.exports = { | 
				
			||||
 | 
					   salesList, | 
				
			||||
 | 
					   add, | 
				
			||||
 | 
					   edit, | 
				
			||||
 | 
					   del, | 
				
			||||
 | 
					   //exportData,
 | 
				
			||||
 | 
					   addSalesMemberBulk, | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,71 @@ | 
				
			|||||
 | 
					/* eslint-disable*/ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					'use strict'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					module.exports = dc => { | 
				
			||||
 | 
					  const DataTypes = dc.ORM; | 
				
			||||
 | 
					  const sequelize = dc.orm; | 
				
			||||
 | 
					  const SalesDistribution = sequelize.define("salesDistribution", { | 
				
			||||
 | 
					    id: { | 
				
			||||
 | 
					      type: DataTypes.INTEGER, | 
				
			||||
 | 
					      allowNull: false, | 
				
			||||
 | 
					      defaultValue: null, | 
				
			||||
 | 
					      comment: null, | 
				
			||||
 | 
					      primaryKey: true, | 
				
			||||
 | 
					      field: "id", | 
				
			||||
 | 
					      autoIncrement: true, | 
				
			||||
 | 
					      unique: "sales_distribution_id_uindex" | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    pepUserId: { | 
				
			||||
 | 
					      type: DataTypes.INTEGER, | 
				
			||||
 | 
					      allowNull: false, | 
				
			||||
 | 
					      defaultValue: null, | 
				
			||||
 | 
					      comment: null, | 
				
			||||
 | 
					      primaryKey: false, | 
				
			||||
 | 
					      field: "pep_user_id", | 
				
			||||
 | 
					      autoIncrement: false | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    provinces: { | 
				
			||||
 | 
					      type: DataTypes.STRING, | 
				
			||||
 | 
					      allowNull: false, | 
				
			||||
 | 
					      defaultValue: null, | 
				
			||||
 | 
					      comment: null, | 
				
			||||
 | 
					      primaryKey: false, | 
				
			||||
 | 
					      field: "provinces", | 
				
			||||
 | 
					      autoIncrement: false | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    cities: { | 
				
			||||
 | 
					      type: DataTypes.STRING, | 
				
			||||
 | 
					      allowNull: true, | 
				
			||||
 | 
					      defaultValue: null, | 
				
			||||
 | 
					      comment: null, | 
				
			||||
 | 
					      primaryKey: false, | 
				
			||||
 | 
					      field: "cities", | 
				
			||||
 | 
					      autoIncrement: false | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    del: { | 
				
			||||
 | 
					      type: DataTypes.BOOLEAN, | 
				
			||||
 | 
					      allowNull: true, | 
				
			||||
 | 
					      defaultValue: null, | 
				
			||||
 | 
					      comment: null, | 
				
			||||
 | 
					      primaryKey: false, | 
				
			||||
 | 
					      field: "del", | 
				
			||||
 | 
					      autoIncrement: false | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    businessLines: { | 
				
			||||
 | 
					      type: DataTypes.STRING, | 
				
			||||
 | 
					      allowNull: true, | 
				
			||||
 | 
					      defaultValue: null, | 
				
			||||
 | 
					      comment: null, | 
				
			||||
 | 
					      primaryKey: false, | 
				
			||||
 | 
					      field: "business_lines", | 
				
			||||
 | 
					      autoIncrement: false | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  }, { | 
				
			||||
 | 
					    tableName: "sales_distribution", | 
				
			||||
 | 
					    comment: "", | 
				
			||||
 | 
					    indexes: [] | 
				
			||||
 | 
					  }); | 
				
			||||
 | 
					  dc.models.SalesDistribution = SalesDistribution; | 
				
			||||
 | 
					  return SalesDistribution; | 
				
			||||
 | 
					}; | 
				
			||||
@ -0,0 +1,24 @@ | 
				
			|||||
 | 
					'use strict'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const salesDistribution = require('../../controllers/salesDistribution'); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					module.exports = function (app, router, opts) { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   app.fs.api.logAttr['GET/sales/member/list'] = { content: '查询销售人员列表', visible: true }; | 
				
			||||
 | 
					   router.get('/sales/member/list', salesDistribution.salesList); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   app.fs.api.logAttr['POST/sales/member/add'] = { content: '添加销售人员信息', visible: true }; | 
				
			||||
 | 
					   router.post('/sales/member/add', salesDistribution.add); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   app.fs.api.logAttr['PUT/sales/member/modify'] = { content: '编辑销售人员信息', visible: true }; | 
				
			||||
 | 
					   router.put('/sales/member/modify', salesDistribution.edit); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   app.fs.api.logAttr['DEL/sales/member/del'] = { content: '删除销售人员信息', visible: true }; | 
				
			||||
 | 
					   router.del('/sales/member/del', salesDistribution.del); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   app.fs.api.logAttr['POST/add/sales/members/bulk'] = { content: '导入销售人员信息', visible: true }; | 
				
			||||
 | 
					   router.post('/add/sales/members/bulk', salesDistribution.addSalesMemberBulk); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					   // app.fs.api.logAttr['GET/sales/members/export'] = { content: '导出销售人员信息', visible: true };
 | 
				
			||||
 | 
					   // router.get('/sales/members/export', salesDistribution.exportData);
 | 
				
			||||
 | 
					}; | 
				
			||||
@ -0,0 +1,11 @@ | 
				
			|||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					CREATE TABLE sales_distribution | 
				
			||||
 | 
					( | 
				
			||||
 | 
					    id serial PRIMARY KEY NOT NULL, | 
				
			||||
 | 
					    pep_user_id int NOT NULL, | 
				
			||||
 | 
					    provinces text NOT NULL, | 
				
			||||
 | 
					    cities text, | 
				
			||||
 | 
					    del boolean DEFAULT false NULL | 
				
			||||
 | 
					); | 
				
			||||
 | 
					CREATE UNIQUE INDEX sales_distribution_id_uindex ON sales_distribution (id); | 
				
			||||
@ -0,0 +1,4 @@ | 
				
			|||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					alter table sales_distribution | 
				
			||||
 | 
						add "business_lines" text; | 
				
			||||
@ -0,0 +1,89 @@ | 
				
			|||||
 | 
					'use strict'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					import { ApiTable, basicAction } from '$utils' | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export function addSalesMemberBulk(values) { | 
				
			||||
 | 
					    return dispatch => basicAction({ | 
				
			||||
 | 
					        type: 'post', | 
				
			||||
 | 
					        dispatch: dispatch, | 
				
			||||
 | 
					        actionType: 'SALES_MEMBER_BULK_ADD', | 
				
			||||
 | 
					        url: ApiTable.addSalesMemberBulk, | 
				
			||||
 | 
					        data: values, | 
				
			||||
 | 
					        msg: { option: '导入销售人员信息' }, | 
				
			||||
 | 
					    }); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export function postSalesMember(data) {//添加/编辑
 | 
				
			||||
 | 
					    let msg = '' | 
				
			||||
 | 
					    if (data) { | 
				
			||||
 | 
					        msg = data.msg | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    return (dispatch) => | 
				
			||||
 | 
					        basicAction({ | 
				
			||||
 | 
					            type: "post", | 
				
			||||
 | 
					            dispatch: dispatch, | 
				
			||||
 | 
					            data, | 
				
			||||
 | 
					            actionType: "POST_SALES_MEMBER", | 
				
			||||
 | 
					            url: `${ApiTable.addSalesMember}`, | 
				
			||||
 | 
					            msg: { option: msg }, //添加/编辑
 | 
				
			||||
 | 
					            reducer: { name: "" }, | 
				
			||||
 | 
					        }); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export function getSalesList(query) {//查询
 | 
				
			||||
 | 
					    return (dispatch) => basicAction({ | 
				
			||||
 | 
					        type: "get", | 
				
			||||
 | 
					        dispatch: dispatch, | 
				
			||||
 | 
					        actionType: "GET_SALES_MENBER_LIST", | 
				
			||||
 | 
					        query: query, | 
				
			||||
 | 
					        url: `${ApiTable.getSalesList}`, | 
				
			||||
 | 
					        msg: { option: "查询销售人员列表" }, | 
				
			||||
 | 
					        reducer: { name: "SalesMemberList", params: { noClear: true } }, | 
				
			||||
 | 
					    }); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					export function delSalesMember(data) {//删除
 | 
				
			||||
 | 
					    let msg = '' | 
				
			||||
 | 
					    if (data) { | 
				
			||||
 | 
					        msg = data.msg | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    return (dispatch) => | 
				
			||||
 | 
					        basicAction({ | 
				
			||||
 | 
					            type: "del", | 
				
			||||
 | 
					            query: data, | 
				
			||||
 | 
					            dispatch: dispatch, | 
				
			||||
 | 
					            actionType: "DEL_SALES_MEMBER", | 
				
			||||
 | 
					            url: `${ApiTable.delSalesMember}`, | 
				
			||||
 | 
					            msg: { option: msg }, //删除
 | 
				
			||||
 | 
					            reducer: {}, | 
				
			||||
 | 
					        }); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// export function getMemberExport(query) {//导出员工信息
 | 
				
			||||
 | 
					//     return (dispatch) => basicAction({
 | 
				
			||||
 | 
					//         type: "get",
 | 
				
			||||
 | 
					//         dispatch: dispatch,
 | 
				
			||||
 | 
					//         actionType: "GET_MemberEXPORT",
 | 
				
			||||
 | 
					//         query: query,
 | 
				
			||||
 | 
					//         url: `${ApiTable.getMemberExport}`,
 | 
				
			||||
 | 
					//         msg: { option: "导出员工信息" },
 | 
				
			||||
 | 
					//         reducer: { name: "MemberExport", params: { noClear: true } },
 | 
				
			||||
 | 
					//     });
 | 
				
			||||
 | 
					// }
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export function editSalesMember(data) {//更新
 | 
				
			||||
 | 
					    let msg = '' | 
				
			||||
 | 
					    if (data) { | 
				
			||||
 | 
					        msg = data.msg | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    return (dispatch) => | 
				
			||||
 | 
					        basicAction({ | 
				
			||||
 | 
					            type: "put", | 
				
			||||
 | 
					            dispatch: dispatch, | 
				
			||||
 | 
					            data, | 
				
			||||
 | 
					            actionType: "PUT_SALES_MEMBER", | 
				
			||||
 | 
					            url: `${ApiTable.editSalesMember}`, | 
				
			||||
 | 
					            msg: { option: msg }, //更新
 | 
				
			||||
 | 
					            reducer: {}, | 
				
			||||
 | 
					        }); | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,272 @@ | 
				
			|||||
 | 
					'use strict'; | 
				
			||||
 | 
					import React, { useState, useEffect } from 'react'; | 
				
			||||
 | 
					import { connect } from 'react-redux'; | 
				
			||||
 | 
					import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui'; | 
				
			||||
 | 
					import { IconUpload } from '@douyinfe/semi-icons'; | 
				
			||||
 | 
					import cityData from '../../components/city.json'; | 
				
			||||
 | 
					import XLSX from 'xlsx' | 
				
			||||
 | 
					//下载模板和上传文件读取
 | 
				
			||||
 | 
					const ImportSalersModal = props => { | 
				
			||||
 | 
					    const { dispatch, actions, onCancel, rzMembers } = props | 
				
			||||
 | 
					    const { humanAffairs } = actions; | 
				
			||||
 | 
					    const [msg, setMsg] = useState('') | 
				
			||||
 | 
					    const [loading, setLoading] = useState('') | 
				
			||||
 | 
					    const [postData, setPostData] = useState([]) | 
				
			||||
 | 
					    const [allProvinces, setAllProvinces] = useState([]) | 
				
			||||
 | 
					    //初始化
 | 
				
			||||
 | 
					    useEffect(() => { | 
				
			||||
 | 
					        let allProvinces = [];//所有省
 | 
				
			||||
 | 
					        cityData.map(cd => { | 
				
			||||
 | 
					            allProvinces.push(cd.name); | 
				
			||||
 | 
					        }); | 
				
			||||
 | 
					        setAllProvinces(allProvinces); | 
				
			||||
 | 
					    }, []); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const confirm = () => { | 
				
			||||
 | 
					        if (postData.length) { | 
				
			||||
 | 
					            setLoading(true) | 
				
			||||
 | 
					            dispatch(humanAffairs.addSalesMemberBulk(postData)).then(res => { | 
				
			||||
 | 
					                if (res.success) { | 
				
			||||
 | 
					                    onCancel() | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                setLoading(false) | 
				
			||||
 | 
					            }) | 
				
			||||
 | 
					        } else { | 
				
			||||
 | 
					            Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 }) | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const dldCsvMb = () => { | 
				
			||||
 | 
					        //表头
 | 
				
			||||
 | 
					        let head = "员工编号,姓名,销售区域(省/直辖市),销售区域(市),业务线\n" | 
				
			||||
 | 
					        //数据
 | 
				
			||||
 | 
					        //let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
 | 
				
			||||
 | 
					        let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head; | 
				
			||||
 | 
					        //创建一个a标签
 | 
				
			||||
 | 
					        let link = document.createElement("a"); | 
				
			||||
 | 
					        //为a标签设置属性
 | 
				
			||||
 | 
					        link.setAttribute("href", templateCsv); | 
				
			||||
 | 
					        link.setAttribute("download", "人资系统销售人员信息导入模板.csv"); | 
				
			||||
 | 
					        //点击a标签
 | 
				
			||||
 | 
					        link.click(); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    const download = () => { | 
				
			||||
 | 
					        //dldTemplate();
 | 
				
			||||
 | 
					        dldCsvMb(); | 
				
			||||
 | 
					        // let str = "";
 | 
				
			||||
 | 
					        // rule.forEach((v, i) => {
 | 
				
			||||
 | 
					        //     str += `${v}\r\n`
 | 
				
			||||
 | 
					        // })
 | 
				
			||||
 | 
					        // dldText("填写说明.txt", str);
 | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // const dldText = (filename, text) => {
 | 
				
			||||
 | 
					    //     var element = document.createElement('a');
 | 
				
			||||
 | 
					    //     element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
 | 
				
			||||
 | 
					    //     element.setAttribute('download', filename);
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    //     element.style.display = 'none';
 | 
				
			||||
 | 
					    //     document.body.appendChild(element);
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    //     element.click();
 | 
				
			||||
 | 
					    //     document.body.removeChild(element);
 | 
				
			||||
 | 
					    // }
 | 
				
			||||
 | 
					    const fileLimit = '.csv'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const getFileBlob = (url) => { | 
				
			||||
 | 
					        return new Promise((resolve, reject) => { | 
				
			||||
 | 
					            let request = new XMLHttpRequest() | 
				
			||||
 | 
					            request.open("GET", url, true) | 
				
			||||
 | 
					            request.responseType = "blob" | 
				
			||||
 | 
					            request.onreadystatechange = e => { | 
				
			||||
 | 
					                if (request.readyState == 4) { | 
				
			||||
 | 
					                    if (request.status == 200) { | 
				
			||||
 | 
					                        if (window.FileReader) { | 
				
			||||
 | 
					                            let reader = new FileReader(); | 
				
			||||
 | 
					                            reader.readAsBinaryString(request.response); | 
				
			||||
 | 
					                            reader.onload = event => { | 
				
			||||
 | 
					                                try { | 
				
			||||
 | 
					                                    const { result } = event.target; | 
				
			||||
 | 
					                                    // 以二进制流方式读取得到整份excel表格对象
 | 
				
			||||
 | 
					                                    const workbook = XLSX.read(result, { | 
				
			||||
 | 
					                                        type: "binary", | 
				
			||||
 | 
					                                        cellDates: true,//设为true,将天数的时间戳转为时间格式
 | 
				
			||||
 | 
					                                        codepage: 936//解决了乱码问题
 | 
				
			||||
 | 
					                                    }); | 
				
			||||
 | 
					                                    let data = []; // 存储获取到的数据
 | 
				
			||||
 | 
					                                    // 遍历每张工作表进行读取(这里默认只读取第一张表)
 | 
				
			||||
 | 
					                                    for (const sheet in workbook.Sheets) { | 
				
			||||
 | 
					                                        if (workbook.Sheets.hasOwnProperty(sheet)) { | 
				
			||||
 | 
					                                            data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet])); | 
				
			||||
 | 
					                                        } | 
				
			||||
 | 
					                                    } | 
				
			||||
 | 
					                                    resolve(data);//导出数据
 | 
				
			||||
 | 
					                                } catch (e) { | 
				
			||||
 | 
					                                    reject("失败"); | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            request.send(); | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const judgeProvinces = (provinces) => { | 
				
			||||
 | 
					        let noMark = 0; | 
				
			||||
 | 
					        provinces?.split('、')?.map(p => { | 
				
			||||
 | 
					            if (allProvinces.indexOf(p) == -1) { | 
				
			||||
 | 
					                noMark++ | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					        return !noMark; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const judgeCities = (provinces, cities) => { | 
				
			||||
 | 
					        if (!cities) {//可以不填
 | 
				
			||||
 | 
					            return true; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					        let nowCities = []; | 
				
			||||
 | 
					        cityData.filter(cd => provinces?.split('、').indexOf(cd.name) != -1).map(d => { | 
				
			||||
 | 
					            d.children?.map(c => { | 
				
			||||
 | 
					                nowCities.push(c.name) | 
				
			||||
 | 
					            }) | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					        let noMark = 0; | 
				
			||||
 | 
					        cities?.split('、')?.map(p => { | 
				
			||||
 | 
					            if (nowCities.indexOf(p) == -1) { | 
				
			||||
 | 
					                noMark++ | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					        return !noMark; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const judgeNull = (value) => { | 
				
			||||
 | 
					        return value ? String(value).trim().replace(/\s*/g, "") : null; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    return ( | 
				
			||||
 | 
					        <Modal | 
				
			||||
 | 
					            title="导入" visible={true} | 
				
			||||
 | 
					            onOk={confirm} width={620} | 
				
			||||
 | 
					            confirmLoading={loading} | 
				
			||||
 | 
					            onCancel={() => { | 
				
			||||
 | 
					                setMsg('') | 
				
			||||
 | 
					                setLoading(false) | 
				
			||||
 | 
					                setPostData([]) | 
				
			||||
 | 
					                onCancel() | 
				
			||||
 | 
					            }} | 
				
			||||
 | 
					        > | 
				
			||||
 | 
					            <div style={{ borderBottom: '1px solid #DCDEE0', margin: '0px -24px' }}></div> | 
				
			||||
 | 
					            <Form> | 
				
			||||
 | 
					                <Form.Upload | 
				
			||||
 | 
					                    label={'销售人员信息'} labelPosition='left' | 
				
			||||
 | 
					                    action={'/'} accept={fileLimit} | 
				
			||||
 | 
					                    maxSize={200} limit={1} | 
				
			||||
 | 
					                    onRemove={(currentFile, fileList, fileItem) => { | 
				
			||||
 | 
					                        setMsg(''); | 
				
			||||
 | 
					                        setPostData([]); | 
				
			||||
 | 
					                    }} | 
				
			||||
 | 
					                    customRequest={(data) => { | 
				
			||||
 | 
					                        const { file, onSuccess, onError } = data | 
				
			||||
 | 
					                        getFileBlob(file.url).then(res => { | 
				
			||||
 | 
					                            const error = (msg) => { | 
				
			||||
 | 
					                                setMsg(msg) | 
				
			||||
 | 
					                                onError({ message: msg }) | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                            if (res.length > 1000) { | 
				
			||||
 | 
					                                error('一次性上传数据行数应小于1000行,请分批上传') | 
				
			||||
 | 
					                                return | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                            if (!res.length) { | 
				
			||||
 | 
					                                error('请填写至少一行数据') | 
				
			||||
 | 
					                                return | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                            let postData = []; | 
				
			||||
 | 
					                            const zmsz = /^[A-Za-z0-9]+$/;//字母数字组合
 | 
				
			||||
 | 
					                            for (let i = 0; i < res.length; i++) { | 
				
			||||
 | 
					                                let d = res[i]; | 
				
			||||
 | 
					                                let number = judgeNull(d['员工编号']); | 
				
			||||
 | 
					                                let name = judgeNull(d['姓名']); | 
				
			||||
 | 
					                                let provinces = judgeNull(d['销售区域(省/直辖市)']); | 
				
			||||
 | 
					                                let cities = judgeNull(d['销售区域(市)']); | 
				
			||||
 | 
					                                let businessLines = judgeNull(d['业务线']); | 
				
			||||
 | 
					                                if (!number) {//人员编号不为空,唯一,字母和数字
 | 
				
			||||
 | 
					                                    error(`第${i + 2}行人员编号为空,请填写`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                let rzExist = rzMembers.find(m => m.userCode == number); | 
				
			||||
 | 
					                                if (!rzExist) { | 
				
			||||
 | 
					                                    error(`第${i + 2}行的人员编号无对应的员工信息`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                if (postData.some(p => p.number == number)) {//人员编号 唯一
 | 
				
			||||
 | 
					                                    error(`第${i + 2}行人员编号重复,请更改后重新上传`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                if (!zmsz.test(number)) { | 
				
			||||
 | 
					                                    error(`第${i + 2}行人员编号错误,请填写字母和数字的组合`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                if (!name) {//姓名必填
 | 
				
			||||
 | 
					                                    error(`第${i + 2}行姓名为空,请填写`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                if (!provinces) {//销售区域(省/直辖市)必填
 | 
				
			||||
 | 
					                                    error(`第${i + 2}行销售区域(省/直辖市)为空,请填写`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                let pValid = judgeProvinces(provinces); | 
				
			||||
 | 
					                                if (!pValid) { | 
				
			||||
 | 
					                                    error(`第${i + 2}行销售区域(省/直辖市)错误`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                let cValid = judgeCities(provinces, cities); | 
				
			||||
 | 
					                                if (!cValid) { | 
				
			||||
 | 
					                                    error(`第${i + 2}行销售区域(市)错误`) | 
				
			||||
 | 
					                                    return | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                //todo 业务线判断
 | 
				
			||||
 | 
					                                postData.push({ | 
				
			||||
 | 
					                                    pepUserId: rzExist.pepUserId, name, number, | 
				
			||||
 | 
					                                    provinces: provinces || '', cities: cities || '', businessLines: businessLines || '', | 
				
			||||
 | 
					                                    del: false | 
				
			||||
 | 
					                                }) | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                            setPostData(postData) | 
				
			||||
 | 
					                            let msg = '文件解析完成,点击确定按钮上传保存!' | 
				
			||||
 | 
					                            setMsg(msg) | 
				
			||||
 | 
					                            onSuccess({ message: msg }) | 
				
			||||
 | 
					                        }) | 
				
			||||
 | 
					                    }}> | 
				
			||||
 | 
					                    <Button icon={<IconUpload />} theme="light"> | 
				
			||||
 | 
					                        请选择文件 | 
				
			||||
 | 
					                    </Button> | 
				
			||||
 | 
					                </Form.Upload> | 
				
			||||
 | 
					                <span>{msg}</span> | 
				
			||||
 | 
					                <div style={{ color: '#ccc' }}>最大不超过200M,导入文件需与 | 
				
			||||
 | 
					                    <span onClick={() => download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板</span> | 
				
			||||
 | 
					                    一致</div> | 
				
			||||
 | 
					                <div style={{ marginTop: 20, border: '1px solid #C7E1FF', background: '#F4F8FF', borderRadius: 2, padding: '8px 0px 7px 12px', alignItems: 'center', color: '#0F7EFB', fontSize: 12 }}> | 
				
			||||
 | 
					                    <div>填写要求:</div> | 
				
			||||
 | 
					                    <div>员工编号:必填,唯一,数字和字母的组合;</div> | 
				
			||||
 | 
					                    <div>姓名:必填,若与员工编号对应的项企用户姓名不同,将以项企数据为准;</div> | 
				
			||||
 | 
					                    <div>销售区域(省/直辖市):必填,省或直辖市顿号隔开,如“北京市、江西省、江苏省”;</div> | 
				
			||||
 | 
					                    <div>销售区域(市):非必填,归属所填省的地级市顿号隔开,如“南昌市、镇江市”。</div> | 
				
			||||
 | 
					                </div> | 
				
			||||
 | 
					            </Form> | 
				
			||||
 | 
					        </Modal > | 
				
			||||
 | 
					    ) | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					function mapStateToProps(state) { | 
				
			||||
 | 
					    const { auth, global, MemberList } = state; | 
				
			||||
 | 
					    return { | 
				
			||||
 | 
					        user: auth.user, | 
				
			||||
 | 
					        actions: global.actions, | 
				
			||||
 | 
					        rzMembers: MemberList.data?.rows || [], | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export default connect(mapStateToProps)(ImportSalersModal); | 
				
			||||
@ -0,0 +1,350 @@ | 
				
			|||||
 | 
					import React, { useEffect, useRef, useState, useMemo } from 'react'; | 
				
			||||
 | 
					import { connect } from 'react-redux'; | 
				
			||||
 | 
					import moment from 'moment' | 
				
			||||
 | 
					import { Select, Input, Button, Popconfirm, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui'; | 
				
			||||
 | 
					import SalesMemberModal from './salesMemberModal' | 
				
			||||
 | 
					import ImportSalersModal from './importSalersModal' | 
				
			||||
 | 
					import { IconSearch } from '@douyinfe/semi-icons'; | 
				
			||||
 | 
					import { SkeletonScreen } from "$components"; | 
				
			||||
 | 
					import '../../style.less' | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const PersonnelDistribution = (props) => { | 
				
			||||
 | 
					    const { dispatch, actions } = props | 
				
			||||
 | 
					    const { humanAffairs } = actions; | 
				
			||||
 | 
					    const [keywordTarget, setKeywordTarget] = useState('dep'); | 
				
			||||
 | 
					    const [keyword, setKeyword] = useState('');//搜索内容 | 
				
			||||
 | 
					    const [limits, setLimits] = useState()//每页实际条数 | 
				
			||||
 | 
					    const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息 | 
				
			||||
 | 
					    const [modalV, setModalV] = useState(false); | 
				
			||||
 | 
					    const [dataToEdit, setDataToEdit] = useState(null); | 
				
			||||
 | 
					    const [tableData, setTableData] = useState([]); | 
				
			||||
 | 
					    const [importModalV, setImportModalV] = useState(false); | 
				
			||||
 | 
					    const page = useRef(query.page); | 
				
			||||
 | 
					    function seachValueChange(value) { | 
				
			||||
 | 
					        setKeyword(value) | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    useEffect(() => { | 
				
			||||
 | 
					        dispatch(humanAffairs.getMemberList()) | 
				
			||||
 | 
					        getMemberSearchList() | 
				
			||||
 | 
					    }, []); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    useEffect(() => { | 
				
			||||
 | 
					        getMemberSearchList()//查询人员列表 | 
				
			||||
 | 
					    }, [query]) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    function getMemberSearchList() { | 
				
			||||
 | 
					        let kt = keywordTarget == 'place' ? '' : keywordTarget; | 
				
			||||
 | 
					        let k = keywordTarget == 'place' ? '' : keyword; | 
				
			||||
 | 
					        let placeSearch = keywordTarget == 'place' ? keyword : ''; | 
				
			||||
 | 
					        dispatch(humanAffairs.getSalesList({ keywordTarget: kt, keyword: k, placeSearch, ...query })).then(r => { | 
				
			||||
 | 
					            if (r.success) { | 
				
			||||
 | 
					                setTableData(r.payload?.data?.rows); | 
				
			||||
 | 
					                setLimits(r.payload?.data?.count) | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    function handleRow(record, index) {// 给偶数行设置斑马纹 | 
				
			||||
 | 
					        if (index % 2 === 0) { | 
				
			||||
 | 
					            return { | 
				
			||||
 | 
					                style: { | 
				
			||||
 | 
					                    background: '#FAFCFF', | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            }; | 
				
			||||
 | 
					        } else { | 
				
			||||
 | 
					            return {}; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const closeAndFetch = () => { | 
				
			||||
 | 
					        setModalV(false) | 
				
			||||
 | 
					        getMemberSearchList(); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const starHeader = (header) => { | 
				
			||||
 | 
					        return <div> | 
				
			||||
 | 
					            <img src="/assets/images/hrImg/V.png" style={{ width: 14, height: 14 }} /> {header} | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const getMultis = (arrStr) => {//默认展示2个 | 
				
			||||
 | 
					        return <div style={{ display: 'flex' }}> | 
				
			||||
 | 
					            { | 
				
			||||
 | 
					                arrStr.length ? | 
				
			||||
 | 
					                    arrStr.map((ite, idx) => { | 
				
			||||
 | 
					                        return ( | 
				
			||||
 | 
					                            <div key={idx} style={{ display: 'flex' }}> | 
				
			||||
 | 
					                                {idx < 2 ? | 
				
			||||
 | 
					                                    <div style={{ padding: '0px 4px 1px 4px', color: '#FFF', fontSize: 12, background: 'rgba(0,90,189,0.8)', borderRadius: 2, marginRight: 4 }}> | 
				
			||||
 | 
					                                        {ite} | 
				
			||||
 | 
					                                    </div> : '' | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                                { | 
				
			||||
 | 
					                                    arrStr.length > 2 && idx == 2 ? | 
				
			||||
 | 
					                                        <Tooltip content={arrStr.join(',')} trigger="click" style={{ lineHeight: 2 }}> | 
				
			||||
 | 
					                                            <div style={{ padding: '0px 4px 1px 4px ', color: 'rgba(0,90,189,0.8)', fontSize: 12, marginRight: 4, cursor: "pointer" }}> | 
				
			||||
 | 
					                                                +{arrStr.length - 2} | 
				
			||||
 | 
					                                            </div> | 
				
			||||
 | 
					                                        </Tooltip> | 
				
			||||
 | 
					                                        : '' | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            </div> | 
				
			||||
 | 
					                        ) | 
				
			||||
 | 
					                    }) : '-' | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const columns = [{ | 
				
			||||
 | 
					        title: '序号', | 
				
			||||
 | 
					        dataIndex: 'id', | 
				
			||||
 | 
					        key: 'id', | 
				
			||||
 | 
					        width: 60, | 
				
			||||
 | 
					        render: (text, record, index) => index + 1 | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: starHeader('姓名'), | 
				
			||||
 | 
					        dataIndex: 'name', | 
				
			||||
 | 
					        key: 'name', | 
				
			||||
 | 
					        width: 80 | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    { | 
				
			||||
 | 
					        title: starHeader('部门名称'), | 
				
			||||
 | 
					        dataIndex: 'department', | 
				
			||||
 | 
					        key: 'department', | 
				
			||||
 | 
					        width: 200, | 
				
			||||
 | 
					        render: (text, r, index) => { | 
				
			||||
 | 
					            let arrStr = text.map(t => t.name); | 
				
			||||
 | 
					            return getMultis(arrStr); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: '销售区域(省/直辖市)', | 
				
			||||
 | 
					        dataIndex: 'provinces', | 
				
			||||
 | 
					        key: 'provinces', | 
				
			||||
 | 
					        width: 160, | 
				
			||||
 | 
					        render: (text, record, index) => { | 
				
			||||
 | 
					            return getMultis(text?.split('、') || []); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: '销售区域(市)', | 
				
			||||
 | 
					        dataIndex: 'cities', | 
				
			||||
 | 
					        key: 'cities', | 
				
			||||
 | 
					        width: 160, | 
				
			||||
 | 
					        render: (text, record, index) => { | 
				
			||||
 | 
					            return text ? getMultis(text?.split('、') || []) : '-'; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: '业务线', | 
				
			||||
 | 
					        dataIndex: 'businessLines', | 
				
			||||
 | 
					        key: 'businessLines', | 
				
			||||
 | 
					        width: 120, | 
				
			||||
 | 
					        render: (text, record, index) => { | 
				
			||||
 | 
					            return text ? getMultis(text?.split('、') || []) : '-'; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: starHeader('岗位'), | 
				
			||||
 | 
					        dataIndex: 'post', | 
				
			||||
 | 
					        key: 'post', | 
				
			||||
 | 
					        width: 120, | 
				
			||||
 | 
					        render: (text, record) => <span>{text || '-'}</span> | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: starHeader('入职时间'), | 
				
			||||
 | 
					        dataIndex: 'hireDate', | 
				
			||||
 | 
					        key: 'hireDate', | 
				
			||||
 | 
					        width: 120, | 
				
			||||
 | 
					        render: (text, record) => <span>{text || '-'}</span> | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: starHeader('转正时间'), | 
				
			||||
 | 
					        dataIndex: 'regularDate', | 
				
			||||
 | 
					        key: 'regularDate', | 
				
			||||
 | 
					        width: 120, | 
				
			||||
 | 
					        render: (text, record) => <span>{text || '-'}</span> | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: starHeader('工龄'), | 
				
			||||
 | 
					        dataIndex: 'workYears', | 
				
			||||
 | 
					        key: 'workYears', | 
				
			||||
 | 
					        width: 120, | 
				
			||||
 | 
					        render: (_, r, index) => { | 
				
			||||
 | 
					            return (r.hireDate ? <span style={{ color: '#1890FF' }}>{String(moment(new Date()).diff(r.hireDate, 'years', true)).substring(0, 3) + '年'}</span> : '-') | 
				
			||||
 | 
					        }, | 
				
			||||
 | 
					    }, { | 
				
			||||
 | 
					        title: '操作', | 
				
			||||
 | 
					        dataIndex: 'action', | 
				
			||||
 | 
					        width: 120, | 
				
			||||
 | 
					        render: (text, record) => { | 
				
			||||
 | 
					            return <div> | 
				
			||||
 | 
					                <span style={{ color: '#1890FF', cursor: 'pointer' }} onClick={() => onEdit(record)}>编辑</span>   | 
				
			||||
 | 
					                <Popconfirm | 
				
			||||
 | 
					                    title='提示' content="确认删除该销售人员信息?" position='topLeft' | 
				
			||||
 | 
					                    onConfirm={() => confirmDelete(record.pepUserId)} style={{ width: 330 }} | 
				
			||||
 | 
					                > <span style={{ color: '#1890FF', cursor: 'pointer' }}>删除</span></Popconfirm> | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    }]; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const onEdit = (data) => { | 
				
			||||
 | 
					        setModalV(true); | 
				
			||||
 | 
					        setDataToEdit(data); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const confirmDelete = (pepUserId) => { | 
				
			||||
 | 
					        dispatch(humanAffairs.delSalesMember({ pepUserId, msg: '删除销售人员信息' })).then(res => { | 
				
			||||
 | 
					            if (res.success) { | 
				
			||||
 | 
					                getMemberSearchList(); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        }); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    const scroll = useMemo(() => ({}), []); | 
				
			||||
 | 
					    return (<div style={{ padding: '0px 12px' }}> | 
				
			||||
 | 
					        <div style={{ display: 'flex' }}> | 
				
			||||
 | 
					            <div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14 }}>招聘</div> | 
				
			||||
 | 
					            <div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14, margin: '0px 8px' }}>/</div> | 
				
			||||
 | 
					            <div style={{ color: 'rgba(0,0,0,0.45)', fontSize: 14 }}>销售统计</div> | 
				
			||||
 | 
					            <div style={{ color: '#033C9A', fontSize: 14, margin: '0px 8px' }}>/</div> | 
				
			||||
 | 
					            <div style={{ color: '#033C9A', fontSize: 14 }}>销售人员分布</div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					        <div style={{ background: '#FFFFFF', boxShadow: '0px 0px 12px 2px rgba(220,222,224,0.2)', borderRadius: 2, padding: '20px 0px 20px 19px ', marginTop: 12 }}> | 
				
			||||
 | 
					            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> | 
				
			||||
 | 
					                <div style={{ display: 'flex', alignItems: 'baseline' }}> | 
				
			||||
 | 
					                    <div style={{ width: 0, height: 20, borderLeft: '3px solid #0F7EFB', borderTop: '3px solid transparent', borderBottom: '3px solid transparent' }}></div> | 
				
			||||
 | 
					                    <div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#033C9A', marginLeft: 8 }}>销售人员分布</div> | 
				
			||||
 | 
					                    <div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>DISTRIBUTION OF SALES PERSONNEL</div> | 
				
			||||
 | 
					                </div> | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					            <div style={{ margin: '18px 0px' }}> | 
				
			||||
 | 
					                <div style={{ display: 'flex', margin: '16px 0px', justifyContent: 'space-between' }}> | 
				
			||||
 | 
					                    <div style={{ display: 'flex' }}> | 
				
			||||
 | 
					                        <div style={{ padding: '6px 20px', background: '#0F7EFB', color: '#FFFFFF', fontSize: 14, cursor: "pointer", marginRight: 18 }} | 
				
			||||
 | 
					                            onClick={() => { | 
				
			||||
 | 
					                                setModalV(true); | 
				
			||||
 | 
					                                setDataToEdit(null); | 
				
			||||
 | 
					                            }}> | 
				
			||||
 | 
					                            新增 | 
				
			||||
 | 
					                        </div> | 
				
			||||
 | 
					                        <div> | 
				
			||||
 | 
					                            <Select value={keywordTarget} onChange={setKeywordTarget} style={{ width: 100 }} > | 
				
			||||
 | 
					                                <Select.Option value='dep'>部门</Select.Option> | 
				
			||||
 | 
					                                <Select.Option value='place'>地区</Select.Option> | 
				
			||||
 | 
					                            </Select> | 
				
			||||
 | 
					                        </div> | 
				
			||||
 | 
					                        <div style={{ margin: '0px 18px' }}> | 
				
			||||
 | 
					                            <Input suffix={<IconSearch />} | 
				
			||||
 | 
					                                showClear | 
				
			||||
 | 
					                                placeholder='请输入关键词搜索' | 
				
			||||
 | 
					                                value={keyword} | 
				
			||||
 | 
					                                style={{ width: 346 }} | 
				
			||||
 | 
					                                onChange={seachValueChange}> | 
				
			||||
 | 
					                            </Input> | 
				
			||||
 | 
					                        </div> | 
				
			||||
 | 
					                        <Button theme='solid' type='primary' style={{ width: 80, borderRadius: 2, height: 32, background: '#DBECFF', color: '#005ABD' }} | 
				
			||||
 | 
					                            onClick={() => { | 
				
			||||
 | 
					                                setQuery({ limit: 10, page: 0 }) | 
				
			||||
 | 
					                            }}>查询</Button> | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                    <div style={{ display: 'flex', marginRight: 20 }}> | 
				
			||||
 | 
					                        <div style={{ padding: '6px 20px', background: '#0F7EFB', color: '#FFFFFF', fontSize: 14, cursor: "pointer" }} | 
				
			||||
 | 
					                            onClick={() => { setImportModalV(true); }}> | 
				
			||||
 | 
					                            导入 | 
				
			||||
 | 
					                        </div> | 
				
			||||
 | 
					                        {/* <div style={{ padding: '6px 20px', background: '#00BA85', color: '#FFFFFF', fontSize: 14, marginLeft: 18 }}> | 
				
			||||
 | 
					                            导出 | 
				
			||||
 | 
					                        </div> */} | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                <div style={{ border: '1px solid #C7E1FF', background: '#F4F8FF', borderRadius: 2, height: 32, width: 669, padding: '8px 0px 7px 12px', display: 'flex', alignItems: 'center', color: '#0F7EFB', fontSize: 12 }}> | 
				
			||||
 | 
					                    <img src="/assets/images/hrImg/!.png" alt="" style={{ width: 14, height: 14, marginRight: 8 }} /> | 
				
			||||
 | 
					                    表格中带有认证标识" | 
				
			||||
 | 
					                    <img src="/assets/images/hrImg/V.png" alt="" style={{ width: 14, height: 14 }} /> | 
				
			||||
 | 
					                    "信息的为系统基础数据,来源于项企PEP、钉钉等系统,其他数据均为导入或自定义数据 | 
				
			||||
 | 
					                </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                <div style={{ marginTop: 20 }}> | 
				
			||||
 | 
					                    <Skeleton | 
				
			||||
 | 
					                        // loading={loading} | 
				
			||||
 | 
					                        loading={false} | 
				
			||||
 | 
					                        active={true} | 
				
			||||
 | 
					                        placeholder={SkeletonScreen()} | 
				
			||||
 | 
					                    > | 
				
			||||
 | 
					                        <Table | 
				
			||||
 | 
					                            columns={columns} | 
				
			||||
 | 
					                            dataSource={tableData} | 
				
			||||
 | 
					                            bordered={false} | 
				
			||||
 | 
					                            empty="暂无数据" | 
				
			||||
 | 
					                            pagination={false} | 
				
			||||
 | 
					                            onChange={({ sorter }) => { | 
				
			||||
 | 
					                                if (sorter.key == 'userCode') { | 
				
			||||
 | 
					                                    if (sorter.sortOrder == 'descend') { | 
				
			||||
 | 
					                                        setOrder({ orderBy: 'code', orderDirection: 'DESC' }) | 
				
			||||
 | 
					                                    } else { | 
				
			||||
 | 
					                                        setOrder({ orderBy: 'code', orderDirection: 'ASC' }) | 
				
			||||
 | 
					                                    } | 
				
			||||
 | 
					                                } else if (sorter.key == 'age') { | 
				
			||||
 | 
					                                    if (sorter.sortOrder == 'descend') { | 
				
			||||
 | 
					                                        setOrder({ orderBy: 'age', orderDirection: 'DESC' }) | 
				
			||||
 | 
					                                    } else { | 
				
			||||
 | 
					                                        setOrder({ orderBy: 'age', orderDirection: 'ASC' }) | 
				
			||||
 | 
					                                    } | 
				
			||||
 | 
					                                } else { | 
				
			||||
 | 
					                                    if (sorter.sortOrder == 'descend') { | 
				
			||||
 | 
					                                        setOrder({ orderBy: 'hiredate', orderDirection: 'DESC' }) | 
				
			||||
 | 
					                                    } else { | 
				
			||||
 | 
					                                        setOrder({ orderBy: 'hiredate', orderDirection: 'ASC' }) | 
				
			||||
 | 
					                                    } | 
				
			||||
 | 
					                                } | 
				
			||||
 | 
					                            }} | 
				
			||||
 | 
					                            onRow={handleRow} | 
				
			||||
 | 
					                            scroll={scroll} | 
				
			||||
 | 
					                        /> | 
				
			||||
 | 
					                    </Skeleton> | 
				
			||||
 | 
					                    <div style={{ | 
				
			||||
 | 
					                        display: "flex", | 
				
			||||
 | 
					                        justifyContent: "space-between", | 
				
			||||
 | 
					                        padding: "20px 20px", | 
				
			||||
 | 
					                    }}> | 
				
			||||
 | 
					                        <div></div> | 
				
			||||
 | 
					                        <div style={{ display: 'flex', }}> | 
				
			||||
 | 
					                            <span style={{ lineHeight: "30px", fontSize: 13, color: 'rgba(0,90,189,0.8)' }}> | 
				
			||||
 | 
					                                共{limits}条信息 | 
				
			||||
 | 
					                            </span> | 
				
			||||
 | 
					                            <Pagination | 
				
			||||
 | 
					                                total={limits} | 
				
			||||
 | 
					                                showSizeChanger | 
				
			||||
 | 
					                                currentPage={query.page + 1} | 
				
			||||
 | 
					                                pageSizeOpts={[10, 20, 30, 40]} | 
				
			||||
 | 
					                                onChange={(currentPage, pageSize) => { | 
				
			||||
 | 
					                                    setQuery({ limit: pageSize, page: currentPage - 1 }); | 
				
			||||
 | 
					                                    page.current = currentPage - 1 | 
				
			||||
 | 
					                                }} | 
				
			||||
 | 
					                            /> | 
				
			||||
 | 
					                        </div> | 
				
			||||
 | 
					                    </div> | 
				
			||||
 | 
					                </div> | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					        { | 
				
			||||
 | 
					            modalV ? <SalesMemberModal | 
				
			||||
 | 
					                dataToEdit={dataToEdit} getMultis={getMultis} | 
				
			||||
 | 
					                close={() => closeAndFetch()} | 
				
			||||
 | 
					                onCancel={() => setModalV(false)} /> : '' | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					        { | 
				
			||||
 | 
					            importModalV ? <ImportSalersModal | 
				
			||||
 | 
					                onCancel={() => { | 
				
			||||
 | 
					                    setImportModalV(false); | 
				
			||||
 | 
					                    getMemberSearchList(); | 
				
			||||
 | 
					                }} /> : '' | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    </div>) | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					function mapStateToProps(state) { | 
				
			||||
 | 
					    const { auth, global, SalesMemberList } = state; | 
				
			||||
 | 
					    return { | 
				
			||||
 | 
					        user: auth.user, | 
				
			||||
 | 
					        actions: global.actions, | 
				
			||||
 | 
					        salesMemberList: SalesMemberList.data | 
				
			||||
 | 
					    }; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export default connect(mapStateToProps)(PersonnelDistribution); | 
				
			||||
@ -0,0 +1,263 @@ | 
				
			|||||
 | 
					import React, { useEffect, useRef, useState } from 'react'; | 
				
			||||
 | 
					import moment from 'moment'; | 
				
			||||
 | 
					import { connect } from "react-redux"; | 
				
			||||
 | 
					import { Select, Modal, Form, Notification } from "@douyinfe/semi-ui"; | 
				
			||||
 | 
					import cityData from '../../components/city.json'; | 
				
			||||
 | 
					const businessLines = ['市政', '地灾', '水利', '智慧城市', '工地', '环保', '安防', '产品投标', '交通', '矿山', '产品线'] | 
				
			||||
 | 
					const SalesMemberModal = (props) => { | 
				
			||||
 | 
					    const { dispatch, actions, user, meetingList, onConfirm, getMultis, onCancel, close, rzMembers, dataToEdit } = props; | 
				
			||||
 | 
					    const { humanAffairs } = actions; | 
				
			||||
 | 
					    const form = useRef();//表单
 | 
				
			||||
 | 
					    const [lineOptions, setLineOptions] = useState([]); | 
				
			||||
 | 
					    const [options, setOptions] = useState([]); | 
				
			||||
 | 
					    const [cityOptions, setCityOptions] = useState([]); | 
				
			||||
 | 
					    const [peoplePro, setPeoplePro] = useState({}); //人员信息
 | 
				
			||||
 | 
					    //初始化
 | 
				
			||||
 | 
					    useEffect(() => { | 
				
			||||
 | 
					        let optionItems = cityData.map(m => { | 
				
			||||
 | 
					            return <Select.Option value={m.name} key={m.code}> | 
				
			||||
 | 
					                {m.name} | 
				
			||||
 | 
					            </Select.Option> | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					        setOptions(optionItems); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        let lineOptions = businessLines.map((l, index) => { | 
				
			||||
 | 
					            return <Select.Option value={l} key={index}> | 
				
			||||
 | 
					                {l} | 
				
			||||
 | 
					            </Select.Option> | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					        setLineOptions(lineOptions); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        if (dataToEdit) { | 
				
			||||
 | 
					            setPeoplePro(dataToEdit); | 
				
			||||
 | 
					            onChange(dataToEdit.provinces?.split('、') || []);//市options
 | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    }, []); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const onChange = (value) => { | 
				
			||||
 | 
					        let cityOptions = [], citiesRange = []; | 
				
			||||
 | 
					        cityData.filter(cd => value.indexOf(cd.name) !== -1).map(d => { | 
				
			||||
 | 
					            d.children?.map(c => { | 
				
			||||
 | 
					                cityOptions.push(<Select.Option value={c.name} key={c.code}> | 
				
			||||
 | 
					                    {c.name} | 
				
			||||
 | 
					                </Select.Option>) | 
				
			||||
 | 
					                citiesRange.push(c.name) | 
				
			||||
 | 
					            }) | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					        setCityOptions(cityOptions) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        let citiesValue = form?.current?.getValues()?.cities || []; | 
				
			||||
 | 
					        if (citiesValue.length) { | 
				
			||||
 | 
					            let newCities = []; | 
				
			||||
 | 
					            citiesValue?.map(cv => { | 
				
			||||
 | 
					                if (citiesRange.indexOf(cv) != -1) { | 
				
			||||
 | 
					                    newCities.push(cv); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            }) | 
				
			||||
 | 
					            form.current.setValue('cities', newCities) | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    function handleOk() { | 
				
			||||
 | 
					        form.current.validate().then((values) => { | 
				
			||||
 | 
					            if (peoplePro == 'noValid') { | 
				
			||||
 | 
					                Notification.error({ | 
				
			||||
 | 
					                    content: '你填写的员工编号无对应的人员信息', | 
				
			||||
 | 
					                    duration: 2, | 
				
			||||
 | 
					                }) | 
				
			||||
 | 
					            } else if (peoplePro?.userCode) { | 
				
			||||
 | 
					                if (values.userCode == peoplePro.userCode) { | 
				
			||||
 | 
					                    values.provinces = values.provinces.join('、') | 
				
			||||
 | 
					                    values.cities = values.cities.join('、') | 
				
			||||
 | 
					                    values.businessLines = values.businessLines.join('、') | 
				
			||||
 | 
					                    if (dataToEdit) { | 
				
			||||
 | 
					                        dispatch(humanAffairs.editSalesMember({ pepUserId: peoplePro.pepUserId, msg: '编辑销售人员信息', ...values })).then((res) => { | 
				
			||||
 | 
					                            if (res.success) { | 
				
			||||
 | 
					                                close(); | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                        }) | 
				
			||||
 | 
					                    } else { | 
				
			||||
 | 
					                        dispatch(humanAffairs.postSalesMember({ pepUserId: peoplePro.pepUserId, msg: '新增销售人员', ...values })).then((res) => { | 
				
			||||
 | 
					                            if (res.success) { | 
				
			||||
 | 
					                                close(); | 
				
			||||
 | 
					                            } | 
				
			||||
 | 
					                        }) | 
				
			||||
 | 
					                    } | 
				
			||||
 | 
					                } else { | 
				
			||||
 | 
					                    Notification.error({ | 
				
			||||
 | 
					                        content: '你填写的员工编号无对应的人员信息', | 
				
			||||
 | 
					                        duration: 2, | 
				
			||||
 | 
					                    }) | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } else { | 
				
			||||
 | 
					                Notification.error({ | 
				
			||||
 | 
					                    content: '请查询人员编号对应的员工信息', | 
				
			||||
 | 
					                    duration: 2, | 
				
			||||
 | 
					                }) | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        }) | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const memberSeach = (id) => {//搜索项企用户
 | 
				
			||||
 | 
					        // dispatch(humanAffairs.getMemberSearch({ code: id })).then((res) => {//搜索项企用户
 | 
				
			||||
 | 
					        //     if (res.success) {
 | 
				
			||||
 | 
					        //         if (res.payload.data.length) {
 | 
				
			||||
 | 
					        //             let user = res.payload.data[0]
 | 
				
			||||
 | 
					        let exist = rzMembers.find(m => m.userCode == id);//人员档案里面需要有
 | 
				
			||||
 | 
					        if (exist) { | 
				
			||||
 | 
					            let item = { | 
				
			||||
 | 
					                pepUserId: exist.pepUserId, | 
				
			||||
 | 
					                name: exist.userName, | 
				
			||||
 | 
					                department: exist.departmrnt, | 
				
			||||
 | 
					                hireDate: exist.hiredate, | 
				
			||||
 | 
					                regularDate: exist.regularDate, | 
				
			||||
 | 
					                userCode: exist.userCode, | 
				
			||||
 | 
					                post: exist.userPost//岗位
 | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					            setPeoplePro(item) | 
				
			||||
 | 
					        } else { | 
				
			||||
 | 
					            setPeoplePro('noValid') | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					        //         } else {
 | 
				
			||||
 | 
					        //             setPeoplePro('noValid')
 | 
				
			||||
 | 
					        //         }
 | 
				
			||||
 | 
					        //     }
 | 
				
			||||
 | 
					        // })
 | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const renderSimpleInfo = () => { | 
				
			||||
 | 
					        let arrStr = peoplePro?.department?.map(t => t.name) || []; | 
				
			||||
 | 
					        return <div> | 
				
			||||
 | 
					            { | 
				
			||||
 | 
					                peoplePro == 'noValid' ? | 
				
			||||
 | 
					                    <div style={{ background: '#F4F5FC', border: '1px solid rgba(0, 90, 189, 0.2)', marginTop: 8, padding: '10px 0px', textAlign: 'center', color: '#4A4A4A' }}> | 
				
			||||
 | 
					                        没有符合条件的结果 | 
				
			||||
 | 
					                    </div> : | 
				
			||||
 | 
					                    peoplePro?.name ? ( | 
				
			||||
 | 
					                        <div style={{ background: '#F4F5FC', border: '1px solid rgba(0, 90, 189, 0.2)', marginTop: 8, padding: '20px 83px 20px 50px' }}> | 
				
			||||
 | 
					                            <div style={{ display: 'flex' }}> | 
				
			||||
 | 
					                                {renderPeopleItem('姓名:', peoplePro.name)} | 
				
			||||
 | 
					                                <div style={{ display: 'flex', flexBasis: '50%' }}> | 
				
			||||
 | 
					                                    <div style={{ width: 16, height: 16, marginRight: 9 }}> | 
				
			||||
 | 
					                                        <img src="/assets/images/hrImg/department.png" alt="" style={{ width: '100%', height: '100%' }} /> | 
				
			||||
 | 
					                                    </div> | 
				
			||||
 | 
					                                    <div style={{ color: 'rgba(0,0,0,0.6)', fontSize: 12 }}> | 
				
			||||
 | 
					                                        所属部门: | 
				
			||||
 | 
					                                    </div> | 
				
			||||
 | 
					                                    {getMultis(arrStr)} | 
				
			||||
 | 
					                                </div> | 
				
			||||
 | 
					                            </div> | 
				
			||||
 | 
					                            <div style={{ display: 'flex' }}> | 
				
			||||
 | 
					                                {renderPeopleItem('入职时间:', peoplePro.hireDate)} | 
				
			||||
 | 
					                                {renderPeopleItem('岗位:', peoplePro.post)} | 
				
			||||
 | 
					                            </div> <div style={{ display: 'flex' }}> | 
				
			||||
 | 
					                                {renderPeopleItem('转正时间:', peoplePro.regularDate)} | 
				
			||||
 | 
					                                {renderPeopleItem('工龄:', peoplePro.hireDate ? String(moment(new Date()).diff(peoplePro.hireDate, 'years', true)).substring(0, 3) + '年' : '-')} | 
				
			||||
 | 
					                            </div> | 
				
			||||
 | 
					                        </div> | 
				
			||||
 | 
					                    ) : ('') | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const renderPeopleItem = (label, value) => { | 
				
			||||
 | 
					        return <div style={{ display: 'flex', flexBasis: '50%' }}> | 
				
			||||
 | 
					            <div style={{ width: 16, height: 16, marginRight: 9 }}> | 
				
			||||
 | 
					                <img src="/assets/images/hrImg/department.png" style={{ width: '100%', height: '100%' }} /> | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					            <div style={{ color: 'rgba(0,0,0,0.6)', fontSize: 12 }}> | 
				
			||||
 | 
					                {label} | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					            <div style={{ color: '#4A4A4A', fontSize: 12 }}> | 
				
			||||
 | 
					                {value || '-'} | 
				
			||||
 | 
					            </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    const onClear = () => { | 
				
			||||
 | 
					        form.current.setValue('cities', []) | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    return ( | 
				
			||||
 | 
					        <Modal title={dataToEdit?.pepUserId ? '修改销售人员信息' : '新增销售人员'} | 
				
			||||
 | 
					            visible={true} | 
				
			||||
 | 
					            destroyOnClose | 
				
			||||
 | 
					            okText='保存' width={800} | 
				
			||||
 | 
					            onOk={handleOk} | 
				
			||||
 | 
					            onCancel={onCancel}> | 
				
			||||
 | 
					            <Form getFormApi={(formApi) => (form.current = formApi)} | 
				
			||||
 | 
					                labelPosition={'left'} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}> | 
				
			||||
 | 
					                <Form.Input | 
				
			||||
 | 
					                    field="userCode" | 
				
			||||
 | 
					                    label='人员编号' | 
				
			||||
 | 
					                    initValue={dataToEdit?.userCode || ""} | 
				
			||||
 | 
					                    placeholder="请输入人员编号" | 
				
			||||
 | 
					                    showClear | 
				
			||||
 | 
					                    style={{ width: '100%' }} | 
				
			||||
 | 
					                    rules={[{ required: true, message: "请输入人员编号" }]} | 
				
			||||
 | 
					                    onChange={() => setPeoplePro({})} | 
				
			||||
 | 
					                    addonAfter={<div style={{ margin: '0px 12px', color: '#005ABD', cursor: "pointer", fontSize: 14 }} onClick={() => { | 
				
			||||
 | 
					                        let formList = form.current.getValues() | 
				
			||||
 | 
					                        if (formList.userCode) { | 
				
			||||
 | 
					                            memberSeach(formList.userCode) | 
				
			||||
 | 
					                        } | 
				
			||||
 | 
					                    }}> | 
				
			||||
 | 
					                        搜索 | 
				
			||||
 | 
					                    </div>} /> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					                {peoplePro ? renderSimpleInfo() : ''} | 
				
			||||
 | 
					                <Form.Select | 
				
			||||
 | 
					                    initValue={dataToEdit?.provinces?.split('、') || []} | 
				
			||||
 | 
					                    label="销售区域(省/直辖市)" | 
				
			||||
 | 
					                    field='provinces' | 
				
			||||
 | 
					                    showClear | 
				
			||||
 | 
					                    rules={[{ required: true, message: '请选择销售区域(省/直辖市)' }]} | 
				
			||||
 | 
					                    placeholder='请选择销售区域(省/直辖市)' | 
				
			||||
 | 
					                    multiple filter | 
				
			||||
 | 
					                    style={{ width: '100%' }} | 
				
			||||
 | 
					                    onClear={() => onClear()} | 
				
			||||
 | 
					                    onChange={value => onChange(value)} | 
				
			||||
 | 
					                    maxTagCount={5} | 
				
			||||
 | 
					                > | 
				
			||||
 | 
					                    {options} | 
				
			||||
 | 
					                </Form.Select> | 
				
			||||
 | 
					                <Form.Select | 
				
			||||
 | 
					                    initValue={dataToEdit?.cities ? dataToEdit?.cities?.split('、') : []} | 
				
			||||
 | 
					                    label="销售区域(市)" | 
				
			||||
 | 
					                    field='cities' | 
				
			||||
 | 
					                    showClear | 
				
			||||
 | 
					                    placeholder='请选择销售区域(市)' | 
				
			||||
 | 
					                    multiple filter | 
				
			||||
 | 
					                    style={{ width: '100%' }} | 
				
			||||
 | 
					                    maxTagCount={5} | 
				
			||||
 | 
					                > | 
				
			||||
 | 
					                    {cityOptions} | 
				
			||||
 | 
					                </Form.Select> | 
				
			||||
 | 
					                <Form.Select | 
				
			||||
 | 
					                    initValue={dataToEdit?.businessLines ? dataToEdit?.businessLines?.split('、') : []} | 
				
			||||
 | 
					                    label="业务线" | 
				
			||||
 | 
					                    field='businessLines' | 
				
			||||
 | 
					                    showClear | 
				
			||||
 | 
					                    placeholder='请选择业务线' | 
				
			||||
 | 
					                    multiple filter | 
				
			||||
 | 
					                    style={{ width: '100%' }} | 
				
			||||
 | 
					                    maxTagCount={5} | 
				
			||||
 | 
					                > | 
				
			||||
 | 
					                    {lineOptions} | 
				
			||||
 | 
					                </Form.Select> | 
				
			||||
 | 
					            </Form> | 
				
			||||
 | 
					        </Modal> | 
				
			||||
 | 
					    ) | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					function mapStateToProps(state) { | 
				
			||||
 | 
					    const { auth, global, MemberList } = state; | 
				
			||||
 | 
					    return { | 
				
			||||
 | 
					        user: auth.user, | 
				
			||||
 | 
					        actions: global.actions, | 
				
			||||
 | 
					        apiRoot: global.apiRoot, | 
				
			||||
 | 
					        rzMembers: MemberList.data?.rows || [], | 
				
			||||
 | 
					    }; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export default connect(mapStateToProps)(SalesMemberModal); | 
				
			||||
@ -0,0 +1,8 @@ | 
				
			|||||
 | 
					'use strict'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export const UserAttribute = { | 
				
			||||
 | 
					    jobDataSource: ['普通员工', '中层', '高层'], | 
				
			||||
 | 
					    activeStatusDataSource: ['在职', '离职', '特殊状态-特殊账号'], | 
				
			||||
 | 
					    organizationDataSource: ['江西飞尚科技有限公司', '江西飞尚工程质量检测有限公司', | 
				
			||||
 | 
					        '江西飞尚科技有限公司江苏分公司', '江西汇派科技有限公司'], | 
				
			||||
 | 
					}; | 
				
			||||
					Loading…
					
					
				
		Reference in new issue