Other translations

pull/436/head
Lorenzo 2025-06-25 23:52:44 +02:00
parent cdac12bb2f
commit daaef316e9
18 changed files with 1540 additions and 1328 deletions

View File

@ -1,120 +1,132 @@
<template>
<div class="cron-editor">
<div class="flex-o">
<cron-light :disabled="disabled" :readonly="readonly" :period="period" class="flex-o cron-ant" locale="zh-CN" format="quartz" :model-value="modelValue" @update:model-value="onUpdate" @error="onError" />
</div>
<div class="mt-5 flex">
<a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input>
<fs-icon icon="ion:close-circle" class="pointer fs-16 ml-5 color-gray" title="清除选择" @click="onClear"></fs-icon>
</div>
<div class="helper">下次触发时间{{ nextTime }}</div>
<div class="fs-helper">{{ errorMessage }}</div>
</div>
<div class="cron-editor">
<div class="flex-o">
<cron-light :disabled="disabled" :readonly="readonly" :period="period" class="flex-o cron-ant"
locale="zh-CN" format="quartz" :model-value="modelValue" @update:model-value="onUpdate"
@error="onError" />
</div>
<div class="mt-5 flex">
<a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input>
<fs-icon icon="ion:close-circle" class="pointer fs-16 ml-5 color-gray" :title="t('certd.cron.clearTip')"
@click="onClear"></fs-icon>
</div>
<div class="helper">{{ t('certd.cron.nextTrigger') }}{{ nextTime }}</div>
<div class="fs-helper">{{ errorMessage }}</div>
</div>
</template>
<script lang="ts" setup>
import parser from "cron-parser";
import { computed, ref } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import { getCronNextTimes } from "/@/components/cron-editor/utils";
defineOptions({
name: "CronEditor",
name: "CronEditor",
});
const props = defineProps<{
modelValue?: string;
disabled?: boolean;
readonly?: boolean;
allowEveryMin?: boolean;
modelValue?: string;
disabled?: boolean;
readonly?: boolean;
allowEveryMin?: boolean;
}>();
const period = ref<string>("");
if (props.modelValue == null || props.modelValue.endsWith("* * *")) {
period.value = "day";
period.value = "day";
} else if (props.modelValue.endsWith("* *")) {
period.value = "month";
period.value = "month";
} else if (props.modelValue.endsWith("*")) {
period.value = "year";
period.value = "year";
}
const emit = defineEmits<{
"update:modelValue": any;
change: any;
"update:modelValue": any;
change: any;
}>();
const errorMessage = ref<string | null>(null);
const onUpdate = (value: string) => {
if (value === props.modelValue) {
return;
}
const arr: string[] = value.split(" ");
if (arr[0] === "*") {
arr[0] = "0";
}
if (!props.allowEveryMin) {
if (arr[1] === "*") {
arr[1] = "0";
}
}
if (value === props.modelValue) {
return;
}
const arr: string[] = value.split(" ");
if (arr[0] === "*") {
arr[0] = "0";
}
if (!props.allowEveryMin) {
if (arr[1] === "*") {
arr[1] = "0";
}
}
value = arr.join(" ");
value = arr.join(" ");
emit("update:modelValue", value);
errorMessage.value = undefined;
emit("update:modelValue", value);
errorMessage.value = undefined;
};
const onPeriod = (value: string) => {
period.value = value;
period.value = value;
};
const onChange = (e: any) => {
const value = e.target.value;
onUpdate(value);
const value = e.target.value;
onUpdate(value);
};
const onError = (error: any) => {
errorMessage.value = error;
errorMessage.value = error;
};
const onClear = () => {
if (props.disabled) {
return;
}
onUpdate("");
if (props.disabled) {
return;
}
onUpdate("");
};
const nextTime = computed(() => {
if (props.modelValue == null) {
return "请先设置正确的cron表达式";
}
if (props.modelValue == null) {
return t("certd.cron.tip");
}
try {
const nextTimes = getCronNextTimes(props.modelValue, 2);
return nextTimes.join("");
} catch (e) {
console.log(e);
return "请先设置正确的cron表达式";
}
try {
const nextTimes = getCronNextTimes(props.modelValue, 2);
return nextTimes.join("");
} catch (e) {
console.log(e);
return t("certd.cron.tip");
}
});
</script>
<style lang="less">
.cron-editor {
.cron-ant {
flex-wrap: wrap;
&* > {
margin-bottom: 2px;
display: flex;
align-items: center;
}
.vcron-select-list {
min-width: 56px;
}
.vcron-select-input {
min-height: 22px;
background-color: #fff;
}
.vcron-select-container {
display: flex;
align-items: center;
}
}
.cron-ant {
flex-wrap: wrap;
&*> {
margin-bottom: 2px;
display: flex;
align-items: center;
}
.vcron-select-list {
min-width: 56px;
}
.vcron-select-input {
min-height: 22px;
background-color: #fff;
}
.vcron-select-container {
display: flex;
align-items: center;
}
}
}
</style>

View File

