435 lines
17 KiB
TypeScript
435 lines
17 KiB
TypeScript
import { Input , Badge , Avatar , Menu } from 'antd';
|
||
import { Dispatch, Link, connect } from 'umi';
|
||
import './index.less';
|
||
import { EnterpriseModelState } from '@/models/enterprise';
|
||
import { UsersModelState } from '@/models/user';
|
||
import { useEffect, useState } from 'react';
|
||
import { WorkStandModelState } from '@/models/workStand';
|
||
import { Table , Row , Col , Progress , Pagination , Spin } from 'antd';
|
||
import type { ColumnsType } from 'antd/es/table';
|
||
import { workItemIcon , workItemType , workItemStatus} from '@/constant/project';
|
||
import { StatusTag } from '@/components/Tags/Tags';
|
||
import { toTimeFormat } from '@/utils/util';
|
||
// import { iterationStatusList } from '@/constant/project';
|
||
import styles from './index.less';
|
||
import NoData from '@/components/NoData';
|
||
|
||
interface HomePageProps {
|
||
enterprise: EnterpriseModelState;
|
||
user:UsersModelState
|
||
dispatch:Dispatch,
|
||
workStand:WorkStandModelState
|
||
}
|
||
const Index: React.FC<HomePageProps> = ({ enterprise ,user , dispatch , workStand }) => {
|
||
const { currentUser } = user;
|
||
const { identifier:enterIdentifier } = enterprise;
|
||
const [ search , setSearch ] = useState<string>("");
|
||
const [ selectKey , setSelectKey ] = useState<string>("assignedme");
|
||
|
||
const [ statictisData , setStatictisData ] = useState<any>([]);
|
||
const [ dataSource , setDataSource ] = useState<any>([]);
|
||
|
||
const [ workLoading , setWorkLoading ] =useState<boolean>(true);
|
||
const [ workPage , setWorkPage ] = useState<number>(1);
|
||
const [ workTotal , setWorkTotal ] = useState<number>(0);
|
||
|
||
const [ projectData , setProjectData ] = useState<any>([]);
|
||
const [ projectLoading , setProjectLoading ] =useState<boolean>(true);
|
||
const [ projectPage , setProjectPage ] = useState<number>(1);
|
||
const [ projectTotal , setProjectTotal ] = useState<number>(0);
|
||
|
||
const [ planData , setPlanData ] = useState<any>([]);
|
||
const [ planLoading , setPlanLoading ] =useState<boolean>(true);
|
||
const [ planPage , setPlanPage ] = useState<number>(1);
|
||
const [ planTotal , setPlanTotal ] = useState<number>(0);
|
||
|
||
const [ authoredmeCount , setAuthoredmeCount ] = useState<number>(0);
|
||
const [ assignedmeCount , setAssignedmeCount ] = useState<number>(0);
|
||
|
||
function getCurrentTime() {
|
||
const currentHour = new Date().getHours();
|
||
|
||
if (currentHour >= 6 && currentHour < 9) {
|
||
return "早上好!";
|
||
} else if (currentHour >= 9 && currentHour < 12) {
|
||
return "上午好!";
|
||
}else if (currentHour >= 12 && currentHour < 14) {
|
||
return "中午好!";
|
||
} else if (currentHour >= 14 && currentHour < 18) {
|
||
return "下午好!";
|
||
} else if(currentHour >= 18) {
|
||
return "晚上好!";
|
||
}
|
||
}
|
||
|
||
useEffect(()=>{
|
||
if(enterIdentifier){
|
||
getStatistics();
|
||
}
|
||
},[enterIdentifier])
|
||
|
||
useEffect(()=>{
|
||
if(enterIdentifier){
|
||
getIteratioins();
|
||
}
|
||
},[enterIdentifier,planPage])
|
||
|
||
useEffect(()=>{
|
||
if(enterIdentifier){
|
||
getProjects();
|
||
}
|
||
},[enterIdentifier,projectPage])
|
||
|
||
|
||
useEffect(()=>{
|
||
if(enterIdentifier){
|
||
setWorkPage(1);
|
||
getWorkItem(1);
|
||
}
|
||
},[enterIdentifier,selectKey,search])
|
||
|
||
// 获取我的工作台统计信息
|
||
async function getStatistics() {
|
||
let res:any = await dispatch({
|
||
type:"workStand/getMyStatistics",
|
||
})
|
||
setStatictisData(res?.data);
|
||
setAssignedmeCount(res?.data?.assignedmeCount);
|
||
setAuthoredmeCount(res?.data?.authoredmeCount);
|
||
}
|
||
// 获取我的迭代
|
||
async function getIteratioins() {
|
||
setPlanLoading(true);
|
||
let res:any = await dispatch({
|
||
type:"workStand/getMyIterations",
|
||
payload:{
|
||
pageNum:planPage,pageSize:5,statusIds:1
|
||
}
|
||
})
|
||
if(res?.code === 200){
|
||
setPlanData(res?.rows);
|
||
setPlanTotal(res?.total);
|
||
}
|
||
setPlanLoading(false);
|
||
}
|
||
// 获取我的项目
|
||
async function getProjects() {
|
||
setProjectLoading(true);
|
||
let res:any = await dispatch({
|
||
type:"workStand/getMyProjects",
|
||
payload:{
|
||
pageNum:projectPage,pageSize:4,status:1
|
||
}
|
||
})
|
||
if(res?.code === 200){
|
||
setProjectData(res?.rows);
|
||
setProjectTotal(res?.total);
|
||
}
|
||
setProjectLoading(false);
|
||
}
|
||
// 获取我的工作项
|
||
async function getWorkItem(page:number) {
|
||
setWorkLoading(true);
|
||
let res:any = await dispatch({
|
||
type:"workStand/getMyWorkItem",
|
||
payload:{
|
||
participantCategory:selectKey,page,limit:5,category:"opened",keyword:search
|
||
}
|
||
})
|
||
if(res?.code === 200){
|
||
setDataSource(res?.data?.issues);
|
||
setWorkTotal(res?.data?.total_count);
|
||
if(selectKey === "assignedme"){
|
||
setAssignedmeCount(res?.data?.opened_count);
|
||
}else{
|
||
setAuthoredmeCount(res?.data?.opened_count);
|
||
}
|
||
}
|
||
setWorkLoading(false);
|
||
}
|
||
|
||
const columns: ColumnsType<API.projects.workItem>=[
|
||
{
|
||
title: '标题',
|
||
dataIndex: 'subject',
|
||
key: 'subject',
|
||
width: '40%',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:string,record:any)=>{
|
||
return(
|
||
<a className="alignCenter" onClick={async()=>{
|
||
await dispatch({
|
||
type:"project/setCurrent",
|
||
payload: { id: record?.pm_project_id }
|
||
})
|
||
await dispatch({
|
||
type:'project/setOpen',
|
||
payload:{
|
||
open:false,
|
||
id:record?.id,
|
||
editOpen:true,
|
||
pmIssueType:record?.pm_issue_type,
|
||
parent:"workStand",
|
||
onOk:getWorkItem
|
||
}
|
||
})
|
||
window.history.pushState({},"0",`/${enterIdentifier}/projects/${record?.pm_project_id}/${workItemType[record?.pm_issue_type]}/${record?.id}`)
|
||
}}><i className={`iconfontColor ${workItemIcon[record?.pm_issue_type]} font16 mr10`} /><span className={"hide"}>{value}</span></a>
|
||
)
|
||
}
|
||
},{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
width: '14%',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:any,record:any)=>{
|
||
let type = record?.pm_issue_type === 1? workItemType.demand : record?.pm_issue_type === 2 ? workItemType.task :workItemType.bug;
|
||
let connectIssueStatus = workItemStatus[type];
|
||
let text = value;
|
||
text.name = connectIssueStatus[value?.id];
|
||
return <StatusTag item={ text }></StatusTag>
|
||
}
|
||
},{
|
||
title: '优先级',
|
||
dataIndex: 'priority',
|
||
key: 'priority',
|
||
width: '15%',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:any)=>{
|
||
return <div className={"alignCenter"}>
|
||
<i style={{display: "inlineBlock", width: 7, height: 7, borderRadius:" 50%",marginRight: 6, background:`${value?.pm_color}`}}></i>
|
||
{ value?.name }
|
||
</div>
|
||
}
|
||
},{
|
||
title: '负责人',
|
||
dataIndex: 'assigners',
|
||
key: 'assigners',
|
||
width: '18%',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:any)=>{
|
||
let list = value.length > 0 ? value.map((i:any)=>{return i?.name}):[];
|
||
// <Avatar shape={"circle"} style={{backgroundColor:"#C1DAFF",marginRight:6}} size={24} src={value?.image_url} alt="" />
|
||
return list?.length ? list.toString() :"--"
|
||
}
|
||
},{
|
||
title: '时间',
|
||
dataIndex: 'created_at',
|
||
key: 'created_at',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
align:"center",
|
||
render:(value:any)=>{
|
||
return <span>{toTimeFormat(value)}</span>
|
||
}
|
||
}
|
||
]
|
||
|
||
const planColumns:ColumnsType<API.projects.iteration>=[
|
||
{
|
||
title: '标题',
|
||
dataIndex: 'sprintName',
|
||
key: 'sprintName',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:string,record:any)=>{
|
||
return(
|
||
<Link to={`/${enterIdentifier}/projects/${record?.pmsProjectId}/iteration/${record?.id}`} className={"font15 fontw400 col0d0Text alignCenter"}><img src={require("@/assets/image/workStand/planTitle.png")} alt="" className="mr20"/><span className="hide">{value}</span></Link>
|
||
)
|
||
}
|
||
},
|
||
// {
|
||
// title: '状态',
|
||
// dataIndex: 'status',
|
||
// key: 'status',
|
||
// width: '15%',
|
||
// ellipsis:{
|
||
// showTitle: false
|
||
// },
|
||
// render:(value:any,record:any)=>{
|
||
// const item = iterationStatusList.find(e => e.id === +value)
|
||
// return item ? <StatusTag item={ item }></StatusTag> : ''
|
||
// }
|
||
// },
|
||
{
|
||
title: '负责人',
|
||
dataIndex: 'sprintAssignee',
|
||
key: 'sprintAssignee',
|
||
width: '13%',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:any)=>{
|
||
return <span><Avatar shape={"circle"}gap={0} style={{backgroundColor:"#fff",marginRight:6}} size={24} src={value?.avatar} alt="" />{value?.nickName}</span>
|
||
}
|
||
},{
|
||
title: '工作进度',
|
||
dataIndex: 'sprintIssuesStatistics',
|
||
key: 'sprintIssuesStatistics',
|
||
width: '25%',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:any,record:any)=>{
|
||
let closeCount = +value?.count_closed;
|
||
let totalCount = +value?.count_total;
|
||
return <span className="alignCenter">工作项进度<Progress percent={+(closeCount/totalCount).toFixed(2)*100} style={{marginLeft:10,marginBottom:5}} strokeColor={"#2C75FF"} showInfo={false}/>{closeCount}/{totalCount}</span>
|
||
}
|
||
},{
|
||
title: '工作进度',
|
||
dataIndex: 'sprintIssuesStatistics',
|
||
key: 'sprintIssuesStatistics',
|
||
width: '25%',
|
||
ellipsis:{
|
||
showTitle: false
|
||
},
|
||
render:(value:any,record:any)=>{
|
||
let closeCount = +value?.hour_closed;
|
||
let totalCount = +value?.hour_total;
|
||
|
||
return <span className="alignCenter">工时容量<Progress percent={+(closeCount/totalCount).toFixed(2)*100} style={{marginLeft:10,marginBottom:5}} strokeColor={"#00AA82"} showInfo={false}/>{!(closeCount && totalCount) ? 0 :+(+(closeCount||0) / +(totalCount)).toFixed(2)*100}%</span>
|
||
}
|
||
}
|
||
]
|
||
|
||
return (
|
||
<div className={styles.staging}>
|
||
<div className={`${styles.stagingInfo} ${styles.stagingItem}`}>
|
||
<div className={`alignCenter ${styles.staginInfoFirst}`}>
|
||
{/* <Badge dot={true} offset={[-6,6]} color={"#14CA54"}> </Badge>*/}
|
||
<Avatar shape={"circle"} style={{backgroundColor:"#C1DAFF"}} size={48} src={currentUser?.avatar} />
|
||
<span className={`ml15 fontw500 col283Text font20`}>{currentUser?.nickName}</span>
|
||
<span className={`ml20 fontw400 col283Text font16`}>{getCurrentTime()}!</span>
|
||
</div>
|
||
<ul className={styles.stagingCard}>
|
||
<li>
|
||
<img src={require(`@/assets/image/workStand/task.png`)} alt="" />
|
||
<div>
|
||
<span>{statictisData?.projectTaskCount}</span>
|
||
<span>我负责的任务</span>
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<img src={require(`@/assets/image/workStand/needs.png`)} alt="" />
|
||
<div>
|
||
<span>{statictisData?.projectRequirementCount}</span>
|
||
<span>我负责的需求</span>
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<img src={require(`@/assets/image/workStand/issue.png`)} alt="" />
|
||
<div>
|
||
<span>{statictisData?.projectBugCount}</span>
|
||
<span>我负责的缺陷</span>
|
||
</div>
|
||
</li>
|
||
{
|
||
statictisData?.productRequirementCount ?
|
||
<li>
|
||
<img src={require(`@/assets/image/workStand/productNeeds.png`)} alt="" />
|
||
<div>
|
||
<span>{statictisData?.productRequirementCount}</span>
|
||
<span>我创建的产品需求</span>
|
||
</div>
|
||
</li>
|
||
:""
|
||
}
|
||
</ul>
|
||
</div>
|
||
<div className={`${styles.stagingWorkItem} ${styles.stagingItem}`}>
|
||
<div className={`alignCenter ${styles.titles}`}>
|
||
<img src={require('@/assets/image/workStand/workitem.png')} width="22px" style={{marginTop:"3px"}} alt="" />
|
||
<span className="ml5">我的工作项</span>
|
||
<Input
|
||
className="search-input ml30"
|
||
value={ search }
|
||
onBlur={ (e:any) => { setSearch(e.target.value) } }
|
||
onChange={ (e:any) => { setSearch(e.target.value) } }
|
||
suffix={ <i className="iconfont"></i> }
|
||
placeholder="搜索..."
|
||
/>
|
||
<img onClick={()=>{getWorkItem(workPage)}} style={{marginLeft:"auto",cursor:"pointer"}} src={require('@/assets/image/workStand/reset.png')} alt=""/>
|
||
</div>
|
||
<Menu mode="horizontal" selectedKeys={[selectKey]} onSelect={(e:any)=>{setSelectKey(e.key)}} className={styles.menusStyle}>
|
||
<Menu.Item key="assignedme">我负责的<Badge count={assignedmeCount} showZero style={{backgroundColor:"rgba(136, 149, 168, 0.07)",color:"#445A7A",marginLeft:4}}/></Menu.Item>
|
||
<Menu.Item key="authoredme">我创建的<Badge count={authoredmeCount} showZero style={{backgroundColor:"rgba(136, 149, 168, 0.07)",color:"#445A7A",marginLeft:4}}/></Menu.Item>
|
||
</Menu>
|
||
<Table
|
||
columns={columns}
|
||
dataSource={dataSource}
|
||
showHeader={false}
|
||
rowClassName={styles.rowStyle}
|
||
loading={workLoading}
|
||
pagination={{current:workPage,total:workTotal,pageSize:5,showQuickJumper:false,showSizeChanger:false,onChange:(p:number)=>{setWorkPage(p);getWorkItem(p)},hideOnSinglePage:true }}
|
||
/>
|
||
</div>
|
||
<Row justify={"start"}>
|
||
<Col span={8}>
|
||
<div className={`${styles.stagingProjects} ${styles.stagingItem}`}>
|
||
<div className={`alignCenter ${styles.titles}`}>
|
||
<img src={require('@/assets/image/project/reportChart.png')} width="28px" style={{marginTop:"7px"}} alt="" />
|
||
<span className="ml3">我的项目</span>
|
||
<img onClick={()=>{getProjects()}} style={{marginLeft:"auto",cursor:"pointer"}} src={require('@/assets/image/workStand/reset.png')} alt=""/>
|
||
</div>
|
||
<Spin spinning={projectLoading}>
|
||
{
|
||
projectData?.length > 0 ?
|
||
<div className={styles.listItems}>
|
||
{
|
||
projectData.map((i:any,key:number)=>{
|
||
return(
|
||
<div className={styles.listItemCard} key={key}>
|
||
<div className="alignCenter mb10">
|
||
<Link to={`/${enterIdentifier}/projects/${i?.id}`} className={`col283Text font16 fontw500 hide`} style={{flex:'1'}}>{i?.projectName}</Link>
|
||
<span className={styles.cardTagicon}><i className="iconfont icon-xiangmuicon font18"/></span>
|
||
</div>
|
||
<p className="mb15"><span className="mr40">工作项 {i?.projectIssuesCount}</span><span>成员 {i?.projectMemberCount}</span></p>
|
||
<Avatar shape={"circle"} style={{backgroundColor:"#F3F8FF",marginRight:6}} size={20} src={i?.projectAssignee?.avatar} alt="" /><span>{i?.projectAssignee?.nickName}</span>
|
||
</div>
|
||
)
|
||
})
|
||
}
|
||
</div>
|
||
: <NoData />
|
||
}
|
||
<div style={{textAlign:"right",paddingBottom:15,paddingTop:12}}>
|
||
<Pagination current={projectPage} pageSize={4} hideOnSinglePage={true} total={projectTotal} onChange={(p:number)=>{setProjectPage(p)}}/>
|
||
</div>
|
||
</Spin>
|
||
</div>
|
||
</Col>
|
||
<Col span={16}>
|
||
<div className={`${styles.stagingPlan} ${styles.stagingItem} ml25`}>
|
||
<div className={`alignCenter ${styles.titles}`} style={{marginBottom:0}}>
|
||
<img src={require('@/assets/image/workStand/plan.png')} width="22px" style={{marginTop:"3px"}} alt="" />
|
||
<span className="ml5">进行中的迭代</span>
|
||
<img onClick={()=>{getIteratioins()}} style={{marginLeft:"auto",cursor:"pointer"}} src={require('@/assets/image/workStand/reset.png')} alt=""/>
|
||
</div>
|
||
<Table
|
||
columns={planColumns}
|
||
dataSource={planData}
|
||
showHeader={false}
|
||
rowClassName={styles.rowStyle}
|
||
loading={planLoading}
|
||
pagination={{current:planPage,total:planTotal,pageSize:5,showQuickJumper:false,showSizeChanger:false,onChange:(p:number)=>{setPlanPage(p)},hideOnSinglePage:true }}
|
||
/>
|
||
</div>
|
||
</Col>
|
||
</Row>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default connect(({ enterprise , user , workStand }: { enterprise: EnterpriseModelState , user:UsersModelState , workStand:WorkStandModelState }) => ({enterprise,user,workStand}))(Index);
|