Prechádzať zdrojové kódy

Merge branch 'dev' of HuangKaiSheng/seecoder-devcloud-frontend into master

HuangKaiSheng 5 rokov pred
rodič
commit
d02b4ebe41

+ 2 - 5
mock/routers/project.js

@@ -6,20 +6,17 @@ const wrapper = require('../wrapper');
 
 const { Random } = Mock;
 
-const groups = [101, 102, 103, 104];
-
 router.get('/listByUser',
   (req, res) => {
     res.json(wrapper(
-      Mock.mock(Mock.mock({
+      Mock.mock({
         'data|5-10': [{
           'id|+1': 100,
           description: Random.csentence(5, 40),
           gitRemoteUrl: Random.url('http', 'gitlab.seecoder.cn'),
-          'groupId|1': groups,
           name: Random.cname(),
         }],
-      }).data),
+      }).data,
     ));
   });
 

+ 126 - 140
mock/routers/tree.js

@@ -59,93 +59,91 @@ router.get('/:projectId', (req, res) => {
   res.json(wrapper(
     Mock.mock([
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
+        deep: 1,
+        description: Random.csentence(10, 40),
+        endTime: Random.datetime(),
+        id: 1,
+        leaf: false,
+        parentId: null,
+        'priority|1': ['紧急', '一般', '宽松'],
+        processorId: 101,
+        processorName: 'Jinyao',
+        projectId: 101,
+        startTime: '2021-5-14 20:39:21',
+        state: '进行中',
+        timeToTake: 10,
+        title: '软件开发云系统',
+        'type|1': ['需求', '任务'],
+      },
+      {
         deep: 2,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 3,
+        id: 2,
         leaf: false,
         parentId: 1,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
-        processorName: 'Jinyao',
+        processorName: 'hkshu12',
         projectId: 101,
         startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        state: '已创建',
         timeToTake: 10,
-        title: Random.cname(),
+        title: '需求管理模块',
         'type|1': ['需求', '任务'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
-        deep: 3,
+        deep: 2,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 9,
+        id: 3,
         leaf: false,
-        parentId: 2,
+        parentId: 1,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
         processorName: 'Jinyao',
         projectId: 101,
-        startTime: Random.datetime(),
+        startTime: '2021-5-14 20:39:21',
         'state|1': ['已创建', '进行中', '已完成'],
         timeToTake: 10,
-        title: Random.cname(),
+        title: '项目管理模块',
+        'type|1': ['需求', '任务'],
+      },
+      {
+        deep: 3,
+        description: '这是需求4。',
+        endTime: '',
+        id: 4,
+        leaf: false,
+        parentId: 3,
+        'priority|1': ['紧急', '一般', '宽松'],
+        processorId: 101,
+        processorName: 'Jinyao',
+        projectId: 101,
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
+        timeToTake: 10,
+        title: '成员管理',
         'type|1': ['需求', '任务'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
         deep: 3,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 10,
+        id: 5,
         leaf: false,
-        parentId: 2,
+        parentId: 3,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
         processorName: 'Jinyao',
         projectId: 101,
-        startTime: Random.datetime(),
+        startTime: '2021-5-14 20:39:21',
         'state|1': ['已创建', '进行中', '已完成'],
         timeToTake: 10,
-        title: Random.cname(),
+        title: '项目管理',
         'type|1': ['需求', '任务'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
         deep: 4,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
@@ -156,159 +154,147 @@ router.get('/:projectId', (req, res) => {
         processorId: 101,
         processorName: 'Jinyao',
         projectId: 101,
-        startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
         timeToTake: 10,
-        title: Random.cname(),
-        'type|1': ['需求', '任务'],
+        title: '创建项目',
+        'type|1': ['任务'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
-        deep: 3,
+        deep: 4,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 4,
+        id: 7,
         leaf: false,
-        parentId: 3,
+        parentId: 5,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
         processorName: 'Jinyao',
         projectId: 101,
-        startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        startTime: '2021-5-14 20:39:21',
+        state: '进行中',
         timeToTake: 10,
-        title: Random.cname(),
-        'type|1': ['需求', '任务'],
+        title: '获取项目列表',
+        'type|1': ['任务'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
-        deep: 1,
+        deep: 4,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 1,
+        id: 8,
         leaf: false,
-        parentId: null,
+        parentId: 5,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
         processorName: 'Jinyao',
         projectId: 101,
         startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        state: '已完成',
         timeToTake: 10,
-        title: Random.cname(),
+        title: '添加关联项目',
+        'type|1': ['任务'],
+      },
+      {
+        deep: 3,
+        description: '提供需求管理能力,包括增删改查功能',
+        endTime: '',
+        id: 9,
+        leaf: false,
+        parentId: 2,
+        'priority|1': ['紧急', '一般', '宽松'],
+        processorId: 101,
+        processorName: 'hkshu12',
+        projectId: 101,
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
+        timeToTake: 10,
+        title: '需求管理',
         'type|1': ['需求', '任务'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
         deep: 3,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 5,
+        id: 11,
         leaf: false,
-        parentId: 3,
+        parentId: 2,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
-        processorName: 'Jinyao',
+        processorName: 'hkshu12',
         projectId: 101,
-        startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
         timeToTake: 10,
-        title: Random.cname(),
-        'type|1': ['需求', '任务'],
+        title: '需求树展示',
+        'type|1': ['需求'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
-        deep: 2,
+        deep: 4,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 2,
+        id: 12,
         leaf: false,
-        parentId: 1,
+        parentId: 9,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
-        processorName: 'Jinyao',
+        processorName: 'hkshu12',
         projectId: 101,
-        startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
         timeToTake: 10,
-        title: Random.cname(),
-        'type|1': ['需求', '任务'],
+        title: '需求增删改查',
+        'type|1': ['任务'],
       },
       {
-        'commits|0-10': [
-          {
-            'gitlabUsername|1': ['hks', 'wph', 'zxr', 'cyt'],
-            'id|+1': 101,
-            message: 'git commit message',
-
-            timestamp: Random.datetime(),
-            title: 'titleeee',
-          },
-        ],
         deep: 4,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 7,
+        id: 13,
         leaf: false,
-        parentId: 5,
+        parentId: 11,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
-        processorName: 'Jinyao',
+        processorName: 'hkshu12',
         projectId: 101,
-        startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
         timeToTake: 10,
-        title: Random.cname(),
-        'type|1': ['需求', '任务'],
+        title: '需求树可视化',
+        'type|1': ['任务'],
       },
       {
         deep: 4,
         description: Random.csentence(10, 40),
         endTime: Random.datetime(),
-        id: 8,
+        id: 14,
         leaf: false,
-        parentId: 5,
+        parentId: 4,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
-        processorName: 'Jinyao',
+        processorName: 'hkshu12',
         projectId: 101,
-        startTime: Random.datetime(),
-        'state|1': ['已创建', '进行中', '已完成'],
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
         timeToTake: 10,
-        title: Random.cname(),
-        'type|1': ['需求', '任务'],
+        title: '邀请成员加入项目',
+        'type|1': ['任务'],
+      },
+      {
+        deep: 4,
+        description: Random.csentence(10, 40),
+        endTime: Random.datetime(),
+        id: 15,
+        leaf: false,
+        parentId: 9,
+        'priority|1': ['紧急', '一般', '宽松'],
+        processorId: 101,
+        processorName: 'hkshu12',
+        projectId: 101,
+        startTime: '2021-5-14 20:39:21',
+        state: '已创建',
+        timeToTake: 10,
+        title: '这是一个新建的子节点',
+        'type|1': ['任务'],
       },
     ]),
   ));

BIN
public/favicon.ico


BIN
public/favicon.png


+ 1 - 1
public/index.html

@@ -4,7 +4,7 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <link rel="icon" href="<%= BASE_URL %>favicon.png">
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
   <body>

+ 6 - 0
src/api/devcloud/project.ts

@@ -25,3 +25,9 @@ export const getProjectMembersByProjectId = (projectId: number) => axios.get(`${
     projectId,
   },
 });
+
+export const generateRelateCode = (projectId: number) => axios.post(`${PROJECT_PREFIX}/relate/code?projectId=${projectId}`);
+
+export const relateProject = (projectId: number, code: string) => axios.post(
+  `${PROJECT_PREFIX}/relate/code?projectId=${projectId}&relateCode=${code}`,
+);

+ 1 - 0
src/assets/icon/link-hover.svg

@@ -0,0 +1 @@
+<svg t="1621414539761" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2375" width="200" height="200"><path d="M612.266667 162.133333c36.266667 0 66.133333 12.8 91.733333 38.4l81.066667 81.066667c25.6 25.6 38.4 55.466667 38.4 91.733333 0 14.933333-4.266667 34.133333-14.933334 53.333334s-21.333333 36.266667-36.266666 49.066666l-17.066667 17.066667c-12.8-19.2-25.6-38.4-44.8-55.466667l-10.666667-10.666666 53.333334-53.333334-140.8-140.8-221.866667 221.866667 121.6 121.6-59.733333 59.733333-10.666667-10.666666-81.066667-81.066667c-25.6-25.6-38.4-55.466667-38.4-89.6 0-36.266667 12.8-66.133333 38.4-89.6l160-160c25.6-29.866667 55.466667-42.666667 91.733334-42.666667z m-100.266667 290.133334l59.733333-59.733334c4.266667 2.133333 6.4 6.4 10.666667 10.666667l81.066667 81.066667c25.6 25.6 38.4 55.466667 38.4 89.6 0 36.266667-12.8 66.133333-38.4 89.6l-160 160c-25.6 25.6-55.466667 38.4-89.6 38.4-36.266667 0-66.133333-12.8-89.6-38.4l-81.066667-81.066667c-25.6-25.6-38.4-55.466667-38.4-91.733333 0-36.266667 12.8-66.133333 38.4-91.733334l32-32c10.666667 21.333333 25.6 40.533333 42.666667 57.6l10.666666 10.666667-53.333333 53.333333 140.8 140.8 221.866667-221.866666-125.866667-115.2z" p-id="2376" fill="#8a8a8a"></path></svg>

+ 1 - 0
src/assets/icon/link-using.svg

@@ -0,0 +1 @@
+<svg t="1621414539761" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2375" width="200" height="200"><path d="M612.266667 162.133333c36.266667 0 66.133333 12.8 91.733333 38.4l81.066667 81.066667c25.6 25.6 38.4 55.466667 38.4 91.733333 0 14.933333-4.266667 34.133333-14.933334 53.333334s-21.333333 36.266667-36.266666 49.066666l-17.066667 17.066667c-12.8-19.2-25.6-38.4-44.8-55.466667l-10.666667-10.666666 53.333334-53.333334-140.8-140.8-221.866667 221.866667 121.6 121.6-59.733333 59.733333-10.666667-10.666666-81.066667-81.066667c-25.6-25.6-38.4-55.466667-38.4-89.6 0-36.266667 12.8-66.133333 38.4-89.6l160-160c25.6-29.866667 55.466667-42.666667 91.733334-42.666667z m-100.266667 290.133334l59.733333-59.733334c4.266667 2.133333 6.4 6.4 10.666667 10.666667l81.066667 81.066667c25.6 25.6 38.4 55.466667 38.4 89.6 0 36.266667-12.8 66.133333-38.4 89.6l-160 160c-25.6 25.6-55.466667 38.4-89.6 38.4-36.266667 0-66.133333-12.8-89.6-38.4l-81.066667-81.066667c-25.6-25.6-38.4-55.466667-38.4-91.733333 0-36.266667 12.8-66.133333 38.4-91.733334l32-32c10.666667 21.333333 25.6 40.533333 42.666667 57.6l10.666666 10.666667-53.333333 53.333333 140.8 140.8 221.866667-221.866666-125.866667-115.2z" p-id="2376" fill="#1296db"></path></svg>

+ 1 - 0
src/assets/icon/link.svg

@@ -0,0 +1 @@
+<svg t="1621414539761" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2375" width="200" height="200"><path d="M612.266667 162.133333c36.266667 0 66.133333 12.8 91.733333 38.4l81.066667 81.066667c25.6 25.6 38.4 55.466667 38.4 91.733333 0 14.933333-4.266667 34.133333-14.933334 53.333334s-21.333333 36.266667-36.266666 49.066666l-17.066667 17.066667c-12.8-19.2-25.6-38.4-44.8-55.466667l-10.666667-10.666666 53.333334-53.333334-140.8-140.8-221.866667 221.866667 121.6 121.6-59.733333 59.733333-10.666667-10.666666-81.066667-81.066667c-25.6-25.6-38.4-55.466667-38.4-89.6 0-36.266667 12.8-66.133333 38.4-89.6l160-160c25.6-29.866667 55.466667-42.666667 91.733334-42.666667z m-100.266667 290.133334l59.733333-59.733334c4.266667 2.133333 6.4 6.4 10.666667 10.666667l81.066667 81.066667c25.6 25.6 38.4 55.466667 38.4 89.6 0 36.266667-12.8 66.133333-38.4 89.6l-160 160c-25.6 25.6-55.466667 38.4-89.6 38.4-36.266667 0-66.133333-12.8-89.6-38.4l-81.066667-81.066667c-25.6-25.6-38.4-55.466667-38.4-91.733333 0-36.266667 12.8-66.133333 38.4-91.733334l32-32c10.666667 21.333333 25.6 40.533333 42.666667 57.6l10.666666 10.666667-53.333333 53.333333 140.8 140.8 221.866667-221.866666-125.866667-115.2z" p-id="2376" fill="#cdcdcd"></path></svg>

+ 1 - 1
src/components/BugEditDrawer.vue

@@ -95,7 +95,7 @@
                       <el-tag>{{processorName}}</el-tag>
                     </div>
                     <div v-else>
-                      <el-select style="width: 160px" size="mini" v-model="editedBugRecord.userId" >
+                      <el-select style="width: 160px" size="mini" v-model="editedBugRecord.processorId" >
                         <el-option
                           v-for="item in  userOptions"
                           :key="item.value"

+ 2 - 1
src/components/Menu.vue

@@ -1,7 +1,7 @@
 <template>
   <ElMenu class="menu" mode="horizontal" @select="handleSelect">
     <ElMenuItem class="menu-logo" index="logo" disabled>
-      LOGO
+      Devcloud
     </ElMenuItem>
     <ElSubmenu index="project">
       <template #title>{{selectedProject.name}}</template>
@@ -211,6 +211,7 @@ export default defineComponent({
   line-height: 58px;
   margin-left: 30px;
   transition: all .2s linear;
+  user-select: none;
 }
 .menu-logo.is-disabled {
   opacity: 1;

+ 29 - 1
src/components/ProjectCard.vue

@@ -4,7 +4,9 @@
       <span>{{name}}</span>
     </template>
     <div>描述:{{description}}</div>
-    <div>链接:{{gitRemoteUrl}}</div>
+    <el-tooltip class="item" effect="dark" :content="gitRemoteUrl + ' 点击复制'" placement="top">
+      <div class="link" @click.stop="AddToClipboard(gitRemoteUrl)">链接:{{gitRemoteUrl}}</div>
+    </el-tooltip>
   </ElCard>
 </template>
 
@@ -19,6 +21,25 @@ export default defineComponent({
     gitRemoteUrl: String,
     groupId: Number,
   },
+  methods: {
+    AddToClipboard(text: string) {
+      const textarea = document.createElement('textarea');
+      textarea.innerHTML = text;
+      textarea.value = text;
+      document.body.appendChild(textarea);
+      textarea.select();
+      try {
+        if (document.execCommand('Copy')) {
+          alert('复制成功!');
+        } else {
+          alert('复制失败!');
+        }
+      } catch (err) {
+        alert('复制失败!');
+      }
+      document.body.removeChild(textarea);
+    },
+  },
 });
 </script>
 
@@ -27,4 +48,11 @@ export default defineComponent({
   width: calc(25% - 24px);
   margin: 12px;
 }
+.link {
+  white-space:nowrap;
+  overflow:hidden;
+  text-overflow:ellipsis;
+  width: 80%;
+  cursor: pointer;
+}
 </style>

+ 12 - 0
src/components/SideBar.vue

@@ -18,6 +18,9 @@
     <el-menu-item index="apitest">
       <i class="icon" target="apitest"></i>
     </el-menu-item>
+    <el-menu-item index="invite">
+      <i class="icon" target="invite"></i>
+    </el-menu-item>
   </el-menu>
 </template>
 
@@ -101,4 +104,13 @@ export default {
     background-image: url("../assets/icon/test-using.svg");
   }
 }
+.icon[target=invite]{
+   background-image: url("../assets/icon/link.svg");
+  .el-menu-item:hover & {
+    background-image: url("../assets/icon/link-hover.svg");
+  }
+  .el-menu-item.is-active & {
+    background-image: url("../assets/icon/link-using.svg");
+  }
+}
 </style>

+ 4 - 10
src/components/Tree.vue

@@ -76,15 +76,6 @@ export default {
           console.warn('需求树数据有误,树高度错误');
         }
         if (!item.parentId) rootNode = item;
-        // if (item.parent) {
-        //   const tempParent = nodeMap.get(item.parent);
-        //   if (!tempParent.childs) {
-        //     tempParent.childs = [];
-        //   }
-        //   tempParent.childs.push(item);
-        // } else {
-        //   rootNode = item;
-        // }
       });
       // 计算一个节点包含他的后代所需要的空间
       const calculateNodeContentHeight = (node) => {
@@ -92,7 +83,10 @@ export default {
           node.contentHeight = 1;
           return 1;
         }
-        node.contentHeight = node.childs.reduce((total, current) => total + calculateNodeContentHeight(current), 0);
+        node.contentHeight = node.childs.reduce(
+          (total, current) => total + calculateNodeContentHeight(current),
+          0,
+        );
         return node.contentHeight;
       };
       calculateNodeContentHeight(rootNode);

+ 2 - 2
src/components/TreeNode.vue

@@ -54,7 +54,7 @@ export default {
   computed: {
     containerStyle() {
       return {
-        top: `${this.positionY * 140}px`,
+        top: `${this.positionY * 180}px`,
         left: `${280 * this.positionX - 100}px`,
         width: `${this.nodeStyle.width + this.nodeStyle.padding * 2}px`,
         height: `${this.nodeStyle.height + this.nodeStyle.padding * 2}px`,
@@ -101,7 +101,7 @@ export default {
   user-select: none;
   & .node-postfix {
     position: absolute;
-    bottom: 0px;
+    bottom: 10px;
     right: 25px;
     width: 100%;
     display: none;

+ 1 - 1
src/composable/useProject.ts

@@ -13,7 +13,7 @@ export const useProjectList = () => {
   const handleProjectChange = (projectId: number) => {
     WorkspaceDispatcher.changeProject(projectId);
     if (router.currentRoute.value.path.includes('schedule')) {
-      router.go(0);
+      setTimeout(() => router.go(0), 200);
     } else {
       router.push(`/project/${projectId}/schedule`);
     }

+ 4 - 0
src/router/index.ts

@@ -58,6 +58,10 @@ const routes: Array<RouteRecordRaw> = [
         path: 'apitest',
         component: () => import(/* webpackChunkName: "project" */'@/views/Project/APITest.vue'),
       },
+      {
+        path: 'invite',
+        component: () => import(/* webpackChunkName: "project" */'@/views/Project/Invite.vue'),
+      },
     ],
   }),
 ];

+ 5 - 4
src/views/Project/APITest.vue

@@ -178,6 +178,7 @@
         <template #title>创建测试任务</template>
         <el-form
           :model="createAPITestForm"
+          :rules="rules"
           ref="createAPITestForm"
           label-position="right"
           label-width="100px"
@@ -214,8 +215,8 @@
               </ElOption>
             </ElSelect>
           </el-form-item>
-          <el-form-item label="并发数" prop="qps" required>
-            <el-input v-model="createAPITestForm.qps" placeholder="请输入并发数"></el-input>
+          <el-form-item label="并发数" prop="qps" required="">
+            <el-input v-model="createAPITestForm.qps" placeholder="请输入并发数(10的倍数)"></el-input>
           </el-form-item>
         </el-form>
         <template #footer>
@@ -646,7 +647,7 @@
           width="60">
         </ElTableColumn>
         <ElTableColumn
-          prop="test_id"
+          prop="testId"
           label="所属测试ID"
           align="center"
           width="120">
@@ -835,7 +836,7 @@ export default {
           await APITestAPI.createAPITest({
             projectId: this.$store.state.workspace.currentProjectId,
             createTime: this.createAPITestForm.createTime,
-            username: '',
+            username: this.$store.state.user.username,
             testName: this.createAPITestForm.testName,
             url: this.createAPITestForm.url,
             params: this.createAPITestForm.params,

+ 80 - 0
src/views/Project/Invite.vue

@@ -0,0 +1,80 @@
+<template>
+<div class="container">
+  <el-row class="row-item title">
+    <h4>生成邀请码</h4>
+  </el-row>
+  <div class="row-item">
+    <span>邀请码:</span>
+    <span>{{relateCode}}</span>
+  </div>
+  <div class="row-item">
+    <span>
+      <el-button
+        @click="generateRelateCode()"
+        type="primary"
+        size="small"
+      >
+        生成
+      </el-button>
+    </span>
+  </div>
+  <el-divider content-position="left">或</el-divider>
+  <el-row class="row-item title">
+    <h4>关联其他项目</h4>
+  </el-row>
+  <el-row style="row-item">
+    <el-input v-model="targetCode" placeholder="请输入要关联的项目生成的邀请码" style="width: 270px"></el-input>
+    <el-button
+      @click="relate()"
+      type="primary"
+      size="small"
+      style="margin-left: 16px"
+    >
+      关联
+    </el-button>
+  </el-row>
+</div>
+</template>
+
+<script>
+import { ProjectAPI } from '@/api';
+import { ElMessage } from 'element-plus';
+
+export default {
+  data() {
+    return {
+      relateCode: '',
+      targetCode: '',
+    };
+  },
+  methods: {
+    async generateRelateCode() {
+      this.relateCode = await ProjectAPI.generateRelateCode(this.$store.state.workspace.currentProjectId);
+      ElMessage.success('邀请码将在5分钟后失效,请及时完成操作!');
+    },
+    async relate() {
+      await ProjectAPI.relateProject(this.$store.state.workspace.currentProjectId, this.targetCode);
+      ElMessage.success('关联成功');
+      this.targetCode = '';
+    },
+  },
+};
+</script>
+
+<style scoped>
+.container {
+  margin: 50px 100px;
+  width: 30%
+}
+
+.row-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.title {
+  color: #333333
+}
+</style>

+ 69 - 16
src/views/Project/Pipeline.vue

@@ -30,20 +30,25 @@
           label="执行状态"
           align="center"
           width="110"
-          :filters="[{ text: '成功', value: '成功' }, { text: '失败', value: '失败' }]"
+          :filters="[
+              { text: '成功', value: '部署成功' },
+              { text: '失败', value: '部署失败' },
+              { text: '部署中', value: '部署中' },
+              { text: '无记录', value: '无执行记录' }
+          ]"
           :filter-method="(value, row) => {
             return row.result === value;
           }"
           filter-placement="bottom-end"
         >
           <template #default="scope">
-            <template v-if="scope.row.result === '成功'">
+            <template v-if="scope.row.result === '部署成功'">
               <ElTag type="success">{{scope.row.result}}</ElTag>
             </template>
-            <template v-else-if="scope.row.result === '失败'">
+            <template v-else-if="scope.row.result === '部署失败'">
               <ElTag type="danger">{{scope.row.result}}</ElTag>
             </template>
-            <template v-else-if="scope.row.result === '执行中'">
+            <template v-else-if="scope.row.result === '部署中'">
               <ElTag>{{scope.row.result}}<i class="el-icon-loading"></i></ElTag>
             </template>
             <template v-else-if="scope.row.result === '无执行记录'">
@@ -86,10 +91,11 @@
         <template #title>历史执行结果详情</template>
         <pre class="log" v-loading="dialogLoading" >{{detail}}</pre>
       </ElDialog>
-      <ElDialog v-model="showPipelineHistoryModal" destroy-on-close>
+      <ElDialog v-model="showPipelineHistoryModal" width="60%" destroy-on-close>
         <template #title>流水线构建历史</template>
         <ElTable
           :data="pipelineHistory"
+          :default-sort="{prop: 'id', order: 'descending'}"
           v-loading="dialogLoading"
           height="400"
         >
@@ -121,20 +127,25 @@
             label="执行状态"
             align="center"
             width="100"
-            :filters="[{ text: '成功', value: '成功' }, { text: '失败', value: '失败' }]"
+            :filters="[
+              { text: '成功', value: '部署成功' },
+              { text: '失败', value: '部署失败' },
+              { text: '部署中', value: '部署中' },
+              { text: '无记录', value: '无执行记录' }
+            ]"
             :filter-method="(value, row) => {
               return row.result === value;
             }"
             filter-placement="bottom-end"
           >
             <template #default="scope">
-              <template v-if="scope.row.result === '成功'">
+              <template v-if="scope.row.result === '部署成功'">
                 <ElTag type="success">{{scope.row.result}}</ElTag>
               </template>
-              <template v-else-if="scope.row.result === '失败'">
+              <template v-else-if="scope.row.result === '部署失败'">
                 <ElTag type="danger">{{scope.row.result}}</ElTag>
               </template>
-              <template v-else-if="scope.row.result === '执行中'">
+              <template v-else-if="scope.row.result === '部署中'">
                 <ElTag>{{scope.row.result}}<i class="el-icon-loading"></i></ElTag>
               </template>
               <template v-else-if="scope.row.result === '无执行记录'">
@@ -154,13 +165,16 @@
       </ElDialog>
       <ElDialog
         v-model="showPipelineEditModal"
-        width="80%"
         destroy-on-close
       >
         <template #title>
           <span>编辑流水线配置</span>
         </template>
-        <ElSteps :active="activeStep" v-loading="dialogLoading">
+          <ElSteps
+            :active="activeStep"
+            v-loading="dialogLoading"
+            style="width: 80%;margin: 0 auto;"
+            align-center>
           <ElStep
             v-for="(config, index) in pipelineConfig"
             :key="index"
@@ -179,7 +193,7 @@
             </ElTooltip>
           </template>
           </ElStep>
-        </ElSteps>
+          </ElSteps>
         <div class="edit-pipeline-form" v-for="(config, index) in pipelineConfig" :key="index" v-show="activeStep === index">
           <ElForm label-width="160px">
             <div style="margin: 20px 0 20px 160px;">
@@ -188,12 +202,22 @@
                 v-model="config.active"
               >启用该阶段</ElCheckbox>
             </div>
-            <ElFormItem v-for="(val, key) in config.configs" :key="key" :label="key">
-              <ElInput
+            <template v-for="(val, key) in config.configs" :key="key" placeholder="请选择">
+            <ElFormItem :label="key">
+              <ElSelect v-if="key === 'apiTestIds'" v-model="config.configs[key]" multiple >
+                <el-option
+                  v-for="item in apiTestOptions"
+                  :key="item.testId"
+                  :label="item.testName"
+                  :value="item.testId">
+                </el-option>
+              </ElSelect>
+              <ElInput v-else
                 :disabled="!getStage(config.name).configs.find((config) => config.name === key).editable"
                 v-model="config.configs[key]"
               ></ElInput>
             </ElFormItem>
+            </template>
           </ElForm>
         </div>
         <template #footer>
@@ -322,7 +346,7 @@
 </template>
 
 <script>
-import { PipelineAPI } from '@/api';
+import { APITestAPI, PipelineAPI } from '@/api';
 
 import { formatter } from '@/utils/time';
 
@@ -354,6 +378,8 @@ export default {
       showProductionLogModal: false,
       currentLogId: -1,
       currentLog: '',
+      apiTestOptions: [],
+      pipelineConfigTemplate: '',
     };
   },
   computed: {
@@ -380,6 +406,7 @@ export default {
     this.productions = await PipelineAPI.getDeploymentsByProjectId(this.$store.state.workspace.currentProjectId);
     this.templates = await PipelineAPI.getPipelineTemplates();
     this.stages = await PipelineAPI.getStages();
+    this.apiTestOptions = await APITestAPI.getAPITestInfosByProjectId(this.$store.state.workspace.currentProjectId);
     this.loading = false;
   },
   methods: {
@@ -434,6 +461,17 @@ export default {
             pipelineId: id,
           })).configJson;
           this.pipelineConfig = JSON.parse(config || '{}');
+          this.pipelineConfigTemplate = JSON.parse(config || '{}');
+          this.pipelineConfig.forEach((item) => {
+            const targetStage = this.stages.find((stage) => stage.name === item.name);
+            if (targetStage) {
+              targetStage.configs.forEach((configTemplate) => {
+                if (!item.configs[configTemplate.name]) {
+                  item.configs[configTemplate.name] = this.parseConfigValue(configTemplate.value);
+                }
+              });
+            }
+          });
           this.dialogLoading = false;
           break;
         }
@@ -453,10 +491,17 @@ export default {
     },
     async handlePipelineUpdateConfirm() {
       this.confirmLoading = true;
+      this.pipelineConfigTemplate.forEach((item) => {
+        const target = this.pipelineConfig.find((i) => i.name === item.name);
+        item.active = target.active;
+        Object.keys(item.configs).forEach((key) => {
+          item.configs[key] = target.configs[key];
+        });
+      });
       await PipelineAPI.updatePipeline({
         projectId: this.$store.state.workspace.currentProjectId,
         pipelineId: this.editingPipelineId,
-        configJson: JSON.stringify(this.pipelineConfig),
+        configJson: JSON.stringify(this.pipelineConfigTemplate),
       });
       this.confirmLoading = false;
       this.showPipelineEditModal = false;
@@ -513,6 +558,14 @@ export default {
       });
       this.dialogLoading = false;
     },
+    parseConfigValue(str) {
+      const project = this.$store.state.workspace.projectList.find(
+        (item) => item.id === this.$store.state.workspace.currentProjectId,
+      );
+      const pipeline = this.pipelines.find((item) => item.id === this.editingPipelineId);
+      return str.replaceAll('{projectName}', project.name).replaceAll('{projectId}', project.id)
+        .replaceAll('{pipelineName}', pipeline.name);
+    },
   },
 };
 </script>

+ 57 - 14
src/views/Project/Schedule.vue

@@ -7,7 +7,9 @@
       <el-col :span="12">
         <div class="description-card-text" style="font-size: 18px;font-weight: bold">{{selectedProject.name}}</div>
         <div class="description-card-text">项目地址:
-          <span><a target="_blank" :href="selectedProject.gitRemoteUrl">{{selectedProject.gitRemoteUrl}}</a></span>
+          <span>
+            <el-link target="_blank" type="primary" :href="selectedProject.gitRemoteUrl">{{selectedProject.gitRemoteUrl}}</el-link>
+          </span>
         </div>
         <div class="description-card-text">项目介绍:
           <span>{{selectedProject.description}}</span>
@@ -46,7 +48,7 @@
 <script>
 import Tree from '@/components/Tree.vue';
 import * as echarts from 'echarts';
-import { RequirementAPI } from '@/api';
+import { BugListAPI, RequirementAPI } from '@/api';
 import RequirementDrawer from '@/components/RequirementDrawer.vue';
 import { useProjectList } from '../../composable/useProject';
 
@@ -124,9 +126,51 @@ export default {
     handleDrawerContentChange(id) {
       this.requirementInDrawer = Array.prototype.find.call(this.requirements, (requirement) => requirement.id === id);
     },
-    drawChart() {
+    async drawChart() {
+      const bugList = await BugListAPI.getBugListByProjectId(this.$store.state.workspace.currentProjectId);
+      const requirementsValuesMap = new Map();
+      const requirementsValues = [];
+      const bugListValuesMap = new Map();
+      const bugListValues = [];
+      this.requirements.forEach((item) => {
+        if (item.type === '任务') {
+          if (!requirementsValuesMap.get(item.state)) requirementsValuesMap.set(item.state, 0);
+          requirementsValuesMap.set(item.state, requirementsValuesMap.get(item.state) + 1);
+        }
+      });
+      requirementsValuesMap.forEach((value, key) => {
+        requirementsValues.push(
+          { value, name: key },
+        );
+      });
+      bugList.forEach((item) => {
+        if (!bugListValuesMap.get(item.state)) bugListValuesMap.set(item.state, 0);
+        bugListValuesMap.set(item.state, bugListValuesMap.get(item.state) + 1);
+      });
+      bugListValuesMap.forEach((value, key) => {
+        bugListValues.push(
+          { value, name: key },
+        );
+      });
+      if (bugListValues.length === 0) {
+        bugListValues.push({
+          name: '无记录', value: 0,
+        });
+      }
+      if (requirementsValues.length === 0) {
+        requirementsValues.push({
+          name: '无记录', value: 0,
+        });
+      }
       const requirementPie = echarts.init(document.getElementById('requirement_pie'));
       requirementPie.setOption({
+        title: {
+          text: '任务完成情况',
+          left: 'center',
+          textStyle: {
+            fontSize: 12,
+          },
+        },
         tooltip: {
           trigger: 'item',
         },
@@ -150,22 +194,25 @@ export default {
             labelLine: {
               show: false,
             },
-            data: [
-              { value: 1048, name: '已完成' },
-              { value: 735, name: '进行中' },
-              { value: 580, name: '已结束' },
-            ],
+            data: requirementsValues,
           },
         ],
       });
       const bugPie = echarts.init(document.getElementById('bug_pie'));
       bugPie.setOption({
+        title: {
+          text: 'Bug处理情况',
+          left: 'center',
+          textStyle: {
+            fontSize: 12,
+          },
+        },
         tooltip: {
           trigger: 'item',
         },
         series: [
           {
-            name: '任务情况',
+            name: 'Bug情况',
             type: 'pie',
             radius: ['40%', '70%'],
             avoidLabelOverlap: false,
@@ -183,11 +230,7 @@ export default {
             labelLine: {
               show: false,
             },
-            data: [
-              { value: 1048, name: '已完成' },
-              { value: 735, name: '进行中' },
-              { value: 580, name: '已结束' },
-            ],
+            data: bugListValues,
           },
         ],
       });