Pārlūkot izejas kodu

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

HuangKaiSheng 5 gadi atpakaļ
vecāks
revīzija
d9fd6dda75

+ 1 - 1
.env

@@ -1,2 +1,2 @@
-VUE_APP_PORTAL_HOST='p.seecoder.cn'
+VUE_APP_PORTAL_HOST='p.seec.seecoder.cn'
 VUE_APP_API_URL = '/api'

+ 6 - 1
src/App.vue

@@ -19,7 +19,12 @@ export default defineComponent({
   components: { Menu },
   setup() {
     const store = useStore();
-    console.log(store.state);
+
+    // 调试输出vuex信息
+    if (document.location.host !== 'devcloud.seec.seecoder.cn') {
+      console.log(store.state);
+    }
+
     const token = getVariableFromQueryString('id_token');
     if (token) {
       TokenDispatcher.updateToken(token);

+ 1 - 0
src/api/devcloud/prefix.ts

@@ -10,3 +10,4 @@ export const STAGE_PREFIX = '/stages';
 export const COMMIT_PREFIX = '/commits';
 export const FUNCTEST_PREFIX = '/func_test';
 export const AUTOTEST_PREFIX = '/auto_test';
+export const SQL_PREFIX = '/sql';

+ 2 - 2
src/api/devcloud/project.ts

@@ -3,7 +3,7 @@ import axios from '@/utils/axios';
 import { PROJECT_PREFIX } from './prefix';
 
 interface AddProjectMemberPayload {
-  userId: number;
+  phone: string;
   projectId: number;
 }
 
@@ -29,5 +29,5 @@ export const getProjectMembersByProjectId = (projectId: number) => axios.get(`${
 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}`,
+  `${PROJECT_PREFIX}/relate?projectId=${projectId}&relateCode=${code}`,
 );

+ 12 - 0
src/api/devcloud/sql.ts

@@ -0,0 +1,12 @@
+import axios from '@/utils/axios';
+
+import { SQL_PREFIX } from './prefix';
+
+type SQLExecPayload = {
+  db: string;
+  deploymentId: number;
+  password: string;
+  sql: string;
+  username: string;
+}
+export const SQLExec = (SqlExecPayload: SQLExecPayload) => axios.post(`${SQL_PREFIX}/exec`, SqlExecPayload);

+ 2 - 0
src/api/index.ts

@@ -6,6 +6,7 @@ import * as PipelineAPI from './devcloud/pipeline';
 import * as APITestAPI from './devcloud/apitest';
 import * as FuncTestAPI from './devcloud/functest';
 import * as AutoTestAPI from './devcloud/autotest';
+import * as SQLExecAPI from './devcloud/sql';
 import * as PortalAPI from './portal';
 
 export {
@@ -18,4 +19,5 @@ export {
   APITestAPI,
   FuncTestAPI,
   AutoTestAPI,
+  SQLExecAPI,
 };

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
src/assets/icon/database-hover.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
src/assets/icon/database-using.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
src/assets/icon/database.svg


+ 11 - 10
src/components/BugEditDrawer.vue

@@ -11,7 +11,12 @@
         <el-col :span="2">
           <div class="node-type">Bug</div>
         </el-col>
-        <el-col :span="12" :offset="2">
+        <el-col :span="4">
+          <template class="entry-row">
+            <span>#{{id}}</span>
+          </template>
+        </el-col>
+        <el-col :span="8">
           <template class="entry-row" v-if="isEditing">
             <el-input placeholder="请输入标题" size="mini" style="width: 200px;display: inline;" v-model="editedBugRecord.title"/>
           </template>
@@ -130,20 +135,15 @@
                 <el-table :data="commits">
                   <el-table-column type="expand">
                     <template #default="scope">
-                      <RowItem :label="'message:'" :label-span="5" :value-span="19">{{scope.row.message}}</RowItem>
-                      <RowItem :label="'timestamp:'" :label-span="5" :value-span="19">{{scope.row.timestamp}}</RowItem>
+                      <RowItem :label="'ID:'" :label-span="5" :value-span="19">{{scope.row.id}}</RowItem>
+                      <RowItem :label="'信息:'" :label-span="5" :value-span="19">{{scope.row.message}}</RowItem>
+                      <RowItem :label="'时间:'" :label-span="5" :value-span="19">{{formatter(scope.row.timestamp)}}</RowItem>
                     </template>
                   </el-table-column>
                   <el-table-column
-                    prop="id"
                     label="ID"
                     align="center"
-                  >
-                  </el-table-column>
-                  <el-table-column
-                    prop="title"
-                    label="标题"
-                    align="center"
+                    :formatter="(row)=>row.id.substr(0,7)"
                   >
                   </el-table-column>
                   <el-table-column
@@ -231,6 +231,7 @@ export default {
       BugListAPI.updateBugRecord(param).then((res) => {
         this.isEditing = false;
         this.$emit('bug-record-update');
+        this.showDrawer = false;
       });
       this.confirmLoading = false;
     },

+ 8 - 18
src/components/Menu.vue

@@ -1,6 +1,6 @@
 <template>
   <ElMenu class="menu" mode="horizontal" @select="handleSelect">
-    <ElMenuItem class="menu-logo" index="logo" disabled>
+    <ElMenuItem class="menu-logo" index="logo">
       Devcloud
     </ElMenuItem>
     <ElSubmenu index="project">
@@ -75,9 +75,10 @@ import { ElMessage } from 'element-plus';
 import { useProjectList } from '@/composable/useProject';
 import { useUser } from '@/composable/useUser';
 import { defineComponent, ref, reactive } from 'vue';
-import { PortalAPI, ProjectAPI } from '@/api';
+import { ProjectAPI } from '@/api';
 
 import { WorkspaceDispatcher } from '@/store/dispatchers';
+import { useRouter } from 'vue-router';
 
 export default defineComponent({
   name: 'Menu',
@@ -85,6 +86,8 @@ export default defineComponent({
     const { projectList, selectedProject, handleProjectChange } = useProjectList();
     const { userInfo, userLoggedIn, userLogOut } = useUser();
 
+    const router = useRouter();
+
     const showProjectDrawer = ref(false);
     const showInviteDrawer = ref(false);
 
@@ -114,20 +117,6 @@ export default defineComponent({
       phone: '',
     });
     const inviteMember = async () => {
-      let invitedUserId: number;
-
-      const res = await PortalAPI.getUserIdByPhoneNumber(inviteProjectForm.phone) as unknown as {id: number};
-
-      if (res) {
-        invitedUserId = res.id;
-      } else {
-        ElMessage({
-          type: 'error',
-          message: '成员不存在,请再次确认手机号',
-        });
-        return;
-      }
-
       if (!inviteProjectForm.projectId) {
         ElMessage({
           type: 'error',
@@ -138,7 +127,7 @@ export default defineComponent({
 
       await ProjectAPI.addProjectMember({
         projectId: inviteProjectForm.projectId,
-        userId: invitedUserId,
+        phone: inviteProjectForm.phone,
       });
       showInviteDrawer.value = false;
       if (selectedProject.value.id !== -1) { WorkspaceDispatcher.changeProject(selectedProject.value.id); }
@@ -148,6 +137,7 @@ export default defineComponent({
       const [type, id] = index.split('-');
       switch (type) {
         case 'logo': {
+          router.push('/home');
           break;
         }
         case 'project': {
@@ -171,7 +161,7 @@ export default defineComponent({
     };
 
     const login = () => {
-      window.location = `http://${process.env.VUE_APP_PORTAL_HOST}/api/interaction?scope=openid&client_id=seec-seec&redirect_uri=${
+      window.location = `http://${process.env.VUE_APP_PORTAL_HOST}/interaction?scope=openid&client_id=seec-seec&redirect_uri=${
         encodeURI(`http://${window.location.host}/`)
       }` as unknown as Location;
     };

+ 6 - 20
src/components/ProjectCard.vue

@@ -4,15 +4,17 @@
       <span>{{name}}</span>
     </template>
     <div>描述:{{description}}</div>
-    <el-tooltip class="item" effect="dark" :content="gitRemoteUrl + ' 点击复制'" placement="top">
-      <div class="link" @click.stop="AddToClipboard(gitRemoteUrl)">链接:{{gitRemoteUrl}}</div>
-    </el-tooltip>
+    <ElTooltip class="item" effect="dark" :content="gitRemoteUrl" placement="top">
+      <div class="link">链接:<ElLink class="url" @click.stop.prevent="copy(gitRemoteUrl)">{{gitRemoteUrl}}</ElLink></div>
+    </ElTooltip>
   </ElCard>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
 
+import { copy } from '@/utils/clipboard';
+
 export default defineComponent({
   props: {
     id: Number,
@@ -22,23 +24,7 @@ export default defineComponent({
     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);
-    },
+    copy,
   },
 });
 </script>

+ 4 - 9
src/components/RequirementDrawer.vue

@@ -152,20 +152,15 @@
                 <el-table :data="commits">
                   <el-table-column type="expand">
                     <template #default="scope">
-                      <RowItem :label="'message:'" :label-span="5" :value-span="19">{{scope.row.message}}</RowItem>
-                      <RowItem :label="'timestamp:'" :label-span="5" :value-span="19">{{scope.row.timestamp}}</RowItem>
+                      <RowItem :label="'ID:'" :label-span="5" :value-span="19">{{scope.row.id}}</RowItem>
+                      <RowItem :label="'信息:'" :label-span="5" :value-span="19">{{scope.row.message}}</RowItem>
+                      <RowItem :label="'时间:'" :label-span="5" :value-span="19">{{formatter(scope.row.timestamp)}}</RowItem>
                     </template>
                   </el-table-column>
                   <el-table-column
-                    prop="id"
                     label="ID"
                     align="center"
-                  >
-                  </el-table-column>
-                  <el-table-column
-                    prop="title"
-                    label="标题"
-                    align="center"
+                    :formatter="(row)=>row.id.substr(0,7)"
                   >
                   </el-table-column>
                   <el-table-column

+ 36 - 21
src/components/SideBar.vue

@@ -1,32 +1,38 @@
 <template>
-  <el-menu default-active="schedule" class="side-bar" :collapse="true" :router="true">
-    <el-menu-item index="schedule">
-      <i class="icon" target="schedule"></i>
-    </el-menu-item>
-    <el-menu-item index="taskList">
-      <i class="icon" target="taskList"></i>
-    </el-menu-item>
-    <el-menu-item index="bugList">
-      <i class="icon" target="bugList"></i>
-    </el-menu-item>
-    <!-- <el-menu-item index="statistics">
-      <i class="icon" target="statistics"></i>
-    </el-menu-item> -->
-    <el-menu-item index="pipeline">
-      <i class="icon" target="pipeline"></i>
-    </el-menu-item>
-    <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 :default-active="active" class="side-bar" :collapse="true" :router="true">
+    <el-menu-item v-for="tab in sidebar" :index="tab.name" :key="tab.name">
+      <el-tooltip placement="right" :content="tab.description" v-show="!tab.hide">
+        <i class="icon" :target="tab.name"></i>
+      </el-tooltip>
     </el-menu-item>
   </el-menu>
 </template>
 
 <script>
+const sidebar = [
+  { name: 'schedule', description: '项目规划', hide: false },
+  { name: 'taskList', description: '任务列表', hide: false },
+  { name: 'bugList', description: '缺陷列表', hide: false },
+  { name: 'pipeline', description: '流水线与产物', hide: false },
+  { name: 'database', description: '数据库', hide: false },
+  { name: 'apitest', description: '测试', hide: false },
+  { name: 'statistics', description: '详细数据', hide: true },
+  { name: 'invite', description: '项目关联', hide: false },
+];
+
 export default {
   name: 'SideBar',
+  data() {
+    return {
+      sidebar,
+    };
+  },
+  computed: {
+    active() {
+      const li = this.$route.path.split('/');
+      return li[li.length - 1];
+    },
+  },
 };
 </script>
 
@@ -95,6 +101,15 @@ export default {
     background-image: url("../assets/icon/training-using.svg");
   }
 }
+.icon[target=database]{
+  background-image: url("../assets/icon/database.svg");
+  .el-menu-item:hover & {
+    background-image: url("../assets/icon/database-hover.svg");
+  }
+  .el-menu-item.is-active & {
+    background-image: url("../assets/icon/database-using.svg");
+  }
+}
 .icon[target=apitest]{
    background-image: url("../assets/icon/test.svg");
   .el-menu-item:hover & {

+ 1 - 5
src/composable/useProject.ts

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

+ 2 - 0
src/element-plus.ts

@@ -11,6 +11,7 @@ import {
   ElDialog,
   ElDivider,
   ElDrawer,
+  ElEmpty,
   ElForm,
   ElFormItem,
   ElHeader,
@@ -60,6 +61,7 @@ export const Components = [
   ElDialog,
   ElDivider,
   ElDrawer,
+  ElEmpty,
   ElForm,
   ElFormItem,
   ElHeader,

+ 6 - 8
src/router/index.ts

@@ -54,6 +54,10 @@ const routes: Array<RouteRecordRaw> = [
         path: 'pipeline',
         component: () => import(/* webpackChunkName: "project" */'@/views/Project/Pipeline.vue'),
       },
+      {
+        path: 'database',
+        component: () => import(/* webpackChunkName: "project" */'@/views/Project/Database.vue'),
+      },
       {
         path: 'apitest',
         component: () => import(/* webpackChunkName: "project" */'@/views/Project/APITest.vue'),
@@ -74,10 +78,10 @@ const router = createRouter({
 router.beforeEach((to, from, next) => {
   if (to.matched.some((record) => record.meta.requiresAuth)) {
     // this route requires auth, check if logged in
-    // if not, redirect to login page.
+    // if not, redirect to home page.
     if (!isUserLoggedIn()) {
       next({
-        name: LOGIN,
+        name: HOME,
         query: { redirect: to.fullPath },
       });
     } else {
@@ -88,12 +92,6 @@ router.beforeEach((to, from, next) => {
   }
 });
 
-router.beforeEach((to, from, next) => {
-  if (to.name !== LOGIN || !isUserLoggedIn()) {
-    next();
-  }
-});
-
 router.afterEach((to, from) => {
   if (to.matched.some((record) => record.meta.hideNavigator)) {
     document.getElementsByTagName('header')[0]!.style.display = 'none';

+ 8 - 0
src/utils/axios.ts

@@ -40,6 +40,14 @@ _axios.interceptors.response.use(
       window.location.href = '/';
       return;
     }
+    if (response.data.code >= 500) {
+      ElMessage({
+        type: 'error',
+        showClose: true,
+        message: '接口出错,错误信息:' + response.data.msg,
+      })
+      return;
+    }
     return response.data.data;
   },
   (error) => {

+ 35 - 0
src/utils/clipboard.ts

@@ -0,0 +1,35 @@
+/* eslint-disable @typescript-eslint/ban-ts-ignore */
+/**
+ * 请注意,此处引用了外部的this,因此在Vue实例中导入并使用该方法,否则将出现错误
+ */
+export function copy(content: string) {
+  const textarea = document.createElement('textarea');
+  textarea.innerHTML = content;
+  textarea.value = content;
+  document.body.appendChild(textarea);
+  textarea.select();
+  try {
+    if (document.execCommand('copy')) {
+      // @ts-ignore
+      this.$message({
+        type: 'success',
+        message: '成功复制到剪贴板',
+      });
+    } else {
+      // @ts-ignore
+      this.$message({
+        type: 'error',
+        message: '复制到剪贴板失败',
+      });
+    }
+  } catch (err) {
+    // @ts-ignore
+    this.$message({
+      type: 'error',
+      message: '复制到剪贴板失败',
+    });
+    console.error(err);
+  } finally {
+    document.body.removeChild(textarea);
+  }
+}

+ 2 - 2
src/utils/constants.ts

@@ -1,2 +1,2 @@
-export const stateOptions = [{ value: '已完成' }, { value: '进行中' }, { value: '已创建' }, { value: '已结束' }];
-export const priorityOptions = [{ value: 0, label: '紧急' }, { value: 1, label: '宽松' }, { value: 2, label: '一般' }];
+export const stateOptions = [{ value: '进行中' }, { value: '已创建' }, { value: '已结束' }];
+export const priorityOptions = [{ value: '紧急', label: '紧急' }, { value: '宽松', label: '宽松' }, { value: '一般', label: '一般' }];

+ 1 - 1
src/views/Login.vue

@@ -11,7 +11,7 @@ import { defineComponent } from 'vue';
 export default defineComponent({
   methods: {
     async login() {
-      window.location = `http://${process.env.VUE_APP_PORTAL_HOST}/api/interaction?scope=openid&client_id=seec-seec&redirect_uri=${
+      window.location = `http://${process.env.VUE_APP_PORTAL_HOST}/interaction?scope=openid&client_id=seec-seec&redirect_uri=${
         encodeURI(`http://${window.location.host}/`)
       }` as unknown as Location;
     },

+ 176 - 0
src/views/Project/Database.vue

@@ -0,0 +1,176 @@
+<template>
+  <ElForm inline ref="sql-form" :model="form" label-width="80px">
+    <ElFormItem label="实例">
+      <ElSelect class="input" v-model="form.deploymentId" placeholder="请选择数据库实例">
+        <ElOption v-for="db in dbs" :key="db.id" :label="db.name" :value="db.id"></ElOption>
+      </ElSelect>
+    </ElFormItem>
+    <ElFormItem label="用户名">
+      <ElInput class="input" v-model="form.username" placeholder="请输入用户名"></ElInput>
+    </ElFormItem>
+    <ElFormItem label="密码">
+      <ElInput class="input" v-model="form.password" type="password" placeholder="请输入密码"></ElInput>
+    </ElFormItem>
+    <ElFormItem label="数据库">
+      <ElInput class="input" v-model="form.db" placeholder="请输入数据库名"></ElInput>
+    </ElFormItem>
+    <ElFormItem label="SQL">
+      <ElInput class="sql" v-model="form.sql" :rows="5" type="textarea" clearable></ElInput>
+    </ElFormItem>
+    <ElFormItem>
+      <ElButton type="primary" @click="exec">执行</ElButton>
+    </ElFormItem>
+  </ElForm>
+  <ElDivider content-position="left">当前执行记录</ElDivider>
+  <ElRow type="flex" justify="space-between" v-if="execHistory.size > 0">
+    <ElCol :span="4" height="600px">
+      <div
+        :class="['list-item', active === key?'active':'']"
+        v-for="key in [...execHistory.keys()].reverse()"
+        :key="key"
+        @click="switchRecord(key)"
+      >{{key}}</div>
+    </ElCol>
+    <ElDivider direction="vertical"></ElDivider>
+    <ElCol :span="18" v-show="active > 0">
+      <ElDivider content-position="left">SQL</ElDivider>
+      <pre>{{activeRecord.sql}}</pre>
+      <ElDivider content-position="left">结果</ElDivider>
+      <template v-if="showTable">
+        <pre>{{b}}</pre>
+        <table>
+          <tr v-for="(row, i) in content" :key="i">
+            <td v-for="(cell,j) in row" :key="j">{{cell}}</td>
+          </tr>
+        </table>
+      </template>
+      <template v-else>
+        <pre>{{activeRecord.result}}</pre>
+      </template>
+    </ElCol>
+  </ElRow>
+  <ElEmpty v-else description="暂无记录"></ElEmpty>
+</template>
+
+<script>
+import { SQLExecAPI, PipelineAPI } from '@/api';
+
+export default {
+  name: 'Database',
+  data() {
+    return {
+      dbs: [],
+      execHistory: new Map(),
+      active: -1,
+      showTable: true,
+      form: {
+        db: '',
+        deploymentId: null,
+        password: '',
+        sql: '',
+        username: 'root',
+      },
+      b: '',
+      content: '',
+    };
+  },
+  async mounted() {
+    this.dbs = await PipelineAPI.getDeploymentsByProjectId(this.$store.state.workspace.currentProjectId);
+  },
+  computed: {
+    activeRecord() {
+      if (this.active < 0) return {};
+      return this.execHistory.get(this.active);
+    },
+  },
+  methods: {
+    async exec() {
+      const result = await SQLExecAPI.SQLExec(this.form);
+      this.execHistory.set(this.execHistory.size + 1, {
+        sql: this.form.sql,
+        result,
+      });
+      this.switchRecord(this.execHistory.size);
+    },
+    switchRecord(key) {
+      this.active = key;
+      const { b, content } = this.process(this.activeRecord.result);
+      if (typeof content === 'string') {
+        this.showTable = false;
+      } else {
+        this.showTable = true;
+      }
+      this.b = b;
+      this.content = content;
+    },
+    process(result) {
+      const i = result.indexOf('\n');
+      const b = result.substr(0, i);
+      let content = result.substr(i + 1);
+      if (!content.startsWith('[[')) {
+        return {
+          b,
+          content,
+        };
+      }
+      content = content.substring(1, content.length - 1);
+      const recordList = content.match(/\[.*?\]/g);
+      content = [];
+      recordList.forEach((_record) => content.push(_record.substring(1, _record.length - 1).split(',').map((val) => val.trim())));
+      return {
+        b,
+        content,
+      };
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.input {
+  width: 240px;
+}
+.sql {
+  width: 1000px;
+  input {
+    font: 1em monospace;
+  }
+}
+.list-item {
+  height: 32px;
+  line-height: 32px;
+  padding: 4px 4px 4px 32px;
+  width: 80%;
+  &.active {
+    background-color: #ecf5ff;
+    color: #409eff;
+  }
+  &:hover {
+    background-color: #ecf5ff;
+    color: #409eff;
+  }
+}
+
+pre {
+  font-size: 1.2em;
+  word-break: break-word;
+  white-space: pre-wrap;
+}
+
+table {
+  margin-top: 24px;
+  color: #333333;
+  border-width: 1px;
+  border-color: #666666;
+  border-collapse: collapse;
+  td {
+    border-width: 1px;
+    padding: 8px 12px;
+    border-style: solid;
+    border-color: #666666;
+    background-color: #ffffff;
+    white-space: nowrap;
+  }
+}
+
+</style>

+ 4 - 1
src/views/Project/Invite.vue

@@ -5,7 +5,7 @@
   </el-row>
   <div class="row-item">
     <span>邀请码:</span>
-    <span>{{relateCode}}</span>
+    <span @click="copy(relateCode)">{{relateCode}}</span>
   </div>
   <div class="row-item">
     <span>
@@ -40,6 +40,8 @@
 import { ProjectAPI } from '@/api';
 import { ElMessage } from 'element-plus';
 
+import { copy } from '@/utils/clipboard';
+
 export default {
   data() {
     return {
@@ -48,6 +50,7 @@ export default {
     };
   },
   methods: {
+    copy,
     async generateRelateCode() {
       this.relateCode = await ProjectAPI.generateRelateCode(this.$store.state.workspace.currentProjectId);
       ElMessage.success('邀请码将在5分钟后失效,请及时完成操作!');

+ 20 - 11
src/views/Project/Pipeline.vue

@@ -212,6 +212,12 @@
                   :value="item.testId">
                 </el-option>
               </ElSelect>
+              <ElInput v-else-if="key === 'nginxConfig'"
+                :disabled="!getStage(config.name).configs.find((config) => config.name === key).editable"
+                v-model="config.configs[key]"
+                type="textarea"
+                :rows="5"
+              ></ElInput>
               <ElInput v-else
                 :disabled="!getStage(config.name).configs.find((config) => config.name === key).editable"
                 v-model="config.configs[key]"
@@ -280,18 +286,17 @@
           prop="name"
           label="名称"
           align="center"
-          width="100"
+          width="200"
         ></ElTableColumn>
         <ElTableColumn
           prop="pipelineId"
           label="流水线ID"
           align="center"
-          width="60"
+          width="100"
         ></ElTableColumn>
         <ElTableColumn
           label="部署时间"
           align="center"
-          width="320"
           :formatter="(row) => formatter(row.deployedTime)"
         ></ElTableColumn>
         <ElTableColumn
@@ -300,7 +305,7 @@
         >
           <template #default="scope">
             <ElTooltip effect="dark" :content="scope.row.accessUrl" placement="top-start">
-              <ElButton type="text" @click="open(scope.row.accessUrl, '_blank')">
+              <ElButton type="text" @click="() => copy(scope.row.accessUrl)">
                 {{scope.row.accessUrl}}
               </ElButton>
             </ElTooltip>
@@ -332,7 +337,7 @@
           </template>
         </ElTableColumn>
       </ElTable>
-      <ElDialog v-model="showProductionLogModal" destroy-on-close>
+      <ElDialog v-model="showProductionLogModal" width="60%" destroy-on-close>
         <template #title>
           产物日志
         </template>
@@ -349,6 +354,7 @@
 import { APITestAPI, PipelineAPI } from '@/api';
 
 import { formatter } from '@/utils/time';
+import { copy } from '@/utils/clipboard';
 
 export default {
   data() {
@@ -384,7 +390,7 @@ export default {
   },
   computed: {
     isLastStep() {
-      return this.activeStep === this.stages.length - 1;
+      return this.activeStep === this.pipelineConfig.length - 1;
     },
     stageConfigsBase() {
       return this.stages.map((stage) => ({
@@ -411,6 +417,7 @@ export default {
   },
   methods: {
     formatter,
+    copy,
     async handleClick(action, id) {
       switch (action) {
         case 'create': {
@@ -477,10 +484,11 @@ export default {
         }
         case 'delete': {
           this.loading = true;
-          PipelineAPI.deletePipeline({
+          await PipelineAPI.deletePipeline({
             projectId: this.$store.state.workspace.currentProjectId,
             pipelineId: id,
           });
+          this.pipelines = await PipelineAPI.getPipelinesByProjectId(this.$store.state.workspace.currentProjectId);
           this.loading = false;
           break;
         }
@@ -519,12 +527,12 @@ export default {
             templateName: this.createPipelineForm.template,
           });
           this.showPipelineCreateModal = false;
+          this.confirmLoading = false;
+          this.loading = true;
+          this.pipelines = await PipelineAPI.getPipelinesByProjectId(this.$store.state.workspace.currentProjectId);
+          this.loading = false;
         }
       });
-      this.confirmLoading = false;
-      this.loading = true;
-      this.pipelines = await PipelineAPI.getPipelinesByProjectId(this.$store.state.workspace.currentProjectId);
-      this.loading = false;
     },
     open(url, target) {
       window.open(url, target);
@@ -535,6 +543,7 @@ export default {
         projectId: this.$store.state.workspace.currentProjectId,
         pipelineId,
       });
+      this.productions = await PipelineAPI.getDeploymentsByProjectId(this.$store.state.workspace.currentProjectId);
       this.loading = false;
     },
     getStage(stageName) {

+ 32 - 13
src/views/Project/Schedule.vue

@@ -8,7 +8,13 @@
         <div class="description-card-text" style="font-size: 18px;font-weight: bold">{{selectedProject.name}}</div>
         <div class="description-card-text">项目地址:
           <span>
-            <el-link target="_blank" type="primary" :href="selectedProject.gitRemoteUrl">{{selectedProject.gitRemoteUrl}}</el-link>
+            <el-link
+              target="_blank"
+              type="primary"
+              @click.prevent="copy(selectedProject.gitRemoteUrl)"
+            >
+              {{selectedProject.gitRemoteUrl}}
+            </el-link>
           </span>
         </div>
         <div class="description-card-text">项目介绍:
@@ -32,11 +38,14 @@
   </el-card>
   <el-row  style="display: flex;flex-direction: row;align-items: center;margin-bottom: 10px;color: #333333">
     <h4>项目规划</h4>
-    <el-tooltip class="item" effect="dark" :content="scheduleGuide" placement="right-end">
-      <i class="el-icon-question"></i>
-    </el-tooltip>
   </el-row>
-  <Tree :data="requirements" v-loading="loading" @change="handleTreeChange" @nodeClick="handleNodeClick" ref="tree"></Tree>
+  <Tree
+    :data="requirements"
+    v-loading="loading"
+    @change="handleTreeChange"
+    @nodeClick="handleNodeClick"
+    ref="tree"
+  ></Tree>
   <RequirementDrawer
     ref="drawer"
     v-bind="requirementInDrawer"
@@ -50,9 +59,9 @@ import Tree from '@/components/Tree.vue';
 import * as echarts from 'echarts';
 import { BugListAPI, RequirementAPI } from '@/api';
 import RequirementDrawer from '@/components/RequirementDrawer.vue';
+import { copy } from '@/utils/clipboard';
 import { useProjectList } from '../../composable/useProject';
 
-const scheduleGuide = '项目规划用于asdsadsadda';
 export default {
   name: 'Schedule',
   components: { RequirementDrawer, Tree },
@@ -64,7 +73,6 @@ export default {
   },
   data() {
     return {
-      scheduleGuide,
       loading: false,
       requirements: [],
       showRequirementDrawer: false,
@@ -74,16 +82,27 @@ export default {
       projectMembers: [],
     };
   },
-  async mounted() {
-    this.projectMembers = this.$store.state.workspace.currentProjectMembers;
-    await this.getTreeData();
-    this.drawChart();
+  computed: {
+    projectId() {
+      return this.$route.params.projectId;
+    },
+  },
+  watch: {
+    $route: {
+      async handler() {
+        this.projectMembers = this.$store.state.workspace.currentProjectMembers;
+        await this.getTreeData();
+        this.drawChart();
+      },
+      immediate: true,
+    },
   },
   methods: {
+    copy,
     async getTreeData() {
       this.loading = true;
       try {
-        this.requirements = this.addChild2Node(await RequirementAPI.getTreeNodesByProjectId(this.$store.state.workspace.currentProjectId));
+        this.requirements = this.addChild2Node(await RequirementAPI.getTreeNodesByProjectId(this.projectId));
       } catch (e) {
         console.error(e);
       }
@@ -127,7 +146,7 @@ export default {
       this.requirementInDrawer = Array.prototype.find.call(this.requirements, (requirement) => requirement.id === id);
     },
     async drawChart() {
-      const bugList = await BugListAPI.getBugListByProjectId(this.$store.state.workspace.currentProjectId);
+      const bugList = await BugListAPI.getBugListByProjectId(this.projectId);
       const requirementsValuesMap = new Map();
       const requirementsValues = [];
       const bugListValuesMap = new Map();

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels