Merge pull request 'issue评论+里程碑 bug修复' (#513) from durian/forgeplus-react:gitlink_server_issue into gitlink_server_issue

This commit is contained in:
caishi 2023-02-28 16:36:57 +08:00
commit 942c63964d
6 changed files with 112 additions and 73 deletions

View File

@ -8,9 +8,10 @@ import EditComment from './editComment';
import Nodata from '../../../Nodata';
import CheckProfile from '../../../Component/ProfileModal/Profile';
import './list.scss';
import { Link } from 'react-router-dom';
function IssueCommentList(props){
const{history: {location}, reload, reloadComment, showNotification, current_user:{login, admin, image_url}, isManager, showLoginDialog, issueInfo:{author}} = props;
const{history, history: {location}, reload, reloadComment, showNotification, current_user:{login, admin, image_url, user_id}, isManager, showLoginDialog, issueInfo:{author}} = props;
const {owner, projectsId, index} = props.match.params;
const [category, setCategory] = useState('all');
const [journals, setJournals] = useState(undefined);
@ -34,7 +35,8 @@ function IssueCommentList(props){
'description': 'icon-xiugaibaobiaomoban',
'subject': 'icon-xiugaibaobiaomoban',
'start_date': 'icon-riqi',
'priority_id': 'icon-youxian'
'priority_id': 'icon-youxian',
'attachment': 'icon-xiugaibaobiaomoban'
}
useEffect(()=>{
@ -50,6 +52,10 @@ function IssueCommentList(props){
journals.map((item, index)=>{
!isJournalCount && (start = index)
item.is_journal_detail && (isJournalCount++)
if(!item.is_journal_detail && isJournalCount < 10){
isJournalCount = 0;
return
}
if(isJournalCount >= 10 && (!item.is_journal_detail || journals.length-1 === index)){
array.push({start, count: isJournalCount});
isJournalCount = 0;
@ -116,11 +122,11 @@ function IssueCommentList(props){
{journals && (journals.length > 0 && journals.map(item=>{return item.is_journal_detail ? (!item.closeAndSpan || item.id === item.start || open === item.start) ? <div key={item.id} className='operationLog'>
{/* 操作日志 */}
<div className='operationLogTopBor'></div>
<div className='logContent font-15'>
<div style={{display: 'flex'}}>
<div className='flexCenter font-15'>
<div className='flexCenter'>
<span className='iconBackBox mr10'><i className={`iconfont font-12 ${journalsIcon[item.operate_category]}`}></i></span>
<img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr5'/>
<span>{item.user.name} </span>
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr5'/></Link>
<Link to={`/${item.user.login}`}>{item.user.name}&nbsp;</Link>
<Tooltip title={<div><span>{item.user.name} </span><span dangerouslySetInnerHTML={{__html:item.operate_content}}></span></div>}><span className='task-hide' dangerouslySetInnerHTML={{__html:item.operate_content}} style={{maxWidth: '450px'}}></span></Tooltip>
<span className='ml15 timeAgo'>{timeAgo(item.created_at)}</span>
</div>
@ -130,18 +136,18 @@ function IssueCommentList(props){
</div> : '' : <div key={item.id} className='commentContentBox'>
{/* 评论 */}
{/* 判断是否是编辑状态 */}
{(showEdit === 2 && updateId === item.id) ? <div className='mt15'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={item.notes} defaultFileList={item.attachments}/></div> : <div>
{(showEdit === 2 && updateId === item.id) ? <div className='mt15 mr20'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={item.notes} defaultFileList={item.attachments}/></div> : <div>
<div className='issueCommentTopBor'></div>
<div className='commentContent'>
<div className='flexCenter mt15 font-15'>
<div>
<img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr8'/>
<span>{item.user.name}</span>
<Link to={`/${item.user.login}`}><img src={getImageUrl(item.user.image_url)} alt="" className='commentUserImg mr8'/></Link>
<Link to={`/${item.user.login}`}>{item.user.name}</Link>
<span className='ml15 timeAgo'>{timeAgo(item.created_at)}</span>
</div>
{login && <div>
{/* 平台管理员/仓库管理员/发布评论者/issue创建者 */}
{(admin || isManager || login === item.user.login || login === author.id) && <Popconfirm
{(admin || isManager || login === item.user.login || user_id === author.id) && <Popconfirm
placement="bottom"
title={`确定要删除此条评论吗?${item.children_journals.length > 0 ? '子评论也将被一起删除。' : ''}`}
okText="是"
@ -163,26 +169,26 @@ function IssueCommentList(props){
/></div>}
</div>
</div>}
{showEdit === 3 && replyId === item.id && <div className='contentHtml'><EditComment {...props} cancelMd={cancelMd} parentId={parentId} replyId={replyId} reloadComment={reloadComment}/></div>}
{showEdit === 3 && replyId === item.id && <div className='contentHtml mr20'><EditComment {...props} cancelMd={cancelMd} parentId={parentId} replyId={replyId} reloadComment={reloadComment}/></div>}
{/* 评论回复部分 */}
{item.children_journals.map(i =>{return <div className='commentReply' key={i.id}>
{(showEdit === 4 && updateId === i.id) ? <div className='mt15'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={i.notes} defaultFileList={i.attachments}/></div> : <div>
{(showEdit === 4 && updateId === i.id) ? <div className='mt15 mr20'><EditComment {...props} cancelMd={cancelMd} updateId={updateId} reloadComment={reloadComment} content={i.notes} defaultFileList={i.attachments}/></div> : <div>
<div className='flexCenter'>
<div>
<img src={getImageUrl(i.user.image_url)} alt="" className='commentUserImg mr8'/>
<span>{i.user.name}</span>
<Link to={`/${i.user.login}`}><img src={getImageUrl(i.user.image_url)} alt="" className='commentUserImg mr8'/></Link>
<Link to={`/${i.user.login}`}>{i.user.name}</Link>
<span className='ml5 timeAgo mr3'>回复</span>
<span>{i.reply_user.name}</span>
<span className='ml15 timeAgo'>{timeAgo(i.created_at)}</span>
</div>
{login && <div>
{/* 平台管理员/仓库管理员/发布评论者/回复评论者/issue创建者 */}
{(admin || isManager || login === i.user.login || login === i.reply_user.login || login === author.id) && <Popconfirm
{(admin || isManager || login === i.user.login || login === i.reply_user.login || user_id === author.id) && <Popconfirm
placement="bottom"
title={"确定要删除当前回复吗?"}
okText="是"
cancelText="否"
onConfirm={() => deleteComment(item.id)}
onConfirm={() => deleteComment(i.id)}
>
<Button type='link' className='color-grey-89'><i className='iconfont icon-fuzhi-shanchu font-14 mr8'></i>删除</Button>
</Popconfirm>}
@ -200,7 +206,7 @@ function IssueCommentList(props){
canDelete={false}
/></div>}
</div>}
{showEdit === 5 && replyId === i.id && <div className='contentHtml mt20'><EditComment {...props} cancelMd={cancelMd} parentId={parentId} replyId={replyId} reloadComment={reloadComment}/></div>}
{showEdit === 5 && replyId === i.id && <div className='contentHtml mt20 mr20'><EditComment {...props} cancelMd={cancelMd} parentId={parentId} replyId={replyId} reloadComment={reloadComment}/></div>}
</div>})}
<div className='issueCommentBottomBor'></div></div>}))}
</div>

View File

@ -26,11 +26,6 @@
text-align: center;
background-color:#f2f3f5;
}
.logContent{
display: flex;
align-items: center;
justify-content: space-between;
}
.operationLogTopBor, .operationLogBottomBor{
width: 1px;
height: 20px;
@ -64,8 +59,11 @@
margin-left: 34px;
}
.commentReply{
padding: 15px 0;
border-top: 1px solid #eeeeee;
padding: 15px 0 15px 20px;
background-color: rgba(238, 240, 246, 0.41);
&+.commentReply{
border-top: 1px dashed #eeeeee;
}
}
}
.primaryColor, .primaryColor:link{

View File

@ -8,6 +8,9 @@ import NewPanel from '../Component/newPanel';
import axios from 'axios';
function New(props){
// history search
const {location:{search}, projectDetail} = props;
const feedBack = search && search.indexOf('type=feedback') !== -1;
// id
const milepostId = props.match.params.milepostId;
const [ visible , setVisible ] = useState(false);
@ -41,12 +44,15 @@ function New(props){
const permission = props && props.projectDetail && props.projectDetail.permission;
useEffect(()=>{
const {projectDetail} = props;
if(projectDetail){
if(feedBack){
document.title = `意见反馈`;
return;
}
const { author, name} = projectDetail;
document.title = `新建疑修-${author.name}/${name}`
}
},[])
},[projectDetail, feedBack])
useEffect(()=>{
if(milepostId){
@ -105,6 +111,12 @@ function New(props){
axios.get(url,{params:{keyword:charge,only_name:true}}).then(result=>{
if(result && result.data){
setChargeList(result.data.collaborators);
if(feedBack && !charge){
// -
const {collaborators} = result.data;
const id = collaborators.filter(item=>item.id === 86107);
setAssigner_choose(id.length === 0 ? [collaborators[0]] : id)
}
}
})
}
@ -205,8 +217,8 @@ function New(props){
<p className="font-17 color-grey-3 mt20 mb15">新建疑修</p>
<Box>
<LongWidth>
<NewPanel {...props} createFunc={createFunc}
owner = {owner} projectsId = {projectsId}/>
{/* 意见反馈初始化内容 */}
<NewPanel {...props} createFunc={createFunc} owner = {owner} projectsId = {projectsId} desc={feedBack ? "####问题描述\n\n\n####重现问题步骤\n\n\n####截图\n\n\n####建议解决办法\n" : undefined}/>
</LongWidth>
<div className="shortwidth">
<DropMenu

View File

@ -1,12 +1,9 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Dropdown, Icon, Menu, Pagination, Typography, Popconfirm, Spin, Button, Tooltip } from 'antd';
import NoneData from '../Nodata';
import { Dropdown, Icon, Menu, Pagination, Popconfirm, Spin, Button, Tooltip } from 'antd';
import emp from '../../forge/Issues/Img/emp.png';
import axios from 'axios';
import './milepost.scss';
const { Text } = Typography;
class Milepost extends Component {
constructor(props) {
super(props);
@ -63,7 +60,7 @@ class Milepost extends Component {
this.setState({
status: type === 1 ? 'open' : 'closed'
})
this.getList(1, this.state.limit, type === 1 ? 'open' : 'closed');
this.getList(1, this.state.limit, type === 1 ? 'open' : 'closed', this.state.order_name, this.state.order_type);
}
}
@ -76,7 +73,7 @@ class Milepost extends Component {
status: status
}).then(result => {
if (result) {
this.getList(1, this.state.limit, this.state.status);
this.getList(1, this.state.limit, this.state.status, this.state.order_name, this.state.order_type);
const { getDetail } = this.props;
getDetail && getDetail();
}
@ -95,7 +92,7 @@ class Milepost extends Component {
}
}).then((result) => {
if (result) {
this.getList(1, this.state.limit, this.state.status);
this.getList(1, this.state.limit, this.state.status, this.state.order_name, this.state.order_type);
const { getDetail } = this.props;
getDetail && getDetail();
}
@ -109,16 +106,18 @@ class Milepost extends Component {
this.setState({
page
})
this.getList(page, this.state.limit, this.state.status);
this.getList(page, this.state.limit, this.state.status, this.state.order_name, this.state.order_type);
}
// 排序
arrayList = (e) => {
const name = e.key !== 'order' ? e.key : undefined;
const type = e.item.props.value !== 'order' ? e.item.props.value : undefined;
this.setState({
order_name: e.key,
order_type: e.item.props.value
order_name: name,
order_type: type
})
this.getList(1, this.state.limit, this.state.status, e.key, e.item.props.value);
this.getList(1, this.state.limit, this.state.status, name, type);
}
// 分页组件
@ -127,15 +126,16 @@ class Milepost extends Component {
page: 1,
limit: pageSize
})
this.getList(1, pageSize, this.state.status);
this.getList(1, pageSize, this.state.status, this.state.order_name, this.state.order_type);
}
render() {
const { data, limit, page, spinings, status } = this.state;
const { data, limit, page, spinings, status, order_name, order_type } = this.state;
const { projectsId , owner } = this.props.match.params;
const { isManager, isDeveloper} = this.props;
const menu = (
<Menu className="orderCondition milepostSort" onClick={this.arrayList}>
<Menu.Item key={'order'} value={'order'}>默认排序</Menu.Item>
<Menu.Item key={'effective_date'} value="desc">到期日从后到先</Menu.Item>
<Menu.Item key={'effective_date'} value="asc">到期日从先到后</Menu.Item>
<Menu.Item key={'percent'} value="asc">完成度从低到高</Menu.Item>
@ -145,6 +145,24 @@ class Milepost extends Component {
</Menu>
)
// 排序字段 选中显示文本
const sortByMile = {
'effective_date':{
'desc': '到期日从后到先',
'asc': '到期日从先到后'
},
'percent':{
'desc': '完成度从低到高',
'asc': '完成度从高到低'
},
'issues_count':{
'desc': '任务从多到少',
'asc': '任务从少到多'
},
'order':{
'order': '排序'
}
}
return (
<Spin spinning={spinings}>
@ -160,11 +178,11 @@ class Milepost extends Component {
<span className={`pointBox ml35 font-15 postStatus ${status === "closed" ? "active" : ""}`} onClick={() => this.opneMilelist(2)}>已关闭<span className='statusCount font-13'>{data && data.closed_milestone_count}</span></span>
</div>
<Dropdown className="topWrapperSelect" overlay={menu} trigger={['click']} placement="bottomCenter">
<span className='pointBox'>排序<Icon type="caret-down" className="ml5"/></span>
<span className='pointBox'>{order_name ? sortByMile[order_name][order_type] : '排序'}<Icon type="caret-down" className="ml5"/></span>
</Dropdown>
</div>
{/* 里程碑列表展示 */}
{data && data.milestones && data.milestones.length === 0 && <div className="milestonesNoDate"><NoneData _html="暂无里程碑"></NoneData></div>}
{data && data.milestones && data.milestones.length === 0 && <div className="milestonesNoDate"><img src={emp} alt=""/></div>}
{data && data.milestones && data.milestones.length > 0 && <div className='milepostList'>
{data.milestones.map((item, key)=>{
return <div className='flexSpaceBetween milepostItemBox'>
@ -173,7 +191,7 @@ class Milepost extends Component {
<i className="iconfont icon-lichengbeiicon1 font-12 mr10 primaryColor"></i>
<Link to={`/${owner}/${projectsId}/milestones/${item.id}`} className="font-16 task-hide milepostInfo">{item.name}</Link>
</div>
<Tooltip title={item.description} placement={'topLeft'} overlayStyle={{width: '600px'}}><Text type="secondary" ellipsis={{ rows: 30, expandable: false, onExpand: Function }} className='color-grey-89 task-hide milepostInfo'>{item.description}</Text></Tooltip>
<Tooltip title={item.description} placement={'topLeft'} overlayStyle={{width: '600px'}}><span className='color-grey-89 task-hide milepostInfo'>{item.description}</span></Tooltip>
</div>
<div className="flexSpaceBetween actionMileBox">
<div className="grid-item effectiveDate">

View File

@ -6,10 +6,10 @@ import Datas from '../Issues/Component/datas';
import axios from 'axios';
import { FlexAJ } from '../Component/layout';
import CheckProfile from '../Component/ProfileModal/Profile';
import emp from '../../forge/Issues/Img/emp.png';
import './milepost.scss';
import './order.scss';
import '../Issues/index.scss';
import Nodata from '../Nodata';
function MilepostDetail(props){
const [ milepost, setMilepost] = useState(undefined);
@ -30,6 +30,7 @@ function MilepostDetail(props){
const [ limit, setLimit] = useState(10);
const [ page , setPage ] = useState(1);
const [ total , setTotal ] = useState(undefined);
const [ issuesCount, setIssueCount] = useState(undefined);
const menuRef = useRef(null);
const { projectsId, mileId , owner } = props.match.params;
@ -69,20 +70,21 @@ function MilepostDetail(props){
page,
limit,
category,
// participant_category:aboutMe,
// keyword,category,
...params
// sort_direction:(params && params.sort_by) ? ((params.sort_by === 1 || params.sort_by === 3) ? "desc" : "asc") :undefined,
// sort_by:(params && params.sort_by) ? ((params.sort_by === 1 || params.sort_by === 2) ? "created_on" : "updated_on") : undefined
}
}).then(result=>{
if(result){
const {milestone, issues, total_count, closed_issues_count, opened_issues_count} = result.data;
const {milestone, issues, total_count, closed_issues_count, opened_issues_count, total_issues_count} = result.data;
if(issues.length === 0 && page>1){
setPage(page-1);
return;
}
setMilepost(milestone);
setIssueList(issues);
setTotal(total_count);
setClosedCount(closed_issues_count);
setOpenedCount(opened_issues_count);
setIssueCount(total_issues_count)
const ids = issues.length>0 ? issues.map(i=>{return i.id}) : [];
setAllIds(ids);
}
@ -104,16 +106,6 @@ function MilepostDetail(props){
setAboutMe(e.key);
}
//
function clearCondition(){
setKeyword(undefined);
setAboutMe("aboutme");
Init();
//
menuRef.current && menuRef.current.clearChoose();
}
// issue
function chooseAll(e){
setCheckAll(e.target.checked);
@ -141,18 +133,20 @@ function MilepostDetail(props){
//
menuRef.current && menuRef.current.clearChoose();
}
// allValue updateIds
function sureUpdate(){
const url = `/v1/${owner}/${projectsId}/issues/batch_update`;
axios.patch(url,{
assigner_ids: [updateIds && updateIds.assigner_id],
assigner_ids: updateIds && updateIds.assigner_id && updateIds.assigner_id.split(","),
ids: allValue,
issue_tag_ids: [updateIds && updateIds.issue_tag_ids],
issue_tag_ids: updateIds && updateIds.issue_tag_ids && updateIds.issue_tag_ids.split(","),
milestone_id: updateIds && updateIds.milestone_id,
priority_id: updateIds && updateIds.issue_priorities_id,
status_id: updateIds && updateIds.status_id
}).then(result=>{
if(result){
setUpdateIds(undefined);
setAllValue([]);
setTotal(undefined);
Init();
cancelUpdate();
}
@ -205,13 +199,13 @@ function MilepostDetail(props){
<div className="lists mt25">
<div className="listheader">
<div style={{display:"flex"}}>
<Checkbox value="all" style={{marginRight: "16px"}} checked={checkAll} onChange={chooseAll}></Checkbox>
<Checkbox value="all" style={{marginRight: "16px", display: (permission && permission !== "Reporter") ?"block":"none"}} checked={checkAll} onChange={chooseAll}></Checkbox>
{
allValue && allValue.length>0 ?
<span>选择{allValue.length}个issue</span>
:
<ul className="statusul">
<li className={category === "all" ?"active":""} onClick={()=>setCategory("all")}>全部<span>{total}</span></li>
<li className={category === "all" ?"active":""} onClick={()=>setCategory("all")}>全部<span>{issuesCount}</span></li>
<li className={category === "opened" ?"active":""} onClick={()=>setCategory("opened")}>开启中<span>{openedCount}</span></li>
<li className={category === "closed" ?"active":""} onClick={()=>setCategory("closed")}>已关闭<span>{closedCount}</span></li>
</ul>
@ -236,7 +230,7 @@ function MilepostDetail(props){
</div>
</div>
{
total === 0 && <div className="listdatas noDataMile"><Nodata _html="暂无数据"/></div>
total === 0 && <div className="milestonesNoDate"><img src={emp} alt=""/></div>
}
{
total > 0 &&
@ -247,7 +241,7 @@ function MilepostDetail(props){
return(
<Datas
key={key}
checkbox={ <Checkbox value={item.id} key={item.id} style={{marginRight: "16px"}}></Checkbox> }
checkbox={ <Checkbox value={item.id} key={item.id} style={{marginRight: "16px", display: (permission && permission !== "Reporter") ?"block":"none"}}></Checkbox> }
item={item}
owner={owner}
projectsId={projectsId}
@ -260,7 +254,7 @@ function MilepostDetail(props){
{
total > 10 &&
<div className="pt25 pb30" style={{textAlign:"right"}}>
<Pagination total={total} current={page} onChange={changepage} showSizeChanger showQuickJumper onShowSizeChange={(current, pageSize)=>{setPage(1);setLimit(pageSize);}}/>
<Pagination total={total} pageSize={limit} current={page} onChange={changepage} showSizeChanger showQuickJumper onShowSizeChange={(current, pageSize)=>{setPage(1);setLimit(pageSize);}}/>
</div>
}
</React.Fragment>

View File

@ -17,16 +17,24 @@
padding: 15px 15px 15px 20px;
border-radius:4px 4px 0px 0px;
.postStatus.active{
font-weight:700;
color:#333333;
color:$primary-color;
.statusCount{
color: $primary-color;
border: 1px solid $primary-color;
background-color: #fff;
}
}
.statusCount{
background-color:rgba(70, 106, 255, 0.09);
border-radius:10px;
margin-left: 5px;
padding: 4px 7px;
color:#666666;
font-weight: normal;
height: 19px;
line-height: 19px;
display: inline-block;
min-width: 30px;
text-align: center;
}
}
.pointBox{
@ -69,6 +77,9 @@
.milestonesNoDate{
border:1px solid#d0d0d0;
border-top: none;
text-align: center;
min-height: 500px;
line-height: 500px;
}
// 浅灰色按钮
.grayButton{