Преглед изворни кода

feat: add commit detail page

claws пре 4 година
родитељ
комит
c8a005f2a7
6 измењених фајлова са 381 додато и 28 уклоњено
  1. 1 0
      package.json
  2. 12 0
      src/api/devcloud/project.ts
  3. 4 0
      src/router/index.ts
  4. 66 13
      src/views/Project/Commit.vue
  5. 238 0
      src/views/Project/CommitDetails.vue
  6. 60 15
      yarn.lock

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
   "dependencies": {
     "axios": "^0.21.1",
     "core-js": "^3.6.5",
+    "diff2html": "^3.4.16",
     "echarts": "^5.1.1",
     "element-plus": "^1.0.2-beta.31",
     "jsonwebtoken": "^8.5.1",

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

@@ -36,4 +36,16 @@ export const getCommits = (projectId: number) => axios.get(
   `${COMMIT_PREFIX}/list?projectId=${projectId}`,
 );
 
+export const getCommitsByBranch = (projectId: number, branchName: string) => axios.get(
+  `${COMMIT_PREFIX}/by_branch?projectId=${projectId}&branchName=${branchName}`,
+);
+
+export const getCommitByHash = (projectId: number, hash: string) => axios.get(
+  `commit?projectId=${projectId}&hash=${hash}`,
+);
+
+export const getCommitsDiff = (projectId: number, from: string, to: string) => axios.get(
+  `${COMMIT_PREFIX}/diff?projectId=${projectId}&from=${from}&to=${to}`,
+);
+
 export const getRelatedProjects = (projectId: number) => axios.get(`${PROJECT_PREFIX}/relate/${projectId}`);

+ 4 - 0
src/router/index.ts

@@ -70,6 +70,10 @@ const routes: Array<RouteRecordRaw> = [
         path: 'git',
         component: () => import(/* webpackChunkName: "project" */'@/views/Project/Commit.vue'),
       },
+      {
+        path: 'commit-detail',
+        component: () => import(/* webpackChunkName: "project" */'@/views/Project/CommitDetails.vue'),
+      },
     ],
   }),
 ];

+ 66 - 13
src/views/Project/Commit.vue

@@ -5,6 +5,33 @@
     </el-row>
     <div>
       <div v-if="gits.length === 0">暂无commit记录!</div>
+<!--      <div v-else>-->
+<!--        <ElTimeline>-->
+<!--          <ElTimelineItem-->
+<!--            v-for="(git, index) in gits"-->
+<!--            :key="index"-->
+<!--            :timestamp="git.timestamp"-->
+<!--            placement="top"-->
+<!--            :color="git.relatedType === 'bug_id' ? 'chocolate' : '#42b983'"-->
+<!--            size="large">-->
+<!--            <ElCard>-->
+<!--              <h3 class="id">#{{git.id}}</h3>-->
+<!--              <div style="margin-top: 10px; font-size: 16px">{{git.message}}</div>-->
+<!--              <div style="margin-top: 10px">-->
+<!--                <el-row>-->
+<!--                  <el-col :span="12">-->
+<!--                    处理人:<span style="font-weight: bold">{{git.gitlabUsername}}</span>-->
+<!--                  </el-col>-->
+<!--                  <el-col :span="12">-->
+<!--                    类型:-->
+<!--                    <span v-if="git.relatedType === 'bug_id'" class="node bug">缺陷</span>-->
+<!--                    <span v-else class="node requirement">需求</span></el-col>-->
+<!--                </el-row>-->
+<!--              </div>-->
+<!--            </ElCard>-->
+<!--          </ElTimelineItem>-->
+<!--        </ElTimeline>-->
+<!--      </div>-->
       <div v-else>
         <ElTimeline>
           <ElTimelineItem
@@ -14,15 +41,27 @@
             placement="top"
             :color="git.relatedType === 'bug_id' ? 'chocolate' : '#42b983'"
             size="large">
