搜索结果列表页面开发
This commit is contained in:
61
src/components/BranchItem/README.md
Normal file
61
src/components/BranchItem/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# BranchItem
|
||||
|
||||
### 说明
|
||||
- 分支列表里面的分支卡片信息
|
||||
|
||||
``` js
|
||||
import { mock } from 'mockjs';
|
||||
|
||||
// 数据定义为 @/components/BranchItem/type.ts
|
||||
const branch = ref({
|
||||
name: mock('@word'),
|
||||
behindCount: mock('@natural(0, 500)'),
|
||||
aheadCount: mock('@natural(0, 500)'),
|
||||
|
||||
repo: {
|
||||
name: mock('@word'),
|
||||
namespace: mock('@first')
|
||||
},
|
||||
|
||||
lastCommit: {
|
||||
id: mock('@id'),
|
||||
message: mock('@paragraph'),
|
||||
username: mock('@first'),
|
||||
createTime: mock('@datetime')
|
||||
}
|
||||
});
|
||||
|
||||
// 此处使用v-bind统一prop,可以分开传入,如 <BranchItem :name="xxx" ...... />
|
||||
<BranchItem v-bind="branch" />
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|-----------|------------------------|----------------------------|----------|-----------|
|
||||
| name | 分支名称 | string | - | - |
|
||||
| behindCount | 落后默认分支commit个数 | number | - | - |
|
||||
| aheadCount | 领先默认分支commit个数 | number | - | - |
|
||||
| repo | 分支所属的仓库信息 | Repo | - | - |
|
||||
| lastCommit | 分支下的最后一次commit | Commit | - | - |
|
||||
|
||||
#### Repo
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|-----------|------------------------|----------------------------|----------|-----------|
|
||||
| name | 仓库名称 | string | - | - |
|
||||
| namespace | 仓库所属命名空间(用户或组织的namespace) | string | - | - |
|
||||
|
||||
#### Commit
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|-----------|------------------------|----------------------------|----------|-----------|
|
||||
| id | commit的hash Id | string | - | - |
|
||||
| message | 提交描述 | string | - | - |
|
||||
| username | commit提交人 | string | - | - |
|
||||
| createTime | 提交时间 | string | - | - |
|
||||
|
||||
### Emits
|
||||
| 方法 | 说明 | 返回值 |
|
||||
|-----------------|------------------------------------|---------------------|
|
||||
| onView | 查看分支统计 | props整体数据 |
|
||||
| onEdit | 编辑分支事件 | props整体数据 |
|
||||
| onDelete | 删除分支事件 | props整体数据 |
|
||||
355
src/components/BranchItem/index.vue
Normal file
355
src/components/BranchItem/index.vue
Normal file
@@ -0,0 +1,355 @@
|
||||
<template>
|
||||
<div class="g-branch-item">
|
||||
<div class="g-branch-item-icon">
|
||||
<Icon v-if="isProtected" name="gt-shield-lock" size="14px" color="var(--color-light)" />
|
||||
</div>
|
||||
<div class="g-branch-item-meta">
|
||||
<Copy class="g-branch-item-meta-name font-bold" always size="14px">
|
||||
<GLink :to="branchLink">
|
||||
<d-tooltip position="top" :content="name">
|
||||
<span class="text-G900 name-text ellipsis">{{ name }}</span>
|
||||
</d-tooltip>
|
||||
</GLink>
|
||||
</Copy>
|
||||
|
||||
<span class="overflow-hidden whitespace-nowrap inline-block ml-2 align-middle text-ellipsis" v-if="description">
|
||||
<span class="overflow-hidden text-ellipsis inline-block max-w-[100px] align-middle">{{ description }}</span>
|
||||
<span
|
||||
class="inline-flex justify-center items-center align-middle rounded-sm ml-2 cursor-pointer bg-CG200 hover:bg-CG300 active:bg-CG300"
|
||||
>
|
||||
<Icon :name="isFold ? 'gt-chevron-up' : 'gt-chevron-down'" @click="isFold = !isFold"/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<p v-if="description" class="text-ellipsis break-all whitespace-pre-wrap overflow-hidden my-1"
|
||||
:style="{ transition: 'height linear 0.1s', height: isFold ? 'auto' : 0 }">{{ description }}</p>
|
||||
<div v-if="lastCommit" class="g-branch-item-meta-last-commit">
|
||||
<GLink :to="authorLink"><span class="text-light">{{ lastCommit?.author_name }}</span></GLink>
|
||||
<span class="text-light">提交于<Time :time="lastCommit?.created_at" /></span>
|
||||
<GLink :to="commitLink"><span class="text-light">{{ lastCommit.id?.slice(0,8) }}</span></GLink>
|
||||
<d-tooltip :position="['right']" :content="lastCommit.message">
|
||||
<span class="g-branch-item-meta-last-commit-message text-light">{{ lastCommit.message }}</span>
|
||||
<template #content>
|
||||
<div class="py-1 px-4 max-w-[400px] max-h-[200px] overflow-auto">
|
||||
{{ lastCommit.message }}
|
||||
</div>
|
||||
</template>
|
||||
</d-tooltip>
|
||||
<!-- 分支最近一次commit不用添加 -->
|
||||
<!-- <GpgTag :open_gpg_verified="lastCommit.open_gpg_verified" :gpg_primary_key_id="lastCommit.gpg_primary_key_id" :verification_status="lastCommit.verification_status" :name="lastCommit.name"></GpgTag> -->
|
||||
</div>
|
||||
</div>
|
||||
<d-tooltip v-if="!isDefault" :content="diffSummary">
|
||||
<div class="g-branch-item-git" >
|
||||
<div class="count left">
|
||||
<span :style="{ width: getWidth(behindCount) }"><i>{{ behindCount }}</i></span>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="count right">
|
||||
<span :style="{ width: getWidth(aheadCount) }"><i>{{ aheadCount }}</i></span>
|
||||
</div>
|
||||
</div>
|
||||
</d-tooltip>
|
||||
<div class="g-branch-item-default default-branch" v-else>
|
||||
<span>默认分支</span>
|
||||
</div>
|
||||
<div class="g-branch-item-operation">
|
||||
<div class="merge text-ellipsis" v-if="mergeRequestStatus">
|
||||
<div class="merge-type">
|
||||
<Icon :name="mergeRequestStatus.icon" size="12px" :color="mergeRequestStatus.color" />
|
||||
<span :style="{ color: mergeRequestStatus.color }">{{ mergeRequestStatus.text }}</span>
|
||||
</div>
|
||||
<div class="merge-id">
|
||||
<span>#{{ mergeRequestBasic.iid }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="diff">
|
||||
<GLink :to="compareLink" v-if="!isDefault">
|
||||
<d-tooltip position="top" content="比较">
|
||||
<div class="diff-branch">
|
||||
<Icon :operable="true" name="gt-compare-branch" size="14px" color="var(--color-light)" />
|
||||
</div>
|
||||
</d-tooltip>
|
||||
</GLink>
|
||||
</div>
|
||||
<div v-if="canProtect" class="ml-5 min-w-[14px] cursor-pointer">
|
||||
<d-tooltip v-if="!isProtected" position="top" content="创建保护分支">
|
||||
<Icon
|
||||
name="gt-shield-lock-setting"
|
||||
size="14px"
|
||||
color="var(--color-light)"
|
||||
:operable="true"
|
||||
@click="handleProtect"
|
||||
/>
|
||||
</d-tooltip>
|
||||
</div>
|
||||
<div class="more ml-5">
|
||||
<MoreList v-if="canDelete && props.moreOpts?.length > 1" :moreOpts="props.moreOpts" :item="props" />
|
||||
<d-tooltip v-else-if="canDelete && props.moreOpts?.length" position="top" :content="props.moreOpts[0]?.text">
|
||||
<Icon :operable="true" :name="props.moreOpts[0].icon" @click="props.moreOpts[0]?.handle(props)"></Icon>
|
||||
</d-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { Branch } from './type';
|
||||
import MoreList from '@/components/MoreList/index.vue';
|
||||
const props = defineProps<Branch>();
|
||||
const authorLink = computed(() => ({
|
||||
name: 'homepage',
|
||||
params: {
|
||||
namespace: props?.lastCommit?.author_name || ''
|
||||
}
|
||||
}));
|
||||
const diffSummary = computed(() => {
|
||||
if (props.isDefault) {
|
||||
return null;
|
||||
}
|
||||
return `比${props.defaultBranch || '默认'}分支落后${props.behindCount}个提交、超前${props.aheadCount}个提交`;
|
||||
});
|
||||
|
||||
const mergeRequest = reactive({
|
||||
status: [{
|
||||
state: 'opened',
|
||||
text: '开启',
|
||||
icon: 'gt-merge-request',
|
||||
color: 'var(--color-Y500)'
|
||||
}, {
|
||||
state: 'closed',
|
||||
text: '关闭',
|
||||
icon: 'gt-skip-merge',
|
||||
color: 'var(--color-CG600)'
|
||||
}, {
|
||||
state: 'locked',
|
||||
text: '锁定',
|
||||
icon: 'gt-merge-request',
|
||||
color: 'var(--color-Y500)'
|
||||
}, {
|
||||
state: 'merged',
|
||||
text: '合入合并',
|
||||
icon: 'gt-closed-merge',
|
||||
color: 'var(--color-GN500)'
|
||||
}, {
|
||||
state: 'all',
|
||||
text: '全部',
|
||||
icon: 'gt-merge-request',
|
||||
color: 'var(--color-Y500)'
|
||||
}]
|
||||
});
|
||||
const mergeRequestStatus = computed(() => {
|
||||
const stateItem = mergeRequest.status.find((item: any) => item.state === props?.mergeRequestBasic?.state);
|
||||
return stateItem;
|
||||
});
|
||||
const commitLink = computed(() => ({
|
||||
name: 'repoCommitDetail',
|
||||
params: {
|
||||
namespace: props.repo.namespace,
|
||||
repoName: props.repo.name,
|
||||
commitId: props.lastCommit.id
|
||||
},
|
||||
query: {
|
||||
ref: props.name
|
||||
}
|
||||
}));
|
||||
const compareLink = computed(() => {
|
||||
try {
|
||||
return {
|
||||
name: 'repoCompare',
|
||||
params: {
|
||||
namespace: props.repo.namespace,
|
||||
repoName: props.repo.name,
|
||||
fromId: props.defaultBranch || 'main',
|
||||
toId: props.name
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const branchLink = computed(() => ({
|
||||
name: 'repoDir',
|
||||
params: {
|
||||
branchName: props.name
|
||||
}
|
||||
}));
|
||||
|
||||
const MAX_WIDTH = 30;
|
||||
const getWidth = (val: number): string => {
|
||||
const relativeWidth = val * MAX_WIDTH / (props.aheadCount + props.behindCount);
|
||||
const absoluteWidth = val * MAX_WIDTH / Math.max(props.aheadCount, props.behindCount);
|
||||
return `${(relativeWidth + absoluteWidth)}px`;
|
||||
};
|
||||
const isFold = ref(false);
|
||||
const emit = defineEmits(['onProtect']);
|
||||
const handleProtect = () => {
|
||||
emit('onProtect');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.g-branch-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border: 1px solid var(--color-border-light);
|
||||
min-height: 72px;
|
||||
&+.g-branch-item {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
&-meta {
|
||||
flex-basis: 45%;
|
||||
|
||||
&-name {
|
||||
font-size: 16px;
|
||||
line-height: 30px;
|
||||
font-weight: 500;
|
||||
vertical-align: middle;
|
||||
.name-text {
|
||||
display: block;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
&-last-commit {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
color: #999;
|
||||
line-height: 20px;
|
||||
&-message {
|
||||
display: inline-block;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-check {
|
||||
flex-basis: 5%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&-git {
|
||||
display: flex;
|
||||
flex-basis: 20%;
|
||||
align-items: center;
|
||||
|
||||
>.count {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
padding: 0 2px;
|
||||
color: var(--color-G700);
|
||||
transform: translateY(10px);
|
||||
|
||||
&.left {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 4px;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
transform: translate(-6px, -20px);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
text-align: left;
|
||||
margin-left: 4px;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
transform: translate(6px, -20px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 32px;
|
||||
background-color: var(--color-G300);
|
||||
}
|
||||
|
||||
span {
|
||||
padding: 2px 2px 2px 2px;
|
||||
border-bottom: 4px solid var(--color-G300);
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-default {
|
||||
flex-basis: 20%;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
color: var(--color-G700);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&-operation {
|
||||
flex-basis: 30%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
.merge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 60px;
|
||||
gap: 8px;
|
||||
width: 150px;
|
||||
|
||||
.merge-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.merge-id {
|
||||
color: var(--color-CG600);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-branch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
>span {
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
color: var(--color-CG600);
|
||||
}
|
||||
}
|
||||
.more {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
44
src/components/BranchItem/type.ts
Normal file
44
src/components/BranchItem/type.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export interface Commit {
|
||||
id: string,
|
||||
message: string,
|
||||
author_name: string,
|
||||
created_at: string,
|
||||
open_gpg_verified: boolean; // 是否需要验证
|
||||
gpg_primary_key_id: string; // 密钥id
|
||||
verification_status: number; // 验证状态
|
||||
name: string; // 名称
|
||||
}
|
||||
|
||||
export interface MergerRequest {
|
||||
id: string,
|
||||
iid: string,
|
||||
merge_request_type: string,
|
||||
project_id: string,
|
||||
state: string,
|
||||
title: string,
|
||||
updated_at: string,
|
||||
web_url: string
|
||||
}
|
||||
|
||||
export interface Repo {
|
||||
name: string, // 仓库名
|
||||
namespace: string, // 用户名
|
||||
}
|
||||
|
||||
export interface Branch {
|
||||
name: string,
|
||||
description?: string;
|
||||
behindCount: number,
|
||||
aheadCount: number,
|
||||
defaultBranch: string,
|
||||
repo: Repo,
|
||||
lastCommit: Commit,
|
||||
mergeRequestBasic: MergerRequest,
|
||||
|
||||
isDefault: boolean,
|
||||
isProtected: boolean,
|
||||
canDelete: boolean,
|
||||
canProtect: boolean,
|
||||
|
||||
moreOpts: Array<object>,
|
||||
}
|
||||
Reference in New Issue
Block a user