@ -185,4 +185,93 @@ export default {
download: {
title: "Download Certificate"
},
source: "Source Code",
github: "GitHub",
gitee: "Gitee",
cron: {
clearTip: "Clear Selection",
nextTrigger: "Next Trigger Time",
tip: "Please set a valid cron expression first"
},
cronForm: {
title: "Scheduled Script",
helper: "Click the button above to select the time for daily execution.\nIt is recommended to run once a day. Tasks will be skipped if the certificate is not expiring.",
required: "This field is required"
},
email: {
title: "Recipient Email",
helper: "Enter your recipient email addresses. Multiple addresses are supported.",
required: "This field is required"
},
plugin: {
selectTitle: "Certificate Apply Plugin",
jsAcme: "JS-ACME: Easy to use, powerful features [Recommended]",
legoAcme: "Lego-ACME: Based on Lego, supports a wide range of DNS providers, suitable for users familiar with Lego"
},
pipelineForm: {
createTitle: "Create Certificate Pipeline",
moreParams: "More Parameters",
triggerCronTitle: "Scheduled Trigger",
triggerCronHelper:
"Click the button above to choose a daily execution time.\nIt is recommended to trigger once per day. The task will be skipped if the certificate has not expired and will not be executed repeatedly.",
notificationTitle: "Failure Notification",
notificationHelper: "Get real-time alerts when the task fails",
groupIdTitle: "Pipeline Group"
},
notificationDefault: "Use Default Notification",
monitor: {
title: "Site Certificate Monitoring",
description: "Check website certificates' expiration at 0:00 daily; reminders sent 10 days before expiration (using default notification channel);",
settingLink: "Site Monitoring Settings",
limitInfo: "Basic edition limited to 1, professional and above unlimited, current",
checkAll: "Check All",
confirmTitle: "Confirm",
confirmContent: "Confirm to trigger check for all site certificates?",
checkSubmitted: "Check task submitted",
pleaseRefresh: "Please refresh the page later to see the results",
siteName: "Site Name",
enterSiteName: "Please enter the site name",
domain: "Domain",
enterDomain: "Please enter the domain",
enterValidDomain: "Please enter a valid domain",
httpsPort: "HTTPS Port",
enterPort: "Please enter the port",
certInfo: "Certificate Info",
issuer: "Issuer",
certDomains: "Certificate Domains",
certProvider: "Issuer",
certStatus: "Certificate Status",
status: {
ok: "Valid",
expired: "Expired",
},
certExpiresTime: "Certificate Expiration",
expired: "expired",
days: "days",
lastCheckTime: "Last Check Time",
disabled: "Enable/Disable",
ipCheck: "Enable IP Check",
selectRequired: "Please select",
ipCheckConfirm: "Are you sure to {status} IP check?",
ipCount: "IP Count",
checkStatus: "Check Status",
pipelineId: "Linked Pipeline ID",
certInfoId: "Certificate ID",
checkSubmittedRefresh: "Check task submitted. Please refresh later to view the result.",
ipManagement: "IP Management",
bulkImport: "Bulk Import",
basicLimitError: "Basic version allows only one monitoring site. Please upgrade to the Pro version.",
limitExceeded: "Sorry, you can only create up to {max} monitoring records. Please purchase or upgrade your plan.",
},
checkStatus: {
success: "Success",
checking: "Checking",
error: "Error",
},
domainList: {
title: "Domain List",
helper: "Format: domain:port:name, one per line. Port and name are optional.\nExamples:\nwww.baidu.com:443:Baidu\nwww.taobao.com::Taobao\nwww.google.com",
required: "Please enter domains to import",
placeholder: "www.baidu.com:443:Baidu\nwww.taobao.com::Taobao\nwww.google.com\n",
},
};

View File

@ -1,22 +0,0 @@
{
"back": "Back",
"backToHome": "Back To Home",
"login": "Login",
"logout": "Logout",
"prompt": "Prompt",
"cancel": "Cancel",
"confirm": "Confirm",
"reset": "Reset",
"noData": "No Data",
"refresh": "Refresh",
"loadingMenu": "Loading Menu",
"query": "Search",
"search": "Search",
"enabled": "Enabled",
"disabled": "Disabled",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"yes": "Yes",
"no": "No"
}

View File

@ -0,0 +1,22 @@
export default {
"back": "Back",
"backToHome": "Back To Home",
"login": "Login",
"logout": "Logout",
"prompt": "Prompt",
"cancel": "Cancel",
"confirm": "Confirm",
"reset": "Reset",
"noData": "No Data",
"refresh": "Refresh",
"loadingMenu": "Loading Menu",
"query": "Search",
"search": "Search",
"enabled": "Enabled",
"disabled": "Disabled",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"yes": "Yes",
"no": "No"
}

View File

