Browse Source

人员计划:数据源更新为禅道。

master
Julin 1 year ago
parent
commit
7aa5275765
  1. 135
      web/client/src/sections/homePage/containers/index.js
  2. 174
      web/routes/holiday.js
  3. 111
      web/routes/missionboard/index.js
  4. 0
      web/routes/product-excluded.js

135
web/client/src/sections/homePage/containers/index.js

@ -7,10 +7,7 @@ import { push } from 'react-router-redux';
import moment from 'moment'; import moment from 'moment';
import request from 'superagent'; import request from 'superagent';
import ProTable from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table';
import { Scroller } from '$components'; import { getWait } from '../actions/profile';
import {
getPeople, getWait, postKeySearch,
} from '../actions/profile';
import styles from './index.less'; import styles from './index.less';
const schedule = require('node-schedule'); const schedule = require('node-schedule');
@ -20,11 +17,11 @@ const CX = classnames.bind(styles);
const topImg = '/assets/images/top.png'; const topImg = '/assets/images/top.png';
const pointImg = '/assets/images/point.png'; const pointImg = '/assets/images/point.png';
let cachePeopleData = null;
function Management(props) { function Management(props) {
const { dispatch, user } = props; const { dispatch, user } = props;
const [name, setName] = useState();// 名字查询 const [projectData, setProjectData] = useState(null);
const [ajob, setAjob] = useState();// 岗位查询
const [projectData, setProjectData] = useState([]);
const [peopleData, setPeopleData] = useState([]); const [peopleData, setPeopleData] = useState([]);
const [waitData, setWaitData] = useState([]); const [waitData, setWaitData] = useState([]);
const ref = useRef(); const ref = useRef();
@ -35,16 +32,21 @@ function Management(props) {
}); });
} }
function getOngoingPersons() {
request.get('/ongoing/persons').then((res) => {
setPeopleData(res?.body?.projects);
cachePeopleData = res?.body?.projects;
});
}
useEffect(() => { useEffect(() => {
getOngoingProjects(); getOngoingProjects();
schedule.scheduleJob('0 0 7,18 * * ?', () => { // 每天 7时、18时 定时更新 schedule.scheduleJob('0 0 7,18 * * ?', () => { // 每天 7时、18时 定时更新
getOngoingProjects(); getOngoingProjects();
getOngoingPersons();
}); });
dispatch(getPeople()).then((res) => {
setPeopleData(res?.payload.data?.projects);
});
dispatch(getWait()).then((res) => { dispatch(getWait()).then((res) => {
setWaitData(res?.payload.data?.projects); setWaitData(res?.payload.data?.projects);
}); });
@ -87,12 +89,12 @@ function Management(props) {
// sortOrder:'descend' , // sortOrder:'descend' ,
sorter: { sorter: {
compare: (a, b) => { compare: (a, b) => {
const one = moment(a.build_time).format('YYYYMMDD'); const one = moment(a.build_time).format('YYYYMMDDHHmm');
const two = moment(b.build_time).format('YYYYMMDD'); const two = moment(b.build_time).format('YYYYMMDDHHmm');
return one - two; return one - two;
}, },
}, },
render: (dom, record) => <>{record.build_time ? moment(record.build_time).format('YYYY-MM-DD') : '待定'}</>, render: (dom, record) => <>{record.build_time || '待定'}</>,
}, },
{ {
title: '发布时间', title: '发布时间',
@ -140,9 +142,6 @@ function Management(props) {
valueType: 'select', valueType: 'select',
initialValue: null, initialValue: null,
fieldProps: { fieldProps: {
onChange: (value, cs) => {
setAjob(value);
},
options: [ options: [
{ {
label: '全部岗位', label: '全部岗位',
@ -166,63 +165,60 @@ function Management(props) {
}, },
{ {
title: '周一', title: '周一',
key: 'md', key: 'monday',
dataIndex: 'md', dataIndex: 'monday',
hideInSearch: true, hideInSearch: true,
render: (dom, record) => <div>{record.md == 'undefined' ? '--' : record.md}</div>, render: (dom, record) => <div>{record.monday == 'undefined' ? '--' : record.monday}</div>,
}, },
{ {
title: '周二', title: '周二',
key: 'td', key: 'tuesday',
dataIndex: 'td', dataIndex: 'tuesday',
hideInSearch: true, hideInSearch: true,
render: (dom, record) => <div>{record.td == 'undefined' ? '--' : record.td}</div>, render: (dom, record) => <div>{record.tuesday == 'undefined' ? '--' : record.tuesday}</div>,
}, },
{ {
title: '周三', title: '周三',
key: 'wd', key: 'wednesday',
dataIndex: 'wd', dataIndex: 'wednesday',
hideInSearch: true, hideInSearch: true,
render: (dom, record) => <div>{record.wd == 'undefined' ? '--' : record.wd}</div>, render: (dom, record) => <div>{record.wednesday == 'undefined' ? '--' : record.wednesday}</div>,
}, },
{ {
title: '周四', title: '周四',
key: 'thd ', key: 'thursday',
dataIndex: 'thd ', dataIndex: 'thursday',
hideInSearch: true, hideInSearch: true,
render: (dom, record) => <div>{record.thd == 'undefined' ? '--' : record.thd}</div>, render: (dom, record) => <div>{record.thursday == 'undefined' ? '--' : record.thursday}</div>,
}, },
{ {
title: '周五', title: '周五',
key: 'fd', key: 'friday',
dataIndex: 'fd', dataIndex: 'friday',
hideInSearch: true, hideInSearch: true,
render: (dom, record) => <div>{record.fd == 'undefined' ? '--' : record.fd}</div>, render: (dom, record) => <div>{record.friday == 'undefined' ? '--' : record.friday}</div>,
}, },
{ {
title: '周六', title: '周六',
key: 'sd', key: 'saturday',
dataIndex: 'sd', dataIndex: 'saturday',
hideInSearch: true, hideInSearch: true,
render: (dom, record) => <div>{record.sd == 'undefined' ? '--' : record.sd}</div>, render: (dom, record) => <div>{record.saturday == 'undefined' ? '--' : record.saturday}</div>,
}, },
{ {
title: '周日', title: '周日',
key: 'ssd', key: 'sunday',
dataIndex: 'ssd', dataIndex: 'sunday',
hideInSearch: true, hideInSearch: true,
render: (dom, record) => <div>{record.ssd == 'undefined' ? '--' : record.ssd}</div>, render: (dom, record) => <div>{record.sunday == 'undefined' ? '--' : record.sunday}</div>,
}, },
{ {
title: '关键字搜索', title: '关键字搜索',
key: 'direction', key: 'search_keyword',
hideInTable: true, hideInTable: true,
dataIndex: 'direction', dataIndex: 'search_keyword',
order: 6, order: 6,
fieldProps: { fieldProps: {
onChange: (value, cs) => {
setName(value.target.value);
},
placeholder: '请输入关键字进行搜索', placeholder: '请输入关键字进行搜索',
getPopupContainer: (triggerNode) => triggerNode.parentNode, getPopupContainer: (triggerNode) => triggerNode.parentNode,
}, },
@ -271,7 +267,7 @@ function Management(props) {
<img className={CX('index-main-box-point')} src={pointImg} /> <img className={CX('index-main-box-point')} src={pointImg} />
<div className={CX('index-main-box-title')}>本月在研项目</div> <div className={CX('index-main-box-title')}>本月在研项目</div>
<div className={CX('index-main-box-en')}>/Research project this week</div> <div className={CX('index-main-box-en')}>/Research project this week</div>
<Spin spinning={false}> <Spin spinning={!projectData}>
<Table dataSource={projectData} columns={projectCol} size="small" pagination={false} scroll={{ y: '32vh' }} /> <Table dataSource={projectData} columns={projectCol} size="small" pagination={false} scroll={{ y: '32vh' }} />
</Spin> </Spin>
</div> </div>
@ -292,7 +288,6 @@ function Management(props) {
<img className={CX('index-main-box-point')} src={pointImg} /> <img className={CX('index-main-box-point')} src={pointImg} />
<div className={CX('index-main-box-title')}>人员情况</div> <div className={CX('index-main-box-title')}>人员情况</div>
<div className={CX('index-main-box-en')}>/Information on Personnel</div> <div className={CX('index-main-box-en')}>/Information on Personnel</div>
{/* <Scroller containerId={"article-container-query"} height={"80%"}> */}
<ProTable <ProTable
options={false} options={false}
actionRef={ref} actionRef={ref}
@ -301,32 +296,32 @@ function Management(props) {
size="small" size="small"
pagination={false} pagination={false}
scroll={{ y: '20vh' }} scroll={{ y: '20vh' }}
request={async () => { request={async (params) => {
if (name || ajob) { const { search_keyword, post_people } = params;
const query = { if (search_keyword || post_people) {
value1: name, const data = cachePeopleData.filter((member) => {
value2: ajob, if (!post_people) {
}; return member.name_people.includes(search_keyword);
const res = await dispatch(postKeySearch(query));
setPeopleData(res?.payload.data.projects);
return {
...res,
// total: res.payload.data.data ? res.payload.data.total : 0,
};
} }
const res = await dispatch(getPeople()); if (!search_keyword) {
setPeopleData(res?.payload.data.projects); return member.post_people == post_people;
return { }
...res, return member.name_people.includes(search_keyword) && member.post_people == post_people;
// total: res.payload.data.data ? res.payload.data.total : 0, });
}; setPeopleData(data);
}} return { data };
onReset={(v) => { }
setName(null); if (cachePeopleData) {
setAjob(null); setPeopleData(cachePeopleData);
return { data: cachePeopleData };
}
const res = await request.get('/ongoing/persons');
const data = res?.body?.projects;
cachePeopleData = data;
setPeopleData(data);
return { ...res };
}} }}
/> />
{/* </Scroller> */}
</div> </div>
</div> </div>
@ -340,11 +335,7 @@ function Management(props) {
zIndex: 10000, zIndex: 10000,
}} }}
onClick={() => { onClick={() => {
if ( if (user && user.authorized && JSON.parse(sessionStorage.getItem('user'))) {
user
&& user.authorized
&& JSON.parse(sessionStorage.getItem('user'))
) {
dispatch(push('/article')); dispatch(push('/article'));
} else { } else {
dispatch(push('/login')); dispatch(push('/login'));

174
web/routes/holiday.js

@ -0,0 +1,174 @@
// https://github.com/NateScarlet/holiday-cn
// 国内镜像仓库
// https://natescarlet.coding.net/p/github/d/holiday-cn/git/raw/master/{年份}.json
const holiday = {
days: [
{
name: '元旦',
date: '2023-01-01',
isOffDay: true,
},
{
name: '元旦',
date: '2023-01-02',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-21',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-22',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-23',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-24',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-25',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-26',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-27',
isOffDay: true,
},
{
name: '春节',
date: '2023-01-28',
isOffDay: false,
},
{
name: '春节',
date: '2023-01-29',
isOffDay: false,
},
{
name: '清明节',
date: '2023-04-05',
isOffDay: true,
},
{
name: '劳动节',
date: '2023-04-23',
isOffDay: false,
},
{
name: '劳动节',
date: '2023-04-29',
isOffDay: true,
},
{
name: '劳动节',
date: '2023-04-30',
isOffDay: true,
},
{
name: '劳动节',
date: '2023-05-01',
isOffDay: true,
},
{
name: '劳动节',
date: '2023-05-02',
isOffDay: true,
},
{
name: '劳动节',
date: '2023-05-03',
isOffDay: true,
},
{
name: '劳动节',
date: '2023-05-06',
isOffDay: false,
},
{
name: '端午节',
date: '2023-06-22',
isOffDay: true,
},
{
name: '端午节',
date: '2023-06-23',
isOffDay: true,
},
{
name: '端午节',
date: '2023-06-24',
isOffDay: true,
},
{
name: '端午节',
date: '2023-06-25',
isOffDay: false,
},
{
name: '中秋节、国庆节',
date: '2023-09-29',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-09-30',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-10-01',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-10-02',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-10-03',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-10-04',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-10-05',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-10-06',
isOffDay: true,
},
{
name: '中秋节、国庆节',
date: '2023-10-07',
isOffDay: false,
},
{
name: '中秋节、国庆节',
date: '2023-10-08',
isOffDay: false,
},
],
};
module.exports = holiday;

111
web/routes/missionboard/index.js

@ -1,6 +1,7 @@
const request = require('superagent'); const request = require('superagent');
const moment = require('moment'); const moment = require('moment');
const productExcluded = require('../../config-product-excluded'); const productExcluded = require('../product-excluded');
const Holiday = require('../holiday');
module.exports = { module.exports = {
entry(app, router, opts) { entry(app, router, opts) {
@ -26,10 +27,25 @@ module.exports = {
}, 1200000); // 每 20分钟 刷新一次 }, 1200000); // 每 20分钟 刷新一次
}()); }());
function isWorkday(date) {
let workday = false;
const dateString = moment(date).format('YYYY-MM-DD');
const dateInHoliday = Holiday.days.filter((d) => d.date === dateString);
if (dateInHoliday.length) {
if (!dateInHoliday[0].isOffDay) workday = true; // 补班
} else {
const day = moment(date).day();
if (day !== 0 && day !== 6) { // 工作日
workday = true;
}
}
return workday;
}
function extractBuildTime(inputString) { function extractBuildTime(inputString) {
const pattern = /[(\(]构建时间:(.*?)[\))]/; const pattern = /[(\(]构建时间:(.*?)[\))]/;
const match = inputString.match(pattern); const match = inputString.match(pattern);
const buildDate = match ? match[1] : null; const buildDate = match ? moment(new Date(match[1])).format('YYYY-MM-DD HH:mm') : null;
return buildDate; return buildDate;
} }
@ -66,7 +82,7 @@ module.exports = {
return progress; return progress;
} }
async function getExecutionStats(baseURL, recPerPage) { async function getExecutionStats(period, baseURL, recPerPage) {
const { sessionName, sessionID } = app.fs.session; const { sessionName, sessionID } = app.fs.session;
const executionRes = await request const executionRes = await request
@ -89,26 +105,37 @@ module.exports = {
pid++; pid++;
} }
let executionStats = [];
if (period === 'month') {
const currentMonth = moment().format('YYYY-MM'); const currentMonth = moment().format('YYYY-MM');
executionStats = executionAll.filter((item) => item.type === 'sprint' && (
const executionStats = executionAll.filter((item) => item.type === 'sprint' && (
moment(item.begin).format('YYYY-MM') === currentMonth moment(item.begin).format('YYYY-MM') === currentMonth
|| moment(item.end).format('YYYY-MM') === currentMonth || moment(item.end).format('YYYY-MM') === currentMonth
)); ));
} else { // week
const currentDate = moment();
// 获取当前周的第一天(周一)
const firstDayOfWeek = currentDate.clone().startOf('isoWeek');
// 获取当前周的最后一天(周日)
const lastDayOfWeek = currentDate.clone().endOf('isoWeek');
executionStats = executionAll.filter((item) => {
const buildTime = extractBuildTime(item.name) || item.end;
const begin = moment.max(moment(item.begin), moment(firstDayOfWeek));
const end = moment.min(moment(buildTime), moment(lastDayOfWeek));
return item.type === 'sprint' && begin.isSameOrBefore(end);
});
}
return executionStats; return executionStats;
} }
async function getOngoingProjects(ctx, next) { async function getOngoingProjects(ctx, next) {
try { try {
const { sessionName, sessionID } = app.fs.session;
const executions = []; const executions = [];
const currentMonth = moment().format('YYYY-MM');
// 执行(未开始) // 执行(未开始)
const waitExecutionBaseURL = 'https://pms.anxinyun.cn/execution-all-wait-0-order_asc-0.json'; const waitExecutionBaseURL = 'https://pms.anxinyun.cn/execution-all-wait-0-order_asc-0.json';
const waitExecutionStats = await getExecutionStats(waitExecutionBaseURL, 200); const waitExecutionStats = await getExecutionStats('month', waitExecutionBaseURL, 200);
for (const item of waitExecutionStats) { for (const item of waitExecutionStats) {
const { id, name, end } = item; const { id, name, end } = item;
const { executionTeamRDs, executionProducts } = await getExecutionDetail(id); const { executionTeamRDs, executionProducts } = await getExecutionDetail(id);
@ -125,7 +152,7 @@ module.exports = {
// 执行(进行中) // 执行(进行中)
const doingExecutionBaseURL = 'https://pms.anxinyun.cn/execution-all-doing-0-order_asc-0.json'; const doingExecutionBaseURL = 'https://pms.anxinyun.cn/execution-all-doing-0-order_asc-0.json';
const doingExecutionStats = await getExecutionStats(doingExecutionBaseURL, 200); const doingExecutionStats = await getExecutionStats('month', doingExecutionBaseURL, 200);
for (const item of doingExecutionStats) { for (const item of doingExecutionStats) {
const { id, name, end } = item; const { id, name, end } = item;
const { executionTeamRDs, executionProducts } = await getExecutionDetail(id); const { executionTeamRDs, executionProducts } = await getExecutionDetail(id);
@ -157,10 +184,6 @@ module.exports = {
async function getOngoingPersons(ctx, next) { async function getOngoingPersons(ctx, next) {
try { try {
const { sessionName, sessionID } = app.fs.session;
const executions = [];
const currentDate = moment(); const currentDate = moment();
// 获取当前周的第一天(周一) // 获取当前周的第一天(周一)
const firstDayOfWeek = currentDate.clone().startOf('isoWeek'); const firstDayOfWeek = currentDate.clone().startOf('isoWeek');
@ -168,26 +191,26 @@ module.exports = {
const lastDayOfWeek = currentDate.clone().endOf('isoWeek'); const lastDayOfWeek = currentDate.clone().endOf('isoWeek');
// 执行(未开始) // 执行(未开始)
const waitExecutionRes = await request const waitExecutionBaseURL = 'https://pms.anxinyun.cn/execution-all-wait-0-order_asc-0.json';
.get('https://pms.anxinyun.cn/execution-all-wait-0-order_asc-0.json') const waitExecutionStats = await getExecutionStats('week', waitExecutionBaseURL, 200);
.set('Cookie', `${sessionName}=${sessionID}`);
const waitExecutionText = JSON.parse(waitExecutionRes.text);
const waitExecutionStats = JSON.parse(waitExecutionText.data).executionStats
.filter((item) => item.type === 'sprint' && (
moment(item.begin).isBetween(firstDayOfWeek, lastDayOfWeek, undefined, '[]')
|| moment(item.end).isBetween(firstDayOfWeek, lastDayOfWeek, undefined, '[]')
));
for (const item of waitExecutionStats) { // 执行(进行中)
const doingExecutionBaseURL = 'https://pms.anxinyun.cn/execution-all-doing-0-order_asc-0.json';
const doingExecutionStats = await getExecutionStats('week', doingExecutionBaseURL, 200);
const executionAll = waitExecutionStats.concat(doingExecutionStats);
const executions = [];
for (const item of executionAll) {
const { const {
id, name, begin, end, id, name, begin, end,
} = item; } = item;
const { executionTeamRDs, executionProducts } = await getExecutionDetail(id); const { executionTeamRDs, executionProducts } = await getExecutionDetail(id);
if (executionProducts.some((p) => productExcluded.includes(p))) continue; if (executionProducts.some((p) => productExcluded.includes(p))) continue;
executions.push({ executions.push({
name_project: name, name_project: name.replace('&amp;', '&'),
begin, begin,
build_time: extractBuildTime(name), build_time: extractBuildTime(name) || end,
end, end,
part_people: executionTeamRDs, part_people: executionTeamRDs,
}); });
@ -205,10 +228,42 @@ module.exports = {
return p; return p;
}, {}); }, {});
const members = Object.entries(memberExecutions).map(([k, v]) => {
const memberTasks = {};
v.forEach((item) => {
const begin = moment.max(moment(item.begin), moment(firstDayOfWeek));
const end = moment.min(moment(item.build_time), moment(lastDayOfWeek));
const weekday = begin;
while (weekday.isSameOrBefore(end)) {
if (isWorkday(weekday)) {
const weekdayEN = weekday.format('dddd').toLowerCase();
if (memberTasks[`${weekdayEN}`]) {
memberTasks[`${weekdayEN}`] = `${memberTasks[`${weekdayEN}`]}${item.name_project}`;
} else {
memberTasks[`${weekdayEN}`] = item.name_project;
}
}
weekday.add(1, 'days');
}
});
const initData = {
name_people: k,
post_people: '研发',
monday: 'undefined',
tuesday: 'undefined',
wednesday: 'undefined',
thursday: 'undefined',
friday: 'undefined',
saturday: 'undefined',
sunday: 'undefined',
};
return { ...initData, ...memberTasks };
});
ctx.status = 200; ctx.status = 200;
ctx.body = { ctx.body = {
total: Object.keys(memberExecutions).length, total: members.length,
projects: memberExecutions, projects: members,
}; };
} catch (e) { } catch (e) {
ctx.status = 400; ctx.status = 400;

0
web/config-product-excluded.js → web/routes/product-excluded.js

Loading…
Cancel
Save