-            <ElCard>
-              <h3 class="id">#{{git.id}}</h3>
-              <div style="margin-top: 10px; font-size: 16px">{{git.message}}</div>
+            <ElCard shadow="hover"
+                    @click="clickCommitCard(git.id)">
+              <h3>{{git.title}}</h3>
+<!--              <h3 class="id">{{git.id}}</h3>-->
+              <div class="view-text" style="margin-top: 10px; font-size: 16px; color: #444">{{git.message}}</div>
               <div style="margin-top: 10px">
                 <el-row>
+                  <el-col :span="6">
+                    <el-link
+                      target="_blank"
+                      type="info"
+                      @click.stop.prevent="copy(git.id)"
+                    >
+                      <span style="color: #888">{{git.id.substring(0, 8)}}</span>
+                      <i class="el-icon-document-copy"></i>
+                    </el-link>
+                  </el-col>
                   <el-col :span="12">
                     处理人:<span style="font-weight: bold">{{git.gitlabUsername}}</span>
                   </el-col>
-                  <el-col :span="12">
+                  <el-col :span="6">
                     类型:
                     <span v-if="git.relatedType === 'bug_id'" class="node bug">缺陷</span>
                     <span v-else class="node requirement">需求</span></el-col>
@@ -38,6 +77,8 @@
 
 <script>
 import { ProjectAPI } from '@/api';
+import { useRouter } from 'vue-router';
+import { copy } from '@/utils/clipboard';
 import { formatter } from '../../utils/time';
 
 export default {
@@ -48,17 +89,22 @@ export default {
     };
   },
   async mounted() {
-    this.gits = await ProjectAPI.getCommits(this.$store.state.workspace.currentProjectId);
+    this.gits = await ProjectAPI.getCommitsByBranch(this.$store.state.workspace.currentProjectId, 'dev');
     for (let i = 0; i < this.gits.length; i += 1) {
       this.gits[i].timestamp = formatter(this.gits[i].timestamp);
     }
-    this.gits.sort((a, b) => {
-      const aTimeString = a.timestamp;
-      const bTimeString = b.timestamp;
-      const aTime = new Date(aTimeString).getTime();
-      const bTime = new Date(bTimeString).getTime();
-      return bTime - aTime;
-    });
+  },
+  methods: {
+    copy,
+    clickCommitCard(id) {
+      console.log(id);
+      this.$router.push({
+        path: `/project/${this.$store.state.workspace.currentProjectId}/commit-detail`,
+        query: {
+          id,
+        },
+      });
+    },
   },
 };
 </script>