@ -5,6 +5,7 @@ import tutorial from './tutorial';
import preferences from './preferences';
import ui from './ui';
import guide from './guide';
import common from './common';
export default {
certd,
@ -13,5 +14,6 @@ export default {
ui,
tutorial,
preferences,
guide
guide,
common
};

View File

@ -191,4 +191,93 @@ export default {
download: {
title: "下载证书"
},
source: "源码",
github: "github",
gitee: "gitee",
cron: {
clearTip: "清除选择",
nextTrigger: "下次触发时间",
tip: "请先设置正确的cron表达式"
},
cronForm: {
title: "定时脚本",
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行",
required: "此项必填"
},
email: {
title: "收件邮箱",
helper: "输入你的收件邮箱地址,支持多个邮箱",
required: "此项必填"
},
plugin: {
selectTitle: "证书申请插件",
jsAcme: "JS-ACME使用简单方便功能强大【推荐】",
legoAcme: "Lego-ACME基于Lego实现支持海量DNS提供商熟悉LEGO的用户可以使用"
},
pipelineForm: {
createTitle: "创建证书流水线",
moreParams: "更多参数",
triggerCronTitle: "定时触发",
triggerCronHelper:
"点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行",
notificationTitle: "失败通知",
notificationHelper: "任务执行失败实时提醒",
groupIdTitle: "流水线分组"
},
notificationDefault: "使用默认通知",
monitor: {
title: "站点证书监控",
description: "每天0点检查网站证书的过期时间到期前10天时将发出提醒使用默认通知渠道;",
settingLink: "站点监控设置",
limitInfo: "基础版限制1条专业版以上无限制当前",
checkAll: "检查全部",
confirmTitle: "确认",
confirmContent: "确认触发检查全部站点证书吗?",
checkSubmitted: "检查任务已提交",
pleaseRefresh: "请稍后刷新页面查看结果",
siteName: "站点名称",
enterSiteName: "请输入站点名称",
domain: "网站域名",
enterDomain: "请输入域名",
enterValidDomain: "请输入正确的域名",
httpsPort: "HTTPS端口",
enterPort: "请输入端口",
certInfo: "证书信息",
issuer: "证书颁发机构",
certDomains: "证书域名",
certProvider: "颁发机构",
certStatus: "证书状态",
status: {
ok: "正常",
expired: "过期",
},
certExpiresTime: "证书到期时间",
expired: "过期",
days: "天",
lastCheckTime: "上次检查时间",
disabled: "禁用启用",
ipCheck: "开启IP检查",
selectRequired: "请选择",
ipCheckConfirm: "确定{status}IP检查",
ipCount: "IP数量",
checkStatus: "检查状态",
pipelineId: "关联流水线ID",
certInfoId: "证书ID",
checkSubmittedRefresh: "检查任务已提交,请稍后刷新查看结果",
ipManagement: "IP管理",
bulkImport: "批量导入",
basicLimitError: "基础版只能添加一个监控站点,请赞助升级专业版",
limitExceeded: "对不起,您最多只能创建条{max}监控记录,请购买或升级套餐",
},
checkStatus: {
success: "成功",
checking: "检查中",
error: "异常",
},
domainList: {
title: "域名列表",
helper: "格式【域名:端口:名称】,一行一个,其中端口、名称可以省略\n比如\nwww.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com",
required: "请输入要导入的域名",
placeholder: "www.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com\n",
},
};

View File

@ -1,22 +0,0 @@
{
"back": "返回",
"backToHome": "返回首页",
"login": "登录",
"logout": "退出登录",
"prompt": "提示",
"cancel": "取消",
"confirm": "确认",
"reset": "重置",
"noData": "暂无数据",
"refresh": "刷新",
"loadingMenu": "加载菜单中",
"query": "查询",
"search": "搜索",
"enabled": "已启用",
"disabled": "已禁用",
"edit": "修改",
"delete": "删除",
"create": "新增",
"yes": "是",
"no": "否"
}

View File

@ -0,0 +1,22 @@
export default {
"back": "返回",
"backToHome": "返回首页",
"login": "登录",
"logout": "退出登录",
"prompt": "提示",
"cancel": "取消",
"confirm": "确认",
"reset": "重置",
"noData": "暂无数据",
"refresh": "刷新",
"loadingMenu": "加载菜单中",
"query": "查询",
"search": "搜索",
"enabled": "已启用",
"disabled": "已禁用",
"edit": "修改",
"delete": "删除",
"create": "新增",
"yes": "是",
"no": "否"
}

View File

@ -5,6 +5,7 @@ import tutorial from './tutorial';
import preferences from './preferences';
import ui from './ui';
import guide from './guide';
import common from './common';
export default {
certd,
@ -13,5 +14,6 @@ export default {
ui,
tutorial,
preferences,
guide
guide,
common
};

View File

@ -1,33 +1,36 @@
import i18n from '/@/locales/i18n';
export const headerResource = [
{
title: "文档",
path: "https://certd.docmirror.cn",
meta: {
icon: "ion:document-text-outline"
}
},
{
title: "源码",
name: "source",
key: "source",
meta: {
icon: "ion:git-branch-outline"
},
children: [
{
title: "github",
path: "https://github.com/certd/certd",
meta: {
icon: "ion:logo-github"
}
},
{
title: "gitee",
path: "https://gitee.com/certd/certd",
meta: {
icon: "ion:logo-octocat"
}
}
]
}
{
title: i18n.global.t("certd.helpDoc"),
path: "https://certd.docmirror.cn",
meta: {
icon: "ion:document-text-outline"
}
},
{
title: i18n.global.t("certd.source"),
name: "source",
key: "source",
meta: {
icon: "ion:git-branch-outline"
},
children: [
{
title: i18n.global.t("certd.github"),
path: "https://github.com/certd/certd",
meta: {
icon: "ion:logo-github"
}
},
{
title: i18n.global.t("certd.gitee"),
path: "https://gitee.com/certd/certd",
meta: {
icon: "ion:logo-octocat"
}
}
]
}
];

View File

@ -2,23 +2,25 @@ import { IFrameView } from "/@/vben/layouts";
import { useSettingStore } from "/@/store/settings";
import { computed } from "vue";
import TutorialButton from "/@/components/tutorial/index.vue";
import i18n from '/@/locales/i18n';
export const aboutResource = [
{
title: "文档",
name: "document",
path: "/about/doc",
component: IFrameView,
meta: {
icon: "lucide:book-open-text",
link: "https://certd.docmirror.cn",
title: "文档",
order: 9999,
show: () => {
const settingStore = useSettingStore();
return !settingStore.isComm;
},
},
},
{
title: i18n.global.t("certd.dashboard.helpDoc"),
name: "document",
path: "/about/doc",
component: IFrameView,
meta: {
icon: "lucide:book-open-text",
link: "https://certd.docmirror.cn",
title: i18n.global.t("certd.dashboard.helpDoc"),
order: 9999,
show: () => {
const settingStore = useSettingStore();
return !settingStore.isComm;
},
},
},
];
export default aboutResource;

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +1,61 @@
<template>
<fs-page>
<template #header>
<div class="title flex items-center">
站点证书监控
<div class="sub flex-1">
<div>
每天0点检查网站证书的过期时间到期前10天时将发出提醒使用默认通知渠道;
<router-link to="/certd/monitor/setting">站点监控设置</router-link>
</div>
<div class="flex items-center">基础版限制1条专业版以上无限制当前<vip-button class="ml-5" mode="nav"></vip-button></div>
</div>
</div>
<div class="more">
<a-button type="primary" @click="checkAll"></a-button>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
<fs-page>
<template #header>
<div class="title flex items-center">
{{ t("certd.monitor.title") }}
<div class="sub flex-1">
<div>
{{ t("certd.monitor.description") }}
<router-link to="/certd/monitor/setting">{{ t("certd.monitor.settingLink") }}</router-link>
</div>
<div class="flex items-center">
{{ t("certd.monitor.limitInfo") }}
<vip-button class="ml-5" mode="nav"></vip-button>
</div>
</div>
</div>
<div class="more">
<a-button type="primary" @click="checkAll">{{ t("certd.monitor.checkAll") }}</a-button>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { siteInfoApi } from "./api";
import { Modal, notification } from "ant-design-vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
defineOptions({
name: "SiteCertMonitor",
name: "SiteCertMonitor",
});
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
function checkAll() {
Modal.confirm({
title: "确认",
content: "确认触发检查全部站点证书吗?",
onOk: async () => {
await siteInfoApi.CheckAll();
notification.success({
message: "检查任务已提交",
description: "请稍后刷新页面查看结果",
});
},
});
Modal.confirm({
title: t("certd.monitor.confirmTitle"), // ""
content: t("certd.monitor.confirmContent"), // "?"
onOk: async () => {
await siteInfoApi.CheckAll();
notification.success({
message: t("certd.monitor.checkSubmitted"), // ""
description: t("certd.monitor.pleaseRefresh"), // ""
});
},
});
}
//
onMounted(() => {
crudExpose.doRefresh();
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
crudExpose.doRefresh();
});
</script>

View File

@ -1,41 +1,44 @@
import { useFormWrapper } from "@fast-crud/fast-crud";
import { siteInfoApi } from "./api";
import { useI18n } from "vue-i18n";
export function useSiteImport() {
const { openCrudFormDialog } = useFormWrapper();
const { t } = useI18n();
const { openCrudFormDialog } = useFormWrapper();
async function openSiteImportDialog(opts: { afterSubmit: any }) {
const { afterSubmit } = opts;
await openCrudFormDialog<any>({
crudOptions: {
columns: {
text: {
type: "textarea",
title: "域名列表",
form: {
helper: "格式【域名:端口:名称】,一行一个,其中端口、名称可以省略\n比如\nwww.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com",
rules: [{ required: true, message: "请输入要导入的域名" }],
component: {
placeholder: "www.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com\n",
rows: 8,
},
col: {
span: 24,
},
},
},
},
form: {
async doSubmit({ form }) {
return siteInfoApi.Import(form);
},
afterSubmit,
},
},
});
}
async function openSiteImportDialog(opts: { afterSubmit: any }) {
const { afterSubmit } = opts;
await openCrudFormDialog<any>({
crudOptions: {
columns: {
text: {
type: "textarea",
title: t("certd.domainList.title"), // 域名列表
form: {
helper: t("certd.domainList.helper"),
rules: [{ required: true, message: t("certd.domainList.required") }],
component: {
placeholder: t("certd.domainList.placeholder"),
rows: 8,
},
col: {
span: 24,
},
},
},
},
return {
openSiteImportDialog,
};
form: {
async doSubmit({ form }) {
return siteInfoApi.Import(form);
},
afterSubmit,
},
},
});
}
return {
openSiteImportDialog,
};
}

View File

@ -1,36 +1,26 @@
<template>
<div class="notification-selector">
<div class="flex-o w-100">
<fs-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled" :render-label="renderLabel" :slots="selectSlots" :allow-clear="true" v-bind="select" @update:value="onChange" />
<fs-table-select
ref="tableSelectRef"
class="flex-0"
:model-value="modelValue"
:dict="optionsDictRef"
:create-crud-options="createCrudOptions"
:crud-options-override="{
search: { show: false },
table: {
scroll: {
x: 540,
},
},
}"
:show-current="false"
:show-select="false"
:dialog="{ width: 960 }"
:destroy-on-close="false"
height="400px"
v-bind="tableSelect"
@update:model-value="onChange"
@dialog-closed="doRefresh"
>
<template #default="scope">
<fs-button class="ml-5" :disabled="disabled" :size="size" type="primary" icon="ant-design:edit-outlined" @click="scope.open"></fs-button>
</template>
</fs-table-select>
</div>
</div>
<div class="notification-selector">
<div class="flex-o w-100">
<fs-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled"
:render-label="renderLabel" :slots="selectSlots" :allow-clear="true" v-bind="select"
@update:value="onChange" />
<fs-table-select ref="tableSelectRef" class="flex-0" :model-value="modelValue" :dict="optionsDictRef"
:create-crud-options="createCrudOptions" :crud-options-override="{
search: { show: false },
table: {
scroll: {
x: 540,
},
},
}" :show-current="false" :show-select="false" :dialog="{ width: 960 }" :destroy-on-close="false" height="400px"
v-bind="tableSelect" @update:model-value="onChange" @dialog-closed="doRefresh">
<template #default="scope">
<fs-button class="ml-5" :disabled="disabled" :size="size" type="primary"
icon="ant-design:edit-outlined" @click="scope.open"></fs-button>
</template>
</fs-table-select>
</div>
</div>
</template>
<script lang="tsx" setup>
@ -41,23 +31,26 @@ import { dict } from "@fast-crud/fast-crud";
import createCrudOptions from "../crud";
import { notificationProvide } from "/@/views/certd/notification/common";
import { useUserStore } from "/@/store/user";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
defineOptions({
name: "NotificationSelector",
name: "NotificationSelector",
});
const props = defineProps<{
modelValue?: number | string | number[] | string[];
type?: string;
placeholder?: string;
size?: string;
disabled?: boolean;
select?: any;
tableSelect?: any;
modelValue?: number | string | number[] | string[];
type?: string;
placeholder?: string;
size?: string;
disabled?: boolean;
select?: any;
tableSelect?: any;
}>();
const onChange = async (value: number) => {
await emitValue(value);
await emitValue(value);
};
const emit = defineEmits(["update:modelValue", "selectedChange", "change"]);
@ -76,89 +69,90 @@ notificationProvide(api);
// loadNotificationTypes();
const tableSelectRef = ref();
const optionsDictRef = dict({
url: "/pi/notification/options",
value: "id",
label: "name",
onReady: ({ dict }) => {
const data = [
{
id: 0,
name: "使用默认通知",
icon: "ion:notifications",
},
...dict.data,
];
dict.setData(data);
},
url: "/pi/notification/options",
value: "id",
label: "name",
onReady: ({ dict }) => {
const data = [
{
id: 0,
name: t("certd.notificationDefault"),
icon: "ion:notifications",
},
...dict.data,
];
dict.setData(data);
},
});
const renderLabel = (option: any) => {
return <span>{option.name}</span>;
return <span>{option.name}</span>;
};
async function openTableSelectDialog() {
selectOpened.value = false;
await tableSelectRef.value.open({});
await tableSelectRef.value.crudExpose.openAdd({});
selectOpened.value = false;
await tableSelectRef.value.open({});
await tableSelectRef.value.crudExpose.openAdd({});
}
const selectOpened = ref(false);
const selectSlots = ref({
dropdownRender({ menuNode, props }: any) {
const res = [];
res.push(menuNode);
// res.push(<a-divider style="margin: 4px 0" />);
// res.push(<a-space style="padding: 4px 8px" />);
// res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="" onClick={openTableSelectDialog}></fs-button>);
return res;
},
dropdownRender({ menuNode, props }: any) {
const res = [];
res.push(menuNode);
// res.push(<a-divider style="margin: 4px 0" />);
// res.push(<a-space style="padding: 4px 8px" />);
// res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="" onClick={openTableSelectDialog}></fs-button>);
return res;
},
});
const target: Ref<any> = ref({});
function clear() {
if (props.disabled) {
return;
}
emitValue(null);
if (props.disabled) {
return;
}
emitValue(null);
}
const userStore = useUserStore();
async function emitValue(value: any) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
}
emit("change", value);
emit("update:modelValue", value);
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
}
emit("change", value);
emit("update:modelValue", value);
}
watch(
() => {
return props.modelValue;
},
async value => {
await optionsDictRef.loadDict();
//@ts-ignore
target.value = optionsDictRef.dataMap[value];
emit("selectedChange", target.value);
},
{
immediate: true,
}
() => {
return props.modelValue;
},
async value => {
await optionsDictRef.loadDict();
//@ts-ignore
target.value = optionsDictRef.dataMap[value];
emit("selectedChange", target.value);
},
{
immediate: true,
}
);
//pipeline
const pipeline = inject("pipeline", null);
async function doRefresh() {
await optionsDictRef.reloadDict();
await optionsDictRef.reloadDict();
}
</script>
<style lang="less">
.notification-selector {
width: 100%;
width: 100%;
}
</style>

