CodeReviewDetail.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <template>
  2. <div class="container">
  3. <h2>{{codeReviewDetail.title}}</h2>
  4. <el-divider/>
  5. <div class="title-line">
  6. <el-tag type="success" effect="dark" v-if="codeReviewDetail.status === 'Opened'">Open</el-tag>
  7. <el-tag type="danger" effect="dark" v-if="codeReviewDetail.status === 'Closed'">Closed</el-tag>
  8. <el-tag type="info" effect="dark" v-if="codeReviewDetail.status === 'Merged'">Merged</el-tag>
  9. <div>opened on {{codeReviewDetail.createTime.split('T')[0]}} by {{getCreatorName(codeReviewDetail.creatorId)}}</div>
  10. <el-button type="warning" plain size="medium" v-if="codeReviewDetail.status === 'Opened'" @click="closeCodeReview()">Close merge request</el-button>
  11. <el-button type="success" plain size="medium" v-if="codeReviewDetail.status === 'Closed'" @click="reopenCodeReview()">Reopen merge request</el-button>
  12. </div>
  13. <el-divider/>
  14. <div>Request to merge <span class="branch-name">{{codeReviewDetail.sourceBranch}}</span> into <span class="branch-name">{{codeReviewDetail.targetBranch}}</span></div>
  15. <div class="operation-line">
  16. <el-button type="success" size="medium" plain
  17. :disabled="disableMergeButton()"
  18. @click="mergeCodeReview()">
  19. Merge
  20. </el-button>
  21. <!-- <el-switch v-model="codeReviewDetail.deleteSourceBranch" active-text="合并后删除源分支"></el-switch> -->
  22. </div>
  23. <el-divider/>
  24. <ElTabs v-model="activeTab">
  25. <ElTabPane :label="'评审意见'" name="comments">
  26. <div v-for="comment in commentList" :key="comment.id" class="comment-line">
  27. <div>
  28. <i class="el-icon-plus"/>{{comment.creatorName}}
  29. <span class="vice-comment"> @ {{comment.creatorName}} {{comment.commentType}}ed at {{comment.time}}</span>
  30. </div>
  31. <div class="comment-content">{{comment.content}}</div>
  32. </div>
  33. <el-timeline reverse = "true">
  34. <el-timeline-item v-for="(item, index) in commentList" :key="index" :timestamp="formatTime(item.time)" class="comment-item">
  35. <el-row>
  36. <el-tag size="small" type="primary" v-if="item.commentType==='Commit'">Commited</el-tag>
  37. <el-tag size="small" type="info" v-if="item.commentType==='Comment'">Commented</el-tag>
  38. <el-tag size="small" type="success" v-if="item.commentType==='Reopen'">Reopened</el-tag>
  39. <el-tag size="small" type="danger" v-if="item.commentType==='Close'">Closed</el-tag>
  40. <el-tag size="small" type="success" v-if="item.commentType==='Merge'">Merged</el-tag>
  41. <span class="creator-name-plate">by {{item.creatorName}}</span>
  42. </el-row>
  43. <el-row>{{item.content}}</el-row>
  44. </el-timeline-item>
  45. </el-timeline>
  46. <div class="text-container">
  47. <div>创建新的评审意见</div>
  48. <el-input type="textarea" :rows="5" v-model="newComment.content"/>
  49. <el-button type="success" size="medium" plain :disabled="newComment.length === 0" @click="submitComment">创建</el-button>
  50. </div>
  51. </ElTabPane>
  52. <ElTabPane :label="'代码差异'" name="codeDiff">
  53. <el-row v-if="codeDiffMsg.length === 0" class="codeDiffHint">
  54. <i class="el-icon-info"></i>
  55. 源分支与目标分支间的代码无差异,无法进行代码评审以及合并操作。
  56. </el-row>
  57. <CodeDifferencePlate :data="codeDiffMsg"/>
  58. </ElTabPane>
  59. </ElTabs>
  60. </div>
  61. </template>
  62. <script>
  63. import CodeDifferencePlate from '../../components/CodeDifferencePlate.vue';
  64. import { CodeReviewAPI } from '../../api';
  65. export default {
  66. name: 'CodeResviewDetail.vue',
  67. components: { CodeDifferencePlate },
  68. data() {
  69. return {
  70. activeTab: 'comments',
  71. codeReviewId: 0,
  72. codeReviewDetail: {
  73. creatorId: 0,
  74. createTime: '',
  75. reviewerId: 0,
  76. },
  77. commentList: [
  78. {
  79. id: 0,
  80. codeReviewId: 1,
  81. creatorId: 1479,
  82. creatorName: 'Shanshan',
  83. content: '4893310c - feat: commit message',
  84. time: '2022-02-25',
  85. commentType: 'Commit',
  86. },
  87. {
  88. id: 1,
  89. codeReviewId: 1,
  90. creatorId: 1479,
  91. creatorName: 'Shanshan',
  92. content: 'Commene Content, Point out some flaws',
  93. time: '2022-02-26',
  94. commentType: 'Comment',
  95. },
  96. {
  97. id: 2,
  98. codeReviewId: 1,
  99. creatorId: 1479,
  100. creatorName: 'Shanshan',
  101. content: 'bd308312 - feat: fix some flaws',
  102. time: '2022-02-27',
  103. commentType: 'Commit',
  104. },
  105. ],
  106. codeDiffMsg: [],
  107. newComment: {
  108. codeReviewId: 0,
  109. creatorId: 0,
  110. creatorName: '',
  111. content: '',
  112. commentType: 'Comment',
  113. replyId: 0,
  114. },
  115. };
  116. },
  117. methods: {
  118. getCreatorName(creatorId) {
  119. const members = this.projectMemberList;
  120. let creatorName = 'Unknown User';
  121. Object.keys(members).forEach((key) => {
  122. if (members[key].value === creatorId) creatorName = members[key].label;
  123. return true;
  124. });
  125. return creatorName;
  126. },
  127. submitComment() {
  128. CodeReviewAPI.createComment(this.newComment).then(
  129. async (res) => {
  130. this.commentList = await CodeReviewAPI.getComment(this.codeReviewId);
  131. this.newComment.content = '';
  132. },
  133. );
  134. },
  135. formatTime(timestamp) {
  136. return new Date(timestamp).toLocaleString();
  137. },
  138. closeCodeReview() {
  139. CodeReviewAPI.closeCodeReview(this.codeReviewId).then(
  140. async (res) => {
  141. this.codeReviewDetail.status = 'Closed';
  142. this.$message('代码评审任务已关闭');
  143. this.commentList = await CodeReviewAPI.getComment(this.codeReviewId);
  144. },
  145. );
  146. },
  147. reopenCodeReview() {
  148. CodeReviewAPI.reopenCodeReview(this.codeReviewId).then(
  149. async (res) => {
  150. this.codeReviewDetail.status = 'Opened';
  151. this.$message('代码评审任务已重新开启');
  152. this.commentList = await CodeReviewAPI.getComment(this.codeReviewId);
  153. },
  154. );
  155. },
  156. disableMergeButton() {
  157. if (this.codeReviewDetail.status !== 'Opened') return true;
  158. if (this.currentUser.id !== this.codeReviewDetail.reviewerId) return true;
  159. if (this.codeDiffMsg.length === 0) return true;
  160. return false;
  161. },
  162. mergeCodeReview() {
  163. CodeReviewAPI.mergeCodeReview(this.codeReviewId).then(
  164. async (res) => {
  165. if (res === null) {
  166. this.codeReviewDetail.status = 'Merged';
  167. this.$message('代码评审任务已合并分支');
  168. this.commentList = await CodeReviewAPI.getComment(this.codeReviewId);
  169. }
  170. },
  171. );
  172. },
  173. },
  174. computed: {
  175. projectMemberList() {
  176. return this.$store.getters.userOptions;
  177. },
  178. currentUser() {
  179. return this.$store.state.user;
  180. },
  181. },
  182. async mounted() {
  183. this.codeReviewId = this.$route.query.codeReviewId;
  184. this.newComment.codeReviewId = this.codeReviewId;
  185. this.newComment.creatorId = this.currentUser.id;
  186. this.newComment.creatorName = this.currentUser.username;
  187. this.codeReviewDetail = await CodeReviewAPI.getCodeReviewDetail(this.codeReviewId);
  188. this.commentList = await CodeReviewAPI.getComment(this.codeReviewId);
  189. this.codeDiffMsg = await CodeReviewAPI.compareBranch(this.codeReviewDetail.projectId, this.codeReviewDetail.targetBranch, this.codeReviewDetail.sourceBranch);
  190. },
  191. };
  192. </script>
  193. <style lang="scss" scoped>
  194. ::v-deep {
  195. .el-tabs__item{
  196. font-size: 16px;
  197. }
  198. }
  199. .container {
  200. margin: 20px 50px;
  201. .el-divider {
  202. width: 90%;
  203. margin-top: 12px;
  204. margin-bottom: 12px;
  205. }
  206. .el-tabs {
  207. width: 90%;
  208. font-size: 2em;
  209. }
  210. }
  211. .title-line {
  212. .el-tag {
  213. display: inline-block;
  214. font-size: 0.95em;
  215. }
  216. div {
  217. display: inline-block;
  218. margin-left: 10px;
  219. }
  220. .el-button {
  221. position: absolute;
  222. right: 10%;
  223. }
  224. }
  225. .operation-line {
  226. margin-top: 8px;
  227. .el-switch {
  228. margin-left: 10px;
  229. }
  230. }
  231. .branch-name {
  232. color: #1b69b6;
  233. font-weight: 600;
  234. }
  235. .comment-item {
  236. .el-row {
  237. padding: 3px;
  238. }
  239. }
  240. .creator-name-plate {
  241. margin-left: 6px;
  242. }
  243. .text-container {
  244. font-size: 16px;
  245. border: solid gainsboro 1px;
  246. border-radius: 5px;
  247. margin-top: 20px;
  248. padding: 12px 20px;
  249. div {
  250. margin-bottom: 8px;;
  251. }
  252. .el-input {
  253. font-size: 13px;
  254. }
  255. }
  256. .codeDiffHint {
  257. font-size: 16px;
  258. color: grey;
  259. .el-icon-info {
  260. color: #ff4444;
  261. margin-right: 4px;
  262. }
  263. }
  264. </style>