374 lines
9.8 KiB
Vue
374 lines
9.8 KiB
Vue
|
|
<template>
|
||
|
|
<d-modal v-model="vModels" class="branch-add-modal" :before-close="handleCancel">
|
||
|
|
<template #header>
|
||
|
|
<gc-modal-header>
|
||
|
|
<span class="text-G900 text-base font-bold leading-[24px]">新建分支</span>
|
||
|
|
</gc-modal-header>
|
||
|
|
<div class="line bg-G200"></div>
|
||
|
|
</template>
|
||
|
|
<div class="invi-modal-body pt-20 px-24">
|
||
|
|
<d-form ref="branchFormRef" layout="vertical" :rules="rules" :data="branchForm">
|
||
|
|
<d-form-item field="ref" label="基于">
|
||
|
|
<div>
|
||
|
|
<div class="g-branch-compare-content">
|
||
|
|
<div ref="ele" class="g-branch-compare-repo">
|
||
|
|
<div class="g-branch-compare-custom-icon">
|
||
|
|
<slot name="repo-prefix">
|
||
|
|
<Icon :name="repoIcon" :size="repoIconSize"></Icon>
|
||
|
|
</slot>
|
||
|
|
</div>
|
||
|
|
<d-editable-select class="g-branch-compare-select" v-model="repoName" :options="repoList"
|
||
|
|
:disabled="true">
|
||
|
|
<template #item="{ option, index }">
|
||
|
|
<div class="g-branch-compare-option">
|
||
|
|
<slot name="repo-option" :data="option" :index="index">
|
||
|
|
<Icon :name="repoIcon" :size="repoIconSize"></Icon>
|
||
|
|
<div class="g-branch-compare-label">{{ option.label }}</div>
|
||
|
|
</slot>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
</d-editable-select>
|
||
|
|
</div>
|
||
|
|
<div class="g-branch-compare-branch">
|
||
|
|
<div class="g-branch-compare-custom-icon">
|
||
|
|
<slot name="branch-prefix">
|
||
|
|
<Icon :name="branchIcon" :size="branchIconSize" />
|
||
|
|
</slot>
|
||
|
|
</div>
|
||
|
|
<BranchSelector v-model="branchForm.ref" type="branch" :branch="branch" :repoId="repoId" :branchParams="{
|
||
|
|
branch_type: ''
|
||
|
|
}" :isCommits="isCommits"/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</d-form-item>
|
||
|
|
<d-form-item field="branch" label="分支名称" :rules="[{ required: true, message: '分支名称不能为空', trigger: 'blur' }]">
|
||
|
|
<d-input v-model="branchForm.branch" placeholder="请输入分支名称" maxlength="200" />
|
||
|
|
</d-form-item>
|
||
|
|
<div v-if="prompt" class="mt-[-16px] text-[#c7000b]">{{ prompt }}</div>
|
||
|
|
<d-form-item field="description" label="描述" class="mb-0">
|
||
|
|
<d-textarea v-model="branchForm.description" :rows="5" placeholder="请补充分支的描述信息" show-count
|
||
|
|
maxlength="2000"></d-textarea>
|
||
|
|
</d-form-item>
|
||
|
|
</d-form>
|
||
|
|
</div>
|
||
|
|
<template #footer>
|
||
|
|
<div class="g-modal-footer">
|
||
|
|
<d-button @click="handleCancel">取消</d-button>
|
||
|
|
<d-button :disabled="isConfirm" variant="solid" color="primary" :loading="loading"
|
||
|
|
@click="handleConfirm">确认</d-button>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
</d-modal>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, reactive, watch, computed, provide } from 'vue';
|
||
|
|
import { useModel } from '@/utils/hooks/useModel';
|
||
|
|
import { getBranches, createBranches } from '@/api/branch';
|
||
|
|
import { useResizeObserver } from '@vueuse/core';
|
||
|
|
import getRepoId from '@/utils/getRepoId';
|
||
|
|
import { getRepoName } from '@/utils/index';
|
||
|
|
import { Message } from 'vue-devui/message';
|
||
|
|
import BranchSelector from '@/components/BranchTagSelector/SingleSelector.vue';
|
||
|
|
import { repoInfoStore } from '@/stores/Repo';
|
||
|
|
import { branchNameRegExp, tagNamePrefixHeadsRegExp, tagNamePrefixRemotesRegExp } from '@/utils/regex';
|
||
|
|
|
||
|
|
const repoId = ref(getRepoId());
|
||
|
|
const branchFormRef = ref<any>(null);
|
||
|
|
|
||
|
|
const repoName = getRepoName();
|
||
|
|
const { getRepoInfo } = repoInfoStore();
|
||
|
|
|
||
|
|
const props = withDefaults(defineProps<{
|
||
|
|
modelValue?: boolean
|
||
|
|
'onUpdate:modelValue'?: Function
|
||
|
|
branches?: string[]
|
||
|
|
options?: { name: string, value: string }[],
|
||
|
|
branch?: string;
|
||
|
|
repoIcon?: string;
|
||
|
|
branchIcon?: string;
|
||
|
|
repoIconSize?: string;
|
||
|
|
branchIconSize?: string;
|
||
|
|
repoReadonly?: boolean;
|
||
|
|
maxHeight?: number;
|
||
|
|
isCommits?: boolean
|
||
|
|
}>(), {
|
||
|
|
data: () => {
|
||
|
|
return {
|
||
|
|
repoId: null,
|
||
|
|
sourceId: null,
|
||
|
|
targetId: null
|
||
|
|
};
|
||
|
|
},
|
||
|
|
branchList: () => [],
|
||
|
|
repoIcon: 'version-history',
|
||
|
|
branchIcon: 'gt-branches',
|
||
|
|
repoIconSize: '16px',
|
||
|
|
branchIconSize: '16px',
|
||
|
|
repoReadonly: true,
|
||
|
|
maxHeight: 250
|
||
|
|
|
||
|
|
});
|
||
|
|
const emits = defineEmits(['confirm', 'update:modelValue']);
|
||
|
|
|
||
|
|
const branchForm = reactive({
|
||
|
|
ref: props.branch || '',
|
||
|
|
branch: '',
|
||
|
|
description: ''
|
||
|
|
});
|
||
|
|
watch(() => props.branch, val => {
|
||
|
|
branchForm.ref = val;
|
||
|
|
});
|
||
|
|
const checkRepoNameRule = (rule: object, value: string, callback: Function) => {
|
||
|
|
if (!branchNameRegExp.test(value)) {
|
||
|
|
return callback(new Error('分支名称由字母、数字-_和/组成;且/不能连续,分支名称必须以字母或数字开头,分支名称最多可以包含 200 个字符。'));
|
||
|
|
} else if (!tagNamePrefixRemotesRegExp.test(value)) {
|
||
|
|
return callback(new Error('格式错误,请勿以"refs/remotes/"开头'));
|
||
|
|
} else if (!tagNamePrefixHeadsRegExp.test(value)) {
|
||
|
|
return callback(new Error('格式错误,请勿以"refs/heads/"开头'));
|
||
|
|
}
|
||
|
|
return callback();
|
||
|
|
};
|
||
|
|
const rules = {
|
||
|
|
ref: [{ required: true, message: '基于分支不能为空', trigger: 'blur' }],
|
||
|
|
branch: [{ validator: checkRepoNameRule }, { required: true, message: '分支名称不能为空', trigger: 'blur' }]
|
||
|
|
};
|
||
|
|
|
||
|
|
const { vModels } = useModel(props, emits);
|
||
|
|
|
||
|
|
const repoList = ref([]);
|
||
|
|
|
||
|
|
const ele = ref(null);
|
||
|
|
const selectWidth = ref(0);
|
||
|
|
const { stop } = useResizeObserver(ele, (entries) => {
|
||
|
|
const entry = entries[0];
|
||
|
|
const { width } = entry.contentRect;
|
||
|
|
selectWidth.value = width;
|
||
|
|
});
|
||
|
|
|
||
|
|
const handleCancel = () => {
|
||
|
|
resetForm();
|
||
|
|
vModels.value = false;
|
||
|
|
};
|
||
|
|
const resetForm = () => {
|
||
|
|
branchForm.ref = '';
|
||
|
|
branchForm.branch = '';
|
||
|
|
branchForm.description = '';
|
||
|
|
};
|
||
|
|
|
||
|
|
const loading = ref(false);
|
||
|
|
const handleConfirm = () => {
|
||
|
|
branchFormRef.value.validate(async(isValid: any, invalidFields: any) => {
|
||
|
|
if (!isValid) return;
|
||
|
|
|
||
|
|
loading.value = true;
|
||
|
|
|
||
|
|
const params = {
|
||
|
|
repoId: repoId.value,
|
||
|
|
ref: branchForm.ref,
|
||
|
|
branch: branchForm.branch,
|
||
|
|
description: branchForm.description
|
||
|
|
};
|
||
|
|
const { error } = await createBranches(params);
|
||
|
|
if (!error) {
|
||
|
|
emits('confirm', params.branch);
|
||
|
|
resetForm();
|
||
|
|
vModels.value = false;
|
||
|
|
Message.success('分支创建成功');
|
||
|
|
// 更新顶部tag信息
|
||
|
|
getRepoInfo();
|
||
|
|
}
|
||
|
|
|
||
|
|
loading.value = false;
|
||
|
|
});
|
||
|
|
};
|
||
|
|
const isConfirm = computed(() => {
|
||
|
|
if (branchForm.branch && branchForm.ref) {
|
||
|
|
return false;
|
||
|
|
} else {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
watch(() => vModels.value, () => {
|
||
|
|
branchForm.ref = props.branch;
|
||
|
|
});
|
||
|
|
const prompt = ref('');
|
||
|
|
const bOptions = ref();
|
||
|
|
provide('bOptions', bOptions);
|
||
|
|
watch(() => branchForm.branch, val => {
|
||
|
|
const bstr = val.split('/');
|
||
|
|
const b = bOptions.value.find((item: any) => item.name === bstr[0]);
|
||
|
|
if (b) {
|
||
|
|
prompt.value = bstr[0] + '分支已存在';
|
||
|
|
} else {
|
||
|
|
prompt.value = '';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
$g-branch-compare-border-color: var(--color-G300);
|
||
|
|
$g-branch-compare-border-color-hover: var(--color-CG900);
|
||
|
|
$g-branch-compare-border-color-focus: var(--color-link);
|
||
|
|
$g-branch-compare-border-radius: 3px;
|
||
|
|
$g-branch-compare-color: var(--color-CG900);
|
||
|
|
$g-branch-compare-select-bg-color: linear-gradient(180deg, #FCFCFC 0%, #F8F9FB 100%);
|
||
|
|
|
||
|
|
.g-branch-compare {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
overflow: hidden;
|
||
|
|
|
||
|
|
&-tip {
|
||
|
|
min-height: 36px;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-box {
|
||
|
|
flex: 1;
|
||
|
|
border: 1px solid $g-branch-compare-border-color;
|
||
|
|
border-radius: $g-branch-compare-border-radius;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-header {
|
||
|
|
padding: 24px 32px 20px 32px;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-content {
|
||
|
|
padding: 0px 0px 0 0px;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-repo {
|
||
|
|
position: relative;
|
||
|
|
margin-bottom: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-branch {
|
||
|
|
position: relative;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
&-custom-icon {
|
||
|
|
position: absolute;
|
||
|
|
left: 0;
|
||
|
|
top: 0;
|
||
|
|
width: 32px;
|
||
|
|
height: 32px;
|
||
|
|
line-height: 38px;
|
||
|
|
padding-left: 16px;
|
||
|
|
z-index: 2;
|
||
|
|
display: flex;
|
||
|
|
|
||
|
|
:deep(.icon) {
|
||
|
|
align-self: center;
|
||
|
|
color: $g-branch-compare-color !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.devui-icon__container) {
|
||
|
|
align-self: center;
|
||
|
|
color: $g-branch-compare-color !important;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
&-title {
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 500;
|
||
|
|
line-height: normal;
|
||
|
|
color: $g-branch-compare-color;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-select {
|
||
|
|
:deep(.devui-editable-select-input__wrapper) {
|
||
|
|
background: $g-branch-compare-select-bg-color;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.devui-editable-select-input__inner) {
|
||
|
|
color: $g-branch-compare-color;
|
||
|
|
padding-left: 32px;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.devui-editable-select__item) {
|
||
|
|
padding: 8px 4px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
&-option {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-label {
|
||
|
|
margin-left: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
&-switch {
|
||
|
|
margin: 0 32px;
|
||
|
|
width: 32px;
|
||
|
|
height: 32px;
|
||
|
|
border-radius: $g-branch-compare-border-radius;
|
||
|
|
background: linear-gradient(180deg, #FCFCFC 0%, #F8F9FB 100%);
|
||
|
|
box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.05);
|
||
|
|
border: 1px solid var(--color-CG400);
|
||
|
|
display: flex;
|
||
|
|
|
||
|
|
:deep(.icon) {
|
||
|
|
margin: 0 auto;
|
||
|
|
align-self: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
&:hover {
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.devui-form__label--required:before) {
|
||
|
|
content: "*";
|
||
|
|
color: var(--color-danger);
|
||
|
|
display: inline-block;
|
||
|
|
margin-right: 8px;
|
||
|
|
margin-left: -12px
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.devui-select__input) {
|
||
|
|
color: $g-branch-compare-color;
|
||
|
|
padding-left: 36px;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
|
||
|
|
<style lang="scss">
|
||
|
|
.invi-modal-body {
|
||
|
|
}
|
||
|
|
|
||
|
|
.branch-add-modal {
|
||
|
|
width: 410px;
|
||
|
|
box-sizing: border-box;
|
||
|
|
padding: 0 0;
|
||
|
|
|
||
|
|
.devui-modal__body {
|
||
|
|
padding: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.tip-desc {
|
||
|
|
// @apply text-G900 px-[22px] my-[18px] leading-[28px] break-all;
|
||
|
|
}
|
||
|
|
|
||
|
|
.devui-modal__header {
|
||
|
|
padding: 14px 24px;
|
||
|
|
height: auto !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-close {
|
||
|
|
right: 24px;
|
||
|
|
top: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.line {
|
||
|
|
width: 100%;
|
||
|
|
height: 1px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|