@@ -66,7 +112,7 @@ export default {
 <style scoped>
 .container {
   margin: 50px 100px;
-  width: 30%;
+  width: 50%;
 }
 .row-item {
   display: flex;
@@ -95,4 +141,11 @@ export default {
 .requirement {
   background-color: #42b983;
 }
+.view-text{
+  display: inline-block;
+  white-space: nowrap;
+  width: 100%;
+  overflow: hidden;
+  text-overflow:ellipsis;
+}
 </style>

+ 238 - 0
src/views/Project/CommitDetails.vue

@@ -0,0 +1,238 @@
+<template>
+  <div>
+<!--    <el-row-->
+<!--      style="display: flex;flex-direction: row;align-items: center;margin-bottom: 10px;color: #333333">-->
+<!--      <el-button plain icon="el-icon-arrow-left" @click="goBack">上一页</el-button>-->
+<!--    </el-row>-->
+    <el-card shadow="never" class="description-card">
+<!--      <el-button plain icon="el-icon-arrow-left" @click="goBack" style="margin-right: 20px">返回</el-button>-->
+      <el-link
+        target="_blank"
+        class="commit-id description-card-text"
+        @click.stop.prevent="copy(commitId.id)"
+      >
+        <span>{{ commitId }}</span>
+        <i class="el-icon-document-copy"></i>
+      </el-link>
+      <div class="description-card-text commit-title">标题:
+        <span>{{ commitInfo.title }}</span>
+      </div>
+      <div class="description-card-text">内容:
+        <span>{{ commitInfo.message }}</span>
+      </div>
+      <div class="description-card-text">时间:
+        <span>{{ commitInfo.timestamp }}</span>
+      </div>
+      <div class="description-card-text">提交人:
+        <span>{{ commitInfo.gitlabUsername }}</span>
+      </div>
+      <div class="description-card-text">
+        <el-row>
+          parents:
+          <span><el-tag v-for="(parent, index) in commitInfo.parents"
+                        :key="index"
+                        @click="onParentTagClick(parent)"
+          >{{ parent }}</el-tag></span>
+        </el-row>
+
+      </div>
+    </el-card>
+    <el-divider content-position="left">
+      变更详情
+    </el-divider>
+    <div v-if="parentsCount > 0">
+      <el-row>
+        <el-col :span="8">
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="grid-content">
+                <el-select v-model="currentParentIndex" style="width: 100%">
+                  <el-option
+                    v-for="(item, index)  in commitInfo.parents"
+                    :key="index"
+                    :label="'相比于 parent ' + item.substring(0,8)"
+                    :value="index">
+                  </el-option>
+                </el-select>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <el-input
+                v-model="fileNameKeyword"
+                class="w-50 m-2"
+                placeholder="检索文件名"
+                :prefix-icon="Search"
+              >
+                <template #append>
+                  <el-button :icon="Search" @click="searchFileName">搜索</el-button>
+                </template>
+              </el-input>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-table :data="displayDiff[currentParentIndex]"
+                      max-height="300px"
+                      style="width: 100%"
+                      highlight-current-row
+                      @current-change="handleCurrentChange">
+              <el-table-column prop="newPath" label="文件"/>
+              <el-table-column prop="state" label="状态" width="100">
+                <template #default="scope">
+                  <el-tag
+                    :type="scope.row.state < 0 ? 'danger' : (scope.row.state === 0 ? 'success' : '')"
+                    disable-transitions>
+                    {{ scope.row.stateDes }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-row>
+        </el-col>
+        <el-col :span="1"> </el-col>
+        <el-col :span="15">
+          <el-empty v-if="diffHtml === ''" description="请在左侧选择文件查看" />
+          <div v-html="diffHtml" v-cloak></div>
+        </el-col>
+      </el-row>
+    </div>
+    <div v-else>
+      <el-empty description="本次提交为首次提交,无法比较" />
+    </div>
+
+  </div>
+
+  <!--  {{commitInfo}}-->
+</template>
+
+<script>
+import { ProjectAPI } from '@/api';
+import * as Diff2Html from 'diff2html';
+import 'diff2html/bundles/css/diff2html.min.css';
+import { copy } from '@/utils/clipboard';
+import { ref } from 'vue';
+import { formatter } from '../../utils/time';
+
+export default {
+  name: 'CommitDetials.vue',
+  data() {
+    return {
+      commitInfo: {},
+      originAllDiffs: [],
+      diffString: '',
+      parentsCount: 0,
+      currentParentIndex: 0,
+      diffHtml: '',
+      fileNameKeyword: '',
+      displayDiff: [],
+      currentFileRow: ref(),
+    };
+  },
+  computed: {
+    projectId() {
+      return this.$route.params.projectId;
+    },
+    commitId() {
+      return this.$route.query.id;
+    },
+  },
+  async mounted() {
+    await this.loadCommitInfo();
+  },
+  methods: {
+    copy,
+    async loadCommitInfo() {
+      this.commitInfo = await ProjectAPI.getCommitByHash(this.$store.state.workspace.currentProjectId, this.commitId);
+      this.commitInfo.timestamp = formatter(this.commitInfo.timestamp);
+      this.parentsCount = this.commitInfo.parents.length;
+      for (let i = 0; i < this.parentsCount; i += 1) {
+        const parentHash = this.commitInfo.parents[i];
+        // eslint-disable-next-line no-await-in-loop
+        const diffFiles = await ProjectAPI.getCommitsDiff(this.$store.state.workspace.currentProjectId, parentHash, this.commitId);
+        for (let j = 0; j < diffFiles.length; j += 1) {
+          const diffFile = diffFiles[j];
+          if (diffFile.deletedFile) {
+            diffFile.state = -1;
+            diffFile.stateDes = '删除';
+          } else if (diffFile.newFile) {
+            diffFile.state = 0;
+            diffFile.stateDes = '新增';
+          } else if (diffFile.renamedFile) {
+            diffFile.state = 1;
+            diffFile.stateDes = '改名';
+          } else {
+            diffFile.state = 2;
+            diffFile.stateDes = '修改';
+          }
+        }
+        this.originAllDiffs.push(diffFiles);
+      }
+      this.displayDiff = this.originAllDiffs;
+    },
+    drawDiffHtml() {
+      // this.diffString = this.diff[this.currentParentIndex][0].diff;
+      this.diffString = this.currentFileRow.diff;
+      this.diffHtml = Diff2Html.html(this.diffString, {
+        drawFileList: false,
+        matching: 'lines',
+        outputFormat: 'line-by-line',
+      });
+    },
+    goBack() {
+      this.$router.go(-1);
+      this.loadCommitInfo();
+    },
+    handleCurrentChange(val) {
+      this.currentFileRow = val;
+      this.drawDiffHtml();
+    },
+    onParentTagClick(id) {
+      console.log(id);
+      // this.$router.push({
+      //   path: `/project/${this.$store.state.workspace.currentProjectId}/commit-detail`,
+      //   query: {
+      //     id,
+      //   },
+      // });
+      // this.loadCommitInfo();
+    },
+    searchFileName() {
+      this.displayDiff = [];
+      for (let i = 0; i < this.parentsCount; i += 1) {
+        this.displayDiff.push(this.originAllDiffs[i]
+          .filter((diffFile) => diffFile.newPath.indexOf(this.fileNameKeyword) >= 0));
+      }
+    },
+  },
+};
+</script>
+
+<style scoped>
+.el-row {
+  margin-bottom: 20px;
+}
+
+[v-cloak] {
+  display: none;
+}
+
+.description-card {
+  margin: 10px 0;
+}
+
+.description-card-text {
+  margin: 5px 0;
+}
+
+.commit-id {
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.commit-title {
+  font-weight: bold;
+}
+
+.el-tag {
+  margin: 3px 10px;
+}
+</style>

+ 60 - 15
yarn.lock

@@ -1747,6 +1747,11 @@
   resolved "https://registry.npm.taobao.org/@xtuc/long/download/@xtuc/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
   integrity sha1-0pHGpOl5ibXGHZrPOWrk/hM6cY0=
 
+abbrev@1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
 accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
   version "1.3.7"
   resolved "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -3486,6 +3491,21 @@ detect-node@^2.0.4:
   resolved "https://registry.npm.taobao.org/detect-node/download/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
   integrity sha1-AU7o+PZpxcWAI9pkuBecCDooxGw=
 
+diff2html@^3.4.16:
+  version "3.4.16"
+  resolved "https://registry.yarnpkg.com/diff2html/-/diff2html-3.4.16.tgz#3d27e88594fa0e1ab954cc11e90895f9fa7b002d"
+  integrity sha512-KfkvEoZTJertjoGPYFLn8n0yRdvzJcHvFJpJwbIxUlHU9x6qqhRZEM+TlgtU09jdLqRZAQyWFoxPiP6HeT2IYA==
+  dependencies:
+    diff "5.0.0"
+    hogan.js "3.0.2"
+  optionalDependencies:
+    highlight.js "11.2.0"
+
+diff@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+  integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
 diff@^4.0.1:
   version "4.0.2"
   resolved "https://registry.npm.taobao.org/diff/download/diff-4.0.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdiff%2Fdownload%2Fdiff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
@@ -3646,13 +3666,13 @@ ecdsa-sig-formatter@1.0.11:
   dependencies:
     safe-buffer "^5.0.1"
 
-echarts@^5.0.2:
-  version "5.1.1"
-  resolved "https://registry.nlark.com/echarts/download/echarts-5.1.1.tgz?cache=0&sync_timestamp=1619495447964&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fecharts%2Fdownload%2Fecharts-5.1.1.tgz#b186f162f017c555cfd67b12ede6762bdc3ddfda"
-  integrity sha1-sYbxYvAXxVXP1nsS7eZ2K9w939o=
+echarts@^5.1.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.3.1.tgz#709e03aaf8c7a4439788272ee5c500cf0dce7e5b"
+  integrity sha512-nWdlbgX3OVY0hpqncSvp0gDt1FRSKWn7lsWEH+PHmfCuvE0QmSw17pczQvm8AvawnLEkmf1Cts7YwQJZNC0AEQ==
   dependencies:
-    tslib "2.0.3"
-    zrender "5.1.0"
+    tslib "2.3.0"
+    zrender "5.3.1"
 
 ee-first@1.1.1:
   version "1.1.1"
@@ -4896,6 +4916,11 @@ hex-color-regex@^1.1.0:
   resolved "https://registry.npm.taobao.org/hex-color-regex/download/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=
 
+highlight.js@11.2.0:
+  version "11.2.0"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.2.0.tgz#a7e3b8c1fdc4f0538b93b2dc2ddd53a40c6ab0f0"
+  integrity sha512-JOySjtOEcyG8s4MLR2MNbLUyaXqUunmSnL2kdV/KuGJOmHZuAR5xC54Ko7goAXBWNhf09Vy3B+U7vR62UZ/0iw==
+
 highlight.js@^10.0.0:
   version "10.5.0"
   resolved "https://registry.npm.taobao.org/highlight.js/download/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f"
@@ -4910,6 +4935,14 @@ hmac-drbg@^1.0.0:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
+hogan.js@3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
+  integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=
+  dependencies:
+    mkdirp "0.3.0"
+    nopt "1.0.10"
+
 hoopy@^0.1.4:
   version "0.1.4"
   resolved "https://registry.npm.taobao.org/hoopy/download/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@@ -6393,6 +6426,11 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
+mkdirp@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
+  integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
+
 mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
   version "0.5.5"
   resolved "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
@@ -6563,6 +6601,13 @@ node-releases@^1.1.70:
   resolved "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.70.tgz?cache=0&sync_timestamp=1611010359089&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08"
   integrity sha1-ZuDtAnOqZWZtf+eP6+djSHVCagg=
 
+nopt@1.0.10:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
+  integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
+  dependencies:
+    abbrev "1"
+
 normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
   version "2.5.0"
   resolved "https://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -9017,10 +9062,10 @@ tsconfig-paths@^3.9.0:
     minimist "^1.2.0"
     strip-bom "^3.0.0"
 
-tslib@2.0.3:
-  version "2.0.3"
-  resolved "https://registry.npm.taobao.org/tslib/download/tslib-2.0.3.tgz?cache=0&sync_timestamp=1617647331485&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
-  integrity sha1-jgdBrEX8DCJuWKF7/D5kubxsphw=
+tslib@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
+  integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
 
 tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
   version "1.14.1"
@@ -9796,9 +9841,9 @@ yorkie@^2.0.0:
     normalize-path "^1.0.0"
     strip-indent "^2.0.0"
 
-zrender@5.1.0:
-  version "5.1.0"
-  resolved "https://registry.nlark.com/zrender/download/zrender-5.1.0.tgz#b6a84c3aa7ccc6642ee0519901ca4c0835c4d85e"
-  integrity sha1-tqhMOqfMxmQu4FGZAcpMCDXE2F4=
+zrender@5.3.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.3.1.tgz#fa8e63ac7e719cfd563831fe8c42a9756c5af384"
+  integrity sha512-7olqIjy0gWfznKr6vgfnGBk7y4UtdMvdwFmK92vVQsQeDPyzkHW1OlrLEKg6GHz1W5ePf0FeN1q2vkl/HFqhXw==
   dependencies:
-    tslib "2.0.3"
+    tslib "2.3.0"