Files
Situation-Awareness-Platfor…/src/components/BranchAdd/index.vue

374 lines
9.8 KiB
Vue
Raw Normal View History

2025-03-12 18:41:20 +08:00
<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>