View File

@ -11,323 +11,328 @@ 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";
export function setRunnableIds(pipeline: any) {
const idMap: any = {};
function createId(oldId: any) {
if (oldId == null) {
return nanoid();
}
const newId = nanoid();
idMap[oldId] = newId;
return newId;
}
if (pipeline.stages) {
for (const stage of pipeline.stages) {
stage.id = createId(stage.id);
if (stage.tasks) {
for (const task of stage.tasks) {
task.id = createId(task.id);
if (task.steps) {
for (const step of task.steps) {
step.id = createId(step.id);
}
}
}
}
}
}
const { t } = useI18n();
const idMap: any = {};
function createId(oldId: any) {
if (oldId == null) {
return nanoid();
}
const newId = nanoid();
idMap[oldId] = newId;
return newId;
}
if (pipeline.stages) {
for (const stage of pipeline.stages) {
stage.id = createId(stage.id);
if (stage.tasks) {
for (const task of stage.tasks) {
task.id = createId(task.id);
if (task.steps) {
for (const step of task.steps) {
step.id = createId(step.id);
}
}
}
}
}
}
for (const trigger of pipeline.triggers) {
trigger.id = nanoid();
}
for (const notification of pipeline.notifications) {
notification.id = nanoid();
}
for (const trigger of pipeline.triggers) {
trigger.id = nanoid();
}
for (const notification of pipeline.notifications) {
notification.id = nanoid();
}
let content = JSON.stringify(pipeline);
for (const key in idMap) {
content = content.replaceAll(key, idMap[key]);
}
return JSON.parse(content);
let content = JSON.stringify(pipeline);
for (const key in idMap) {
content = content.replaceAll(key, idMap[key]);
}
return JSON.parse(content);
}
export function useCertPipelineCreator() {
const { openCrudFormDialog } = useFormWrapper();
const { t } = useI18n();
const { openCrudFormDialog } = useFormWrapper();
const pluginStore = usePluginStore();
const router = useRouter();
const pluginStore = usePluginStore();
const router = useRouter();
function createCrudOptions(certPlugins: any[], getFormData: any, doSubmit: any): CreateCrudOptionsRet {
const inputs: any = {};
const moreParams = [];
for (const plugin of certPlugins) {
for (const inputKey in plugin.input) {
if (inputs[inputKey]) {
//如果两个插件有的字段,直接显示
inputs[inputKey].form.show = true;
continue;
}
const inputDefine = cloneDeep(plugin.input[inputKey]);
if (!inputDefine.required && !inputDefine.maybeNeed) {
moreParams.push(inputKey);
// continue;
}
useReference(inputDefine);
inputs[inputKey] = {
title: inputDefine.title,
form: {
...inputDefine,
show: compute(ctx => {
const form = getFormData();
if (!form) {
return false;
}
function createCrudOptions(certPlugins: any[], getFormData: any, doSubmit: any): CreateCrudOptionsRet {
const inputs: any = {};
const moreParams = [];
for (const plugin of certPlugins) {
for (const inputKey in plugin.input) {
if (inputs[inputKey]) {
//如果两个插件有的字段,直接显示
inputs[inputKey].form.show = true;
continue;
}
const inputDefine = cloneDeep(plugin.input[inputKey]);
if (!inputDefine.required && !inputDefine.maybeNeed) {
moreParams.push(inputKey);
// continue;
}
useReference(inputDefine);
inputs[inputKey] = {
title: inputDefine.title,
form: {
...inputDefine,
show: compute(ctx => {
const form = getFormData();
if (!form) {
return false;
}
let inputDefineShow = true;
if (inputDefine.show != null) {
const computeShow = inputDefine.show as any;
if (computeShow === false) {
inputDefineShow = false;
} else if (computeShow && computeShow.computeFn) {
inputDefineShow = computeShow.computeFn({ form });
}
}
return form?.certApplyPlugin === plugin.name && inputDefineShow;
}),
},
};
}
}
let inputDefineShow = true;
if (inputDefine.show != null) {
const computeShow = inputDefine.show as any;
if (computeShow === false) {
inputDefineShow = false;
} else if (computeShow && computeShow.computeFn) {
inputDefineShow = computeShow.computeFn({ form });
}
}
return form?.certApplyPlugin === plugin.name && inputDefineShow;
}),
},
};
}
}
const pluginStore = usePluginStore();
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const pluginStore = usePluginStore();
const randomHour = Math.floor(Math.random() * 6);
const randomMin = Math.floor(Math.random() * 60);
const groupDictRef = dict({
url: "/pi/pipeline/group/all",
value: "id",
label: "name",
});
const groupDictRef = dict({
url: "/pi/pipeline/group/all",
value: "id",
label: "name",
});
return {
crudOptions: {
form: {
doSubmit,
wrapper: {
width: 1350,
saveRemind: false,
title: "创建证书流水线",
},
group: {
groups: {
more: {
header: "更多参数",
columns: moreParams,
collapsed: true,
},
},
},
},
columns: {
certApplyPlugin: {
title: "证书申请插件",
type: "dict-select",
dict: dict({
data: [
{ value: "CertApply", label: "JS-ACME" },
{ value: "CertApplyLego", label: "Lego-ACME" },
],
}),
form: {
order: 0,
value: "CertApply",
helper: {
render: () => {
return (
<ul>
<li>JS-ACME使便</li>
<li>Lego-ACMELegoDNSLEGO使</li>
</ul>
);
},
},
valueChange: {
handle: async ({ form, value }) => {
const config = await pluginStore.getPluginConfig({
name: value,
type: "builtIn",
});
if (config.sysSetting?.input) {
merge(form, config.sysSetting.input);
}
},
immediate: true,
},
},
},
...inputs,
triggerCron: {
title: "定时触发",
type: "text",
form: {
value: `0 ${randomMin} ${randomHour} * * *`,
component: {
name: "cron-editor",
vModel: "modelValue",
placeholder: "0 0 4 * * *",
},
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行",
order: 100,
},
},
notification: {
title: "失败通知",
type: "text",
form: {
value: 0,
component: {
name: NotificationSelector,
vModel: "modelValue",
on: {
selectedChange({ $event, form }) {
form.notificationTarget = $event;
},
},
},
order: 101,
helper: "任务执行失败实时提醒",
},
},
groupId: {
title: "流水线分组",
type: "dict-select",
dict: groupDictRef,
form: {
component: {
name: GroupSelector,
vModel: "modelValue",
},
order: 9999,
},
},
},
},
};
}
return {
crudOptions: {
form: {
doSubmit,
wrapper: {
width: 1350,
saveRemind: false,
title: t("certd.pipelineForm.createTitle"),
},
group: {
groups: {
more: {
header: t("certd.pipelineForm.moreParams"),
columns: moreParams,
collapsed: true,
},
},
},
},
columns: {
certApplyPlugin: {
title: t("certd.plugin.selectTitle"),
type: "dict-select",
dict: dict({
data: [
{ value: "CertApply", label: "JS-ACME" },
{ value: "CertApplyLego", label: "Lego-ACME" },
],
}),
form: {
order: 0,
value: "CertApply",
helper: {
render: () => {
return (
<ul>
<li>{t("certd.plugin.jsAcme")}</li>
<li>{t("certd.plugin.legoAcme")}</li>
</ul>
);
},
},
valueChange: {
handle: async ({ form, value }) => {
const config = await pluginStore.getPluginConfig({
name: value,
type: "builtIn",
});
if (config.sysSetting?.input) {
merge(form, config.sysSetting.input);
}
},
immediate: true,
},
},
},
...inputs,
triggerCron: {
title: t("certd.pipelineForm.triggerCronTitle"),
type: "text",
form: {
value: `0 ${randomMin} ${randomHour} * * *`,
component: {
name: "cron-editor",
vModel: "modelValue",
placeholder: "0 0 4 * * *",
},
helper: t("certd.pipelineForm.triggerCronHelper"),
order: 100,
},
},
notification: {
title: t("certd.pipelineForm.notificationTitle"),
type: "text",
form: {
value: 0,
component: {
name: NotificationSelector,
vModel: "modelValue",
on: {
selectedChange({ $event, form }) {
form.notificationTarget = $event;
},
},
},
order: 101,
helper: t("certd.pipelineForm.notificationHelper"),
},
},
groupId: {
title: t("certd.pipelineForm.groupIdTitle"),
type: "dict-select",
dict: groupDictRef,
form: {
component: {
name: GroupSelector,
vModel: "modelValue",
},
order: 9999,
},
}
async function getCertPlugins() {
const pluginGroup = await pluginStore.getGroups();
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
const certPluginGroup = pluginGroups.cert;
},
},
};
}
const certPlugins = [];
for (const plugin of certPluginGroup.plugins) {
const detail: any = await pluginStore.getPluginDefine(plugin.name);
certPlugins.push(detail);
}
return certPlugins;
}
async function getCertPlugins() {
const pluginGroup = await pluginStore.getGroups();
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
const certPluginGroup = pluginGroups.cert;
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) {
//检查是否流水线数量超出限制
await checkPipelineLimit();
const certPlugins = [];
for (const plugin of certPluginGroup.plugins) {
const detail: any = await pluginStore.getPluginDefine(plugin.name);
certPlugins.push(detail);
}
return certPlugins;
}
const wrapperRef = ref();
function getFormData() {
if (!wrapperRef.value) {
return null;
}
return wrapperRef.value.getFormData();
}
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) {
//检查是否流水线数量超出限制
await checkPipelineLimit();
async function doSubmit({ form }: any) {
// const certDetail = readCertDetail(form.cert.crt);
// 添加certd pipeline
const triggers = [];
if (form.triggerCron) {
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
}
const notifications = [];
if (form.notification != null) {
notifications.push({
type: "custom",
when: ["error", "turnToSuccess", "success"],
notificationId: form.notification,
title: form.notificationTarget?.name || "自定义通知",
});
}
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]);
let pipeline = {
title: form.domains[0] + "证书自动化",
runnableType: "pipeline",
stages: [
{
title: "证书申请阶段",
maxTaskCount: 1,
runnableType: "stage",
tasks: [
{
title: "证书申请任务",
runnableType: "task",
steps: [
{
title: "申请证书",
runnableType: "step",
input: {
renewDays: 35,
...pluginInput,
},
strategy: {
runStrategy: 0, // 正常执行
},
type: form.certApplyPlugin,
},
],
},
],
},
],
triggers,
notifications,
};
pipeline = setRunnableIds(pipeline);
const wrapperRef = ref();
function getFormData() {
if (!wrapperRef.value) {
return null;
}
return wrapperRef.value.getFormData();
}
/**
* // cert: 证书; backup: 备份; custom:自定义;
* type: string;
* // custom: 自定义; monitor: 监控;
* from: string;
*/
const groupId = form.groupId;
const id = await api.Save({
title: pipeline.title,
content: JSON.stringify(pipeline),
keepHistoryCount: 30,
type: "cert",
groupId,
});
if (form.email) {
try {
//创建一个默认的邮件通知
const notificationApi = createNotificationApi();
await notificationApi.GetOrCreateDefault({ email: form.email });
} catch (e) {
console.error(e);
}
}
message.success("创建成功,请添加证书部署任务");
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
}
const certPlugins = await getCertPlugins();
const { crudOptions } = createCrudOptions(certPlugins, getFormData, doSubmit);
//@ts-ignore
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
const wrapper = await openCrudFormDialog({ crudOptions });
wrapperRef.value = wrapper;
}
async function doSubmit({ form }: any) {
// const certDetail = readCertDetail(form.cert.crt);
// 添加certd pipeline
const triggers = [];
if (form.triggerCron) {
triggers.push({ title: "定时触发", type: "timer", props: { cron: form.triggerCron } });
}
const notifications = [];
if (form.notification != null) {
notifications.push({
type: "custom",
when: ["error", "turnToSuccess", "success"],
notificationId: form.notification,
title: form.notificationTarget?.name || "自定义通知",
});
}
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]);
let pipeline = {
title: form.domains[0] + "证书自动化",
runnableType: "pipeline",
stages: [
{
title: "证书申请阶段",
maxTaskCount: 1,
runnableType: "stage",
tasks: [
{
title: "证书申请任务",
runnableType: "task",
steps: [
{
title: "申请证书",
runnableType: "step",
input: {
renewDays: 35,
...pluginInput,
},
strategy: {
runStrategy: 0, // 正常执行
},
type: form.certApplyPlugin,
},
],
},
],
},
],
triggers,
notifications,
};
pipeline = setRunnableIds(pipeline);
return {
openAddCertdPipelineDialog,
};
/**
* // cert: 证书; backup: 备份; custom:自定义;
* type: string;
* // custom: 自定义; monitor: 监控;
* from: string;
*/
const groupId = form.groupId;
const id = await api.Save({
title: pipeline.title,
content: JSON.stringify(pipeline),
keepHistoryCount: 30,
type: "cert",
groupId,
});
if (form.email) {
try {
//创建一个默认的邮件通知
const notificationApi = createNotificationApi();
await notificationApi.GetOrCreateDefault({ email: form.email });
} catch (e) {
console.error(e);
}
}
message.success("创建成功,请添加证书部署任务");
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
}
const certPlugins = await getCertPlugins();
const { crudOptions } = createCrudOptions(certPlugins, getFormData, doSubmit);
//@ts-ignore
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
const wrapper = await openCrudFormDialog({ crudOptions });
wrapperRef.value = wrapper;
}
return {
openAddCertdPipelineDialog,
};
}

