【修复】表单初始化,申请节点随机时间

This commit is contained in:
cai
2025-09-04 15:21:26 +08:00
parent 8e0c6ca125
commit e9515bb6ae
70 changed files with 3083 additions and 1488 deletions

View File

@@ -33,6 +33,40 @@ export const useController = (props: FlowNodeProps = { type: 'quick', node: flow
// 使用store获取所有需要的方法和状态
const router = useRouter()
const route = useRoute()
const { startNodeSavedByUser } = useStore();
/**
* 生成开始节点的随机时间
* @param {any} childNode - 子节点配置
* @returns {any} 处理后的子节点配置
*/
const generateStartNodeRandomTime = (childNode: any) => {
// 检查是否是开始节点
if (childNode.type === "start") {
if (startNodeSavedByUser.value) {
return childNode;
}
const config = childNode.config;
const hasUserSetTime =
config.hour !== undefined &&
config.minute !== undefined &&
!(config.hour === 1 && config.minute === 0);
if (!hasUserSetTime) {
const randomHour = Math.floor(Math.random() * 4) + 1;
const randomMinute = Math.floor(Math.random() * 12) * 5;
return {
...childNode,
config: {
...config,
hour: randomHour,
minute: randomMinute,
},
};
}
}
return childNode;
};
/**
* 保存节点配置
@@ -45,14 +79,16 @@ export const useController = (props: FlowNodeProps = { type: 'quick', node: flow
if (res.valid && flowData.value.name) {
const { active } = workflowData.value
const { id, name, childNode } = flowData.value
const { exec_type, ...exec_time } = childNode.config as unknown as StartNodeConfig
const param = {
name,
active,
content: JSON.stringify(childNode),
exec_type,
exec_time: JSON.stringify(exec_time || {}),
}
const processedChildNode = generateStartNodeRandomTime(childNode);
const { exec_type, ...exec_time } =
processedChildNode.config as unknown as StartNodeConfig;
const param = {
name,
active,
content: JSON.stringify(processedChildNode),
exec_type,
exec_time: JSON.stringify(exec_time || {}),
};
if (route.query.isEdit) {
updateWorkflowData({ id, ...param })
} else {

File diff suppressed because it is too large Load Diff

View File

@@ -478,6 +478,7 @@ export const useApiFormController = (
btwaf: $t("t_0_1747271295174"),
safeline: $t("t_0_1747300383756"),
lecdn: "请输入正确的URL地址",
webhook: "请输入回调地址",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])

View File

@@ -1,55 +1,62 @@
import { defineComponent, onBeforeMount, onMounted, ref } from 'vue'
import type { Component } from 'vue'
import FlowChart from '@components/FlowChart'
import { useStore } from '@autoDeploy/children/workflowView/useStore'
import { useController } from './useController'
import FlowChart from "@components/FlowChart";
import { useStore } from "@autoDeploy/children/workflowView/useStore";
import { useController } from "./useController";
import { useStore as useFlowStore } from "@components/FlowChart/useStore";
/**
* @description 工作流视图主组件,负责加载和渲染流程图。
*/
export default defineComponent({
name: 'WorkflowView',
setup() {
const { init } = useController()
const { workflowType, workDefalutNodeData, isEdit } = useStore()
name: "WorkflowView",
setup() {
const { init } = useController();
const { workflowType, workDefalutNodeData, isEdit } = useStore();
const { resetStartNodeSavedState } = useFlowStore();
// 使用import.meta.glob一次性加载所有节点组件
const modules = import.meta.glob('./node/*/index.tsx', { eager: true })
// 使用import.meta.glob一次性加载所有节点组件
const modules = import.meta.glob("./node/*/index.tsx", { eager: true });
// 创建节点组件映射
const taskComponents = ref<Record<string, Component>>({})
// 创建节点组件映射
const taskComponents = ref<Record<string, Component>>({});
// 初始化任务组件映射
const initTaskComponents = () => {
const componentsMap: Record<string, Component> = {}
// 获取文件夹名称(对应节点类型)并映射到组件
Object.entries(modules).forEach(([path, module]) => {
// 获取路径中的节点类型
const match = path.match(/\/node\/([^/]+)\/index\.tsx$/)
if (match && match[1]) {
const nodeType = match[1]
const componentKey = `${nodeType}Node`
// @ts-ignore
componentsMap[componentKey] = module.default || module
}
})
taskComponents.value = componentsMap
console.log('已加载节点组件:', Object.keys(componentsMap))
}
// 初始化任务组件映射
const initTaskComponents = () => {
const componentsMap: Record<string, Component> = {};
// 获取文件夹名称(对应节点类型)并映射到组件
Object.entries(modules).forEach(([path, module]) => {
// 获取路径中的节点类型
const match = path.match(/\/node\/([^/]+)\/index\.tsx$/);
if (match && match[1]) {
const nodeType = match[1];
const componentKey = `${nodeType}Node`;
// @ts-ignore
componentsMap[componentKey] = module.default || module;
}
});
taskComponents.value = componentsMap;
console.log("已加载节点组件:", Object.keys(componentsMap));
};
// 初始化组件
onBeforeMount(initTaskComponents)
// 初始化组件
onBeforeMount(initTaskComponents);
// 初始化数据
onMounted(init)
// 初始化数据
onMounted(init);
return () => (
<FlowChart
type={workflowType.value}
node={workDefalutNodeData.value}
isEdit={isEdit.value}
taskComponents={taskComponents.value}
/>
)
},
})
// 组件销毁时重置开始节点保存状态
onUnmounted(() => {
resetStartNodeSavedState();
});
return () => (
<FlowChart
type={workflowType.value}
node={workDefalutNodeData.value}
isEdit={isEdit.value}
taskComponents={taskComponents.value}
/>
);
},
});

View File

