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); return setI18nLanguage(lang);
} }
export function useI18n() {
return {
t: i18n.global.t,
locale: i18n.global.locale,
};
}
export { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, setI18nLanguage }; export { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, setI18nLanguage };
export default i18n; 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 $t = i18n.global.t;
const $te = i18n.global.te; 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 ImportLocaleFn, type LocaleSetupOptions, type SupportedLanguagesType } from "./typing";
// export type { CompileError } from "@intlify/core-base"; // export type { CompileError } from "@intlify/core-base";
export { useI18n } from "vue-i18n"; // export { useI18n } from "vue-i18n";
export type { Locale } 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 { PluginGroup, usePluginStore } from "/@/store/plugin";
import { createNotificationApi } from "/@/views/certd/notification/api"; import { createNotificationApi } from "/@/views/certd/notification/api";
import GroupSelector from "../group/group-selector.vue"; import GroupSelector from "../group/group-selector.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "/src/locales";
export function fillPipelineByDefaultForm(pipeline: any, form: any) { export function fillPipelineByDefaultForm(pipeline: any, form: any) {
const triggers = []; const triggers = [];
@ -233,8 +232,7 @@ export function useCertPipelineCreator() {
}, },
order: 9999, order: 9999,
}, },
} },
}, },
}, },
}; };

View File

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

View File

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

View File

@ -77,27 +77,27 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
buttons: { buttons: {
edit: { show: false }, edit: { show: false },
copy: { show: false }, copy: { show: false },
use: { // use: {
text: null, // text: null,
title: "使用此模版创建流水线", // title: "使用此模版创建流水线",
icon: "ion:duplicate-outline", // icon: "ion:duplicate-outline",
click({ row }) { // click({ row }) {
openCreateFromTemplateDialog({ // openCreateFromTemplateDialog({
templateId: row.id, // templateId: row.id,
onCreated: ({ id }) => { // onCreated: ({ id }) => {
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: true } }); // router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
}, // },
}); // });
}, // },
}, // },
import: { // import: {
text: null, // text: null,
title: "批量导入创建", // title: "批量导入创建",
icon: "ion:duplicate", // icon: "ion:duplicate",
click({ row }) { // click({ row }) {
router.push({ path: "/certd/pipeline/template/import", query: { templateId: row.id } }); // router.push({ path: "/certd/pipeline/template/import", query: { templateId: row.id } });
}, // },
}, // },
}, },
}, },
columns: { columns: {
@ -124,11 +124,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
search: { search: {
show: true, show: true,
}, },
form: {
rules: [{ required: true, message: "请输入模版名称" }],
},
column: { column: {
width: 400, width: 400,
sorter: true, sorter: true,
cellRender({ row, value }) { 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)); const res = await templateApi.GetDetail(parseInt(templateId));
detail.value = res; 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(); const pluginStore = usePluginStore();

View File

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

View File

@ -91,7 +91,10 @@ onMounted(async () => {
await pluginStore.init(); await pluginStore.init();
await nextTick(); await nextTick();
const steps = getStepsMap(props.detail.pipeline); 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) }); appendCrudOptions({ ...buildColumns(steps) });
crudBinding.value.data = []; crudBinding.value.data = [];
await crudExpose.editable.enable({ mode: "row" }); await crudExpose.editable.enable({ mode: "row" });
@ -101,6 +104,9 @@ defineExpose({
getData() { getData() {
return crudBinding.value.data; return crudBinding.value.data;
}, },
clear() {
crudBinding.value.data = [];
},
}); });
</script> </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() { export function useTemplate() {
const { openCrudFormDialog } = useFormWrapper(); const { openCrudFormDialog } = useFormWrapper();
@ -102,28 +130,12 @@ export function useTemplate() {
let newPipeline = cloneDeep(pipeline); let newPipeline = cloneDeep(pipeline);
newPipeline = fillPipelineByDefaultForm(newPipeline, form); newPipeline = fillPipelineByDefaultForm(newPipeline, form);
//填充模版参数 //填充模版参数
const steps: any = {}; const { id } = await createPipelineByTemplate({
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,
templateId: detail.template.id, templateId: detail.template.id,
templateForm: tempInputs,
pipeline: newPipeline,
title: form.title,
groupId: form.groupId,
}); });
if (req.onCreated) { if (req.onCreated) {
req.onCreated({ id }); req.onCreated({ id });