View File

@ -1,36 +1,37 @@
<template>
<div>
<fs-form-item
v-model="optionsFormState.receivers"
:item="{
title: '收件邮箱',
key: 'type',
component: {
name: 'a-select',
vModel: 'value',
mode: 'tags',
open: false
},
helper: '输入你的收件邮箱地址,支持多个邮箱',
rules: [{ required: true, message: '此项必填' }]
}"
/>
<div>
<fs-form-item v-model="optionsFormState.receivers" :item="{
title: t('certd.email.title'),
key: 'type',
component: {
name: 'a-select',
vModel: 'value',
mode: 'tags',
open: false
},
helper: t('certd.email.helper'),
rules: [{ required: true, message: t('certd.email.required') }]
}" />
<a-alert v-if="!settingStore.isPlus" class="m-1" type="info">
<template #message> 还没有配置邮件服务器<router-link :to="{ path: '/sys/settings/email' }">现在就去</router-link> </template>
</a-alert>
</div>
<a-alert v-if="!settingStore.isPlus" class="m-1" type="info">
<template #message> 还没有配置邮件服务器<router-link :to="{ path: '/sys/settings/email' }">现在就去</router-link>
</template>
</a-alert>
</div>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from "vue";
import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({
options: {
type: Object as PropType<any>,
default: () => {}
}
options: {
type: Object as PropType<any>,
default: () => { }
}
});
const settingStore = useSettingStore();
@ -38,30 +39,30 @@ const settingStore = useSettingStore();
const optionsFormState: Ref<any> = ref({});
watch(
() => {
return props.options;
},
() => {
optionsFormState.value = {
...props.options
};
},
{
immediate: true
}
() => {
return props.options;
},
() => {
optionsFormState.value = {
...props.options
};
},
{
immediate: true
}
);
const emit = defineEmits(["change"]);
function doEmit() {
emit("change", { ...optionsFormState.value });
emit("change", { ...optionsFormState.value });
}
function getValue() {
return { ...optionsFormState.value };
return { ...optionsFormState.value };
}
defineExpose({
doEmit,
getValue
doEmit,
getValue
});
</script>

