chore: 模版导入式创建流水线

pull/453/head
xiaojunnuo 2025-06-29 01:33:43 +08:00
parent 37e6548246
commit 04422a4637
10 changed files with 916 additions and 843 deletions

View File

@ -133,5 +133,12 @@ async function loadLocaleMessages(lang: SupportedLanguagesType) {
return setI18nLanguage(lang);
}
export function useI18n() {
return {
t: i18n.global.t,
locale: i18n.global.locale,
};
}
export { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, setI18nLanguage };
export default i18n;

View File

@ -1,12 +1,12 @@
import { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, setI18nLanguage } from "./i18n";
import { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, setI18nLanguage, useI18n } from "./i18n";
const $t = i18n.global.t;
const $te = i18n.global.te;
export { $t, $te, i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, setI18nLanguage };
export { $t, $te, i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, setI18nLanguage, useI18n };
export { type ImportLocaleFn, type LocaleSetupOptions, type SupportedLanguagesType } from "./typing";
// export type { CompileError } from "@intlify/core-base";
export { useI18n } from "vue-i18n";
// export { useI18n } from "vue-i18n";
export type { Locale } from "vue-i18n";

View File

@ -11,8 +11,7 @@ import * as api from "../api";
import { PluginGroup, usePluginStore } from "/@/store/plugin";
import { createNotificationApi } from "/@/views/certd/notification/api";
import GroupSelector from "../group/group-selector.vue";
import { useI18n } from "vue-i18n";
import { useI18n } from "/src/locales";
export function fillPipelineByDefaultForm(pipeline: any, form: any) {
const triggers = [];
@ -233,8 +232,7 @@ export function useCertPipelineCreator() {
},
order: 9999,
},
}
},
},
},
};

View File

@ -1,5 +1,4 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
@ -14,11 +13,14 @@ import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
import { useCertViewer } from "/@/views/certd/pipeline/use";
import { useI18n } from "/src/locales";
export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const lastResRef = ref();
const { t } = useI18n();
const { openAddCertdPipelineDialog } = useCertPipelineCreator();
const { openUploadCreateDialog } = useCertUpload();
@ -367,7 +369,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
const leftDays = dayjs(row.lastVars.certExpiresTime).diff(dayjs(), "day");
const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100;
return <a-progress percent={percent} strokeColor={color} format={(percent) => `${leftDays}`} />;
return <a-progress percent={percent} strokeColor={color} format={percent => `${leftDays}`} />;
},
width: 150,
},
@ -545,7 +547,6 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
sorter: true,
},
},
},
},
};

View File

@ -22,7 +22,6 @@
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted, ref } from "vue";
import { dict, useFs } from "@fast-crud/fast-crud";

View File

@ -77,27 +77,27 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
buttons: {
edit: { show: false },
copy: { show: false },
use: {
text: null,
title: "使用此模版创建流水线",
icon: "ion:duplicate-outline",
click({ row }) {
openCreateFromTemplateDialog({
templateId: row.id,
onCreated: ({ id }) => {
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: true } });
},
});
},
},
import: {
text: null,
title: "批量导入创建",
icon: "ion:duplicate",
click({ row }) {
router.push({ path: "/certd/pipeline/template/import", query: { templateId: row.id } });
},
},
// use: {
// text: null,
// title: "使用此模版创建流水线",
// icon: "ion:duplicate-outline",
// click({ row }) {
// openCreateFromTemplateDialog({
// templateId: row.id,
// onCreated: ({ id }) => {
// router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
// },
// });
// },
// },
// import: {
// text: null,
// title: "批量导入创建",
// icon: "ion:duplicate",
// click({ row }) {
// router.push({ path: "/certd/pipeline/template/import", query: { templateId: row.id } });
// },
// },
},
},
columns: {
@ -124,11 +124,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
search: {
show: true,
},
form: {
rules: [{ required: true, message: "请输入模版名称" }],
},
column: {
width: 400,
sorter: true,
cellRender({ row, value }) {
return <router-link to={{ path: "/certd/pipeline/template/edit", query: { templateId: row.id } }}>{value}</router-link>;
return (
<router-link class={"flex items-center"} to={{ path: "/certd/pipeline/template/edit", query: { templateId: row.id } }}>
<fs-icon icon={"ion:create-outline"}></fs-icon>
<span class={"ml-5"}> {value}</span>
</router-link>
);
},
},
},
@ -182,6 +190,46 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
useCreate: {
title: "使用此模版",
form: { show: false },
column: {
conditionalRender: false,
width: 400,
cellRender({ row }) {
function create() {
openCreateFromTemplateDialog({
templateId: row.id,
onCreated: ({ id }) => {
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
},
});
}
return (
<a class={"flex items-center"} onClick={create}>
<fs-icon icon={"ion:duplicate-outline"}></fs-icon>
<span class={"ml-5"}>线</span>
</a>
);
},
},
},
useImport: {
title: "使用此模版",
form: { show: false },
column: {
conditionalRender: false,
width: 400,
cellRender({ row }) {
return (
<router-link class={"flex items-center"} to={{ path: "/certd/pipeline/template/import", query: { templateId: row.id } }}>
<fs-icon icon={"ion:duplicate"}></fs-icon>
<span class={"ml-5"}>线</span>
</router-link>
);
},
},
},
},
},
};

