From 1bce76c0304e25829886d5ba90532241ef682c80 Mon Sep 17 00:00:00 2001
From: caishi <1149225589@qq.com>
Date: Fri, 1 Sep 2023 11:09:00 +0800
Subject: [PATCH] merge issue files
---
package-lock.json | 21 +
package.json | 1 +
src/forge/Issues/Component/addTagsBox.jsx | 65 ++
src/forge/Issues/Component/allMenus.jsx | 219 +++++
src/forge/Issues/Component/chooseMenu.jsx | 305 +++++++
src/forge/Issues/Component/colorCard.jsx | 47 +
.../Issues/Component/comments/editComment.jsx | 165 ++++
src/forge/Issues/Component/comments/list.jsx | 242 +++++
src/forge/Issues/Component/comments/list.scss | 205 +++++
src/forge/Issues/Component/copy.jsx | 22 +
src/forge/Issues/Component/datas.jsx | 96 ++
src/forge/Issues/Component/date.jsx | 149 +++
src/forge/Issues/Component/delBox.jsx | 32 +
src/forge/Issues/Component/drop.jsx | 47 +
src/forge/Issues/Component/dropMenu.jsx | 210 +++++
src/forge/Issues/Component/menus.jsx | 93 ++
src/forge/Issues/Component/newPanel.jsx | 129 +++
src/forge/Issues/Img/UploadImg.png | Bin 0 -> 2351 bytes
src/forge/Issues/Img/biaoji.png | Bin 0 -> 255 bytes
src/forge/Issues/Img/create.png | Bin 0 -> 133 bytes
src/forge/Issues/Img/emp.png | Bin 0 -> 50682 bytes
src/forge/Issues/Img/gold.png | Bin 0 -> 708 bytes
src/forge/Issues/Img/issue-big.png | Bin 0 -> 2920 bytes
src/forge/Issues/Img/issue.png | Bin 0 -> 608 bytes
src/forge/Issues/Img/pr.png | Bin 0 -> 1025 bytes
src/forge/Issues/Pages/details.jsx | 520 +++++++++++
src/forge/Issues/Pages/list.jsx | 461 ++++++++++
src/forge/Issues/Pages/new.jsx | 303 +++++++
src/forge/Issues/Pages/sign.jsx | 267 ++++++
src/forge/Issues/index.jsx | 80 ++
src/forge/Issues/index.scss | 854 ++++++++++++++++++
src/forge/Main/Detail.js | 26 +-
src/forge/css/index.scss | 18 +
33 files changed, 4564 insertions(+), 13 deletions(-)
create mode 100644 src/forge/Issues/Component/addTagsBox.jsx
create mode 100644 src/forge/Issues/Component/allMenus.jsx
create mode 100644 src/forge/Issues/Component/chooseMenu.jsx
create mode 100644 src/forge/Issues/Component/colorCard.jsx
create mode 100644 src/forge/Issues/Component/comments/editComment.jsx
create mode 100644 src/forge/Issues/Component/comments/list.jsx
create mode 100644 src/forge/Issues/Component/comments/list.scss
create mode 100644 src/forge/Issues/Component/copy.jsx
create mode 100644 src/forge/Issues/Component/datas.jsx
create mode 100644 src/forge/Issues/Component/date.jsx
create mode 100644 src/forge/Issues/Component/delBox.jsx
create mode 100644 src/forge/Issues/Component/drop.jsx
create mode 100644 src/forge/Issues/Component/dropMenu.jsx
create mode 100644 src/forge/Issues/Component/menus.jsx
create mode 100644 src/forge/Issues/Component/newPanel.jsx
create mode 100644 src/forge/Issues/Img/UploadImg.png
create mode 100644 src/forge/Issues/Img/biaoji.png
create mode 100644 src/forge/Issues/Img/create.png
create mode 100644 src/forge/Issues/Img/emp.png
create mode 100644 src/forge/Issues/Img/gold.png
create mode 100644 src/forge/Issues/Img/issue-big.png
create mode 100644 src/forge/Issues/Img/issue.png
create mode 100644 src/forge/Issues/Img/pr.png
create mode 100644 src/forge/Issues/Pages/details.jsx
create mode 100644 src/forge/Issues/Pages/list.jsx
create mode 100644 src/forge/Issues/Pages/new.jsx
create mode 100644 src/forge/Issues/Pages/sign.jsx
create mode 100644 src/forge/Issues/index.jsx
create mode 100644 src/forge/Issues/index.scss
diff --git a/package-lock.json b/package-lock.json
index b99278538..219e90579 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14143,6 +14143,27 @@
"object-assign": "^4.1.1"
}
},
+ "react-copy-to-clipboard": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
+ "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
+ "requires": {
+ "copy-to-clipboard": "^3.3.1",
+ "prop-types": "^15.8.1"
+ },
+ "dependencies": {
+ "prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ }
+ }
+ },
"react-countup": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.1.0.tgz",
diff --git a/package.json b/package.json
index 3b421e973..13dddda43 100644
--- a/package.json
+++ b/package.json
@@ -84,6 +84,7 @@
"react-color": "^2.18.0",
"react-content-loader": "^3.1.1",
"react-cookies": "^0.1.1",
+ "react-copy-to-clipboard": "^5.1.0",
"react-countup": "^6.1.0",
"react-cropper": "^2.1.8",
"react-datepicker": "^2.14.1",
diff --git a/src/forge/Issues/Component/addTagsBox.jsx b/src/forge/Issues/Component/addTagsBox.jsx
new file mode 100644
index 000000000..6b0c72108
--- /dev/null
+++ b/src/forge/Issues/Component/addTagsBox.jsx
@@ -0,0 +1,65 @@
+import React , { useState , useEffect } from 'react';
+import { Form , Input, Button } from 'antd';
+import ColorCard from './colorCard';
+import axios from 'axios';
+
+function AddTagsBox({onSuccess , form ,owner , projectsId,onCancel}){
+ const { getFieldDecorator , getFieldsValue , validateFields } = form;
+ const [ colors , setColors ] = useState(undefined);
+ const [ defaultColor , setDefaultColor ]= useState(undefined);
+
+ useEffect(()=>{
+ const c = Math.random().toString(16).substr(-6);
+ c && setDefaultColor(`#${c}`);
+ },[])
+
+ function saveFunc(){
+ validateFields((error,values)=>{
+ if(!error){
+ const { desc , name } = getFieldsValue();
+ const url = `/v1/${owner}/${projectsId}/issue_tags`;
+ axios.post(url,{
+ name,description:desc,color:colors || defaultColor
+ }).then(result=>{
+ result && onSuccess();
+ }).catch(error=>{})
+ }
+ })
+ }
+
+ function getColor(colors){
+ setColors(colors);
+ }
+
+ return(
+
+
+ {displayColorPicker ? (
+
+
{setDisplayColorPicker(false)}} />
+
+
+ ) : null}
+
+ )
+}
+export default ColorCard;
\ No newline at end of file
diff --git a/src/forge/Issues/Component/comments/editComment.jsx b/src/forge/Issues/Component/comments/editComment.jsx
new file mode 100644
index 000000000..a0083d2fb
--- /dev/null
+++ b/src/forge/Issues/Component/comments/editComment.jsx
@@ -0,0 +1,165 @@
+import React from 'react';
+import { Button, message } from 'antd';
+import { getImageUrl } from 'educoder';
+import MDEditor from "../../../../modules/tpm/challengesnew/tpm-md-editor";
+import Upload from "../../../../forge/Upload/Index";
+import Attachments from "../../../../forge/Upload/attachment";
+import UploadImg from "../../../../forge/Images/upload.png";
+import './list.scss';
+import '../../../../forge/Order/order.scss';
+import { useState } from 'react';
+import { Link } from 'react-router-dom';
+import axios from 'axios';
+
+function EditComment(props){
+ const {owner, projectsId, index} = props.match.params;
+ const {current_user, current_user:{login}, showLoginDialog, showNotification, reloadComment, cancelMd, parentId, replyId, updateId, showUserImg=true} = props;
+ const [content, setContent] = useState(props.content);
+ const [quillFlag, setQuillFlag] = useState(false);
+ // 确认评论按钮loading效果
+ const [journalSpin, setJournalSpin] = useState(false);
+ const [atWhoLoginList, setAtWhoLoginList] = useState(undefined);
+ const [fileList, setFileList] = useState(undefined);
+ const [attachmentClean, setAttachmentClean] = useState(true);
+ const [ save , setSave ] = useState(undefined);
+
+ useState(()=>{
+ setSave(props.defaultFileList);
+ },[props.defaultFileList])
+
+ // 评论点击函数
+ function addJournals(){
+ if(!content){
+ setQuillFlag(true);
+ return
+ }
+ setJournalSpin(true);
+ if(updateId){
+ // 修改评论
+ axios.patch(`/v1/${owner}/${projectsId}/issues/${index}/journals/${updateId}`,{
+ notes: content,
+ attachment_ids: fileList,
+ receivers_login:atWhoLoginList
+ }).then(res=>{
+ if(res && res.data){
+ // 刷新评论列表
+ reloadComment();
+ message.success('评论成功');
+ cancelMd();
+ setContent(undefined);
+ }
+ setJournalSpin(false);
+ })
+ }else{
+ // 新建评论
+ const params = {
+ parent_id: parentId,
+ reply_id: replyId,
+ notes: content,
+ attachment_ids: fileList,
+ receivers_login:atWhoLoginList
+ }
+ axios.post(`/v1/${owner}/${projectsId}/issues/${index}/journals`, params).then(res=>{
+ if(res && res.data){
+ // 刷新评论列表
+ reloadComment();
+ message.success('评论成功');
+ cancelMd();
+ setContent(undefined);
+ }
+ setJournalSpin(false);
+ })
+ }
+ };
+ function deleteLoad(id){
+ let arr = [];
+ let list = save;
+ if(list && list.length>0 ){
+ arr = list.filter(i=>i.id !== id);
+ }
+ setFileList(arr.map(i=>{return i.id}));
+ setSave(arr);
+ }
+ function UploadFunc(f){
+ let list = [];
+ let arr = [];
+ if(save && save.length>0 ){
+ arr = save.map(i=>{return i.id});
+ }
+ list = arr && arr.length>0 ? f.concat(arr) : f ;
+ setFileList(list);
+ };
+
+ return(
+
+
+

+
+
+
{setQuillFlag(false);setContent(value);}}
+ isCanAtme = {true}
+ isQuoteIssue={true}
+ changeAtWhoLoginList = {(loginList)=>{setAtWhoLoginList(loginList); setAttachmentClean(true)}}
+ owner = {owner}
+ projectsId = {projectsId}
+ >
+
+ {quillFlag && 请输入评论内容}
+
+
+ }
+ size={100}
+ showNotification={showNotification}
+ // defaultFileList={props.defaultFileList}
+ />
+ {props.defaultFileList && props.defaultFileList.length > 0 &&
+
+ }
+
+
+
+
+
+
+ )
+}
+export default EditComment;
\ No newline at end of file
diff --git a/src/forge/Issues/Component/comments/list.jsx b/src/forge/Issues/Component/comments/list.jsx
new file mode 100644
index 000000000..afabf21a2
--- /dev/null
+++ b/src/forge/Issues/Component/comments/list.jsx
@@ -0,0 +1,242 @@
+import React, { useEffect, useState } from 'react';
+import { Button, Popconfirm, Radio, Input, message, Tooltip, Spin, Pagination } from 'antd';
+import axios from 'axios';
+import { getImageUrl, timeAgo } from 'educoder';
+import RenderHtml from '../../../../components/render-html';
+import Attachment from '../../../Upload/attachment';
+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, 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('comment');
+ const [journals, setJournals] = useState(undefined);
+ // 是否展示新建/编辑评论markdown部分新建评论1 编辑父级评论2 回复评论3 编辑回复内容4 回复子机评论5
+ const [showEdit, setShowEdit] = useState(false);
+ const [parentId, setParentId] = useState(undefined);
+ const [replyId, setReplyId] = useState(undefined);
+ // 修改评论id
+ const [updateId, setUpdateId] = useState(undefined);
+ // 操作日志展开效果
+ const [open, setOpen] = useState(undefined);
+ // 加载中效果(缓冲css 线条效果)
+ const [spin, setSpin] = useState(false);
+ // issue 评论总数
+ const [journalsCount, setJournalsCount] = useState(undefined);
+ // 分页
+ const limit = 50;
+ const [page, setPage] = useState(1);
+ const [totalCount, setTotalCount] = useState(undefined);
+ // 操作记录 icon
+ const journalsIcon ={
+ 'issue': 'icon-chuangjianqianbao',
+ 'branch_name': 'icon-fenzhi3',
+ 'assigner': 'icon-chengyuan2',
+ 'status_id': 'icon-xiugaibaobiaomoban',
+ 'fixed_version_id': 'icon-lichengbeiicon2',
+ 'due_date': 'icon-riqi',
+ 'issue_tag': 'icon-biaoji2',
+ 'description': 'icon-xiugaibaobiaomoban',
+ 'subject': 'icon-xiugaibaobiaomoban',
+ 'start_date': 'icon-riqi',
+ 'priority_id': 'icon-youxian',
+ 'attachment': 'icon-xiugaibaobiaomoban'
+ }
+
+ useEffect(()=>{
+ setSpin(true);
+ axios.get(`/v1/${owner}/${projectsId}/issues/${index}/journals`,{params:{
+ category,
+ page,
+ limit
+ }}).then(res=>{
+ if(res && res.data){
+ const {journals, total_comment_journals_count, total_count} = res.data;
+ if(category === 'all'){
+ const array = [];
+ let start = undefined;
+ let isJournalCount = 0;
+ 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;
+ }
+ })
+ array.map((item, i)=>{
+ journals[item.start].numCount = item.count-1;
+ for (let index = 0; index < item.count; index++) {
+ journals[item.start+index].start = journals[item.start].id;
+ journals[item.start+index].closeAndSpan = true;
+ }
+ })
+ }
+ setTotalCount(total_count);
+ setJournalsCount(total_comment_journals_count);
+ setJournals(journals);
+ setSpin(false)
+ }
+ })
+ }, [reload, category, page])
+
+ function commentCtx(v){
+ return
;
+ };
+
+ // 删除评论内容
+ function deleteComment(id){
+ axios.delete(`/v1/${owner}/${projectsId}/issues/${index}/journals/${id}`).then(res=>{
+ if(res && res.data && !res.data.status){
+ reloadComment();
+ message.success('删除成功');
+ }
+ })
+ }
+
+ // markdown 取消操作
+ function cancelMd(){
+ setShowEdit(false);
+ }
+
+ return(
+
+ {/* 全部 / 评论 / 操作日志 */}
+
0 && 'pb15'}`}>
+
+ {setSpin(true);setPage(1);setCategory(e.target.value);history.push(history.pathname)}} value={category}>
+ 评论{journalsCount}
+ 操作日志
+ 全部
+
+
+
+ {/* 评论快速入口-仅有评论且登录时展示 */}
+ {login && category !== 'operate' && journalsCount > 0 &&
}
+ {/* 评论/操作日志 展示列表 */}
+
+
+ {journals && (journals.length > 0 && journals.map(item=>{return item.is_journal_detail ? (!item.closeAndSpan || item.id === item.start || open === item.start) ?
+
: '' :
+ {/* 评论 */}
+
+
})
+
+ {/* 判断是否是编辑状态 */}
+ {(showEdit === 2 && updateId === item.id) ?
:
+
+
+
+ {item.user.name}
+ {timeAgo(item.created_at)}
+
+ {login &&
+ {/* 平台管理员/仓库管理员/发布评论者/issue创建者 */}
+ {(admin || isManager || login === item.user.login || user_id === author.id) &&
0 ? '子评论也将被一起删除。' : ''}`}
+ okText="是"
+ cancelText="否"
+ onConfirm={() => deleteComment(item.id)}
+ >
+
+ }
+ {/* 仅评论者可修改 */}
+ {login === item.user.login &&
}
+
{setParentId(item.id); setReplyId(item.id); setShowEdit(3)}}>
+
}
+
+
{commentCtx(item.notes)}
+ {item && item.attachments && item.attachments.length > 0 &&
}
+
+
}
+ {showEdit === 3 && replyId === item.id &&
}
+ {/* 评论回复部分 */}
+ {item.children_journals.map(i =>{return
+ {(showEdit === 4 && updateId === i.id) ?
:
+
+
+
})
+
{i.user.name}
+ {i.reply_user &&
回复}
+
{i.reply_user && i.reply_user.name}
+
{timeAgo(i.created_at)}
+
+ {login &&
+ {/* 平台管理员/仓库管理员/发布评论者/回复评论者/issue创建者 */}
+ {(admin || isManager || login === i.user.login || (i.reply_user && login === i.reply_user.login) || user_id === author.id) &&
deleteComment(i.id)}
+ >
+
+ }
+ {/* 仅回复评论者可修改 */}
+ {login === i.user.login &&
}
+
{setParentId(item.id);setReplyId(i.id); setShowEdit(5)}}>
+
+
+
}
+
+
{commentCtx(i.notes)}
+ {i && i.attachments && i.attachments.length > 0 &&
}
+
}
+ {showEdit === 5 && replyId === i.id &&
}
+
})}
+
+
}))}
+
+
+ {totalCount>limit && }
+ {/* 添加评论 */}
+ {category !== 'operate' &&
+ {login ? showEdit === 1 ?
: : }
+ }
+
+ )
+}
+export default IssueCommentList;
\ No newline at end of file
diff --git a/src/forge/Issues/Component/comments/list.scss b/src/forge/Issues/Component/comments/list.scss
new file mode 100644
index 000000000..be95b6e3d
--- /dev/null
+++ b/src/forge/Issues/Component/comments/list.scss
@@ -0,0 +1,205 @@
+.typeActionBox{
+ padding: 10px 20px 4px;
+ background-color:rgba(241, 243, 252, 0.55);
+ border-radius:4px;
+ .typeActionRadio{
+ color: #333;
+ display: inline-flex;
+ align-items: center;
+ margin-right: 25px;
+ &.ant-radio-wrapper-checked{
+ color: $primary-color;
+ }
+ }
+ .journalsCount{
+ color:#5e6685;
+ background-color:rgba(70, 106, 255, 0.09);
+ border-radius:10px;
+ display: inline-block;
+ padding: 0px 6px;
+ }
+}
+.commentUserImg{
+ height: 28px;
+ width: 28px;
+ border-radius: 50%;
+ object-fit: cover;
+}
+.issueCommentsBox{
+ .iconBackBox{
+ display: inline-block;
+ width:24px;
+ height:24px;
+ border-radius: 50%;
+ line-height: 24px;
+ text-align: center;
+ background-color:#f2f3f5;
+ }
+ .operationLog, .commentContentBox{
+ position: relative;
+ min-height: 62px;
+ display: flex;
+ >.flexCenter, >a{
+ z-index: 2;
+ }
+ &::before, &::after{
+ content: '';
+ width: 1px;
+ height: 50%;
+ position: absolute;
+ background: #eee;
+ left: 12px;
+ z-index: 1;
+ }
+ &::after{
+ background: #eee;
+ top: 50%;
+ }
+ }
+ .commentContentBox+.operationLog .operationCommentBor, .operationLog+.commentContentBox .commentOperationBor{
+ border-top: 1px solid #eee;
+ width: 98.5%;
+ position: absolute;
+ left: 12px;
+ }
+ .operationLog+.commentContentBox{
+ &>a, &>.commentContentRight{
+ margin-top: 25px;
+ }
+ }
+ .timeAgo{
+ color:#acb0bf;
+ }
+ .operationLog:last-child::after, .operationLog:first-child::before, .commentContentBox:last-child::after, &.justComment .commentContentBox::before, &.justComment .commentContentBox::after{
+ display: none;
+ }
+ .operationLog:first-child, .commentContentBox+.operationLog{
+ margin-top: -15px;
+ }
+ .commentContentBox:last-child::before{
+ height: 35%;
+ }
+ .flexCenter{
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+ .commentRenderHtml.markdown-body p{
+ font-size: 14px !important;
+ }
+ .commentReply{
+ padding: 15px 0 0 20px;
+ background-color: rgba(238, 240, 246, 0.41);
+ &>div{
+ padding-bottom: 5px;
+ }
+ &+.commentReply>div{
+ margin-top: -15px;
+ padding-top: 15px;
+ border-top: 1px dashed #eee;
+ }
+ }
+ .opBox{
+ justify-content: flex-start;
+ // flex: 1;
+ // width: 100%;
+ }
+ .commentContentBox{
+ display: flex;
+ align-items: flex-start;
+ }
+ .commentContentRight{
+ flex: 1;
+ background-color:#fafafc;
+ border:1px solid rgba(42, 97, 255, 0.23);
+ border-radius:6px;
+ position: relative;
+ top: -6px;
+ padding: 10px 15px 16px;
+ width: 0;
+ &::before{
+ content: '';
+ display: block;
+ position: absolute;
+ top: 13px;
+ left: -14px;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ font-size: 0;
+ line-height: 0;
+ border: 7px;
+ border-style: solid;
+ border-color: transparent rgba(42, 97, 255, 0.23) transparent transparent;
+ }
+ &::after{
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ font-size: 0;
+ line-height: 0;
+ border: 6px;
+ border-style: solid;
+ border-color: transparent #fafafc transparent transparent;
+ position: absolute;
+ top: 14px;
+ left: -11px;
+ }
+ }
+}
+.primaryColor, .primaryColor:link{
+ color: $primary-color;
+}
+.attachmentBox{
+ margin-top: -8px;
+}
+.color-grey-89{
+ color: #898d9d;
+}
+// 添加评论样式
+.unLoginComment{
+ height:58px;
+ line-height: 58px;
+ background-color:rgba(241, 243, 252, 0.55);
+ border-radius:4px;
+ color: rgba(84, 87, 103, 1);
+ .loginBtn{
+ color: $primary-color;
+ }
+}
+.quillFlagBox{
+ color: red;
+ margin-bottom: 5px !important;
+ height: 28px;
+}
+.addComments{
+ display: flex;
+ align-items: center;
+ background-color:rgba(241, 243, 252, 0.55);
+ border-radius:4px;
+ padding:7px 10px;
+ &>img{
+ height: 28px;
+ width: 28px;
+ border-radius: 50%;
+ margin-right: 13px;
+ }
+}
+.addCommentBox{
+ width: 100%;
+ height: 30px;
+ border: none;
+}
+.paginationIssueComment{
+ text-align: center;
+}
+.gotoComment{
+ background-color:rgba(241, 243, 252, 0.55);
+ border-radius:4px;
+ color: rgba(172, 176, 191, 1);
+ padding: 8px 20px;
+ margin-top: -10px;
+ &>a{color: rgba(70, 106, 255, 1);}
+}
\ No newline at end of file
diff --git a/src/forge/Issues/Component/copy.jsx b/src/forge/Issues/Component/copy.jsx
new file mode 100644
index 000000000..c10bf90e8
--- /dev/null
+++ b/src/forge/Issues/Component/copy.jsx
@@ -0,0 +1,22 @@
+import React ,{ useState } from 'react';
+import { Tooltip } from 'antd';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+
+
+function Copy({ children , value }){
+ const [ title , setTitle ] = useState("点击复制链接");
+ let host = window.location.hostname === "localhost" ? "testforgeplus.trustie.net" : window.location.hostname;
+ let protocol = window.location.protocol;
+ return(
+