View File

@ -1,70 +1,65 @@
<template>
<a-drawer v-model:open="triggerDrawerVisible" placement="right" :closable="true" width="650px" class="pi-trigger-form" @after-open-change="triggerDrawerOnAfterVisibleChange">
<template #title>
<div>
编辑触发器
<a-button v-if="mode === 'edit'" @click="triggerDelete()">
<template #icon><DeleteOutlined /></template>
</a-button>
</div>
</template>
<template v-if="currentTrigger">
<pi-container>
<a-form ref="triggerFormRef" class="trigger-form" :model="currentTrigger" :label-col="labelCol" :wrapper-col="wrapperCol">
<fs-form-item
v-model="currentTrigger.title"
:item="{
title: '触发器名称',
key: 'title',
component: {
name: 'a-input',
vModel: 'value',
disabled: !editMode,
},
rules: [{ required: true, message: '此项必填' }],
}"
/>
<a-drawer v-model:open="triggerDrawerVisible" placement="right" :closable="true" width="650px"
class="pi-trigger-form" @after-open-change="triggerDrawerOnAfterVisibleChange">
<template #title>
<div>
编辑触发器
<a-button v-if="mode === 'edit'" @click="triggerDelete()">
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</div>
</template>
<template v-if="currentTrigger">
<pi-container>
<a-form ref="triggerFormRef" class="trigger-form" :model="currentTrigger" :label-col="labelCol"
:wrapper-col="wrapperCol">
<fs-form-item v-model="currentTrigger.title" :item="{
title: '触发器名称',
key: 'title',
component: {
name: 'a-input',
vModel: 'value',
disabled: !editMode,
},
rules: [{ required: true, message: '此项必填' }],
}" />
<fs-form-item
v-model="currentTrigger.type"
:item="{
title: '类型',
key: 'type',
value: 'timer',
component: {
name: 'a-select',
vModel: 'value',
disabled: !editMode,
options: [{ value: 'timer', label: '定时' }],
},
rules: [{ required: true, message: '此项必填' }],
}"
/>
<fs-form-item v-model="currentTrigger.type" :item="{
title: '类型',
key: 'type',
value: 'timer',
component: {
name: 'a-select',
vModel: 'value',
disabled: !editMode,
options: [{ value: 'timer', label: '定时' }],
},
rules: [{ required: true, message: '此项必填' }],
}" />
<fs-form-item
v-model="currentTrigger.props.cron"
:item="{
title: '定时脚本',
key: 'props.cron',
component: {
disabled: !editMode,
name: 'cron-editor',
vModel: 'modelValue',
},
helper: '点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行',
rules: [{ required: true, message: '此项必填' }],
}"
/>
</a-form>
<fs-form-item v-model="currentTrigger.props.cron" :item="{
title: t('certd.cronForm.title'),
key: 'props.cron',
component: {
disabled: !editMode,
name: 'cron-editor',
vModel: 'modelValue',
},
helper: t('certd.cronForm.helper'),
rules: [{ required: true, message: t('certd.cronForm.required') }],
}" />
</a-form>
<template #footer>
<a-form-item v-if="editMode" :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="triggerSave"> </a-button>
</a-form-item>
</template>
</pi-container>
</template>
</a-drawer>
<template #footer>
<a-form-item v-if="editMode" :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="triggerSave"> </a-button>
</a-form-item>
</template>
</pi-container>
</template>
</a-drawer>
</template>
<script>
@ -73,125 +68,124 @@ import { inject, ref } from "vue";
import * as _ from "lodash-es";
import { nanoid } from "nanoid";
export default {
name: "PiTriggerForm",
props: {
editMode: {
type: Boolean,
default: true,
},
},
emits: ["update"],
setup(props, context) {
/**
* trigger drawer
* @returns
*/
function useTriggerForm() {
const mode = ref("add");
const callback = ref();
const currentTrigger = ref({ title: undefined, input: {} });
const currentPlugin = ref({});
const triggerFormRef = ref(null);
const triggerDrawerVisible = ref(false);
const rules = ref({
name: [
{
type: "string",
required: true,
message: "请输入名称",
},
],
});
name: "PiTriggerForm",
props: {
editMode: {
type: Boolean,
default: true,
},
},
emits: ["update"],
setup(props, context) {
/**
* trigger drawer
* @returns
*/
function useTriggerForm() {
const mode = ref("add");
const callback = ref();
const currentTrigger = ref({ title: undefined, input: {} });
const currentPlugin = ref({});
const triggerFormRef = ref(null);
const triggerDrawerVisible = ref(false);
const rules = ref({
name: [
{
type: "string",
required: true,
message: "请输入名称",
},
],
});
const triggerDrawerShow = () => {
triggerDrawerVisible.value = true;
};
const triggerDrawerClose = () => {
triggerDrawerVisible.value = false;
};
const triggerDrawerShow = () => {
triggerDrawerVisible.value = true;
};
const triggerDrawerClose = () => {
triggerDrawerVisible.value = false;
};
const triggerDrawerOnAfterVisibleChange = val => {
console.log("triggerDrawerOnAfterVisibleChange", val);
};
const triggerDrawerOnAfterVisibleChange = val => {
console.log("triggerDrawerOnAfterVisibleChange", val);
};
const triggerOpen = (trigger, emit) => {
callback.value = emit;
currentTrigger.value = _.cloneDeep(trigger);
console.log("currentTriggerOpen", currentTrigger.value);
triggerDrawerShow();
};
const triggerOpen = (trigger, emit) => {
callback.value = emit;
currentTrigger.value = _.cloneDeep(trigger);
console.log("currentTriggerOpen", currentTrigger.value);
triggerDrawerShow();
};
const triggerAdd = emit => {
mode.value = "add";
const trigger = { id: nanoid(), title: "定时触发", type: "timer", props: {} };
triggerOpen(trigger, emit);
};
const triggerAdd = emit => {
mode.value = "add";
const trigger = { id: nanoid(), title: "定时触发", type: "timer", props: {} };
triggerOpen(trigger, emit);
};
const triggerEdit = (trigger, emit) => {
mode.value = "edit";
triggerOpen(trigger, emit);
};
const triggerEdit = (trigger, emit) => {
mode.value = "edit";
triggerOpen(trigger, emit);
};
const triggerView = (trigger, emit) => {
mode.value = "view";
triggerOpen(trigger, emit);
};
const triggerView = (trigger, emit) => {
mode.value = "view";
triggerOpen(trigger, emit);
};
const triggerSave = async e => {
console.log("currentTriggerSave", currentTrigger.value);
try {
await triggerFormRef.value.validate();
} catch (e) {
console.error("表单验证失败:", e);
return;
}
const triggerSave = async e => {
console.log("currentTriggerSave", currentTrigger.value);
try {
await triggerFormRef.value.validate();
} catch (e) {
console.error("表单验证失败:", e);
return;
}
callback.value("save", currentTrigger.value);
triggerDrawerClose();
};
callback.value("save", currentTrigger.value);
triggerDrawerClose();
};
const triggerDelete = () => {
Modal.confirm({
title: "确认",
content: `确定要删除此触发器吗?`,
async onOk() {
callback.value("delete");
triggerDrawerClose();
},
});
};
const triggerDelete = () => {
Modal.confirm({
title: "确认",
content: `确定要删除此触发器吗?`,
async onOk() {
callback.value("delete");
triggerDrawerClose();
},
});
};
const blankFn = () => {
return {};
};
return {
triggerFormRef,
mode,
triggerAdd,
triggerEdit,
triggerView,
triggerDrawerShow,
triggerDrawerVisible,
triggerDrawerOnAfterVisibleChange,
currentTrigger,
currentPlugin,
triggerSave,
triggerDelete,
rules,
blankFn,
};
}
const blankFn = () => {
return {};
};
return {
triggerFormRef,
mode,
triggerAdd,
triggerEdit,
triggerView,
triggerDrawerShow,
triggerDrawerVisible,
triggerDrawerOnAfterVisibleChange,
currentTrigger,
currentPlugin,
triggerSave,
triggerDelete,
rules,
blankFn,
};
}
return {
...useTriggerForm(),
labelCol: { span: 6 },
wrapperCol: { span: 16 },
};
},
return {
...useTriggerForm(),
labelCol: { span: 6 },
wrapperCol: { span: 16 },
};
},
};
</script>
<style lang="less">
.pi-trigger-form {
}
.pi-trigger-form {}
</style>