View File

@ -112,7 +112,9 @@ async function getTemplateDetail() {
}
const res = await templateApi.GetDetail(parseInt(templateId));
detail.value = res;
templateProps.value = JSON.parse(res.template.content ?? "{input:{}}");
if (res.template.content) {
templateProps.value = JSON.parse(res.template.content);
}
}
const pluginStore = usePluginStore();

View File

@ -20,8 +20,8 @@ import { templateApi } from "../api";
import { createFormOptions } from "/@/views/certd/pipeline/template/import/form";
import { cloneDeep } from "lodash-es";
import { fillPipelineByDefaultForm } from "/@/views/certd/pipeline/certd-form/use";
import { eachSteps } from "/@/views/certd/pipeline/utils";
import { createPipelineByTemplate } from "/@/views/certd/pipeline/template/use";
import { notification, Modal } from "ant-design-vue";
const route = useRoute();
const templateId = route.query.templateId as string;
@ -59,8 +59,10 @@ async function doImport() {
const importTableRef = formRef.value.getComponentRef("templateProps");
const templateList = importTableRef.value.getData();
const templateList = importTableRef.getData();
const progress = ref({ total: templateList.length, current: 0 });
async function requestImport() {
for (let i = 0; i < templateList.length; i++) {
const tempInputs = templateList[i];
const title = tempInputs.title;
@ -68,36 +70,34 @@ async function doImport() {
let newPipeline = cloneDeep(detail.value.pipeline);
newPipeline = fillPipelineByDefaultForm(newPipeline, form);
//
const steps: any = {};
eachSteps(newPipeline, (step: any) => {
steps[step.id] = step;
await createPipelineByTemplate({
templateId: parseInt(templateId),
templateForm: tempInputs,
pipeline: newPipeline,
title: title,
groupId: form.groupId,
});
progress.value.current = progress.value.current + 1;
}
notification.success({
message: "导入完成",
});
for (const inputKey in tempInputs) {
const [stepId, key] = inputKey.split(".");
const step = steps[stepId];
if (step) {
step.input[key] = tempInputs[inputKey];
importTableRef.clear();
}
}
newPipeline.title = title;
const groupId = form.groupId;
await templateApi.CreatePipelineByTemplate({
title,
content: JSON.stringify(newPipeline),
keepHistoryCount: 30,
groupId,
templateId: parseInt(templateId),
pipeline: {
title: form.title,
templateProps: templateList,
requestImport();
Modal.info({
title: "导入中",
content() {
return (
<div>
当前导入进度 {progress.value.current} / {progress.value.total}
</div>
);
},
});
}
}
</script>
<style lang="less">

View File

@ -91,7 +91,10 @@ onMounted(async () => {
await pluginStore.init();
await nextTick();
const steps = getStepsMap(props.detail.pipeline);
templateProps.value = JSON.parse(props.detail.template?.content ?? "{input:{}}");
if (props.detail.template?.content) {
templateProps.value = JSON.parse(props.detail.template?.content);
}
appendCrudOptions({ ...buildColumns(steps) });
crudBinding.value.data = [];
await crudExpose.editable.enable({ mode: "row" });
@ -101,6 +104,9 @@ defineExpose({
getData() {
return crudBinding.value.data;
},
clear() {
crudBinding.value.data = [];
},
});
</script>

View File

@ -67,6 +67,34 @@ export function createExtraColumns() {
};
}
export async function createPipelineByTemplate(opts: { templateId: number; title: string; groupId?: string; pipeline: any; templateForm: any; keepHistoryCount?: number }) {
const { title, groupId, pipeline, templateForm, keepHistoryCount, templateId } = opts;
//填充模版参数
const steps: any = {};
eachSteps(pipeline, (step: any) => {
steps[step.id] = step;
});
for (const stepId in templateForm) {
const step = steps[stepId];
const tempStep = templateForm[stepId];
if (step) {
for (const key in tempStep) {
step.input[key] = tempStep[key];
}
}
}
pipeline.title = title;
return await templateApi.CreatePipelineByTemplate({
title,
content: JSON.stringify(pipeline),
keepHistoryCount: keepHistoryCount ?? 30,
groupId,
templateId,
});
}
export function useTemplate() {
const { openCrudFormDialog } = useFormWrapper();
@ -102,28 +130,12 @@ export function useTemplate() {
let newPipeline = cloneDeep(pipeline);
newPipeline = fillPipelineByDefaultForm(newPipeline, form);
//填充模版参数
const steps: any = {};
eachSteps(newPipeline, (step: any) => {
steps[step.id] = step;
});
for (const inputKey in tempInputs) {
const [stepId, key] = inputKey.split(".");
const step = steps[stepId];
if (step) {
step.input[key] = tempInputs[inputKey];
}
}
const title = form.title;
newPipeline.title = title;
const groupId = form.groupId;
const { id } = await templateApi.CreatePipelineByTemplate({
title,
content: JSON.stringify(newPipeline),
keepHistoryCount: 30,
groupId,
const { id } = await createPipelineByTemplate({
templateId: detail.template.id,
templateForm: tempInputs,
pipeline: newPipeline,
title: form.title,
groupId: form.groupId,
});
if (req.onCreated) {
req.onCreated({ id });