@@ -53,467 +53,548 @@ export default defineComponent({
},
},
setup(props) {
const { updateNodeConfig, advancedOptions, isRefreshNode } = useStore()
// 获取路由信息
const route = useRoute()
// 弹窗辅助
const { confirm } = useModalHooks()
// 获取表单助手函数
const { useFormInput, useFormSelect, useFormMore, useFormHelp, useFormSwitch } = useFormHooks()
// 表单参数
const param = ref(deepClone(props.node.config))
const { updateNodeConfig, advancedOptions, isRefreshNode } = useStore();
// 获取路由信息
const route = useRoute();
// 弹窗辅助
const { confirm } = useModalHooks();
// 获取表单助手函数
const {
useFormInput,
useFormSelect,
useFormMore,
useFormHelp,
useFormSwitch,
} = useFormHooks();
// 表单参数
const param = ref(deepClone(props.node.config));
// 获取路由参数
const isEdit = computed(() => route.query.isEdit === 'true')
const routeEmail = computed(() => (route.query.email as string) || '')
// 获取路由参数
const isEdit = computed(() => route.query.isEdit === "true");
const routeEmail = computed(() => (route.query.email as string) || "");
// CA选项状态
const caOptions = ref<Array<{ label: string; value: string; icon: string }>>([])
const emailOptions = ref<string[]>([])
const isLoadingCA = ref(false)
const isLoadingEmails = ref(false)
const showEmailDropdown = ref(false)
const emailInputRef = ref<InstanceType<typeof NInput> | null>(null)
// CA选项状态
const caOptions = ref<
Array<{ label: string; value: string; icon: string }>
>([]);
const emailOptions = ref<string[]>([]);
const isLoadingCA = ref(false);
const isLoadingEmails = ref(false);
const showEmailDropdown = ref(false);
const emailInputRef = ref<InstanceType<typeof NInput> | null>(null);
// 加载CA选项
const loadCAOptions = async () => {
isLoadingCA.value = true
try {
const { data } = await getEabList({ ca: '', p: 1, limit: 1000 }).fetch()
const uniqueCATypes = new Set<string>()
const caList: Array<{ label: string; value: string; icon: string }> = []
// 加载CA选项
const loadCAOptions = async () => {
isLoadingCA.value = true;
try {
const { data } = await getEabList({
ca: param.value.ca,
p: 1,
limit: 1000,
}).fetch();
const uniqueCATypes = new Set<string>();
const caList: Array<{ label: string; value: string; icon: string }> =
[];
// 优先添加重要的CA类型确保始终显示
const priorityCATypes = ['letsencrypt', 'buypass', 'zerossl']
priorityCATypes.forEach((caType) => {
if (!uniqueCATypes.has(caType)) {
uniqueCATypes.add(caType)
const predefinedCA = Object.values(CACertificateAuthorization).find((ca) => ca.type === caType)
caList.push({
label: predefinedCA ? predefinedCA.name : caType.toUpperCase(),
value: caType,
icon: `cert-${caType}`,
})
}
})
// 优先添加重要的CA类型确保始终显示
const priorityCATypes = ["letsencrypt", "buypass", "zerossl"];
priorityCATypes.forEach((caType) => {
if (!uniqueCATypes.has(caType)) {
uniqueCATypes.add(caType);
const predefinedCA = Object.values(CACertificateAuthorization).find(
(ca) => ca.type === caType
);
caList.push({
label: predefinedCA ? predefinedCA.name : caType.toUpperCase(),
value: caType,
icon: `cert-${caType}`,
});
}
});
// 添加API返回的其他CA类型去重
data?.forEach((item) => {
if (item.ca && !uniqueCATypes.has(item.ca)) {
uniqueCATypes.add(item.ca)
// 添加API返回的其他CA类型去重
data?.forEach((item) => {
if (item.ca && !uniqueCATypes.has(item.ca)) {
uniqueCATypes.add(item.ca);
// 查找预定义配置中对应的CA信息
const predefinedCA = Object.values(CACertificateAuthorization).find((ca) => ca.type === item.ca)
caList.push({
label: predefinedCA ? predefinedCA.name : item.ca.toUpperCase(),
value: item.ca,
icon: predefinedCA ? `cert-${item.ca}` : 'cert-custom', // 如果不在预定义配置中使用custom图标否则使用对应的cert图标
})
}
})
// 查找预定义配置中对应的CA信息
const predefinedCA = Object.values(CACertificateAuthorization).find(
(ca) => ca.type === item.ca
);
caList.push({
label: predefinedCA ? predefinedCA.name : item.ca.toUpperCase(),
value: item.ca,
icon: predefinedCA ? `cert-${item.ca}` : "cert-custom", // 如果不在预定义配置中使用custom图标否则使用对应的cert图标
});
}
});
caOptions.value = caList
} catch (error) {
console.error('加载CA选项失败:', error)
} finally {
isLoadingCA.value = false
}
}
caOptions.value = caList;
} catch (error) {
console.error("加载CA选项失败:", error);
} finally {
isLoadingCA.value = false;
}
};
// 加载邮件选项
const loadEmailOptions = async (ca: string) => {
if (!ca) return
isLoadingEmails.value = true
try {
const { data } = await getEabList({ ca, p: 1, limit: 1000 }).fetch()
emailOptions.value = data?.map((item) => item.email).filter(Boolean) || []
// 加载邮件选项
const loadEmailOptions = async (ca: string) => {
if (!ca) return;
isLoadingEmails.value = true;
try {
const { data } = await getEabList({ ca, p: 1, limit: 1000 }).fetch();
emailOptions.value =
data?.map((item) => item.email).filter(Boolean) || [];
// 检查是否为编辑模式且有外部传入的邮箱
if (isEdit.value && routeEmail.value) {
// 编辑模式:使用外部传入的邮箱地址
param.value.email = routeEmail.value
} else {
// 非编辑模式:保持原有逻辑
if (!emailOptions.value.length) {
param.value.email = ''
}
// 如果邮箱数组有内容且当前邮箱为空,自动填充第一个邮箱地址
if (emailOptions.value.length > 0 && emailOptions.value[0] && !param.value.email) {
param.value.email = emailOptions.value[0]
}
}
} catch (error) {
console.error('加载邮件选项失败:', error)
} finally {
isLoadingEmails.value = false
}
}
// 检查是否为编辑模式且有外部传入的邮箱
if (isEdit.value && routeEmail.value) {
// 编辑模式:使用外部传入的邮箱地址
param.value.email = routeEmail.value;
} else {
// 非编辑模式:保持原有逻辑
if (!emailOptions.value.length) {
param.value.email = "";
}
// 如果邮箱数组有内容且当前邮箱为空,自动填充第一个邮箱地址
if (
emailOptions.value.length > 0 &&
emailOptions.value[0] &&
!param.value.email
) {
param.value.email = emailOptions.value[0];
}
}
} catch (error) {
console.error("加载邮件选项失败:", error);
} finally {
isLoadingEmails.value = false;
}
};
// 处理CA选择变化
const handleCAChange = (value: string) => {
param.value.ca = value
// 移除直接调用 loadEmailOptions让 watch 监听器统一处理
// 这样避免了用户切换CA时的重复 API 请求
}
// 处理CA选择变化
const handleCAChange = (value: string) => {
param.value.ca = value;
// 移除直接调用 loadEmailOptions让 watch 监听器统一处理
// 这样避免了用户切换CA时的重复 API 请求
};
// 跳转到CA管理页面
const goToAddCAProvider = () => {
window.open('/auto-deploy?type=caManage', '_blank')
}
// 跳转到CA管理页面
const goToAddCAProvider = () => {
window.open("/auto-deploy?type=caManage", "_blank");
};
// 渲染CA选择器标签
const renderLabel = (option: { label: string; value: string; icon: string }) => {
return (
<NFlex align="center">
<SvgIcon icon={option.icon} size="2rem" />
<NText>{option.label}</NText>
</NFlex>
)
}
// 渲染CA选择器标签
const renderLabel = (option: {
label: string;
value: string;
icon: string;
}) => {
return (
<NFlex align="center">
<SvgIcon icon={option.icon} size="2rem" />
<NText>{option.label}</NText>
</NFlex>
);
};
// 渲染CA选择器单选标签
const renderSingleSelectTag = ({ option }: any) => {
return (
<NFlex align="center">
{option.label ? renderLabel(option) : <NText class="text-[#aaa]">{$t('t_0_1747990228780')}</NText>}
</NFlex>
)
}
// 渲染CA选择器单选标签
const renderSingleSelectTag = ({ option }: any) => {
return (
<NFlex align="center">
{option.label ? (
renderLabel(option)
) : (
<NText class="text-[#aaa]">{$t("t_0_1747990228780")}</NText>
)}
</NFlex>
);
};
// 过滤函数
const handleFilter = (pattern: string, option: any): boolean => {
return option.label.toLowerCase().includes(pattern.toLowerCase())
}
// 过滤函数
const handleFilter = (pattern: string, option: any): boolean => {
return option.label.toLowerCase().includes(pattern.toLowerCase());
};
// 处理邮箱输入框焦点
const handleEmailFocus = () => {
if (emailOptions.value.length > 0) {
showEmailDropdown.value = true
}
}
// 处理邮箱输入框焦点
const handleEmailFocus = () => {
if (emailOptions.value.length > 0) {
showEmailDropdown.value = true;
}
};
// 处理邮箱输入框失焦
const handleEmailBlur = () => {
// 延迟关闭下拉,确保点击选项有时间触发
setTimeout(() => {
showEmailDropdown.value = false
}, 200)
}
// 处理邮箱输入框失焦
const handleEmailBlur = () => {
// 延迟关闭下拉,确保点击选项有时间触发
setTimeout(() => {
showEmailDropdown.value = false;
}, 200);
};
// 选择邮箱地址
const handleSelectEmail = (email: string) => {
param.value.email = email
showEmailDropdown.value = false
emailInputRef.value?.blur()
}
// 选择邮箱地址
const handleSelectEmail = (email: string) => {
param.value.email = email;
showEmailDropdown.value = false;
emailInputRef.value?.blur();
};
// 创建邮箱下拉选项
const emailDropdownOptions = computed(() => {
return emailOptions.value.map((email) => ({
label: email,
key: email,
}))
})
// 创建邮箱下拉选项
const emailDropdownOptions = computed(() => {
return emailOptions.value.map((email) => ({
label: email,
key: email,
}));
});
// 判断是否需要输入框letsencrypt、buypass、zerossl
const shouldUseInputForEmail = computed(() => {
return ['letsencrypt', 'buypass', 'zerossl'].includes(param.value.ca)
})
// 判断是否需要输入框letsencrypt、buypass、zerossl
const shouldUseInputForEmail = computed(() => {
return ["letsencrypt", "buypass", "zerossl"].includes(param.value.ca);
});
// 表单渲染配置
const config = computed(() => {
// 基本选项
return [
useFormInput($t('t_17_1745227838561'), 'domains', {
placeholder: $t('t_0_1745735774005'),
allowInput: noSideSpace,
onInput: (val: string) => {
param.value.domains = val.replace(//g, ',').replace(/;/g, ',') // 中文逗号分隔
},
}),
{
type: 'custom' as const,
render: () => {
return (
<DnsProviderSelect
type="dns"
path="provider_id"
value={param.value.provider_id}
valueType="value"
isAddMode={true}
{...{
'onUpdate:value': (val: { value: string; type: string }) => {
param.value.provider_id = val.value
param.value.provider = val.type
},
}}
/>
)
},
},
{
type: 'custom' as const,
render: () => {
return (
<NSpin show={isLoadingCA.value}>
<NGrid cols={24}>
<NFormItemGi span={13} label={$t('t_3_1750399513606')} path="ca" showRequireMark={true}>
<NSelect
value={param.value.ca}
options={caOptions.value}
renderLabel={renderLabel}
renderTag={renderSingleSelectTag}
filterable
filter={handleFilter}
loading={isLoadingCA.value}
placeholder={$t('t_0_1747990228780')}
onUpdateValue={handleCAChange}
class="flex-1 w-full"
v-slots={{
empty: () => {
return <span class="text-[1.4rem]">{$t('t_2_1747990228008')}</span>
},
}}
/>
</NFormItemGi>
<NFormItemGi span={11}>
<NButton class="mx-[8px]" onClick={goToAddCAProvider}>
{$t('t_4_1747903685371')}
</NButton>
<NButton onClick={loadCAOptions} loading={isLoadingCA.value}>
{$t('t_0_1746497662220')}
</NButton>
</NFormItemGi>
</NGrid>
</NSpin>
)
},
},
{
type: 'custom' as const,
render: () => {
return (
<NFormItem label={$t('t_68_1745289354676')} path="email">
{shouldUseInputForEmail.value ? (
<NDropdown
trigger="manual"
show={showEmailDropdown.value}
options={emailDropdownOptions.value}
onSelect={handleSelectEmail}
placement="bottom-start"
style="width: 100%"
>
<NInput
ref={emailInputRef}
v-model:value={param.value.email}
placeholder={$t('t_2_1748052862259')}
clearable
loading={isLoadingEmails.value}
onFocus={handleEmailFocus}
onBlur={handleEmailBlur}
class="w-full"
/>
</NDropdown>
) : (
<NSelect
v-model:value={param.value.email}
options={emailOptions.value.map((email) => ({ label: email, value: email }))}
placeholder={$t('t_2_1748052862259')}
clearable
filterable
loading={isLoadingEmails.value}
class="w-full"
/>
)}
</NFormItem>
)
},
},
// 表单渲染配置
const config = computed(() => {
// 基本选项
return [
useFormInput($t("t_17_1745227838561"), "domains", {
placeholder: $t("t_0_1745735774005"),
allowInput: noSideSpace,
onInput: (val: string) => {
param.value.domains = val.replace(//g, ",").replace(/;/g, ","); // 中文逗号分隔
},
}),
{
type: "custom" as const,
render: () => {
return (
<DnsProviderSelect
type="dns"
path="provider_id"
value={param.value.provider_id}
valueType="value"
isAddMode={true}
{...{
"onUpdate:value": (val: { value: string; type: string }) => {
param.value.provider_id = val.value;
param.value.provider = val.type;
},
}}
/>
);
},
},
{
type: "custom" as const,
render: () => {
return (
<NSpin show={isLoadingCA.value}>
<NGrid cols={24}>
<NFormItemGi
span={13}
label={$t("t_3_1750399513606")}
path="ca"
showRequireMark={true}
>
<NSelect
value={param.value.ca}
options={caOptions.value}
renderLabel={renderLabel}
renderTag={renderSingleSelectTag}
filterable
filter={handleFilter}
loading={isLoadingCA.value}
placeholder={$t("t_0_1747990228780")}
onUpdateValue={handleCAChange}
class="flex-1 w-full"
v-slots={{
empty: () => {
return (
<span class="text-[1.4rem]">
{$t("t_2_1747990228008")}
</span>
);
},
}}
/>
</NFormItemGi>
<NFormItemGi span={11}>
<NButton class="mx-[8px]" onClick={goToAddCAProvider}>
{$t("t_4_1747903685371")}
</NButton>
<NButton
onClick={loadCAOptions}
loading={isLoadingCA.value}
>
{$t("t_0_1746497662220")}
</NButton>
</NFormItemGi>
</NGrid>
</NSpin>
);
},
},
{
type: "custom" as const,
render: () => {
return (
<NFormItem label={$t("t_68_1745289354676")} path="email">
{shouldUseInputForEmail.value ? (
<NDropdown
trigger="manual"
show={showEmailDropdown.value}
options={emailDropdownOptions.value}
onSelect={handleSelectEmail}
placement="bottom-start"
style="width: 100%"
>
<NInput
ref={emailInputRef}
v-model:value={param.value.email}
placeholder={$t("t_2_1748052862259")}
clearable
loading={isLoadingEmails.value}
onFocus={handleEmailFocus}
onBlur={handleEmailBlur}
class="w-full"
/>
</NDropdown>
) : (
<NSelect
v-model:value={param.value.email}
options={emailOptions.value.map((email) => ({
label: email,
value: email,
}))}
placeholder={$t("t_2_1748052862259")}
clearable
filterable
loading={isLoadingEmails.value}
class="w-full"
/>
)}
</NFormItem>
);
},
},
{
type: 'custom' as const,
render: () => {
return (
<NFormItem label={$t('t_4_1747990227956')} path="end_day">
<div class="flex items-center">
<span class="text-[1.4rem] mr-[1.2rem]">{$t('t_5_1747990228592')}</span>
<NInputNumber v-model:value={param.value.end_day} showButton={false} min={1} class="w-[120px]" />
<span class="text-[1.4rem] ml-[1.2rem]">{$t('t_6_1747990228465')}</span>
</div>
</NFormItem>
)
},
},
useFormMore(advancedOptions),
...(advancedOptions.value
? [
useFormSelect(
$t('t_0_1747647014927'),
'algorithm',
[
{ label: 'RSA2048', value: 'RSA2048' },
{ label: 'RSA3072', value: 'RSA3072' },
{ label: 'RSA4096', value: 'RSA4096' },
{ label: 'RSA8192', value: 'RSA8192' },
{ label: 'EC256', value: 'EC256' },
{ label: 'EC384', value: 'EC384' },
],
{},
{ showRequireMark: false },
),
useFormInput(
$t('t_7_1747990227761'),
'proxy',
{
placeholder: $t('t_8_1747990235316'),
allowInput: noSideSpace,
},
{ showRequireMark: false },
),
useFormSwitch(
$t('t_2_1749204567193'),
'close_cname',
{
checkedValue: 1,
uncheckedValue: 0,
},
{ showRequireMark: false },
),
useFormSwitch(
$t('t_2_1747106957037'),
'skip_check',
{
checkedValue: 1,
uncheckedValue: 0,
},
{ showRequireMark: false },
),
// 只有在跳过预检查关闭时才显示DNS递归服务器、预检查超时时间和忽略预检查结果
...(param.value.skip_check === 0
? [
useFormInput(
$t('t_0_1747106957037'),
'name_server',
{
placeholder: $t('t_1_1747106961747'),
allowInput: noSideSpace,
onInput: (val: string) => {
param.value.name_server = val.replace(//g, ',').replace(/;/g, ',') // 中文逗号分隔
},
},
{ showRequireMark: false },
),
{
type: 'custom' as const,
render: () => {
return (
<NFormItem label={$t('t_0_1749263105073')} path="max_wait">
<NInputNumber
v-model:value={(param.value as ApplyNodeConfig & { max_wait?: number }).max_wait}
showButton={false}
min={1}
class="w-full"
placeholder={$t('t_1_1749263104936')}
/>
</NFormItem>
)
},
},
{
type: 'custom' as const,
render: () => {
return (
<NFormItem label={$t('t_2_1749263103765')} path="ignore_check">
<div class="flex items-center">
<span class="text-[1.4rem] mr-[1.2rem]">{$t('t_3_1749263104237')}</span>
<NSwitch
v-model:value={param.value.ignore_check}
checkedValue={1}
uncheckedValue={0}
class="mx-[.5rem]"
v-slots={{
checked: () => $t('t_4_1749263101853'),
unchecked: () => $t('t_5_1749263101934'),
}}
/>
<span class="text-[1.4rem] ml-[1.2rem]">{$t('t_6_1749263103891')}</span>
</div>
</NFormItem>
)
},
},
]
: []),
]
: []),
useFormHelp([
{
content: $t('t_0_1747040228657'),
},
{
content: $t('t_1_1747040226143'),
},
]),
]
})
{
type: "custom" as const,
render: () => {
return (
<NFormItem label={$t("t_4_1747990227956")} path="end_day">
<div class="flex items-center">
<span class="text-[1.4rem] mr-[1.2rem]">
{$t("t_5_1747990228592")}
</span>
<NInputNumber
v-model:value={param.value.end_day}
showButton={false}
min={1}
class="w-[120px]"
/>
<span class="text-[1.4rem] ml-[1.2rem]">
{$t("t_6_1747990228465")}
</span>
</div>
</NFormItem>
);
},
},
useFormMore(advancedOptions),
...(advancedOptions.value
? [
useFormSelect(
$t("t_0_1747647014927"),
"algorithm",
[
{ label: "RSA2048", value: "RSA2048" },
{ label: "RSA3072", value: "RSA3072" },
{ label: "RSA4096", value: "RSA4096" },
{ label: "RSA8192", value: "RSA8192" },
{ label: "EC256", value: "EC256" },
{ label: "EC384", value: "EC384" },
],
{},
{ showRequireMark: false }
),
useFormInput(
$t("t_7_1747990227761"),
"proxy",
{
placeholder: $t("t_8_1747990235316"),
allowInput: noSideSpace,
},
{ showRequireMark: false }
),
useFormSwitch(
$t("t_2_1749204567193"),
"close_cname",
{
checkedValue: 1,
uncheckedValue: 0,
},
{ showRequireMark: false }
),
useFormSwitch(
$t("t_2_1747106957037"),
"skip_check",
{
checkedValue: 1,
uncheckedValue: 0,
},
{ showRequireMark: false }
),
// 只有在跳过预检查关闭时才显示DNS递归服务器、预检查超时时间和忽略预检查结果
...(param.value.skip_check === 0
? [
useFormInput(
$t("t_0_1747106957037"),
"name_server",
{
placeholder: $t("t_1_1747106961747"),
allowInput: noSideSpace,
onInput: (val: string) => {
param.value.name_server = val
.replace(//g, ",")
.replace(/;/g, ","); // 中文逗号分隔
},
},
{ showRequireMark: false }
),
{
type: "custom" as const,
render: () => {
return (
<NFormItem
label={$t("t_0_1749263105073")}
path="max_wait"
>
<NInputNumber
v-model:value={
(
param.value as ApplyNodeConfig & {
max_wait?: number;
}
).max_wait
}
showButton={false}
min={1}
class="w-full"
placeholder={$t("t_1_1749263104936")}
/>
</NFormItem>
);
},
},
{
type: "custom" as const,
render: () => {
return (
<NFormItem
label={$t("t_2_1749263103765")}
path="ignore_check"
>
<div class="flex items-center">
<span class="text-[1.4rem] mr-[1.2rem]">
{$t("t_3_1749263104237")}
</span>
<NSwitch
v-model:value={param.value.ignore_check}
checkedValue={1}
uncheckedValue={0}
class="mx-[.5rem]"
v-slots={{
checked: () => $t("t_4_1749263101853"),
unchecked: () => $t("t_5_1749263101934"),
}}
/>
<span class="text-[1.4rem] ml-[1.2rem]">
{$t("t_6_1749263103891")}
</span>
</div>
</NFormItem>
);
},
},
]
: []),
]
: []),
useFormHelp([
{
content: $t("t_0_1747040228657"),
},
{
content: $t("t_1_1747040226143"),
},
]),
];
});
// 创建表单实例
const { component: Form, data, example } = useForm<ApplyNodeConfig>({ defaultValue: param, config, rules })
// 创建表单实例
const {
component: Form,
data,
example,
} = useForm<ApplyNodeConfig>({ defaultValue: param, config, rules });
// 监听CA值变化自动加载邮箱选项
watch(
() => param.value.ca,
async (newCA) => {
if (newCA) {
await loadEmailOptions(newCA)
} else {
emailOptions.value = []
param.value.email = ''
showEmailDropdown.value = false
}
},
)
// 监听CA值变化自动加载邮箱选项
watch(
() => param.value.ca,
async (newCA) => {
if (newCA) {
await loadEmailOptions(newCA);
} else {
emailOptions.value = [];
param.value.email = "";
showEmailDropdown.value = false;
}
}
);
// 监听邮箱选项变化,如果当前下拉显示且没有选项了就关闭下拉
watch(
() => emailOptions.value,
(newOptions) => {
if (showEmailDropdown.value && newOptions.length === 0) {
showEmailDropdown.value = false
}
},
)
// 监听邮箱选项变化,如果当前下拉显示且没有选项了就关闭下拉
watch(
() => emailOptions.value,
(newOptions) => {
if (showEmailDropdown.value && newOptions.length === 0) {
showEmailDropdown.value = false;
}
}
);
onMounted(async () => {
advancedOptions.value = false
await loadCAOptions()
onMounted(async () => {
advancedOptions.value = false;
await loadCAOptions();
// 如果是编辑模式且有外部传入的邮箱,直接设置邮箱值
if (isEdit.value && routeEmail.value) {
param.value.email = routeEmail.value
}
// 如果当前已经有CA值主动加载对应的邮件选项
if (param.value.ca) {
await loadEmailOptions(param.value.ca);
}
// 移除重复调用,让 watch 监听器处理 CA 值变化
// 这样避免了 onMounted 和 watch 同时调用 loadEmailOptions 导致的重复请求
})
// 如果是编辑模式且有外部传入的邮箱,直接设置邮箱值
if (isEdit.value && routeEmail.value) {
param.value.email = routeEmail.value;
}
// 确认事件触发
confirm(async (close) => {
try {
await example.value?.validate()
// 移除重复调用,让 watch 监听器处理 CA 值变化
// 这样避免了 onMounted 和 watch 同时调用 loadEmailOptions 导致的重复请求
});
updateNodeConfig(props.node.id, data.value) // 更新节点配置
isRefreshNode.value = props.node.id // 刷新节点
close()
} catch (error) {
console.log(error)
}
})
// 确认事件触发
confirm(async (close) => {
try {
await example.value?.validate();
return () => (
<div class="apply-node-drawer">
<Form labelPlacement="top" />
</div>
)
},
updateNodeConfig(props.node.id, data.value); // 更新节点配置
isRefreshNode.value = props.node.id; // 刷新节点
close();
} catch (error) {
console.log(error);
}
});
return () => (
<div class="apply-node-drawer">
<Form labelPlacement="top" />
</div>
);
},
})

View File

@@ -28,208 +28,235 @@ export default defineComponent({
},
},
setup(props) {
const { updateNodeConfig, isRefreshNode, flowData } = useStore()
// 弹窗辅助
const { confirm } = useModalHooks()
// 错误处理
const { handleError } = useError()
// 获取表单助手函数
const { useFormRadio, useFormCustom } = useFormHooks()
// 表单参数
const param = ref(deepClone(props.node.config))
// 周期类型选项
const cycleTypeOptions = [
{ label: $t('t_2_1744875938555'), value: 'day' },
{ label: $t('t_0_1744942117992'), value: 'week' },
{ label: $t('t_3_1744875938310'), value: 'month' },
]
// 星期选项
const weekOptions = [
{ label: $t('t_1_1744942116527'), value: 1 },
{ label: $t('t_2_1744942117890'), value: 2 },
{ label: $t('t_3_1744942117885'), value: 3 },
{ label: $t('t_4_1744942117738'), value: 4 },
{ label: $t('t_5_1744942117167'), value: 5 },
{ label: $t('t_6_1744942117815'), value: 6 },
{ label: $t('t_7_1744942117862'), value: 0 },
]
// 定义默认值常量,避免重复
const DEFAULT_AUTO_SETTINGS: Record<string, StartNodeConfig> = {
day: { exec_type: 'auto', type: 'day', hour: 1, minute: 0 },
week: { exec_type: 'auto', type: 'week', hour: 1, minute: 0, week: 1 },
month: { exec_type: 'auto', type: 'month', hour: 1, minute: 0, month: 1 },
}
// 创建时间输入input
const createTimeInput = (value: number, updateFn: (val: number) => void, max: number, label: string): VNode => (
<NInputGroup>
<NInputNumber
value={value}
onUpdateValue={(val: number | null) => {
if (val !== null) {
updateFn(val)
}
}}
max={max}
min={0}
showButton={false}
class="w-full"
/>
<NInputGroupLabel>{label}</NInputGroupLabel>
</NInputGroup>
)
// 表单渲染
const formRender = computed(() => {
const formItems: FormConfig = []
if (param.value.exec_type === 'auto') {
formItems.push(
useFormCustom<StartNodeConfig>(() => {
return (
<NGrid cols={24} xGap={24}>
<NFormItemGi label={$t('t_2_1744879616413')} span={8} showRequireMark path="type">
<NSelect
class="w-full"
options={cycleTypeOptions}
v-model:value={param.value.type}
onUpdateValue={(val: 'day' | 'week' | 'month') => {
if (val) {
param.value.type = val
updateParamValueByType(val)
}
}}
/>
</NFormItemGi>
{param.value.type !== 'day' && (
<NFormItemGi span={5} path={param.value.type === 'week' ? 'week' : 'month'}>
{param.value.type === 'week' ? (
<NSelect
value={param.value.week}
onUpdateValue={(val: number) => {
param.value.week = val
}}
options={weekOptions}
/>
) : (
createTimeInput(
param.value.month || 0,
(val: number) => (param.value.month = val),
31,
$t('t_29_1744958838904'),
)
)}
</NFormItemGi>
)}
<NFormItemGi span={param.value.type === 'day' ? 7 : 5} path="hour">
{createTimeInput(
param.value.hour || 0,
(val: number) => (param.value.hour = val),
23,
$t('t_5_1744879615277'),
)}
</NFormItemGi>
<NFormItemGi span={param.value.type === 'day' ? 7 : 5} path="minute">
{createTimeInput(
param.value.minute || 0,
(val: number) => (param.value.minute = val),
59,
$t('t_3_1744879615723'),
)}
</NFormItemGi>
</NGrid>
)
}),
)
}
return [
// 运行模式选择
useFormRadio($t('t_30_1745735764748'), 'exec_type', [
{ label: $t('t_4_1744875940750'), value: 'auto' },
{ label: $t('t_5_1744875940010'), value: 'manual' },
]),
...formItems,
]
})
// 创建表单实例
const {
component: Form,
data,
example,
} = useForm<StartNodeConfig>({
defaultValue: param,
config: formRender,
rules,
})
updateNodeConfig,
isRefreshNode,
flowData,
setStartNodeSavedByUser,
startNodeSavedByUser,
} = useStore();
// 弹窗辅助
const { confirm } = useModalHooks();
// 错误处理
const { handleError } = useError();
// 获取表单助手函数
const { useFormRadio, useFormCustom } = useFormHooks();
// 表单参数
const param = ref(deepClone(props.node.config));
// 更新参数的函数
const updateParamValue = (updates: StartNodeConfig) => {
console.log(updates)
let newParams = { ...updates }
// if (newParams.exec_type === 'manual') {
// 小时随机 1-6
const randomHour = Math.floor(Math.random() * 4) + 1
// 分钟每5分钟随机0-55
const randomMinute = Math.floor(Math.random() * 12) * 5
newParams = {
...newParams,
hour: randomHour,
minute: randomMinute,
}
param.value = newParams
// }
}
// 周期类型选项
const cycleTypeOptions = [
{ label: $t("t_2_1744875938555"), value: "day" },
{ label: $t("t_0_1744942117992"), value: "week" },
{ label: $t("t_3_1744875938310"), value: "month" },
];
const updateParamValueByType = (type: 'day' | 'week' | 'month') => {
updateParamValue(DEFAULT_AUTO_SETTINGS[type] as StartNodeConfig)
}
// 星期选项
const weekOptions = [
{ label: $t("t_1_1744942116527"), value: 1 },
{ label: $t("t_2_1744942117890"), value: 2 },
{ label: $t("t_3_1744942117885"), value: 3 },
{ label: $t("t_4_1744942117738"), value: 4 },
{ label: $t("t_5_1744942117167"), value: 5 },
{ label: $t("t_6_1744942117815"), value: 6 },
{ label: $t("t_7_1744942117862"), value: 0 },
];
// 监听执行类型变化
watch(
() => param.value.exec_type,
(newVal) => {
if (newVal === 'auto') {
updateParamValue(DEFAULT_AUTO_SETTINGS.day as StartNodeConfig)
} else if (newVal === 'manual') {
updateParamValue({ exec_type: 'manual' })
}
},
)
// 定义默认值常量,避免重复
const DEFAULT_AUTO_SETTINGS: Record<string, StartNodeConfig> = {
day: { exec_type: "auto", type: "day", hour: 1, minute: 0 },
week: { exec_type: "auto", type: "week", hour: 1, minute: 0, week: 1 },
month: { exec_type: "auto", type: "month", hour: 1, minute: 0, month: 1 },
};
// 监听类型变化
watch(
() => param.value.type,
(newVal) => {
if (newVal && param.value.exec_type === 'auto') {
updateParamValue(DEFAULT_AUTO_SETTINGS[newVal] as StartNodeConfig)
}
},
)
// 创建时间输入input
const createTimeInput = (
value: number,
updateFn: (val: number) => void,
max: number,
label: string
): VNode => (
<NInputGroup>
<NInputNumber
value={value}
onUpdateValue={(val: number | null) => {
if (val !== null) {
updateFn(val);
}
}}
max={max}
min={0}
showButton={false}
class="w-full"
/>
<NInputGroupLabel>{label}</NInputGroupLabel>
</NInputGroup>
);
// 确认事件触发
confirm(async (close) => {
try {
await example.value?.validate()
updateNodeConfig(props.node.id, data.value) // 更新节点配置
isRefreshNode.value = props.node.id // 刷新节点
close()
} catch (error) {
handleError(error)
}
})
// 表单渲染
const formRender = computed(() => {
const formItems: FormConfig = [];
if (param.value.exec_type === "auto") {
formItems.push(
useFormCustom<StartNodeConfig>(() => {
return (
<NGrid cols={24} xGap={24}>
<NFormItemGi
label={$t("t_2_1744879616413")}
span={8}
showRequireMark
path="type"
>
<NSelect
class="w-full"
options={cycleTypeOptions}
v-model:value={param.value.type}
onUpdateValue={(val: "day" | "week" | "month") => {
if (val) {
param.value.type = val;
updateParamValueByType(val);
}
}}
/>
</NFormItemGi>
onMounted(() => {
if (isUndefined(flowData.value.id)) {
updateParamValueByType('day')
updateNodeConfig(props.node.id, param.value) // 更新节点配置
}
})
{param.value.type !== "day" && (
<NFormItemGi
span={5}
path={param.value.type === "week" ? "week" : "month"}
>
{param.value.type === "week" ? (
<NSelect
value={param.value.week}
onUpdateValue={(val: number) => {
param.value.week = val;
}}
options={weekOptions}
/>
) : (
createTimeInput(
param.value.month || 0,
(val: number) => (param.value.month = val),
31,
$t("t_29_1744958838904")
)
)}
</NFormItemGi>
)}
<NFormItemGi
span={param.value.type === "day" ? 7 : 5}
path="hour"
>
{createTimeInput(
param.value.hour || 0,
(val: number) => (param.value.hour = val),
23,
$t("t_5_1744879615277")
)}
</NFormItemGi>
<NFormItemGi
span={param.value.type === "day" ? 7 : 5}
path="minute"
>
{createTimeInput(
param.value.minute || 0,
(val: number) => (param.value.minute = val),
59,
$t("t_3_1744879615723")
)}
</NFormItemGi>
</NGrid>
);
})
);
}
return [
// 运行模式选择
useFormRadio($t("t_30_1745735764748"), "exec_type", [
{ label: $t("t_4_1744875940750"), value: "auto" },
{ label: $t("t_5_1744875940010"), value: "manual" },
]),
...formItems,
];
});
// 创建表单实例
const {
component: Form,
data,
example,
} = useForm<StartNodeConfig>({
defaultValue: param,
config: formRender,
rules,
});
// 更新参数的函数
const updateParamValue = (updates: StartNodeConfig) => {
console.log(updates);
let newParams = { ...updates };
// if (newParams.exec_type === 'manual') {
// 小时随机 1-6
const randomHour = Math.floor(Math.random() * 4) + 1;
// 分钟每5分钟随机0-55
const randomMinute = Math.floor(Math.random() * 12) * 5;
newParams = {
...newParams,
hour: randomHour,
minute: randomMinute,
};
param.value = newParams;
// }
};
const updateParamValueByType = (type: "day" | "week" | "month") => {
updateParamValue(DEFAULT_AUTO_SETTINGS[type] as StartNodeConfig);
};
// 监听执行类型变化
watch(
() => param.value.exec_type,
(newVal) => {
if (newVal === "auto") {
updateParamValue(DEFAULT_AUTO_SETTINGS.day as StartNodeConfig);
} else if (newVal === "manual") {
updateParamValue({ exec_type: "manual" });
}
}
);
// 监听类型变化
watch(
() => param.value.type,
(newVal) => {
if (newVal && param.value.exec_type === "auto") {
updateParamValue(DEFAULT_AUTO_SETTINGS[newVal] as StartNodeConfig);
}
}
);
// 确认事件触发
confirm(async (close) => {
try {
await example.value?.validate();
updateNodeConfig(props.node.id, data.value); // 更新节点配置
isRefreshNode.value = props.node.id; // 刷新节点
setStartNodeSavedByUser(true);
close();
} catch (error) {
handleError(error);
}
});
onMounted(() => {
console.log("开始节点初始化");
if (isUndefined(flowData.value.id) && !startNodeSavedByUser.value) {
updateParamValueByType("day");
updateNodeConfig(props.node.id, param.value); // 更新节点配置
}
});
return () => (
<div class="apply-node-drawer">

View File

@@ -91,6 +91,57 @@ export const useController = () => {
// 判断是否为子路由
const hasChildRoutes = computed(() => route.path !== "/auto-deploy");
/**
* @description 格式化执行周期显示
* @param {string} execTime - 执行时间配置JSON字符串
* @param {string} execType - 执行类型
* @returns {string} 格式化后的执行周期文本
*/
const formatExecTime = (execTime: string, execType: string): string => {
// 如果是手动执行,显示 --
if (execType !== "auto") {
return "--";
}
// 如果没有执行时间配置,默认为每日
if (!execTime) {
return "每日";
}
try {
const timeConfig = JSON.parse(execTime);
const { type = "day", hour, minute, week, month } = timeConfig;
// 格式化时间
const timeStr = `${hour.toString().padStart(2, "0")}:${minute
.toString()
.padStart(2, "0")}`;
switch (type) {
case "day":
return `每日 ${timeStr}`;
case "week":
const weekDays = [
"周日",
"周一",
"周二",
"周三",
"周四",
"周五",
"周六",
];
return `每周${weekDays[week] || "周" + week} ${timeStr}`;
case "month":
return `每月${month}${timeStr}`;
default:
return `每日 ${timeStr}`;
}
} catch (error) {
console.error("解析执行时间配置失败:", error);
return "每日";
}
};
/**
* @description 创建表格列配置
* @returns {DataTableColumn<WorkflowItem>[]} 返回表格列配置数组
@@ -140,6 +191,14 @@ export const useController = () => {
render: (row: WorkflowItem) => row.last_run_time || "-",
},
statusCol<WorkflowItem>("last_run_status", $t("t_2_1750129253921")),
{
title: "执行周期",
key: "exec_time",
width: 150,
render: (row: WorkflowItem) => (
<span>{formatExecTime(row.exec_time, row.exec_type)}</span>
),
},
{
title: $t("t_8_1745215914610"),
key: "actions",
@@ -342,12 +401,17 @@ export const useController = () => {
* @description 复制工作流
* @param {WorkflowItem} workflow - 工作流对象
*/
const handleCopyWorkflow = async (workflow: WorkflowItem) => {
console.log(workflow, 'workflow123123123123');
const { name, content, exec_type, active, exec_time } = workflow;
const params = { name: `${name} - 副本`, content, exec_type, active, exec_time };
await copyExistingWorkflow(params as WorkflowItem);
await fetch();
const handleCopyWorkflow = async (workflow: WorkflowItem) => {
const { name, content, exec_type, active, exec_time } = workflow;
const params = {
name: `${name} - 副本`,
content,
exec_type,
active,
exec_time,
};
await copyExistingWorkflow(params as WorkflowItem);
await fetch();
};
/**

View File

@@ -45,18 +45,21 @@ export default defineComponent({
<NSelect
options={[
{ label: "全部", value: "" },
...intermediateCaList.value.map((item) => ({
...(intermediateCaList.value || []).map((item) => ({
label: item.name,
value: item.id,
}))
})),
]}
placeholder="请选择中间证书"
size="large"
style={{ width: "180px" }}
defaultValue={""}
style={{ width: "180px" }}
defaultValue={""}
onUpdateValue={handleCaIdChange}
/>
<SearchComponent placeholder="请输入名称搜索" style={{ width: "240px" }} />
<SearchComponent
placeholder="请输入名称搜索"
style={{ width: "240px" }}
/>
</div>
),
content: () => (

View File

@@ -38,19 +38,24 @@ export const useStore = () => {
const getIntermediateCaList = async () => {
try {
const { fetch, data } = getCaList({
const { fetch, data } = getCaList({
p: "-1",
limit: "-1",
level: "intermediate",
});
await fetch();
if (data.value?.status === true) {
intermediateCaList.value = data.value.data;
return data.value.data;
}
} catch (error) {
console.error('获取中间证书列表失败:', error);
}
});
await fetch();
if (data.value?.status === true) {
intermediateCaList.value = data.value.data || [];
return data.value.data || [];
} else {
intermediateCaList.value = [];
return [];
}
} catch (error) {
console.error("获取中间证书列表失败:", error);
intermediateCaList.value = [];
return [];
}
};
return {

View File

@@ -1,174 +1,177 @@
import { defineComponent, ref, computed, watch } from 'vue';
import { NForm, NFormItem, NInput, NSelect, NSpace, NButton, FormRules, useMessage } from 'naive-ui';
import { useStore } from '../useStore';
import { useAddCaController } from '../useController';
import { useModalClose } from '@baota/naive-ui/hooks';
import {
NForm,
NFormItem,
NInput,
NSelect,
NSpace,
NButton,
FormRules,
useMessage,
NDivider,
NIcon,
} from "naive-ui";
import { useStore } from "../useStore";
import { useAddCaController } from "../useController";
import { useModalClose } from "@baota/naive-ui/hooks";
import { ChevronDown } from "@vicons/ionicons5";
/**
* 添加CA模态框组件
*/
export default defineComponent({
emits: ['success'],
setup(props, { emit }) {
const { addForm, resetAddForm, createType, rootCaList } = useStore();
const message = useMessage();
const closeModal = useModalClose();
// 表单引用
const formRef = ref();
// 有效期单位选择
const validityUnit = ref<'day' | 'year'>('day');
// 使用表单控制器
const { handleSubmit } = useAddCaController();
// 表单验证规则
const rules = computed((): FormRules => {
const baseRules: any = {
name: [
{ required: true, message: '请输入CA名称', trigger: 'blur' }
],
cn: [
{ required: true, message: '请输入通用名称', trigger: 'blur' }
],
o: [
{ required: true, message: '请输入组织名称', trigger: 'blur' }
],
c: [
{ required: true, message: '请选择国家', trigger: 'change' }
],
ou: [
{ required: true, message: '请输入组织单位', trigger: 'blur' }
],
province: [
{ required: true, message: '请输入省份', trigger: 'blur' }
],
locality: [
{ required: true, message: '请输入城市', trigger: 'blur' }
],
key_length: [
{ required: true, message: '请选择密钥长度', trigger: 'change' }
],
valid_days: [
{ required: true, message: '请选择有效期', trigger: 'change' }
]
};
emits: ["success"],
setup(props, { emit }) {
const { addForm, resetAddForm, createType, rootCaList } = useStore();
const message = useMessage();
const closeModal = useModalClose();
if (createType.value === 'root') {
baseRules.algorithm = [
{ required: true, message: '请选择加密算法', trigger: 'change' }
];
}
// 表单引用
const formRef = ref();
if (createType.value === 'intermediate') {
baseRules.root_id = [
{ required: true, message: '请选择父级CA', trigger: 'change' }
];
}
// 有效期单位选择
const validityUnit = ref<"day" | "year">("day");
return baseRules;
});
// 展开收起状态
const showAdvancedConfig = ref(false);
// 算法选项
const algorithmOptions = [
{ label: "ECDSA", value: "ecdsa" },
{ label: "RSA", value: "rsa" },
{ label: "SM2", value: "sm2" },
];
// 使用表单控制器
const { handleSubmit } = useAddCaController();
const keyLengthOptions = computed(() => {
switch (addForm.value.algorithm) {
case 'ecdsa':
return [
{ label: "P-256 (256 bit)", value: "256" },
{ label: "P-384 (384 bit)", value: "384" },
{ label: "P-521 (521 bit)", value: "521" },
];
case 'rsa':
return [
{ label: "2048 bit", value: "2048" },
{ label: "3072 bit", value: "3072" },
{ label: "4096 bit", value: "4096" },
];
case 'sm2':
return [
{ label: "SM2 (256 bit)", value: "256" },
];
default:
return [];
}
});
// 表单验证规则
const rules = computed((): FormRules => {
const baseRules: any = {
name: [{ required: true, message: "请输入CA名称", trigger: "blur" }],
cn: [{ required: true, message: "请输入通用名称", trigger: "blur" }],
c: [{ required: true, message: "请选择国家", trigger: "change" }],
key_length: [
{ required: true, message: "请选择密钥长度", trigger: "change" },
],
valid_days: [
{ required: true, message: "请选择有效期", trigger: "change" },
],
};
// 国家选项
const countryOptions = [
{ label: "中国", value: "CN" },
{ label: "美国", value: "US" },
{ label: "日本", value: "JP" },
{ label: "德国", value: "DE" },
{ label: "英国", value: "GB" },
];
if (createType.value === "root") {
baseRules.algorithm = [
{ required: true, message: "请选择加密算法", trigger: "change" },
];
}
// 监听算法变化,重置密钥长度选择
watch(() => addForm.value.algorithm, (newAlgorithm) => {
addForm.value.key_length = '';
if (newAlgorithm === 'ecdsa') {
addForm.value.key_length = '256';
} else if (newAlgorithm === 'sm2') {
addForm.value.key_length = '256';
} else if (newAlgorithm === 'rsa') {
addForm.value.key_length = '2048';
}
});
if (createType.value === "intermediate") {
baseRules.root_id = [
{ required: true, message: "请选择父级CA", trigger: "change" },
];
}
// 监听父级CA选择自动填充算法值
watch(() => addForm.value.root_id, (newRootId) => {
if (createType.value === 'intermediate' && newRootId) {
const selectedRootCa = rootCaList.value.find(ca => ca.id.toString() === newRootId);
if (selectedRootCa) {
addForm.value.algorithm = selectedRootCa.algorithm;
if (selectedRootCa.algorithm === 'ecdsa') {
addForm.value.key_length = '256';
} else if (selectedRootCa.algorithm === 'sm2') {
addForm.value.key_length = '256';
} else if (selectedRootCa.algorithm === 'rsa') {
addForm.value.key_length = '2048';
}
}
}
});
return baseRules;
});
// 处理表单提交
const handleFormSubmit = async () => {
try {
// 先验证表单
await formRef.value?.validate();
const formData = { ...addForm.value };
if (validityUnit.value === 'year' && formData.valid_days) {
const years = parseInt(formData.valid_days);
if (!isNaN(years)) {
formData.valid_days = (years * 365).toString();
}
}
const success = await handleSubmit(formData);
if (success) {
resetAddForm();
closeModal();
return true;
}
} catch (error) {
console.error('表单验证失败:', error);
}
return false;
};
// 处理取消操作
const handleCancel = () => {
resetAddForm();
closeModal();
};
// 算法选项
const algorithmOptions = [
{ label: "ECDSA", value: "ecdsa" },
{ label: "RSA", value: "rsa" },
{ label: "SM2", value: "sm2" },
];
return () => (
const keyLengthOptions = computed(() => {
switch (addForm.value.algorithm) {
case "ecdsa":
return [
{ label: "P-256 (256 bit)", value: "256" },
{ label: "P-384 (384 bit)", value: "384" },
{ label: "P-521 (521 bit)", value: "521" },
];
case "rsa":
return [
{ label: "2048 bit", value: "2048" },
{ label: "3072 bit", value: "3072" },
{ label: "4096 bit", value: "4096" },
];
case "sm2":
return [{ label: "SM2 (256 bit)", value: "256" }];
default:
return [];
}
});
// 国家选项
const countryOptions = [
{ label: "中国", value: "CN" },
{ label: "美国", value: "US" },
{ label: "日本", value: "JP" },
{ label: "德国", value: "DE" },
{ label: "英国", value: "GB" },
];
// 监听算法变化,重置密钥长度选择
watch(
() => addForm.value.algorithm,
(newAlgorithm) => {
addForm.value.key_length = "";
if (newAlgorithm === "ecdsa") {
addForm.value.key_length = "256";
} else if (newAlgorithm === "sm2") {
addForm.value.key_length = "256";
} else if (newAlgorithm === "rsa") {
addForm.value.key_length = "2048";
}
}
);
// 监听父级CA选择自动填充算法值
watch(
() => addForm.value.root_id,
(newRootId) => {
if (createType.value === "intermediate" && newRootId) {
const selectedRootCa = rootCaList.value.find(
(ca) => ca.id.toString() === newRootId
);
if (selectedRootCa) {
addForm.value.algorithm = selectedRootCa.algorithm;
if (selectedRootCa.algorithm === "ecdsa") {
addForm.value.key_length = "256";
} else if (selectedRootCa.algorithm === "sm2") {
addForm.value.key_length = "256";
} else if (selectedRootCa.algorithm === "rsa") {
addForm.value.key_length = "2048";
}
}
}
}
);
// 处理表单提交
const handleFormSubmit = async () => {
try {
// 先验证表单
await formRef.value?.validate();
const formData = { ...addForm.value };
if (validityUnit.value === "year" && formData.valid_days) {
const years = parseInt(formData.valid_days);
if (!isNaN(years)) {
formData.valid_days = (years * 365).toString();
}
}
const success = await handleSubmit(formData);
if (success) {
resetAddForm();
closeModal();
return true;
}
} catch (error) {
console.error("表单验证失败:", error);
}
return false;
};
// 处理取消操作
const handleCancel = () => {
resetAddForm();
closeModal();
};
return () => (
<NForm
ref={formRef}
model={addForm.value}
@@ -191,42 +194,6 @@ export default defineComponent({
/>
</NFormItem>
<NFormItem label="组织(O)" path="o" required>
<NInput
v-model:value={addForm.value.o}
placeholder="请输入组织名称"
/>
</NFormItem>
<NFormItem label="国家(C)" path="c" required>
<NSelect
v-model:value={addForm.value.c}
options={countryOptions}
placeholder="请选择国家"
/>
</NFormItem>
<NFormItem label="组织单位(OU)" path="ou" required>
<NInput
v-model:value={addForm.value.ou}
placeholder="请输入组织单位"
/>
</NFormItem>
<NFormItem label="省份" path="province" required>
<NInput
v-model:value={addForm.value.province}
placeholder="请输入省份"
/>
</NFormItem>
<NFormItem label="城市" path="locality" required>
<NInput
v-model:value={addForm.value.locality}
placeholder="请输入城市"
/>
</NFormItem>
{createType.value === "intermediate" && (
<NFormItem label="父级CA" path="root_id" required>
<NSelect
@@ -270,14 +237,78 @@ export default defineComponent({
<NSelect
v-model:value={validityUnit.value}
options={[
{ label: '天', value: 'day' },
{ label: '年', value: 'year' }
{ label: "天", value: "day" },
{ label: "年", value: "year" },
]}
style={{ width: '80px' }}
style={{ width: "80px" }}
/>
</NSpace>
</NFormItem>
<div class="mt-4 mb-4">
<div
class="flex items-center justify-center cursor-pointer py-2"
onClick={() =>
(showAdvancedConfig.value = !showAdvancedConfig.value)
}
>
<NDivider>
<div class="flex items-center gap-2">
<span class="text-[#18a058] font-medium"></span>
<NIcon
size="16"
color="#18a058"
class="transition-transform duration-200"
style={{
transform: showAdvancedConfig.value
? "rotate(180deg)"
: "rotate(0deg)",
}}
>
<ChevronDown />
</NIcon>
</div>
</NDivider>
</div>
{showAdvancedConfig.value && (
<div class="space-y-4 mt-4">
<NFormItem label="组织(O)">
<NInput
v-model:value={addForm.value.o}
placeholder="请输入组织名称"
/>
</NFormItem>
<NFormItem label="国家(C)" path="c" required>
<NSelect
v-model:value={addForm.value.c}
options={countryOptions}
placeholder="请选择国家"
/>
</NFormItem>
<NFormItem label="组织单位(OU)">
<NInput
v-model:value={addForm.value.ou}
placeholder="请输入组织单位"
/>
</NFormItem>
<NFormItem label="省份">
<NInput
v-model:value={addForm.value.province}
placeholder="请输入省份"
/>
</NFormItem>
<NFormItem label="城市">
<NInput
v-model:value={addForm.value.locality}
placeholder="请输入城市"
/>
</NFormItem>
</div>
)}
</div>
<div class="flex justify-end gap-3 mt-6">
<NButton onClick={handleCancel}></NButton>
<NButton type="primary" onClick={handleFormSubmit}>
@@ -286,5 +317,5 @@ export default defineComponent({
</div>
</NForm>
);
},
},
});

View File

@@ -428,11 +428,7 @@ export const useAddCaController = () => {
const baseRules: any = {
name: [{ required: true, message: '请输入CA名称', trigger: 'blur' }],
cn: [{ required: true, message: '请输入通用名称', trigger: 'blur' }],
o: [{ required: true, message: '请输入组织名称', trigger: 'blur' }],
c: [{ required: true, message: '请选择国家', trigger: 'change' }],
ou: [{ required: true, message: '请输入组织单位', trigger: 'blur' }],
province: [{ required: true, message: '请输入省份', trigger: 'blur' }],
locality: [{ required: true, message: '请输入城市', trigger: 'blur' }],
key_length: [{ required: true, message: '请选择密钥长度', trigger: 'change' }],
valid_days: [{ required: true, message: '请选择有效期', trigger: 'change' }],
};
@@ -468,7 +464,7 @@ export const useAddCaController = () => {
try {
openLoad();
// 验证必填字段
let requiredFields: string[] = ['name', 'cn', 'o', 'c', 'ou', 'province', 'locality', 'key_length', 'valid_days'];
let requiredFields: string[] = ['name', 'cn', 'c', 'key_length', 'valid_days'];
if (createType.value === 'root') {
requiredFields.push('algorithm');
}