perf: 英文翻译 by @lorenzo93

Translation  @lorenzo93
pull/453/head
greper 2025-06-28 21:45:48 +08:00 committed by GitHub
commit 082f47663d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
142 changed files with 11912 additions and 9875 deletions

View File

@ -8,8 +8,6 @@
</template>
<script lang="ts" setup>
import zhCN from "ant-design-vue/es/locale/zh_CN";
import enUS from "ant-design-vue/es/locale/en_US";
import { computed, onMounted, provide, ref } from "vue";
import "dayjs/locale/zh-cn";
import "dayjs/locale/en";
@ -27,26 +25,6 @@ defineOptions({
});
const [modal, contextHolder] = Modal.useModal();
provide("modal", modal);
//
const locale = ref(zhCN);
async function reload() {}
localeChanged("zh-cn");
provide("fn:router.reload", reload);
provide("fn:locale.changed", localeChanged);
//
function localeChanged(value: any) {
console.log("locale changed:", value);
if (value === "zh-cn") {
locale.value = zhCN;
dayjs.locale("zh-cn");
} else if (value === "en") {
locale.value = enUS;
dayjs.locale("en");
}
}
localeChanged("zh-cn");
provide("fn:router.reload", reload);
provide("fn:locale.changed", localeChanged);
const { isDark } = usePreferences();
@ -65,13 +43,5 @@ const tokenTheme = computed(() => {
token: tokens,
};
});
//
// const resourceStore = useResourceStore();
// resourceStore.init();
// const pageStore = usePageStore();
// pageStore.init();
// const settingStore = useSettingStore();
// settingStore.init();
</script>

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

@ -16,18 +16,23 @@ const slots = defineSlots();
<template>
<div class="tutorial-button pointer" @click="open">
<template v-if="!slots.default">
<fs-icon v-if="showIcon === false" icon="ant-design:question-circle-outlined" class="mr-0.5"></fs-icon>
<div class="hidden md:block">使用教程</div>
<fs-icon
v-if="showIcon === false"
icon="ant-design:question-circle-outlined"
class="mr-0.5"
></fs-icon>
<div class="hidden md:block">{{$t('tutorial.title')}}</div>
</template>
<slot></slot>
<a-modal v-model:open="openedRef" class="tutorial-modal" width="90%">
<template #title> 使用教程 </template>
<template #title>{{$t('tutorial.title')}}</template>
<tutorial-steps v-if="openedRef" />
<template #footer></template>
</a-modal>
</div>
</template>
<style lang="less">
.tutorial-modal {
top: 50px;

View File

@ -4,6 +4,9 @@
<script lang="ts" setup>
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
type Step = {
title: string;
@ -13,15 +16,9 @@ type Step = {
import { ref } from "vue";
const steps = ref<Step[]>([
{
title: "创建证书流水线"
},
{
title: "添加部署任务"
},
{
title: "定时运行"
}
{ title: t('certd.steps.createPipeline') },
{ title: t('certd.steps.addTask') },
{ title: t('certd.steps.scheduledRun') }
]);
const router = useRouter();

View File

@ -20,14 +20,18 @@
</div>
<div class="flex-center actions">
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()"></fs-button>
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()"></fs-button>
<fs-button class="m-10" icon="ion:arrow-back-outline" @click="prev()">{{ t('guide.buttons.prev') }}</fs-button>
<fs-button class="m-10" type="primary" icon-right="ion:arrow-forward-outline" @click="next()">{{ t('guide.buttons.next') }}</fs-button>
</div>
</div>
</template>
<script setup lang="tsx">
import { FsRender } from "@fast-crud/fast-crud";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import SimpleSteps from "./simple-steps.vue";
type Step = {
title: string;
@ -45,130 +49,151 @@ type StepItems = {
import { computed, nextTick, ref } from "vue";
const steps = ref<Step[]>([
{
title: "创建证书申请流水线",
description: "演示证书申请任务如何配置",
{
title: t("guide.createCertPipeline.title"),
description: t("guide.createCertPipeline.description"),
items: [
{
title: "教程演示内容",
descriptions: ["本教程演示如何自动申请证书并部署到Nginx上", "仅需3步全自动申请部署证书"],
title: t("guide.createCertPipeline.items.tutorialTitle"),
descriptions: [
t("guide.createCertPipeline.items.tutorialDesc1"),
t("guide.createCertPipeline.items.tutorialDesc2"),
],
body: () => {
return <SimpleSteps></SimpleSteps>;
},
},
{
image: "/static/doc/images/1-add.png",
title: "创建证书流水线",
descriptions: ["点击添加证书流水线,填写证书申请信息"],
title: t("guide.createCertPipeline.items.createTitle"),
descriptions: [t("guide.createCertPipeline.items.createDesc")],
},
{
image: "/static/doc/images/3-add-success.png",
title: "流水线创建成功",
descriptions: ["点击手动触发即可申请证书"],
title: t("guide.createCertPipeline.items.successTitle"),
descriptions: [t("guide.createCertPipeline.items.successDesc")],
},
{
title: "接下来演示如何自动部署证书",
descriptions: ["如果您只需要申请证书,那么到这一步就可以了"],
title: t("guide.createCertPipeline.items.nextTitle"),
descriptions: [t("guide.createCertPipeline.items.nextDesc")],
},
],
},
{
title: "添加部署证书任务",
description: "这里演示部署证书到Nginx",
title: t("guide.addDeployTask.title"),
description: t("guide.addDeployTask.description"),
items: [
{
image: "/static/doc/images/5-1-add-host.png",
title: "添加证书部署任务",
descriptions: ["这里演示自动部署证书到nginx", "本系统提供海量部署插件,满足您的各种部署需求"],
title: t("guide.addDeployTask.items.addTaskTitle"),
descriptions: [
t("guide.addDeployTask.items.addTaskDesc1"),
t("guide.addDeployTask.items.addTaskDesc2"),
],
},
{
image: "/static/doc/images/5-2-add-host.png",
title: "填写任务参数",
descriptions: ["填写主机上证书文件的路径", "选择主机ssh登录授权"],
title: t("guide.addDeployTask.items.fillParamsTitle"),
descriptions: [
t("guide.addDeployTask.items.fillParamsDesc1"),
t("guide.addDeployTask.items.fillParamsDesc2"),
],
},
{
image: "/static/doc/images/5-3-add-host.png",
title: "让新证书生效",
descriptions: ["执行重启脚本", "让证书生效"],
title: t("guide.addDeployTask.items.activateCertTitle"),
descriptions: [
t("guide.addDeployTask.items.activateCertDesc1"),
t("guide.addDeployTask.items.activateCertDesc2"),
],
},
{
image: "/static/doc/images/5-4-add-host.png",
title: "部署任务添加成功",
descriptions: ["现在可以运行"],
title: t("guide.addDeployTask.items.taskSuccessTitle"),
descriptions: [t("guide.addDeployTask.items.taskSuccessDesc")],
},
{
image: "/static/doc/images/5-5-plugin-list.png",
title: "本系统提供茫茫多的部署插件",
descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"],
},
],
},
{
title: "运行与测试",
description: "演示流水线运行,查看日志,成功后跳过等",
items: [
{
image: "/static/doc/images/9-start.png",
title: "运行测试一下",
descriptions: ["点击手动触发按钮,即可测试运行"],
},
{
image: "/static/doc/images/10-1-log.png",
title: "查看日志",
descriptions: ["点击任务可以查看状态和日志"],
},
{
image: "/static/doc/images/11-1-error.png",
title: "执行失败如何排查",
descriptions: ["查看错误日志"],
},
{
image: "/static/doc/images/11-2-error.png",
title: "执行失败如何排查",
descriptions: ["查看错误日志", "这里报的是nginx容器不存在修改命令改成正确的nginx容器名称即可"],
},
{
image: "/static/doc/images/12-1-log-success.png",
title: "执行成功",
descriptions: ["修改正确后,重新点击手动触发,重新运行一次,执行成功"],
},
{
image: "/static/doc/images/12-2-skip-log.png",
title: "成功后自动跳过",
descriptions: ["可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行"],
},
{
image: "/static/doc/images/13-1-result.png",
title: "查看证书部署成功",
descriptions: ["访问nginx上的网站可以看到证书已经部署成功"],
},
{
image: "/static/doc/images/13-3-download.png",
title: "还可以下载证书,手动部署",
descriptions: ["如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署"],
},
],
},
{
title: "设置定时执行和邮件通知",
description: "自动运行",
items: [
{
image: "/static/doc/images/14-timer.png",
title: "设置定时执行",
descriptions: ["流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了", "推荐配置每天运行一次在到期前35天才会重新申请新证书并部署没到期前会自动跳过不会重复申请。"],
},
{
image: "/static/doc/images/15-1-email.png",
title: "设置邮件通知",
descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"],
},
{
title: "教程结束",
descriptions: ["感谢观看,希望对你有所帮助"],
title: t("guide.addDeployTask.items.pluginsTitle"),
descriptions: [t("guide.addDeployTask.items.pluginsDesc")],
},
],
},
{
title: t('guide.runAndTestTask.runAndTestTitle'),
description: t('guide.runAndTestTask.runAndTestDescription'),
items: [
{
image: "/static/doc/images/9-start.png",
title: t('guide.runAndTestTask.runTestOnce'),
descriptions: [t('guide.runAndTestTask.clickManualTriggerToTest')],
},
{
image: "/static/doc/images/10-1-log.png",
title: t('guide.runAndTestTask.viewLogs'),
descriptions: [t('guide.runAndTestTask.clickTaskToViewStatusAndLogs')],
},
{
image: "/static/doc/images/11-1-error.png",
title: t('guide.runAndTestTask.howToTroubleshootFailure'),
descriptions: [t('guide.runAndTestTask.viewErrorLogs')],
},
{
image: "/static/doc/images/11-2-error.png",
title: t('guide.runAndTestTask.howToTroubleshootFailure'),
descriptions: [
t('guide.runAndTestTask.viewErrorLogs'),
t('guide.runAndTestTask.nginxContainerNotExistFix'),
],
},
{
image: "/static/doc/images/12-1-log-success.png",
title: t('guide.runAndTestTask.executionSuccess'),
descriptions: [t('guide.runAndTestTask.retryAfterFix')],
},
{
image: "/static/doc/images/12-2-skip-log.png",
title: t('guide.runAndTestTask.autoSkipAfterSuccess'),
descriptions: [t('guide.runAndTestTask.successSkipExplanation')],
},
{
image: "/static/doc/images/13-1-result.png",
title: t('guide.runAndTestTask.viewCertDeploymentSuccess'),
descriptions: [t('guide.runAndTestTask.visitNginxToSeeCert')],
},
{
image: "/static/doc/images/13-3-download.png",
title: t('guide.runAndTestTask.downloadCertManualDeploy'),
descriptions: [t('guide.runAndTestTask.downloadIfNoAutoDeployPlugin')],
},
],
},
{
title: t('guide.scheduleAndEmailTask.title'),
description: t('guide.scheduleAndEmailTask.description'),
items: [
{
image: "/static/doc/images/14-timer.png",
title: t('guide.scheduleAndEmailTask.setSchedule'),
descriptions: [
t('guide.scheduleAndEmailTask.pipelineSuccessThenSchedule'),
t('guide.scheduleAndEmailTask.recommendDailyRun'),
],
},
{
image: "/static/doc/images/15-1-email.png",
title: t('guide.scheduleAndEmailTask.setEmailNotification'),
descriptions: [
t('guide.scheduleAndEmailTask.suggestErrorAndRecoveryEmails'),
t('guide.scheduleAndEmailTask.basicVersionNeedsMailServer'),
],
},
{
title: t('guide.scheduleAndEmailTask.tutorialEndTitle'),
descriptions: [t('guide.scheduleAndEmailTask.thanksForWatching')],
},
],
}
]);
const current = ref(0);

View File

@ -1,15 +1,15 @@
<template>
<div v-if="!settingStore.isComm || userStore.isAdmin" class="layout-vip isPlus" @click="openUpgrade">
<contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" />
<div v-if="!settingStore.isComm || userStore.isAdmin" class="layout-vip isPlus" @click="openUpgrade">
<contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" />
<div v-if="mode !== 'icon'" class="text hidden md:block ml-0.5">
<a-tooltip>
<template #title> {{ text.title }}</template>
<span class="">{{ text.name }}</span>
</a-tooltip>
</div>
</div>
<div v-if="mode !== 'icon'" class="text hidden md:block ml-0.5">
<a-tooltip>
<template #title> {{ text.title }}</template>
<span class="">{{ text.name }}</span>
</a-tooltip>
</div>
</div>
</template>
<script lang="tsx" setup>
import { computed, onMounted, reactive } from "vue";
@ -20,6 +20,9 @@ import { useSettingStore } from "/@/store/settings";
import { useRouter } from "vue-router";
import { useUserStore } from "/@/store/user";
import { mitter } from "/@/utils/util.mitt";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const settingStore = useSettingStore();
const props = withDefaults(
@ -39,56 +42,56 @@ const text = computed<Text>(() => {
const map = {
isComm: {
comm: {
name: `${vipLabel}已开通`,
title: "到期时间:" + expireTime.value,
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
},
button: {
name: `${vipLabel}已开通`,
title: "到期时间:" + expireTime.value,
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
},
icon: {
name: "",
title: `${vipLabel}已开通`,
title: t("vip.comm.name", { vipLabel }),
},
nav: {
name: `${vipLabel}`,
title: "到期时间:" + expireTime.value,
name: t("vip.comm.nav", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
},
},
isPlus: {
comm: {
name: "商业版功能",
title: "升级商业版,获取商业授权",
name: t("vip.plus.name"),
title: t("vip.plus.title"),
},
button: {
name: `${vipLabel}已开通`,
title: "到期时间:" + expireTime.value,
name: t("vip.comm.name", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
},
icon: {
name: "",
title: `${vipLabel}已开通`,
title: t("vip.comm.name", { vipLabel }),
},
nav: {
name: `${vipLabel}`,
title: "到期时间:" + expireTime.value,
name: t("vip.comm.nav", { vipLabel }),
title: t("vip.comm.title", { expire: expireTime.value }),
},
},
free: {
comm: {
name: "商业版功能",
title: "升级商业版,获取商业授权",
name: t("vip.free.comm.name"),
title: t("vip.free.comm.title"),
},
button: {
name: "专业版功能",
title: "升级专业版享受更多VIP特权",
name: t("vip.free.button.name"),
title: t("vip.free.button.title"),
},
icon: {
name: "",
title: "专业版功能",
title: t("vip.free.button.name"),
},
nav: {
name: "基础版",
title: "升级专业版享受更多VIP特权",
name: t("vip.free.nav.name"),
title: t("vip.free.nav.title"),
},
},
};
@ -101,6 +104,7 @@ const text = computed<Text>(() => {
}
});
const expireTime = computed(() => {
if (settingStore.isPlus) {
return dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD");
@ -125,22 +129,24 @@ const formState = reactive({
const router = useRouter();
async function doActive() {
if (!formState.code) {
message.error("请输入激活码");
throw new Error("请输入激活码");
message.error(t("vip.enterCode"));
throw new Error(t("vip.enterCode"));
}
const res = await api.doActive(formState);
if (res) {
await settingStore.init();
const vipLabel = settingStore.vipLabel;
Modal.success({
title: "激活成功",
content: `您已成功激活${vipLabel},有效期至:${dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD")}`,
title: t("vip.successTitle"),
content: t("vip.successContent", {
vipLabel,
expireDate: dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"),
}),
onOk() {
if (!(settingStore.installInfo.bindUserId > 0)) {
//
Modal.confirm({
title: "是否绑定袖手账号",
content: "绑定账号后可以避免License丢失强烈建议绑定",
title: t("vip.bindAccountTitle"),
content: t("vip.bindAccountContent"),
onOk() {
router.push("/sys/account");
},
@ -151,6 +157,7 @@ async function doActive() {
}
}
const computedSiteId = computed(() => settingStore.installInfo?.siteId);
const [modal, contextHolder] = Modal.useModal();
const userStore = useUserStore();
@ -162,16 +169,17 @@ function goAccount() {
async function getVipTrial() {
const res = await api.getVipTrial();
message.success(`恭喜,您已获得专业版${res.duration}天试用`);
message.success(t('vip.congratulations_vip_trial', { duration: res.duration }));
await settingStore.init();
}
function openTrialModal() {
Modal.destroyAll();
modal.confirm({
title: "7天专业版试用获取",
okText: "立即获取",
title: t('vip.trial_modal_title'),
okText: t('vip.trial_modal_ok_text'),
onOk() {
getVipTrial();
},
@ -179,14 +187,15 @@ function openTrialModal() {
content: () => {
return (
<div class="flex-col mt-10 mb-10">
<div>感谢您对开源项目的支持</div>
<div>点击确认即可获取7天专业版试用</div>
<div>{t('vip.trial_modal_thanks')}</div>
<div>{t('vip.trial_modal_click_confirm')}</div>
</div>
);
},
});
}
function openStarModal() {
Modal.destroyAll();
const goGithub = () => {
@ -194,8 +203,8 @@ function openStarModal() {
};
modal.confirm({
title: "7天专业版试用获取",
okText: "立即去Star",
title: t("vip.get_7_day_pro_trial"),
okText: t("vip.star_now"),
onOk() {
goGithub();
openTrialModal();
@ -204,7 +213,7 @@ function openStarModal() {
content: () => {
return (
<div class="flex mt-10 mb-10">
<div>可以先请您帮忙点个star吗感谢感谢</div>
<div>{t("vip.please_help_star")}</div>
<img class="ml-5" src="https://img.shields.io/github/stars/certd/certd?logo=github" />
</div>
);
@ -212,174 +221,207 @@ function openStarModal() {
});
}
function openUpgrade() {
if (!userStore.isAdmin) {
message.info("仅限管理员操作");
return;
}
const placeholder = "请输入激活码";
const isPlus = settingStore.isPlus;
let title = "激活专业版/商业版";
if (settingStore.isComm) {
title = "续期商业版";
} else if (settingStore.isPlus) {
title = "续期专业版/升级商业版";
}
if (!userStore.isAdmin) {
message.info(t("vip.admin_only_operation"));
return;
}
const placeholder = t("vip.enter_activation_code");
const isPlus = settingStore.isPlus;
let title = t("vip.activate_pro_business");
if (settingStore.isComm) {
title = t("vip.renew_business");
} else if (settingStore.isPlus) {
title = t("vip.renew_pro_upgrade_business");
}
const productInfo = settingStore.productInfo;
const vipTypeDefine = {
free: {
title: "基础版",
desc: "社区免费版",
type: "free",
icon: "lucide:package-open",
privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn、宝塔、1Panel等部署插件", "邮件、webhook通知方式"],
},
plus: {
title: "专业版",
desc: "开源需要您的赞助支持",
type: "plus",
privilege: ["可加VIP群您的需求将优先实现", "站点证书监控无限制", "更多通知方式", "插件全开放,群辉等更多插件"],
trial: {
title: "点击获取7天试用",
click: () => {
openStarModal();
},
},
icon: "stash:thumb-up",
price: productInfo.plus.price,
price3: `¥${productInfo.plus.price3}/3年`,
tooltip: productInfo.plus.tooltip,
get() {
return (
<a-tooltip title="爱发电赞助“VIP会员”后获取一年期专业版激活码开源需要您的支持">
<a-button size="small" type="primary" href="https://afdian.com/a/greper" target="_blank">
爱发电赞助后获取
</a-button>
</a-tooltip>
);
},
},
comm: {
title: "商业版",
desc: "商业授权,可对外运营",
type: "comm",
icon: "vaadin:handshake",
privilege: ["拥有专业版所有特权", "允许商用可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付"],
price: productInfo.comm.price,
price3: `¥${productInfo.comm.price3}/3年`,
tooltip: productInfo.comm.tooltip,
get() {
return <a-button size="small">请联系作者获取试用</a-button>;
},
},
};
const modalRef = modal.confirm({
title,
async onOk() {
return await doActive();
const productInfo = settingStore.productInfo;
const vipTypeDefine = {
free: {
title: t("vip.basic_edition"),
desc: t("vip.community_free_version"),
type: "free",
icon: "lucide:package-open",
privilege: [
t("vip.unlimited_certificate_application"),
t("vip.unlimited_domain_count"),
t("vip.unlimited_certificate_pipelines"),
t("vip.common_deployment_plugins"),
t("vip.email_webhook_notifications"),
],
},
plus: {
title: t("vip.professional_edition"),
desc: t("vip.open_source_support"),
type: "plus",
privilege: [
t("vip.vip_group_priority"),
t("vip.unlimited_site_certificate_monitoring"),
t("vip.more_notification_methods"),
t("vip.plugins_fully_open"),
],
trial: {
title: t("vip.click_to_get_7_day_trial"),
click: () => {
openStarModal();
},
},
maskClosable: true,
okText: "激活",
width: 1000,
content: () => {
let activationCodeGetWay = (
<span>
<a href="https://afdian.com/a/greper" target="_blank">
爱发电赞助VIP会员后获取一年期专业版激活码
</a>
<span> 商业版请直接联系作者</span>
</span>
icon: "stash:thumb-up",
price: productInfo.plus.price,
price3: `¥${productInfo.plus.price3}/3${t("vip.years")}`,
tooltip: productInfo.plus.tooltip,
get() {
return (
<a-tooltip title={t("vip.afdian_support_vip")}>
<a-button size="small" type="primary" href="https://afdian.com/a/greper" target="_blank">
{t("vip.get_after_support")}
</a-button>
</a-tooltip>
);
const vipLabel = settingStore.vipLabel;
const slots = [];
for (const key in vipTypeDefine) {
// @ts-ignore
const item = vipTypeDefine[key];
const vipBlockClass = `vip-block ${key === settingStore.plusInfo.vipType ? "current" : ""}`;
slots.push(
<a-col span={8}>
<div class={vipBlockClass}>
<h3 class="block-header ">
<span class="flex-o">{item.title}</span>
{item.trial && (
<span class="trial">
<a-tooltip title={item.trial.message}>
<a onClick={item.trial.click}>{item.trial.title}</a>
},
},
comm: {
title: t("vip.business_edition"),
desc: t("vip.commercial_license"),
type: "comm",
icon: "vaadin:handshake",
privilege: [
t("vip.all_pro_privileges"),
t("vip.allow_commercial_use_modify_logo_title"),
t("vip.data_statistics"),
t("vip.plugin_management"),
t("vip.unlimited_multi_users"),
t("vip.support_user_payment"),
],
price: productInfo.comm.price,
price3: `¥${productInfo.comm.price3}/3${t("vip.years")}`,
tooltip: productInfo.comm.tooltip,
get() {
return <a-button size="small">{t("vip.contact_author_for_trial")}</a-button>;
},
},
};
const modalRef = modal.confirm({
title,
async onOk() {
return await doActive();
},
maskClosable: true,
okText: t("vip.activate"),
width: 1000,
content: () => {
let activationCodeGetWay = (
<span>
<a href="https://afdian.com/a/greper" target="_blank">
{t("vip.get_pro_code_after_support")}
</a>
<span> {t("vip.business_contact_author")}</span>
</span>
);
const vipLabel = settingStore.vipLabel;
const slots = [];
for (const key in vipTypeDefine) {
// @ts-ignore
const item = vipTypeDefine[key];
const vipBlockClass = `vip-block ${key === settingStore.plusInfo.vipType ? "current" : ""}`;
slots.push(
<a-col span={8}>
<div class={vipBlockClass}>
<h3 class="block-header ">
<span class="flex-o">{item.title}</span>
{item.trial && (
<span class="trial">
<a-tooltip title={item.trial.message}>
<a onClick={item.trial.click}>{item.trial.title}</a>
</a-tooltip>
</span>
)}
</h3>
<div style="color:green" class="flex-o">
<fs-icon icon={item.icon} class="fs-16 flex-o" />
{item.desc}
</div>
<ul class="flex-1 privilege">
{item.privilege.map((p: string) => (
<li class="flex-baseline">
<fs-icon class="color-green" icon="ion:checkmark-sharp" />
{p}
</li>
))}
</ul>
<div class="footer flex-between flex-vc">
<div class="price-show">
{item.price && (
<span class="flex">
<span class="-text">¥{item.price}</span>
<span>/</span>
{t("vip.year")}
<a-tooltip class="ml-5" title={item.price3}>
<fs-icon class="pointer color-red" icon="ic:outline-discount"></fs-icon>
</a-tooltip>
</span>
)}
</h3>
<div style="color:green" class="flex-o">
<fs-icon icon={item.icon} class="fs-16 flex-o" />
{item.desc}
</div>
<ul class="flex-1 privilege">
{item.privilege.map((p: string) => (
<li class="flex-baseline">
<fs-icon class="color-green" icon="ion:checkmark-sharp" />
{p}
</li>
))}
</ul>
<div class="footer flex-between flex-vc">
<div class="price-show">
{item.price && (
<span class="flex">
<span class="-text">¥{item.price}</span>
<span>/</span>
<a-tooltip class="ml-5" title={item.price3}>
<fs-icon class="pointer color-red" icon="ic:outline-discount"></fs-icon>
</a-tooltip>
</span>
)}
{!item.price && (
<span>
<span class="price-text">免费</span>
</span>
)}
</div>
<div class="get-show">{item.get && <div>{item.get()}</div>}</div>
{!item.price && (
<span>
<span class="price-text">{t("vip.freee")}</span>
</span>
)}
</div>
<div class="get-show">{item.get && <div>{item.get()}</div>}</div>
</div>
</a-col>
);
}
return (
<div class="mt-10 mb-10 vip-active-modal">
{productInfo.notice && (
<div class="mb-10">
<a-alert type="error" message={productInfo.notice}></a-alert>
</div>
)}
<div class="vip-type-vs">
<a-row gutter={20}>{slots}</a-row>
</div>
</a-col>
);
}
return (
<div class="mt-10 mb-10 vip-active-modal">
{productInfo.notice && (
<div class="mb-10">
<a-alert type="error" message={productInfo.notice}></a-alert>
</div>
)}
<div class="vip-type-vs">
<a-row gutter={20}>{slots}</a-row>
</div>
<div class="mt-10">
<h3 class="block-header">{isPlus ? t("vip.renew") : t("vip.activate_immediately")}</h3>
<div>
{isPlus
? `${t("vip.current")} ${vipLabel} ${t("vip.activated_expire_time")}` +
dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD")
: ""}
</div>
<div class="mt-10">
<h3 class="block-header">{isPlus ? "续期" : "立刻激活"}</h3>
<div>{isPlus ? `当前${vipLabel}已激活,到期时间` + dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD") : ""}</div>
<div class="mt-10">
<div class="flex-o w-100">
<span>站点ID</span>
<fs-copyable class="flex-1" v-model={computedSiteId.value}></fs-copyable>
</div>
<a-input class="mt-10" v-model:value={formState.code} placeholder={placeholder} />
<a-input class="mt-10" v-model:value={formState.inviteCode} placeholder={"邀请码【选填】可额外获得专业版30天/商业版15天时长"} />
<div class="flex-o w-100">
<span>{t("vip.site_id")}</span>
<fs-copyable class="flex-1" v-model={computedSiteId.value}></fs-copyable>
</div>
<a-input class="mt-10" v-model:value={formState.code} placeholder={placeholder} />
<a-input
class="mt-10"
v-model:value={formState.inviteCode}
placeholder={t("vip.invite_code_optional")}
/>
</div>
<div class="mt-10">
没有激活码
{activationCodeGetWay}
</div>
<div class="mt-10">
激活码使用过一次之后不可再次使用如果要更换站点<a onClick={goAccount}>绑定账号</a>然后"转移VIP"即可
</div>
<div class="mt-10">
{t("vip.no_activation_code")}
{activationCodeGetWay}
</div>
<div class="mt-10">
{t("vip.activation_code_one_use")}<a onClick={goAccount}>{t("vip.bind_account")}</a>
{t("vip.transfer_vip")}
</div>
</div>
);
},
});
</div>
);
},
});
}
onMounted(() => {
mitter.on("openVipModal", () => {
@ -392,69 +434,75 @@ onMounted(() => {
<style lang="less">
.layout-vip {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.isPlus {
color: #c5913f;
}
&.isPlus {
color: #c5913f;
}
.text {
}
.text {}
}
.vip-active-modal {
.vip-block {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid #eee;
border-radius: 5px;
height: 250px;
//background-color: rgba(250, 237, 167, 0.79);
&.current {
border-color: green;
}
.block-header {
padding: 0px;
display: flex;
justify-content: space-between;
.trial {
font-size: 12px;
font-wight: 400;
}
}
.vip-block {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid #eee;
border-radius: 5px;
height: 250px;
.footer {
padding-top: 5px;
margin-top: 0px;
border-top: 1px solid #eee;
.price-text {
font-size: 18px;
color: red;
}
}
}
//background-color: rgba(250, 237, 167, 0.79);
&.current {
border-color: green;
}
ul {
list-style-type: unset;
margin-left: 0px;
padding: 0;
}
.color-green {
color: green;
}
.vip-type-vs {
.privilege {
.fs-icon {
color: green;
}
}
.fs-icon {
margin-right: 5px;
}
}
.block-header {
padding: 0px;
display: flex;
justify-content: space-between;
.trial {
font-size: 12px;
font-wight: 400;
}
}
.footer {
padding-top: 5px;
margin-top: 0px;
border-top: 1px solid #eee;
.price-text {
font-size: 18px;
color: red;
}
}
}
ul {
list-style-type: unset;
margin-left: 0px;
padding: 0;
}
.color-green {
color: green;
}
.vip-type-vs {
.privilege {
.fs-icon {
color: green;
}
}
.fs-icon {
margin-right: 5px;
}
}
}
</style>

View File

@ -1,24 +0,0 @@
import en from "./locale/en";
import zh from "./locale/zh_CN";
import { SupportedLanguagesType } from "/@/vben/locales";
export const messages = {
"en-US": {
label: "English",
...en
},
"zh-CN": {
label: "简体中文",
...zh
}
};
// export default createI18n({
// legacy: false,
// locale: "zh-cn",
// fallbackLocale: "zh-cn",
// messages
// });
export async function loadMessages(lang: SupportedLanguagesType) {
return messages[lang];
}

View File

@ -1,8 +0,0 @@
export default {
app: { crud: { i18n: { name: "name", city: "city", status: "status" } } },
fs: {
rowHandle: {
title: "Operation"
}
}
};

View File

@ -1,14 +0,0 @@
export default {
app: {
crud: { i18n: { name: "姓名", city: "城市", status: "状态" } },
login: {
logoutTip: "确认",
logoutMessage: "确定要注销登录吗?",
},
},
fs: {
rowHandle: {
title: "操作列",
},
},
};

View File

@ -1,13 +1,13 @@
<template>
<a-dropdown>
<div class="fs-user-info">您好{{ userStore.getUserInfo?.nickName || userStore.getUserInfo?.username }}</div>
<div class="fs-user-info">{{ t('user.greeting') }}{{ userStore.getUserInfo?.nickName || userStore.getUserInfo?.username }}</div>
<template #overlay>
<a-menu>
<a-menu-item>
<div @click="goUserProfile"></div>
<div @click="goUserProfile">{{ t('user.profile') }}</div>
</a-menu-item>
<a-menu-item>
<div @click="doLogout"></div>
<div @click="doLogout">{{ t('user.logout') }}</div>
</a-menu-item>
</a-menu>
</template>

View File

@ -9,96 +9,103 @@ import { useSettingStore } from "/@/store/settings";
import PageFooter from "./components/footer/index.vue";
import { useRouter } from "vue-router";
import MaxKBChat from "/@/components/ai/index.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const userStore = useUserStore();
const router = useRouter();
const menus = computed(() => [
{
handler: () => {
router.push("/certd/mine/user-profile");
},
icon: "fa-solid:book",
text: "账号信息",
},
{
handler: () => {
router.push("/certd/mine/security");
},
icon: "fluent:shield-keyhole-16-regular",
text: "认证安全设置",
},
{
handler: () => {
router.push("/certd/mine/user-profile");
},
icon: "fa-solid:book",
text: t("certd.accountInfo"),
},
{
handler: () => {
router.push("/certd/mine/security");
},
icon: "fluent:shield-keyhole-16-regular",
text: t("certd.securitySettings"),
},
]);
const avatar = computed(() => {
const avt = userStore.getUserInfo?.avatar;
return avt ? `/api/basic/file/download?key=${avt}` : "";
const avt = userStore.getUserInfo?.avatar;
return avt ? `/api/basic/file/download?key=${avt}` : "";
});
async function handleLogout() {
await userStore.logout(true);
await userStore.logout(true);
}
const settingStore = useSettingStore();
const sysPublic = computed(() => {
return settingStore.sysPublic;
return settingStore.sysPublic;
});
const siteInfo = computed(() => {
return settingStore.siteInfo;
return settingStore.siteInfo;
});
onErrorCaptured(e => {
console.error("ErrorCaptured:", e);
// notification.error({ message: e.message });
//
return false;
console.error("ErrorCaptured:", e);
// notification.error({ message: e.message });
//
return false;
});
onMounted(async () => {
await settingStore.checkUrlBound();
await settingStore.checkUrlBound();
});
function goGithub() {
window.open("https://github.com/certd/certd");
window.open("https://github.com/certd/certd");
}
const settingsStore = useSettingStore();
const chatBox = ref();
const openChat = (q: string) => {
chatBox.value.openChat({ q });
chatBox.value.openChat({ q });
};
provide("fn:ai.open", openChat);
</script>
<template>
<BasicLayout @clear-preferences-and-logout="handleLogout">
<template #user-dropdown>
<UserDropdown :avatar="avatar" :menus="menus" :text="userStore.userInfo?.nickName || userStore.userInfo?.username" description="" tag-text="" @logout="handleLogout" />
</template>
<template #lock-screen>
<LockScreen :avatar @to-login="handleLogout" />
</template>
<template #header-right-0>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<tutorial-button class="flex-center header-btn" />
</div>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<vip-button class="flex-center header-btn" mode="nav" />
</div>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<fs-button shape="circle" type="text" icon="ion:logo-github" :text="null" @click="goGithub" />
</div>
</template>
<template #footer>
<PageFooter></PageFooter>
<MaxKBChat v-if="settingsStore.sysPublic.aiChatEnabled !== false" ref="chatBox" />
</template>
</BasicLayout>
<BasicLayout @clear-preferences-and-logout="handleLogout">
<template #user-dropdown>
<UserDropdown :avatar="avatar" :menus="menus"
:text="userStore.userInfo?.nickName || userStore.userInfo?.username" description="" tag-text=""
@logout="handleLogout" />
</template>
<template #lock-screen>
<LockScreen :avatar @to-login="handleLogout" />
</template>
<template #header-right-0>
<div v-if="!settingStore.isComm"
class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full hidden md:block">
<tutorial-button class="flex-center header-btn" />
</div>
<div class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<vip-button class="flex-center header-btn" mode="nav" />
</div>
<div v-if="!settingStore.isComm" class="hover:bg-accent ml-1 mr-2 cursor-pointer rounded-full">
<fs-button shape="circle" type="text" icon="ion:logo-github" :text="null" @click="goGithub" />
</div>
</template>
<template #footer>
<PageFooter></PageFooter>
<MaxKBChat v-if="settingsStore.sysPublic.aiChatEnabled !== false" ref="chatBox" />
</template>
</BasicLayout>
</template>
<style lang="less">
.header-btn {
font-size: 14px;
padding: 5px;
font-size: 14px;
padding: 5px;
}
</style>

View File

@ -0,0 +1,135 @@
import type { App } from "vue";
import type { Locale } from "vue-i18n";
import type { ImportLocaleFn, LoadMessageFn, LocaleSetupOptions, SupportedLanguagesType } from "./typing";
import { unref } from "vue";
import { createI18n } from "vue-i18n";
import en_US from './langs/en-US/index';
import zh_CH from './langs/zh-CN/index';
import { useSimpleLocale } from "/@/vben/composables";
const i18n = createI18n({
globalInjection: true,
legacy: false,
fallbackLocale: 'en_US',
locale: 'en_US',
messages: {
zh_CH: zh_CH,
en_US: en_US
}
});
const modules = import.meta.glob("./langs/**/*.json");
const { setSimpleLocale } = useSimpleLocale();
const localesMap = loadLocalesMapFromDir(/\.\/langs\/([^/]+)\/(.*)\.json$/, modules);
let loadMessages: LoadMessageFn;
/**
* Load locale modules
* @param modules
*/
function loadLocalesMap(modules: Record<string, () => Promise<unknown>>) {
const localesMap: Record<Locale, ImportLocaleFn> = {};
for (const [path, loadLocale] of Object.entries(modules)) {
const key = path.match(/([\w-]*)\.(json)/)?.[1];
if (key) {
localesMap[key] = loadLocale as ImportLocaleFn;
}
}
return localesMap;
}
/**
* Load locale modules with directory structure
* @param regexp - Regular expression to match language and file names
* @param modules - The modules object containing paths and import functions
* @returns A map of locales to their corresponding import functions
*/
function loadLocalesMapFromDir(regexp: RegExp, modules: Record<string, () => Promise<unknown>>): Record<Locale, ImportLocaleFn> {
const localesRaw: Record<Locale, Record<string, () => Promise<unknown>>> = {};
const localesMap: Record<Locale, ImportLocaleFn> = {};
// Iterate over the modules to extract language and file names
for (const path in modules) {
const match = path.match(regexp);
if (match) {
const [_, locale, fileName] = match;
if (locale && fileName) {
if (!localesRaw[locale]) {
localesRaw[locale] = {};
}
if (modules[path]) {
localesRaw[locale][fileName] = modules[path];
}
}
}
}
// Convert raw locale data into async import functions
for (const [locale, files] of Object.entries(localesRaw)) {
localesMap[locale] = async () => {
const messages: Record<string, any> = {};
for (const [fileName, importFn] of Object.entries(files)) {
messages[fileName] = ((await importFn()) as any)?.default;
}
return { default: messages };
};
}
return localesMap;
}
/**
* Set i18n language
* @param locale
*/
function setI18nLanguage(locale: Locale) {
i18n.global.locale.value = locale;
document?.querySelector("html")?.setAttribute("lang", locale);
}
async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
const { defaultLocale = "en-US" } = options;
// app可以自行扩展一些第三方库和组件库的国际化
loadMessages = options.loadMessages || (async () => ({}));
app.use(i18n);
await loadLocaleMessages(defaultLocale);
// 在控制台打印警告
i18n.global.setMissingHandler((locale, key) => {
if (options.missingWarn && key.includes(".")) {
console.warn(`[intlify] Not found '${key}' key in '${locale}' locale messages.`);
}
});
}
/**
* Load locale messages
* @param lang
*/
async function loadLocaleMessages(lang: SupportedLanguagesType) {
if (unref(i18n.global.locale) === lang) {
return setI18nLanguage(lang);
}
setSimpleLocale(lang);
const message = await localesMap[lang]?.();
if (message?.default) {
i18n.global.setLocaleMessage(lang, message.default);
}
const mergeMessage = await loadMessages(lang);
i18n.global.mergeLocaleMessage(lang, mergeMessage);
return setI18nLanguage(lang);
}
export { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n };
export default i18n;

View File

@ -0,0 +1,84 @@
export default {
"welcomeBack": "Welcome Back",
"pageTitle": "Plug-and-play Admin system",
"pageDesc": "Efficient, versatile frontend template",
"loginSuccess": "Login Successful",
"loginSuccessDesc": "Welcome Back",
"loginSubtitle": "Enter your account details to manage your projects",
"selectAccount": "Quick Select Account",
"username": "Username",
"password": "Password",
"usernameTip": "Please enter username",
"passwordErrorTip": "Password is incorrect",
"passwordTip": "Please enter password",
"verifyRequiredTip": "Please complete the verification first",
"rememberMe": "Remember Me",
"createAnAccount": "Create an Account",
"createAccount": "Create Account",
"alreadyHaveAccount": "Already have an account?",
"accountTip": "Don't have an account?",
"signUp": "Sign Up",
"signUpSubtitle": "Make managing your applications simple and fun",
"confirmPassword": "Confirm Password",
"confirmPasswordTip": "The passwords do not match",
"agree": "I agree to",
"privacyPolicy": "Privacy-policy",
"terms": "Terms",
"agreeTip": "Please agree to the Privacy Policy and Terms",
"goToLogin": "Login instead",
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols",
"forgetPassword": "Forget Password?",
"forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password",
"emailTip": "Please enter email",
"emailValidErrorTip": "The email format you entered is incorrect",
"sendResetLink": "Send Reset Link",
"email": "Email",
"qrcodeSubtitle": "Scan the QR code with your phone to login",
"qrcodePrompt": "Click 'Confirm' after scanning to complete login",
"qrcodeLogin": "QR Code Login",
"codeSubtitle": "Enter your phone number to start managing your project",
"code": "Security code",
"codeTip": "Security code required {0} characters",
"mobile": "Mobile",
"mobileLogin": "Mobile Login",
"mobileTip": "Please enter mobile number",
"mobileErrortip": "The phone number format is incorrect",
"sendCode": "Get Security code",
"sendText": "Resend in {0}s",
"thirdPartyLogin": "Or continue with",
"loginAgainTitle": "Please Log In Again",
"loginAgainSubTitle": "Your login session has expired. Please log in again to continue.",
"layout": {
"center": "Align Center",
"alignLeft": "Align Left",
"alignRight": "Align Right"
},
usernamePlaceholder: 'Please enter username/email/phone number',
passwordPlaceholder: 'Please enter your password',
mobilePlaceholder: 'Please enter your mobile number',
loginButton: 'Log In',
forgotAdminPassword: 'Forgot admin password?',
registerLink: 'Register',
smsTab: 'Login via SMS code',
passwordTab: 'Password login',
title: 'Change Password',
weakPasswordWarning: 'For your account security, please change your password immediately',
changeNow: 'Change Now',
successMessage: 'Changed successfully',
oldPassword: 'Old Password',
oldPasswordRequired: 'Please enter the old password',
newPassword: 'New Password',
newPasswordRequired: 'Please enter the new password',
confirmNewPassword: 'Confirm New Password',
confirmNewPasswordRequired: 'Please confirm the new password',
changePasswordButton: 'Change Password',
enterPassword: "Please enter the password",
newPasswordNotSameOld: "The new password cannot be the same as the old password",
enterPasswordAgain: "Please enter the password again",
passwordsNotMatch: "The two passwords do not match!",
avatar: "Avatar",
nickName: "Nickname",
phoneNumber: "Phone Number",
changePassword: "Change Password",
}

View File

@ -0,0 +1,683 @@
export default {
app: {
crud: {
i18n: {
name: "name", city: "city", status: "status"
}
}
},
fs: {
rowHandle: {
title: "Operation"
}
},
order: {
confirmTitle: 'Order Confirmation',
package: 'Package',
description: 'Description',
specifications: 'Specifications',
pipeline: 'Pipeline',
domain: 'Domain',
deployTimes: 'Deployments',
duration: 'Duration',
price: 'Price',
paymentMethod: 'Payment Method',
free: 'Free',
unit: {
pieces: 'pieces',
count: 'count',
times: 'times',
},
},
framework: {
title: "Framework",
home: "Home",
},
title: "Certificate Automation",
pipeline: "Pipeline",
pipelineEdit: "Edit Pipeline",
history: "Execution History",
certStore: "Certificate Repository",
siteMonitor: "Site Certificate Monitor",
settings: "Settings",
accessManager: "Access Management",
cnameRecord: "CNAME Record Management",
subDomain: "Subdomain Delegation Settings",
pipelineGroup: "Pipeline Group Management",
openKey: "Open API Key",
notification: "Notification Settings",
siteMonitorSetting: "Site Monitor Settings",
userSecurity: "Security Settings",
userProfile: "Account Info",
suite: "Suite",
mySuite: "My Suite",
suiteBuy: "Suite Purchase",
myTrade: "My Orders",
paymentReturn: "Payment Return",
user: {
greeting: "Hello",
profile: "Account Info",
logout: "Logout",
},
dashboard: {
greeting: "Hello, {name}, welcome to 【{site}】",
latestVersion: "Latest version: {version}",
validUntil: "Valid until:",
tutorialTooltip: "Click to view detailed tutorial",
tutorialText: "Only 3 steps to automatically apply and deploy certificates",
alertMessage: "Certificates and credentials are sensitive. Do not use untrusted online Certd services or images. Always self-host and use official release channels:",
helpDoc: "Help Docs",
pipelineCount: "Number of Certificate Pipelines",
noPipeline: "You have no certificate pipelines yet",
createNow: "Create Now",
managePipeline: "Manage Pipelines",
pipelineStatus: "Pipeline Status",
recentRun: "Recent Run Statistics",
runCount: "Run Count",
expiringCerts: "Soon-to-Expire Certificates",
supportedTasks: "Overview of Supported Deployment Tasks",
},
steps: {
createPipeline: "Create Certificate Pipeline",
addTask: "Add Deployment Task",
scheduledRun: "Scheduled Run"
},
customPipeline: "Custom Pipeline",
createCertdPipeline: "Create Certificate Pipeline",
commercialCertHosting: "Commercial Certificate Hosting",
tooltip: {
manualUploadOwnCert: "Manually upload your own certificate for automatic deployment",
noAutoApplyCommercialCert: "Does not automatically apply for commercial certificates",
manualUploadOnUpdate: "Must manually upload once when the certificate is updated",
},
table: {
confirmDeleteTitle: "Are you sure you want to delete?",
confirmDeleteMessage: "This will delete all data related to the pipeline, including execution history, certificate files, and certificate repository records.",
},
play: {
runPipeline: "Run Pipeline",
confirm: "Confirm",
confirmTrigger: "Are you sure you want to trigger the run?",
pipelineStarted: "Pipeline has started running",
},
actions: {
editPipeline: "Edit Pipeline",
editConfigGroup: "Modify Configuration/Group",
viewCertificate: "View Certificate",
downloadCertificate: "Download Certificate",
},
fields: {
userId: "User ID",
pipelineName: "Pipeline Name",
keyword: "Keyword",
required: "This field is required",
pipelineContent: "Pipeline Content",
scheduledTaskCount: "Scheduled Task Count",
deployTaskCount: "Deployment Task Count",
remainingValidity: "Remaining Validity",
expiryTime: "Expiry Time",
status: "Status",
lastRun: "Last Run",
enabled: "Enabled",
enabledLabel: "Enabled",
disabledLabel: "Disabled",
group: "Group",
type: "Type",
order: "Order Number",
keepHistoryCount: "History Record Retention Count",
keepHistoryHelper: "Number of history records to keep; excess will be deleted",
createTime: "Creation Time",
updateTime: "Update Time",
triggerType: "Trigger Type",
pipelineId: "Pipeline Id",
},
types: {
certApply: "Certificate Application",
certUpload: "Certificate Upload",
custom: "Custom",
},
myPipelines: "My Pipelines",
selectedCount: "Selected {count} items",
batchDelete: "Batch Delete",
batchForceRerun: "Force Rerun",
applyCertificate: "Apply for Certificate",
pipelineExecutionRecords: "Pipeline Execution Records",
confirm: "Confirm",
confirmBatchDeleteContent: "Are you sure you want to batch delete these {count} records?",
deleteSuccess: "Delete successful",
pleaseSelectRecords: "Please select records first",
triggerTypes: {
manual: "Manual Execution",
timer: "Scheduled Execution",
},
sysResources: {
sysRoot: "System Management",
sysConsole: "Console",
sysSettings: "System Settings",
cnameSetting: "CNAME Service Settings",
emailSetting: "Email Server Settings",
siteSetting: "Site Personalization",
headerMenus: "Top Menu Settings",
sysAccess: "System-level Authorization",
sysPlugin: "Plugin Management",
sysPluginEdit: "Edit Plugin",
sysPluginConfig: "Certificate Plugin Configuration",
accountBind: "Account Binding",
permissionManager: "Permission Management",
roleManager: "Role Management",
userManager: "User Management",
suiteManager: "Suite Management",
suiteSetting: "Suite Settings",
orderManager: "Order Management",
userSuites: "User Suites",
},
certificateRepo: {
title: "Certificate Repository",
sub: "Certificates generated from pipeline"
},
certificateNotGenerated: "Certificate not yet generated, please run the pipeline first",
viewCertificateTitle: "View Certificate",
close: "Close",
viewCert: {
title: "View Certificate"
},
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",
},
accountInfo: "Account Information",
securitySettings: "Security & Settings",
confirmDisable2FA: "Are you sure you want to disable two-factor authentication login?",
disabledSuccess: "Disabled successfully",
saveSuccess: "Saved successfully",
twoFactorAuth: "2FA Two-Factor Authentication Login",
rebind: "Rebind",
twoFactorAuthHelper: "Enable or disable two-factor authentication login",
bindDevice: "Bind Device",
step1: "1. Install any authenticator app, for example:",
tooltipGoogleServiceError: "If you get a Google service not found error, you can install KK Google Assistant",
step2: "2. Scan the QR code to add the account",
step3: "3. Enter the verification code",
inputVerifyCode: "Please enter the verification code",
cancel: "Cancel",
authorizationManagement: "Authorization Management",
manageThirdPartyAuth: "Manage third-party system authorization information",
name: "Name",
pleaseEnterName: "Please enter the name",
nameHelper: "Fill in as you like, useful to distinguish when multiple authorizations of the same type exist",
level: "Level",
system: "System",
usera: "User",
nickName: "Nickname",
max50Chars: "Maximum 50 characters",
myInfo: "My Information",
siteMonitorSettings: "Site Monitor Settings",
notificationChannel: "Notification Channel",
setNotificationChannel: "Set the notification channel",
retryTimes: "Retry Times",
monitorRetryTimes: "Number of retry attempts for monitoring requests",
monitorCronSetting: "Monitoring Schedule",
cronTrigger: "Scheduled trigger for monitoring",
save: "Save",
editSchedule: "Edit Schedule",
timerTrigger: "Timer Trigger",
schedule: "Schedule",
selectCron: "Please select a schedule Cron",
batchEditSchedule: "Batch Edit Schedule",
editTrigger: "Edit Trigger",
triggerName: "Trigger Name",
requiredField: "This field is required",
type: "Type",
enterName: "Please enter a name",
confirmDeleteTrigger: "Are you sure you want to delete this trigger?",
notificationType: "Notification Type",
selectNotificationType: "Please select a notification type",
notificationName: "Notification Name",
helperNotificationName: "Fill freely, helps to distinguish when multiple notifications of the same type exist",
isDefault: "Is Default",
yes: "Yes",
no: "No",
selectIsDefault: "Please select if default",
prompt: "Prompt",
confirmSetDefaultNotification: "Are you sure to set as default notification?",
test: "Test",
scope: "Scope",
scopeOpenApiOnly: "Open API Only",
scopeFullAccount: "Full Account Permissions",
required: "This field is required",
scopeHelper: "Open API only allows access to open APIs; full account permissions allow access to all APIs",
add: "Generate New Key",
gen: {
text: "API Test",
title: "x-certd-token",
okText: "Confirm",
contentPart1: "Test the x-certd-token below, you can use it within 3 minutes to test ",
openApi: "Open API",
contentPart2: " request testing",
},
pending_cname_setup: "Pending CNAME setup",
validating: "Validating",
validation_successful: "Validation successful",
validation_failed: "Validation failed",
validation_timed_out: "Validation timed out",
proxied_domain: "Proxied Domain",
host_record: "Host Record",
please_set_cname: "Please set CNAME",
cname_service: "CNAME Service",
default_public_cname: "Default public CNAME service, you can also ",
customize_cname: "Customize CNAME Service",
public_cname: "Public CNAME",
custom_cname: "Custom CNAME",
validate: "Validate",
validation_started: "Validation started, please wait patiently",
click_to_validate: "Click to Validate",
all: "All",
cname_feature_guide: "CNAME feature principle and usage guide",
batch_delete: "Batch Delete",
confirm_delete_count: "Are you sure to delete these {count} records in batch?",
delete_successful: "Delete successful",
please_select_records: "Please select records first",
edit_notification: "Edit Notification",
other_notification_method: "Other Notification Method",
trigger_time: "Trigger Time",
start_time: "At Start",
success_time: "On Success",
fail_to_success_time: "Fail to Success",
fail_time: "On Failure",
helper_suggest_fail_only: "It is recommended to select only 'On Failure' and 'Fail to Success'",
notification_config: "Notification Configuration",
please_select_notification: "Please select a notification method",
please_select_type: "Please select type",
please_select_trigger_time: "Please select notification trigger time",
please_select_notification_config: "Please select notification configuration",
confirm_delete_trigger: "Are you sure you want to delete this trigger?",
gift_package: "Gift Package",
package_name: "Package Name",
click_to_select: "Click to select",
please_select_package: "Please select a package",
package: "Package",
addon_package: "Addon Package",
domain_count: "Domain Count",
unit_count: "pcs",
field_required: "This field is required",
pipeline_count: "Pipeline Count",
unit_item: "items",
deploy_count: "Deploy Count",
unit_times: "times",
monitor_count: "Certificate Monitor Count",
duration: "Duration",
status: "Status",
active_time: "Activation Time",
expires_time: "Expiration Time",
is_present: "Is Present",
is_present_yes: "Yes",
is_present_no: "No",
basicInfo: "Basic Information",
titlea: "Title",
disabled: "Disabled",
ordera: "Order",
supportBuy: "Support Purchase",
intro: "Introduction",
packageContent: "Package Content",
maxDomainCount: "Max Domain Count",
maxPipelineCount: "Max Pipeline Count",
maxDeployCount: "Max Deploy Count",
maxMonitorCount: "Max Monitor Count",
price: "Price",
durationPrices: "Duration Prices",
packageName: "Package Name",
addon: "Addon",
typeHelper: "Suite: Only the most recently purchased one is active at a time\nAddon: Multiple can be purchased, effective immediately without affecting the suite\nThe quantities of suite and addon can be accumulated",
domainCount: "Domain Count",
pipelineCount: "Pipeline Count",
unitPipeline: "pipelines",
deployCount: "Deployment Count",
unitDeploy: "times",
monitorCount: "Certificate Monitor Count",
unitCount: "pcs",
durationPriceTitle: "Duration and Price",
selectDuration: "Select Duration",
supportPurchase: "Support Purchase",
cannotPurchase: "Cannot Purchase",
shelfStatus: "Shelf Status",
onShelf: "On Shelf",
offShelf: "Off Shelf",
orderHelper: "Smaller values appear first",
description: "Description",
createTime: "Creation Time",
updateTime: "Update Time",
edit: "Edit",
groupName: "Group Name",
enterGroupName: "Please enter group name",
subdomainHosting: "Subdomain Hosting",
subdomainHostingHint: "When your domain has subdomain hosting set, you need to create records here, otherwise certificate application will fail",
batchDeleteConfirm: "Are you sure to batch delete these {count} records?",
selectRecordFirst: "Please select records first",
subdomainHosted: "Hosted Subdomain",
subdomainHelpText: "If you don't understand what subdomain hosting is, please refer to the documentation ",
subdomainManagement: "Subdomain Management",
isDisabled: "Is Disabled",
enabled: "Enabled",
uploadCustomCert: "Upload Custom Certificate",
sourcee: "Source",
sourcePipeline: "Pipeline",
sourceManualUpload: "Manual Upload",
domains: "Domains",
enterDomain: "Please enter domain",
validDays: "Valid Days",
expires: " expires",
days: " days",
expireTime: "Expiration Time",
certIssuer: "Certificate Issuer",
applyTime: "Application Time",
relatedPipeline: "Related Pipeline",
statusSuccess: "Success",
statusChecking: "Checking",
statusError: "Error",
actionImportBatch: "Batch Import",
actionSyncIp: "Sync IP",
modalTitleSyncIp: "Sync IP",
modalContentSyncIp: "Are you sure to sync IP?",
notificationSyncComplete: "Sync Complete",
actionCheckAll: "Check All",
modalTitleConfirm: "Confirm",
modalContentCheckAll: "Confirm to trigger checking all IP site's certificates?",
notificationCheckSubmitted: "Check task submitted",
notificationCheckDescription: "Please refresh later to see results",
tooltipCheckNow: "Check Now",
notificationCheckSubmittedPleaseRefresh: "Check task submitted, please refresh later",
columnId: "ID",
columnIp: "IP",
helperIpCname: "Supports entering CNAME domain name",
ruleIpRequired: "Please enter IP",
columnCertDomains: "Certificate Domains",
columnCertProvider: "Issuer",
columnCertStatus: "Certificate Status",
statusNormal: "Normal",
statusExpired: "Expired",
columnCertExpiresTime: "Certificate Expiration Time",
expired: "expired",
columnCheckStatus: "Check Status",
columnLastCheckTime: "Last Check Time",
columnSource: "Source",
sourceSync: "Sync",
sourceManual: "Manual",
sourceImport: "Import",
columnDisabled: "Enabled/Disabled",
columnRemark: "Remark",
pluginFile: "Plugin File",
selectPluginFile: "Select plugin file",
overrideSameName: "Override same name",
override: "Override",
noOverride: "No override",
overrideHelper: "If a plugin with the same name exists, override it directly",
importPlugin: "Import Plugin",
operationSuccess: "Operation successful",
customPlugin: "Custom Plugin",
import: "Import",
export: "Export",
pluginType: "Plugin Type",
auth: "Authorization",
dns: "DNS",
deployPlugin: "Deploy Plugin",
icon: "Icon",
pluginName: "Plugin Name",
pluginNameHelper: "Must be English letters or digits, camelCase with type prefix\nExample: AliyunDeployToCDN\nDo not modify name once plugin is used",
pluginNameRuleMsg: "Must be English letters or digits, camelCase with type prefix",
author: "Author",
authorHelper: "Used as prefix when uploading to plugin store, e.g., greper/pluginName",
authorRuleMsg: "Must be English letters or digits",
titleHelper: "Plugin name in Chinese",
descriptionHelper: "Description of the plugin",
builtIn: "Built-in",
custom: "Custom",
store: "Store",
version: "Version",
pluginDependencies: "Plugin Dependencies",
pluginDependenciesHelper: "Dependencies to install first in format: [author/]pluginName[:version]",
editableRunStrategy: "Editable Run Strategy",
editable: "Editable",
notEditable: "Not Editable",
runStrategy: "Run Strategy",
normalRun: "Normal Run",
skipOnSuccess: "Skip on success (Deploy task)",
defaultRunStrategyHelper: "Default run strategy",
enableDisable: "Enable/Disable",
clickToToggle: "Click to toggle enable/disable",
confirmToggle: "Are you sure to",
disable: "disable",
enable: "enable",
pluginGroup: "Plugin Group",
icpRegistrationNumber: "ICP Registration Number",
icpPlaceholder: "Guangdong ICP xxxxxxx Number",
publicSecurityRegistrationNumber: "Public Security Registration Number",
publicSecurityPlaceholder: "Beijing Public Security xxxxxxx Number",
enableAssistant: "Enable Assistant",
allowCrawlers: "Allow Crawlers",
httpProxy: "HTTP Proxy",
httpProxyPlaceholder: "http://192.168.1.2:18010/",
httpProxyHelper: "Configure when some websites are blocked",
httpsProxy: "HTTPS Proxy",
httpsProxyPlaceholder: "http://192.168.1.2:18010/",
saveThenTestTitle: "Save first, then click test",
testButton: "Test",
httpsProxyHelper: "Usually both proxies are the same, save first then test",
dualStackNetwork: "Dual Stack Network",
default: "Default",
ipv4Priority: "IPv4 Priority",
ipv6Priority: "IPv6 Priority",
dualStackNetworkHelper: "If IPv6 priority is selected, enable IPv6 in docker-compose.yaml",
enableCommonCnameService: "Enable Public CNAME Service",
commonCnameHelper: "Allow use of public CNAME service. If disabled and no <router-link to='/sys/cname/provider'>custom CNAME service</router-link> is set, CNAME proxy certificate application will not work.",
saveButton: "Save",
stopSuccess: "Stopped successfully",
google: "Google",
baidu: "Baidu",
success: "Success",
testFailed: "Test Failed",
testCompleted: "Test Completed",
manageOtherUserPipeline: "Manage other users' pipelines",
limitUserPipelineCount: "Limit user pipeline count",
limitUserPipelineCountHelper: "0 means no limit",
enableSelfRegistration: "Enable self-registration",
enableUserValidityPeriod: "Enable user validity period",
userValidityPeriodHelper: "Users can use normally within validity; pipelines disabled after expiry",
enableUsernameRegistration: "Enable username registration",
enableEmailRegistration: "Enable email registration",
proFeature: "Pro feature",
emailServerSetup: "Set up email server",
enableSmsLoginRegister: "Enable SMS login and registration",
commFeature: "Commercial feature",
smsProvider: "SMS provider",
aliyunSms: "Aliyun SMS",
yfySms: "YFY SMS",
smsTest: "SMS test",
testMobilePlaceholder: "Enter test mobile number",
saveThenTest: "Save first then test",
enterTestMobile: "Please enter test mobile number",
sendSuccess: "Sent successfully",
atLeastOneLoginRequired: "At least one of password login or SMS login must be enabled",
fieldRequired: "This field is required",
siteHide: "Site Hide",
enableSiteHide: "Enable Site Hide",
siteHideDescription: "You can disable site accessibility normally and enable it when needed to enhance site security",
helpDoc: "Help Document",
randomAddress: "Random Address",
siteHideUrlHelper: "After the site is hidden, you need to visit this URL to unlock to access normally",
fullUnlockUrl: "Full Unlock URL",
saveThisUrl: "Please save this URL carefully",
unlockPassword: "Unlock Password",
unlockPasswordHelper: "Password needed to unlock the hide; set on first time or reset when filled",
autoHideTime: "Auto Hide Time",
autoHideTimeHelper: "Minutes without requests before auto hiding",
hideOpenApi: "Hide Open API",
hideOpenApiHelper: "Whether to hide open APIs; whether to expose /api/v1 prefixed endpoints",
hideSiteImmediately: "Hide Site Immediately",
hideImmediately: "Hide Immediately",
confirmHideSiteTitle: "Are you sure to hide the site immediately?",
confirmHideSiteContent: "After hiding, the site will be inaccessible. Please operate cautiously.",
siteHiddenSuccess: "Site has been hidden",
emailServerSettings: "Email Server Settings",
setEmailSendingServer: "Set the email sending server",
useCustomEmailServer: "Use Custom Email Server",
smtpDomain: "SMTP Domain",
pleaseEnterSmtpDomain: "Please enter SMTP domain or IP",
smtpPort: "SMTP Port",
pleaseEnterSmtpPort: "Please enter SMTP port",
username: "Username",
pleaseEnterUsername: "Please enter username",
password: "Password",
pleaseEnterPassword: "Please enter password",
qqEmailAuthCodeHelper: "If using QQ email, get an authorization code in QQ email settings as the password",
senderEmail: "Sender Email",
pleaseEnterSenderEmail: "Please enter sender email",
useSsl: "Use SSL",
sslPortNote: "SSL and non-SSL SMTP ports are different, please adjust port accordingly",
ignoreCertValidation: "Ignore Certificate Validation",
useOfficialEmailServer: "Use Official Email Server",
useOfficialEmailServerHelper: "Send emails directly using the official server to avoid complicated setup",
testReceiverEmail: "Test Receiver Email",
pleaseEnterTestReceiverEmail: "Please enter test receiver email",
saveBeforeTest: "Save before testing",
sendFailHelpDoc: "Failed to send??? ",
emailConfigHelpDoc: "Email configuration help document",
tryOfficialEmailServer: "You can also try using the official email server ↗↗↗↗↗↗↗↗",
pluginManagement: "Plugin Management",
pluginBetaWarning: "Custom plugins are in BETA and may have breaking changes in future",
pleaseSelectRecord: "Please select records first",
permissionManagement: "Permission Management",
adda: "Add",
rootNode: "Root Node",
permissionName: "Permission Name",
enterPermissionName: "Please enter permission name",
permissionCode: "Permission Code",
enterPermissionCode: "Please enter permission code",
max100Chars: "Maximum 100 characters",
examplePermissionCode: "e.g.: sys:user:view",
sortOrder: "Sort Order",
sortRequired: "Sort order is required",
parentNode: "Parent Node",
roleManagement: "Role Management",
assignPermissions: "Assign Permissions",
roleName: "Role Name",
enterRoleName: "Please enter role name",
unlockLogin: "Unlock Login",
notice: "Notice",
confirmUnlock: "Are you sure you want to unlock this user's login?",
unlockSuccess: "Unlock successful",
enterUsername: "Please enter username",
modifyPasswordIfFilled: "Fill in to change the password",
emaila: "Email",
mobile: "Mobile",
avatar: "Avatar",
validTime: "Valid Time",
remark: "Remark",
roles: "Roles",
cnameTitle: "CNAME Service Configuration",
cnameDescription:
"The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.",
cnameLinkText: "CNAME principle and usage instructions",
confirmTitle: "Confirm",
confirmDeleteBatch: "Are you sure you want to delete these {count} records?",
selectRecordsFirst: "Please select records first",
cnameDomain: "CNAME Domain",
cnameDomainPlaceholder: "cname.handsfree.work",
cnameDomainHelper:
"Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.",
dnsProvider: "DNS Provider",
dnsProviderAuthorization: "DNS Provider Authorization",
setDefault: "Set Default",
confirmSetDefault: "Are you sure to set as default?",
setAsDefault: "Set as Default",
disabledLabel: "Disabled",
confirmToggleStatus: "Are you sure to {action}?",
};

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

@ -0,0 +1,71 @@
export default {
createCertPipeline: {
title: "Create Certificate Application Pipeline",
description: "Demonstrate how to configure a certificate application task",
items: {
tutorialTitle: "Tutorial Demo Content",
tutorialDesc1: "This tutorial demonstrates how to automatically apply for a certificate and deploy it to Nginx",
tutorialDesc2: "Only 3 steps, fully automatic application and deployment",
createTitle: "Create Certificate Pipeline",
createDesc: "Click to add a certificate pipeline and fill in the certificate application information",
successTitle: "Pipeline Created Successfully",
successDesc: "Click manual trigger to apply for the certificate",
nextTitle: "Next, demonstrate how to automatically deploy the certificate",
nextDesc: "If you only need to apply for a certificate, you can stop here",
},
},
buttons: {
prev: "Previous Step",
next: "Next Step",
},
addDeployTask: {
title: "Add Deployment Certificate Task",
description: "Demonstrate deployment of certificate to Nginx",
items: {
addTaskTitle: "Add Certificate Deployment Task",
addTaskDesc1: "Demonstrate automatic deployment of certificate to nginx",
addTaskDesc2: "Our system provides numerous deployment plugins to meet your needs",
fillParamsTitle: "Fill Task Parameters",
fillParamsDesc1: "Fill in the certificate file path on the host",
fillParamsDesc2: "Select SSH login authorization for the host",
activateCertTitle: "Make New Certificate Effective",
activateCertDesc1: "Execute restart script",
activateCertDesc2: "Make the certificate effective",
taskSuccessTitle: "Deployment Task Added Successfully",
taskSuccessDesc: "Now you can run it",
pluginsTitle: "Our System Provides Numerous Deployment Plugins",
pluginsDesc: "You can deploy certificates to various applications and platforms according to your needs",
},
},
runAndTestTask: {
runAndTestTitle: "Run and Test",
runAndTestDescription: "Demonstrate pipeline running, view logs, skip on success, etc.",
runTestOnce: "Run a Test",
clickManualTriggerToTest: "Click the manual trigger button to test the run",
viewLogs: "View Logs",
clickTaskToViewStatusAndLogs: "Click the task to view status and logs",
howToTroubleshootFailure: "How to Troubleshoot Failure",
viewErrorLogs: "View error logs",
nginxContainerNotExistFix: "Shows nginx container not found error, fix by changing to correct nginx container name",
executionSuccess: "Execution Success",
retryAfterFix: "After fixing, click manual trigger again to rerun successfully",
autoSkipAfterSuccess: "Auto Skip After Success",
successSkipExplanation: "Successful runs will be skipped automatically, rerun only if parameters or certificates update",
viewCertDeploymentSuccess: "View Certificate Deployment Success",
visitNginxToSeeCert: "Visit website on nginx to see certificate deployed successfully",
downloadCertManualDeploy: "Download Certificate for Manual Deployment",
downloadIfNoAutoDeployPlugin: "If no deployment plugin available, download certificate for manual deployment",
},
scheduleAndEmailTask: {
title: "Set Scheduled Execution and Email Notifications",
description: "Automatic running",
setSchedule: "Set Scheduled Execution",
pipelineSuccessThenSchedule: "Pipeline tests succeed, then configure scheduled triggers so it runs automatically daily",
recommendDailyRun: "Recommend configuring to run once daily; new certs requested 35 days before expiry and auto-skipped otherwise",
setEmailNotification: "Set Email Notifications",
suggestErrorAndRecoveryEmails: "Suggest listening for 'On Error' and 'Error to Success' to quickly troubleshoot failures (basic version requires mail server setup)",
basicVersionNeedsMailServer: "(basic version requires configuring mail server)",
tutorialEndTitle: "Tutorial End",
thanksForWatching: "Thank you for watching, hope it helps you",
}
}

View File

@ -0,0 +1,19 @@
import certd from './certd';
import authentication from './authentication';
import vip from './vip';
import tutorial from './tutorial';
import preferences from './preferences';
import ui from './ui';
import guide from './guide';
import common from './common';
export default {
certd,
authentication,
vip,
ui,
tutorial,
preferences,
guide,
common
};

View File

@ -0,0 +1,186 @@
export default {
"title": "Preferences",
"subtitle": "Customize Preferences & Preview in Real Time",
"resetTip": "Data has changed, click to reset",
"resetTitle": "Reset Preferences",
"resetSuccess": "Preferences reset successfully",
"appearance": "Appearance",
"layout": "Layout",
"content": "Content",
"other": "Other",
"wide": "Wide",
"compact": "Fixed",
"followSystem": "Follow System",
"vertical": "Vertical",
"verticalTip": "Side vertical menu mode",
"horizontal": "Horizontal",
"horizontalTip": "Horizontal menu mode, all menus displayed at the top",
"twoColumn": "Two Column",
"twoColumnTip": "Vertical Two Column Menu Mode",
"headerSidebarNav": "Header Vertical",
"headerSidebarNavTip": "Header Full Width, Sidebar Navigation Mode",
"headerTwoColumn": "Header Two Column",
"headerTwoColumnTip": "Header Navigation & Sidebar Two Column co-exists",
"mixedMenu": "Mixed Menu",
"mixedMenuTip": "Vertical & Horizontal Menu Co-exists",
"fullContent": "Full Content",
"fullContentTip": "Only display content body, hide all menus",
"normal": "Normal",
"plain": "Plain",
"rounded": "Rounded",
"copyPreferences": "Copy Preferences",
"copyPreferencesSuccessTitle": "Copy successful",
"copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
"clearAndLogout": "Clear Cache & Logout",
"mode": "Mode",
"general": "General",
"language": "Language",
"dynamicTitle": "Dynamic Title",
"watermark": "Watermark",
"checkUpdates": "Periodic update check",
"position": {
"title": "Preferences Postion",
"header": "Header",
"auto": "Auto",
"fixed": "Fixed"
},
"sidebar": {
"title": "Sidebar",
"width": "Width",
"visible": "Show Sidebar",
"collapsed": "Collpase Menu",
"collapsedShowTitle": "Show Menu Title",
"autoActivateChild": "Auto Activate SubMenu",
"autoActivateChildTip": "`Enabled` to automatically activate the submenu while click menu.",
"expandOnHover": "Expand On Hover",
"expandOnHoverTip": "When the mouse hovers over menu, \n `Enabled` to expand children menus \n `Disabled` to expand whole sidebar."
},
"tabbar": {
"title": "Tabbar",
"enable": "Enable Tab Bar",
"icon": "Show Tabbar Icon",
"showMore": "Show More Button",
"showMaximize": "Show Maximize Button",
"persist": "Persist Tabs",
"maxCount": "Max Count of Tabs",
"maxCountTip": "When the number of tabs exceeds the maximum,\nthe oldest tab will be closed.\n Set to 0 to disable count checking.",
"draggable": "Enable Draggable Sort",
"wheelable": "Support Mouse Wheel",
"middleClickClose": "Close Tab when Mouse Middle Button Click",
"wheelableTip": "When enabled, the Tabbar area responds to vertical scrolling events of the scroll wheel.",
"styleType": {
"title": "Tabs Style",
"chrome": "Chrome",
"card": "Card",
"plain": "Plain",
"brisk": "Brisk"
},
"contextMenu": {
"reload": "Reload",
"close": "Close",
"pin": "Pin",
"unpin": "Unpin",
"closeLeft": "Close Left Tabs",
"closeRight": "Close Right Tabs",
"closeOther": "Close Other Tabs",
"closeAll": "Close All Tabs",
"openInNewWindow": "Open in New Window",
"maximize": "Maximize",
"restoreMaximize": "Restore"
}
},
"navigationMenu": {
"title": "Navigation Menu",
"style": "Navigation Menu Style",
"accordion": "Sidebar Accordion Menu",
"split": "Navigation Menu Separation",
"splitTip": "When enabled, the sidebar displays the top bar's submenu"
},
"breadcrumb": {
"title": "Breadcrumb",
"home": "Show Home Button",
"enable": "Enable Breadcrumb",
"icon": "Show Breadcrumb Icon",
"background": "background",
"style": "Breadcrumb Style",
"hideOnlyOne": "Hidden when only one"
},
"animation": {
"title": "Animation",
"loading": "Page Loading",
"transition": "Page Transition",
"progress": "Page Progress"
},
"theme": {
"title": "Theme",
"radius": "Radius",
"light": "Light",
"dark": "Dark",
"darkSidebar": "Semi Dark Sidebar",
"darkHeader": "Semi Dark Header",
"weakMode": "Weak Mode",
"grayMode": "Gray Mode",
"builtin": {
"title": "Built-in",
"default": "Default",
"violet": "Violet",
"pink": "Pink",
"rose": "Rose",
"skyBlue": "Sky Blue",
"deepBlue": "Deep Blue",
"green": "Green",
"deepGreen": "Deep Green",
"orange": "Orange",
"yellow": "Yellow",
"zinc": "Zinc",
"neutral": "Neutral",
"slate": "Slate",
"gray": "Gray",
"custom": "Custom"
}
},
"header": {
"title": "Header",
"visible": "Show Header",
"modeStatic": "Static",
"modeFixed": "Fixed",
"modeAuto": "Auto hide & Show",
"modeAutoScroll": "Scroll to Hide & Show",
"menuAlign": "Menu Align",
"menuAlignStart": "Start",
"menuAlignEnd": "End",
"menuAlignCenter": "Center"
},
"footer": {
"title": "Footer",
"visible": "Show Footer",
"fixed": "Fixed at Bottom"
},
"copyright": {
"title": "Copyright",
"enable": "Enable Copyright",
"companyName": "Company Name",
"companySiteLink": "Company Site Link",
"date": "Date",
"icp": "ICP License Number",
"icpLink": "ICP Site Link"
},
"shortcutKeys": {
"title": "Shortcut Keys",
"global": "Global",
"search": "Global Search",
"logout": "Logout",
"preferences": "Preferences"
},
"widget": {
"title": "Widget",
"globalSearch": "Enable Global Search",
"fullscreen": "Enable Fullscreen",
"themeToggle": "Enable Theme Toggle",
"languageToggle": "Enable Language Toggle",
"notification": "Enable Notification",
"sidebarToggle": "Enable Sidebar Toggle",
"lockScreen": "Enable Lock Screen",
"refresh": "Enable Refresh"
}
}

View File

@ -0,0 +1,3 @@
export default {
title: 'Tutorial',
}

View File

@ -0,0 +1,104 @@
export default {
"formRules": {
"required": "Please enter {0}",
"selectRequired": "Please select {0}",
"minLength": "{0} must be at least {1} characters",
"maxLength": "{0} can be at most {1} characters",
"length": "{0} must be {1} characters long",
"alreadyExists": "{0} `{1}` already exists",
"startWith": "{0} must start with `{1}`",
"invalidURL": "Please input a valid URL"
},
"actionTitle": {
"edit": "Modify {0}",
"create": "Create {0}",
"delete": "Delete {0}",
"view": "View {0}"
},
"actionMessage": {
"deleteConfirm": "Are you sure to delete {0}?",
"deleting": "Deleting {0} ...",
"deleteSuccess": "{0} deleted successfully",
"operationSuccess": "Operation succeeded",
"operationFailed": "Operation failed"
},
"placeholder": {
"input": "Please enter",
"select": "Please select"
},
"captcha": {
"title": "Please complete the security verification",
"sliderSuccessText": "Passed",
"sliderDefaultText": "Slider and drag",
"alt": "Supports img tag src attribute value",
"sliderRotateDefaultTip": "Click picture to refresh",
"sliderRotateFailTip": "Validation failed",
"sliderRotateSuccessTip": "Validation successful, time {0} seconds",
"refreshAriaLabel": "Refresh captcha",
"confirmAriaLabel": "Confirm selection",
"confirm": "Confirm",
"pointAriaLabel": "Click point",
"clickInOrder": "Please click in order"
},
"iconPicker": {
"placeholder": "Select an icon",
"search": "Search icon..."
},
"jsonViewer": {
"copy": "Copy",
"copied": "Copied"
},
"fallback": {
"pageNotFound": "Oops! Page Not Found",
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
"forbidden": "Oops! Access Denied",
"forbiddenDesc": "Sorry, but you don't have permission to access this page.",
"internalError": "Oops! Something Went Wrong",
"internalErrorDesc": "Sorry, but the server encountered an error.",
"offline": "Offline Page",
"offlineError": "Oops! Network Error",
"offlineErrorDesc": "Sorry, can't connect to the internet. Check your connection.",
"comingSoon": "Coming Soon",
"http": {
"requestTimeout": "The request timed out. Please try again later.",
"networkError": "A network error occurred. Please check your internet connection and try again.",
"badRequest": "Bad Request. Please check your input and try again.",
"unauthorized": "Unauthorized. Please log in to continue.",
"forbidden": "Forbidden. You do not have permission to access this resource.",
"notFound": "Not Found. The requested resource could not be found.",
"internalServerError": "Internal Server Error. Something went wrong on our end. Please try again later."
}
},
"widgets": {
"document": "Document",
"qa": "Q&A",
"setting": "Settings",
"logoutTip": "Do you want to logout?",
"viewAll": "View All Messages",
"notifications": "Notifications",
"markAllAsRead": "Make All as Read",
"clearNotifications": "Clear",
"checkUpdatesTitle": "New Version Available",
"checkUpdatesDescription": "Click to refresh and get the latest version",
"search": {
"title": "Search",
"searchNavigate": "Search Navigation",
"select": "Select",
"navigate": "Navigate",
"close": "Close",
"noResults": "No Search Results Found",
"noRecent": "No Search History",
"recent": "Search History"
},
"lockScreen": {
"title": "Lock Screen",
"screenButton": "Locking",
"password": "Password",
"placeholder": "Please enter password",
"unlock": "Click to unlock",
"errorPasswordTip": "Password error, please re-enter",
"backToLogin": "Back to login",
"entry": "Enter the system"
}
}
}

View File

@ -0,0 +1,86 @@
export default {
comm: {
name: "{vipLabel} Activated",
title: "Expires on: {expire}",
nav: "{vipLabel}",
},
plus: {
name: "Pro Features",
title: "Upgrade to Pro for commercial license",
},
free: {
comm: {
name: "Pro Features",
title: "Upgrade to Pro for commercial license",
},
button: {
name: "Advanced Features",
title: "Upgrade to Advanced for more VIP privileges",
},
nav: {
name: "Basic Version",
title: "Upgrade to Advanced for more VIP privileges",
},
},
enterCode: "Please enter the activation code",
successTitle: "Activation Successful",
successContent: "You have successfully activated {vipLabel}, valid until: {expireDate}",
bindAccountTitle: "Bind Your Account",
bindAccountContent: "Binding your account helps prevent license loss. Strongly recommended.",
congratulations_vip_trial: 'Congratulations, you have received a Pro version {duration} days trial',
trial_modal_title: '7-day Pro version trial acquisition',
trial_modal_ok_text: 'Get now',
trial_modal_thanks: 'Thank you for supporting the open source project',
trial_modal_click_confirm: 'Click confirm to get a 7-day Pro version trial',
get_7_day_pro_trial: "7-day professional version trial",
star_now: "Star Now",
please_help_star: "Could you please help by starring? Thanks a lot!",
admin_only_operation: "Admin operation only",
enter_activation_code: "Please enter the activation code",
activate_pro_business: "Activate Professional/Business Edition",
renew_business: "Renew Business Edition",
renew_pro_upgrade_business: "Renew Professional Edition / Upgrade to Business Edition",
basic_edition: "Basic Edition",
community_free_version: "Community Free Version",
unlimited_certificate_application: "Unlimited certificate applications",
unlimited_domain_count: "Unlimited domain count",
unlimited_certificate_pipelines: "Unlimited certificate pipelines",
common_deployment_plugins: "Common host, cloud platform, CDN, Baota, 1Panel deployment plugins",
email_webhook_notifications: "Email, webhook notification methods",
professional_edition: "Professional Edition",
open_source_support: "Open source requires your sponsorship support",
vip_group_priority: "Access to VIP group, your requests will have priority",
unlimited_site_certificate_monitoring: "Unlimited site certificate monitoring",
more_notification_methods: "More notification methods",
plugins_fully_open: "All plugins open, including Synology and more",
click_to_get_7_day_trial: "Click to get 7-day trial",
years: "years",
afdian_support_vip: 'Get a one-year professional activation code after supporting "VIP membership" on Afdian, open source needs your support',
get_after_support: "Get after sponsoring",
business_edition: "Business Edition",
commercial_license: "Commercial license, allowed for external operation",
all_pro_privileges: "All professional edition privileges",
allow_commercial_use_modify_logo_title: "Allows commercial use, can modify logo and title",
data_statistics: "Data statistics",
plugin_management: "Plugin management",
unlimited_multi_users: "Unlimited multi-users",
support_user_payment: "Supports user payments",
contact_author_for_trial: "Please contact the author for trial",
activate: "Activate",
get_pro_code_after_support: 'Get a one-year professional activation code after supporting "VIP membership" on Afdian',
business_contact_author: "Business edition please contact the author directly",
year: "year",
freee: "Free",
renew: "Renew",
activate_immediately: "Activate Immediately",
current: "Current",
activated_expire_time: " activated, expiration date: ",
site_id: "Site ID",
invite_code_optional: "Invite code [optional], can get extra 30 days for Professional / 15 days for Business",
no_activation_code: "No activation code?",
activation_code_one_use: "Activation code can only be used once. To change site, please ",
bind_account: "bind account",
transfer_vip: ' then "Transfer VIP"',
}

View File

@ -0,0 +1,85 @@
export default {
"welcomeBack": "欢迎回来",
"pageTitle": "开箱即用的大型中后台管理系统",
"pageDesc": "工程化、高性能、跨组件库的前端模版",
"loginSuccess": "登录成功",
"loginSuccessDesc": "欢迎回来",
"loginSubtitle": "请输入您的帐户信息以开始管理您的项目",
"selectAccount": "快速选择账号",
"username": "账号",
"password": "密码",
"usernameTip": "请输入用户名",
"passwordTip": "请输入密码",
"verifyRequiredTip": "请先完成验证",
"passwordErrorTip": "密码错误",
"rememberMe": "记住账号",
"createAnAccount": "创建一个账号",
"createAccount": "创建账号",
"alreadyHaveAccount": "已经有账号了?",
"accountTip": "还没有账号?",
"signUp": "注册",
"signUpSubtitle": "让您的应用程序管理变得简单而有趣",
"confirmPassword": "确认密码",
"confirmPasswordTip": "两次输入的密码不一致",
"agree": "我同意",
"privacyPolicy": "隐私政策",
"terms": "条款",
"agreeTip": "请同意隐私政策和条款",
"goToLogin": "去登录",
"passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号",
"forgetPassword": "忘记密码?",
"forgetPasswordSubtitle": "输入您的电子邮件,我们将向您发送重置密码的连接",
"emailTip": "请输入邮箱",
"emailValidErrorTip": "你输入的邮箱格式不正确",
"sendResetLink": "发送重置链接",
"email": "邮箱",
"qrcodeSubtitle": "请用手机扫描二维码登录",
"qrcodePrompt": "扫码后点击 '确认',即可完成登录",
"qrcodeLogin": "扫码登录",
"codeSubtitle": "请输入您的手机号码以开始管理您的项目",
"code": "验证码",
"codeTip": "请输入{0}位验证码",
"mobile": "手机号码",
"mobileTip": "请输入手机号",
"mobileErrortip": "手机号码格式错误",
"mobileLogin": "手机号登录",
"sendCode": "获取验证码",
"sendText": "{0}秒后重新获取",
"thirdPartyLogin": "其他登录方式",
"loginAgainTitle": "重新登录",
"loginAgainSubTitle": "您的登录状态已过期,请重新登录以继续。",
"layout": {
"center": "居中",
"alignLeft": "居左",
"alignRight": "居右"
},
usernamePlaceholder: '请输入用户名/邮箱/手机号',
passwordPlaceholder: '请输入密码',
mobilePlaceholder: '请输入手机号',
loginButton: '登录',
forgotAdminPassword: '忘记管理员密码?',
registerLink: '注册',
smsTab: '短信验证码登录',
passwordTab: '密码登录',
title: '修改密码',
weakPasswordWarning: '为了您的账户安全,请立即修改密码',
changeNow: '立即修改',
successMessage: '修改成功',
oldPassword: '旧密码',
oldPasswordRequired: '请输入旧密码',
newPassword: '新密码',
newPasswordRequired: '请输入新密码',
confirmNewPassword: '确认新密码',
confirmNewPasswordRequired: '请输入确认密码',
changePasswordButton: '修改密码',
enterPassword: "请输入密码",
newPasswordNotSameOld: "新密码不能和旧密码相同",
enterPasswordAgain: "请再次输入密码",
passwordsNotMatch: "两次输入密码不一致!",
avatar: "头像",
nickName: "昵称",
phoneNumber: "手机号",
changePassword: "修改密码",
}

View File

@ -0,0 +1,690 @@
export default {
app: {
crud: {
i18n: {
name: "姓名", city: "城市", status: "状态"
}
},
login: {
logoutTip: "确认",
logoutMessage: "确定要注销登录吗?",
},
},
fs: {
rowHandle: {
title: "操作列",
},
},
order: {
confirmTitle: '订单确认',
package: '套餐',
description: '说明',
specifications: '规格',
pipeline: '流水线',
domain: '域名',
deployTimes: '部署次数',
duration: '时长',
price: '价格',
paymentMethod: '支付方式',
free: '免费',
unit: {
pieces: '条',
count: '个',
times: '次',
},
},
framework: {
title: "框架",
home: "首页",
},
title: "证书自动化",
pipeline: "证书自动化流水线",
pipelineEdit: "编辑流水线",
history: "执行历史记录",
certStore: "证书仓库",
siteMonitor: "站点证书监控",
settings: "设置",
accessManager: "授权管理",
cnameRecord: "CNAME记录管理",
subDomain: "子域名托管设置",
pipelineGroup: "流水线分组管理",
openKey: "开放接口密钥",
notification: "通知设置",
siteMonitorSetting: "站点监控设置",
userSecurity: "认证安全设置",
userProfile: "账号信息",
suite: "套餐",
mySuite: "我的套餐",
suiteBuy: "套餐购买",
myTrade: "我的订单",
paymentReturn: "支付返回",
user: {
greeting: "您好",
profile: "账号信息",
logout: "注销登录",
},
dashboard: {
greeting: "您好,{name},欢迎使用 【{site}】",
latestVersion: "最新版本: {version}",
validUntil: "账户有效期:",
tutorialTooltip: "点击查看详细教程",
tutorialText: "仅需3步全自动申请部署证书",
alertMessage: "证书和授权为敏感信息不要使用来历不明的在线Certd服务和镜像以免泄露请务必私有化部署使用认准官方版本发布渠道",
helpDoc: "帮助文档",
pipelineCount: "证书流水线数量",
noPipeline: "您还没有证书流水线",
createNow: "立即创建",
managePipeline: "管理流水线",
pipelineStatus: "流水线状态",
recentRun: "最近运行统计",
runCount: "运行次数",
expiringCerts: "最快到期证书",
supportedTasks: "已支持的部署任务总览",
},
steps: {
createPipeline: "创建证书流水线",
addTask: "添加部署任务",
scheduledRun: "定时运行"
},
customPipeline: "自定义流水线",
createCertdPipeline: "创建证书流水线",
commercialCertHosting: "商用证书托管",
tooltip: {
manualUploadOwnCert: "手动上传自有证书,执行自动部署",
noAutoApplyCommercialCert: "并不能自动申请商业证书",
manualUploadOnUpdate: "证书有更新时,都需要手动上传一次",
},
table: {
confirmDeleteTitle: "确定要删除吗?",
confirmDeleteMessage: "将删除该流水线相关的所有数据,包括执行历史、证书文件、证书仓库记录等",
},
play: {
runPipeline: "运行流水线",
confirm: "确认",
confirmTrigger: "确定要触发运行吗?",
pipelineStarted: "管道已经开始运行",
},
actions: {
editPipeline: "编辑流水线",
editConfigGroup: "修改配置/分组",
viewCertificate: "查看证书",
downloadCertificate: "下载证书",
},
fields: {
userId: "用户Id",
pipelineName: "流水线名称",
keyword: "关键字",
required: "此项必填",
pipelineContent: "流水线内容",
scheduledTaskCount: "定时任务数",
deployTaskCount: "部署任务数",
remainingValidity: "到期剩余",
expiryTime: "过期时间",
status: "状态",
lastRun: "最后运行",
enabled: "启用",
enabledLabel: "启用",
disabledLabel: "禁用",
group: "分组",
type: "类型",
order: "排序号",
keepHistoryCount: "历史记录保持数",
keepHistoryHelper: "历史记录保持条数,多余的会被删除",
createTime: "创建时间",
updateTime: "更新时间",
triggerType: "触发类型",
pipelineId: "流水线Id",
},
types: {
certApply: "证书申请",
certUpload: "证书上传",
custom: "自定义",
},
myPipelines: "我的流水线",
selectedCount: "已选择 {count} 项",
batchDelete: "批量删除",
batchForceRerun: "强制重新运行",
applyCertificate: "申请证书",
pipelineExecutionRecords: "流水线执行记录",
confirm: "确认",
confirmBatchDeleteContent: "确定要批量删除这{count}条记录吗",
deleteSuccess: "删除成功",
pleaseSelectRecords: "请先勾选记录",
triggerTypes: {
manual: "手动执行",
timer: "定时执行",
},
sysResources: {
sysRoot: "系统管理",
sysConsole: "控制台",
sysSettings: "系统设置",
cnameSetting: "CNAME服务设置",
emailSetting: "邮件服务器设置",
siteSetting: "站点个性化",
headerMenus: "顶部菜单设置",
sysAccess: "系统级授权",
sysPlugin: "插件管理",
sysPluginEdit: "编辑插件",
sysPluginConfig: "证书插件配置",
accountBind: "账号绑定",
permissionManager: "权限管理",
roleManager: "角色管理",
userManager: "用户管理",
suiteManager: "套餐管理",
suiteSetting: "套餐设置",
orderManager: "订单管理",
userSuites: "用户套餐",
},
certificateRepo: {
title: "证书仓库",
sub: "从流水线生成的证书"
},
certificateNotGenerated: "证书还未生成,请先运行流水线",
viewCertificateTitle: "查看证书",
close: "关闭",
viewCert: {
title: "查看证书"
},
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",
},
accountInfo: "账号信息",
securitySettings: "认证安全设置",
confirmDisable2FA: "确定要关闭多重验证登录吗?",
disabledSuccess: "关闭成功",
saveSuccess: "保存成功",
twoFactorAuth: "2FA多重验证登录",
rebind: "重新绑定",
twoFactorAuthHelper: "是否开启多重验证登录",
bindDevice: "绑定设备",
step1: "1. 安装任意一款支持Authenticator的验证APP比如",
tooltipGoogleServiceError: "如果报没有找到谷歌服务的错误您可以安装KK谷歌助手",
step2: "2. 扫描二维码添加账号",
step3: "3. 输入验证码",
inputVerifyCode: "请输入验证码",
cancel: "取消",
authorizationManagement: "授权管理",
manageThirdPartyAuth: "管理第三方系统授权信息",
name: "名称",
pleaseEnterName: "请填写名称",
nameHelper: "随便填,当多个相同类型的授权时,便于区分",
level: "级别",
system: "系统",
usera: "用户",
nickName: "昵称",
max50Chars: "最大50个字符",
myInfo: "我的信息",
siteMonitorSettings: "站点监控设置",
notificationChannel: "通知渠道",
setNotificationChannel: "设置通知渠道",
retryTimes: "重试次数",
monitorRetryTimes: "监控请求重试次数",
monitorCronSetting: "监控定时设置",
cronTrigger: "定时触发监控",
save: "保存",
editSchedule: "修改定时",
timerTrigger: "定时触发",
schedule: "定时",
selectCron: "请选择定时Cron",
batchEditSchedule: "批量修改定时",
editTrigger: "编辑触发器",
triggerName: "触发器名称",
requiredField: "此项必填",
type: "类型",
enterName: "请输入名称",
confirmDeleteTrigger: "确定要删除此触发器吗?",
notificationType: "通知类型",
selectNotificationType: "请选择通知类型",
notificationName: "通知名称",
helperNotificationName: "随便填,当多个相同类型的通知时,便于区分",
isDefault: "是否默认",
yes: "是",
no: "否",
selectIsDefault: "请选择是否默认",
prompt: "提示",
confirmSetDefaultNotification: "确定设置为默认通知?",
test: "测试",
scope: "权限范围",
scopeOpenApiOnly: "仅开放接口",
scopeFullAccount: "账户所有权限",
required: "此项必填",
scopeHelper: "仅开放接口只可以访问开放接口,账户所有权限可以访问所有接口",
add: "生成新的Key",
gen: {
text: "接口测试",
title: "x-certd-token",
okText: "确定",
contentPart1: "测试x-certd-token如下您可以在3分钟内使用它进行",
openApi: "开放接口",
contentPart2: "请求测试",
},
pending_cname_setup: "待设置CNAME",
validating: "验证中",
validation_successful: "验证成功",
validation_failed: "验证失败",
validation_timed_out: "验证超时",
proxied_domain: "被代理域名",
host_record: "主机记录",
please_set_cname: "请设置CNAME",
cname_service: "CNAME服务",
default_public_cname: "默认提供公共CNAME服务您还可以",
customize_cname: "自定义CNAME服务",
public_cname: "公共CNAME",
custom_cname: "自定义CNAME",
validate: "验证",
validation_started: "开始验证,请耐心等待",
click_to_validate: "点击验证",
all: "全部",
cname_feature_guide: "CNAME功能原理及使用说明",
batch_delete: "批量删除",
confirm_delete_count: "确定要批量删除这{count}条记录吗",
delete_successful: "删除成功",
please_select_records: "请先勾选记录",
edit_notification: "编辑通知",
other_notification_method: "其他通知方式",
trigger_time: "触发时机",
start_time: "开始时",
success_time: "成功时",
fail_to_success_time: "失败转成功时",
fail_time: "失败时",
helper_suggest_fail_only: "建议仅选择'失败时'和'失败转成功'两种即可",
notification_config: "通知配置",
please_select_notification: "请选择通知方式",
please_select_type: "请选择类型",
please_select_trigger_time: "请选择通知时机",
please_select_notification_config: "请选择通知配置",
confirm_delete_trigger: "确定要删除此触发器吗?",
gift_package: "赠送套餐",
package_name: "套餐名称",
click_to_select: "点击选择",
please_select_package: "请选择套餐",
package: "套餐",
addon_package: "加量包",
domain_count: "域名数量",
unit_count: "个",
field_required: "此项必填",
pipeline_count: "流水线数量",
unit_item: "条",
deploy_count: "部署次数",
unit_times: "次",
monitor_count: "证书监控数量",
duration: "时长",
status: "状态",
active_time: "激活时间",
expires_time: "过期时间",
is_present: "是否赠送",
is_present_yes: "是",
is_present_no: "否",
basicInfo: "基础信息",
titlea: "名称",
disabled: "是否禁用",
ordera: "排序",
supportBuy: "支持购买",
intro: "介绍",
packageContent: "套餐内容",
maxDomainCount: "最大域名数",
maxPipelineCount: "最大流水线数",
maxDeployCount: "最大部署数",
maxMonitorCount: "最大监控数",
price: "价格",
durationPrices: "时长价格",
packageName: "套餐名称",
addon: "加量包",
typeHelper: "套餐:同一时间只有最新购买的一个生效\n加量包可购买多个购买后立即生效不影响套餐\n套餐和加量包数量可叠加",
domainCount: "域名数量",
pipelineCount: "流水线数量",
unitPipeline: "条",
deployCount: "部署次数",
unitDeploy: "次",
monitorCount: "证书监控数量",
unitCount: "个",
durationPriceTitle: "时长及价格",
selectDuration: "选择时长",
supportPurchase: "支持购买",
cannotPurchase: "不能购买",
shelfStatus: "上下架",
onShelf: "上架",
offShelf: "下架",
orderHelper: "越小越靠前",
description: "说明",
createTime: "创建时间",
updateTime: "更新时间",
edit: "编辑",
groupName: "分组名称",
enterGroupName: "请输入分组名称",
subdomainHosting: "子域名托管",
subdomainHostingHint: "当你的域名设置了子域名托管,需要在此处创建记录,否则申请证书将失败",
batchDeleteConfirm: "确定要批量删除这{count}条记录吗",
selectRecordFirst: "请先勾选记录",
subdomainHosted: "托管的子域名",
subdomainHelpText: "如果您不理解什么是子域托管,可以参考文档",
subdomainManagement: "子域管理",
isDisabled: "是否禁用",
enabled: "启用",
uploadCustomCert: "上传自定义证书",
sourcee: "来源",
sourcePipeline: "流水线",
sourceManualUpload: "手动上传",
domains: "域名",
enterDomain: "请输入域名",
validDays: "有效天数",
expires: "过期",
days: "天",
expireTime: "过期时间",
certIssuer: "证书颁发机构",
applyTime: "申请时间",
relatedPipeline: "关联流水线",
statusSuccess: "成功",
statusChecking: "检查中",
statusError: "异常",
actionImportBatch: "批量导入",
actionSyncIp: "同步IP",
modalTitleSyncIp: "同步IP",
modalContentSyncIp: "确定要同步IP吗",
notificationSyncComplete: "同步完成",
actionCheckAll: "检查全部",
modalTitleConfirm: "确认",
modalContentCheckAll: "确认触发检查全部IP站点的证书吗?",
notificationCheckSubmitted: "检查任务已提交",
notificationCheckDescription: "请稍后刷新页面查看结果",
tooltipCheckNow: "立即检查",
notificationCheckSubmittedPleaseRefresh: "检查任务已提交,请稍后刷新查看结果",
columnId: "ID",
columnIp: "IP",
helperIpCname: "也支持填写CNAME域名",
ruleIpRequired: "请输入IP",
columnCertDomains: "证书域名",
columnCertProvider: "颁发机构",
columnCertStatus: "证书状态",
statusNormal: "正常",
statusExpired: "过期",
columnCertExpiresTime: "证书到期时间",
expired: "过期",
columnCheckStatus: "检查状态",
columnLastCheckTime: "上次检查时间",
columnSource: "来源",
sourceSync: "同步",
sourceManual: "手动",
sourceImport: "导入",
columnDisabled: "禁用启用",
columnRemark: "备注",
pluginFile: "插件文件",
selectPluginFile: "选择插件文件",
overrideSameName: "同名覆盖",
override: "覆盖",
noOverride: "不覆盖",
overrideHelper: "如果已有相同名称插件,直接覆盖",
importPlugin: "导入插件",
operationSuccess: "操作成功",
customPlugin: "自定义插件",
import: "导入",
export: "导出",
pluginType: "插件类型",
auth: "授权",
dns: "DNS",
deployPlugin: "部署插件",
icon: "图标",
pluginName: "插件名称",
pluginNameHelper: "必须为英文或数字,驼峰命名,类型作为前缀\n示例AliyunDeployToCDN\n插件使用后名称不可修改",
pluginNameRuleMsg: "必须为英文或数字,驼峰命名,类型作为前缀",
author: "作者",
authorHelper: "上传插件市场时作为前缀,如 greper/pluginName",
authorRuleMsg: "必须为英文或数字",
titleHelper: "插件中文名称",
descriptionHelper: "插件描述",
builtIn: "内置",
custom: "自定义",
store: "市场",
version: "版本",
pluginDependencies: "插件依赖",
pluginDependenciesHelper: "格式: [作者/]插件名[:版本],需先安装依赖插件",
editableRunStrategy: "可编辑运行策略",
editable: "可编辑",
notEditable: "不可编辑",
runStrategy: "运行策略",
normalRun: "正常运行",
skipOnSuccess: "成功跳过(部署任务)",
defaultRunStrategyHelper: "默认运行策略",
enableDisable: "启用/禁用",
clickToToggle: "点击切换启用/禁用",
confirmToggle: "确认要",
disable: "禁用",
enable: "启用",
pluginGroup: "插件分组",
icpRegistrationNumber: "ICP备案号",
icpPlaceholder: "粤ICP备xxxxxxx号",
publicSecurityRegistrationNumber: "网安备案号",
publicSecurityPlaceholder: "京公网安备xxxxxxx号",
enableAssistant: "开启小助手",
allowCrawlers: "允许爬虫",
httpProxy: "HTTP代理",
httpProxyPlaceholder: "http://192.168.1.2:18010/",
httpProxyHelper: "当某些网站被墙时可以配置",
httpsProxy: "HTTPS代理",
httpsProxyPlaceholder: "http://192.168.1.2:18010/",
saveThenTestTitle: "保存后,再点击测试",
testButton: "测试",
httpsProxyHelper: "一般这两个代理填一样的,保存后再测试",
dualStackNetwork: "双栈网络",
default: "默认",
ipv4Priority: "IPV4优先",
ipv6Priority: "IPV6优先",
dualStackNetworkHelper: "如果选择IPv6优先需要在docker-compose.yaml中启用ipv6",
enableCommonCnameService: "启用公共CNAME服务",
commonCnameHelper: "是否可以使用公共CNAME服务如果禁用且没有设置<router-link to='/sys/cname/provider'>自定义CNAME服务</router-link>则无法使用CNAME代理方式申请证书",
saveButton: "保存",
stopSuccess: "停止成功",
google: "Google",
baidu: "百度",
success: "成功",
testFailed: "测试失败",
testCompleted: "测试完成",
manageOtherUserPipeline: "管理其他用户流水线",
limitUserPipelineCount: "限制用户流水线数量",
limitUserPipelineCountHelper: "0为不限制",
enableSelfRegistration: "开启自助注册",
enableUserValidityPeriod: "开启用户有效期",
userValidityPeriodHelper: "有效期内用户可正常使用,失效后流水线将被停用",
enableUsernameRegistration: "开启用户名注册",
enableEmailRegistration: "开启邮箱注册",
proFeature: "专业版功能",
emailServerSetup: "设置邮箱服务器",
enableSmsLoginRegister: "开启手机号登录、注册",
commFeature: "商业版功能",
smsProvider: "短信提供商",
aliyunSms: "阿里云短信",
yfySms: "易发云短信",
smsTest: "短信测试",
testMobilePlaceholder: "输入测试手机号",
saveThenTest: "保存后再点击测试",
enterTestMobile: "请输入测试手机号",
sendSuccess: "发送成功",
atLeastOneLoginRequired: "密码登录和手机号登录至少开启一个",
fieldRequired: "此项必填",
siteHide: "站点隐藏",
enableSiteHide: "启用站点隐藏",
siteHideDescription: "可以在平时关闭站点的可访问性,需要时再打开,增强站点安全性",
helpDoc: "帮助说明",
randomAddress: "随机地址",
siteHideUrlHelper: "站点被隐藏后需要访问此URL解锁才能正常访问",
fullUnlockUrl: "完整解除隐藏地址",
saveThisUrl: "请保存好此地址",
unlockPassword: "解除密码",
unlockPasswordHelper: "解除隐藏时需要输入密码,第一次需要设置密码,填写则重置密码",
autoHideTime: "自动隐藏时间",
autoHideTimeHelper: "多少分钟内无请求自动隐藏",
hideOpenApi: "隐藏开放接口",
hideOpenApiHelper: "是否隐藏开放接口,是否放开/api/v1开头的接口",
hideSiteImmediately: "立即隐藏站点",
hideImmediately: "立即隐藏",
confirmHideSiteTitle: "确定要立即隐藏站点吗?",
confirmHideSiteContent: "隐藏后,将无法访问站点,请谨慎操作",
siteHiddenSuccess: "站点已隐藏",
emailServerSettings: "邮件服务器设置",
setEmailSendingServer: "设置邮件发送服务器",
useCustomEmailServer: "使用自定义邮件服务器",
smtpDomain: "SMTP域名",
pleaseEnterSmtpDomain: "请输入smtp域名或ip",
smtpPort: "SMTP端口",
pleaseEnterSmtpPort: "请输入smtp端口号",
username: "用户名",
pleaseEnterUsername: "请输入用户名",
password: "密码",
pleaseEnterPassword: "请输入密码",
qqEmailAuthCodeHelper: "如果是qq邮箱需要到qq邮箱的设置里面申请授权码作为密码",
senderEmail: "发件邮箱",
pleaseEnterSenderEmail: "请输入发件邮箱",
useSsl: "是否ssl",
sslPortNote: "ssl和非ssl的smtp端口是不一样的注意修改端口",
ignoreCertValidation: "忽略证书校验",
useOfficialEmailServer: "使用官方邮件服务器",
useOfficialEmailServerHelper: "使用官方邮箱服务器直接发邮件,免除繁琐的配置",
testReceiverEmail: "测试收件邮箱",
pleaseEnterTestReceiverEmail: "请输入测试收件邮箱",
saveBeforeTest: "保存后再点击测试",
sendFailHelpDoc: "发送失败???",
emailConfigHelpDoc: "邮件配置帮助文档",
tryOfficialEmailServer: "您还可以试试使用官方邮件服务器↗↗↗↗↗↗↗↗",
pluginManagement: "插件管理",
pluginBetaWarning: "自定义插件处于BETA测试版后续可能会有破坏性变更",
pleaseSelectRecord: "请先勾选记录",
permissionManagement: "权限管理",
adda: "添加",
rootNode: "根节点",
permissionName: "权限名称",
enterPermissionName: "请输入权限名称",
permissionCode: "权限代码",
enterPermissionCode: "请输入权限代码",
max100Chars: "最大100个字符",
examplePermissionCode: "例如:sys:user:view",
sortOrder: "排序",
sortRequired: "排序号必填",
parentNode: "父节点",
roleManagement: "角色管理",
assignPermissions: "分配权限",
roleName: "角色名称",
enterRoleName: "请输入角色名称",
unlockLogin: "解除登录锁定",
notice: "提示",
confirmUnlock: "确定要解除该用户的登录锁定吗?",
unlockSuccess: "解除成功",
enterUsername: "请输入用户名",
modifyPasswordIfFilled: "填写则修改密码",
emaila: "邮箱",
mobile: "手机号",
avatar: "头像",
validTime: "有效期",
remark: "备注",
roles: "角色",
cnameTitle: "CNAME服务配置",
cnameDescription:
"此处配置的域名作为其他域名校验的代理当别的域名需要申请证书时通过CNAME映射到此域名上来验证所有权。好处是任何域名都可以通过此方式申请证书也无需填写AccessSecret。",
cnameLinkText: "CNAME功能原理及使用说明",
confirmTitle: "确认",
confirmDeleteBatch: "确定要批量删除这{count}条记录吗",
selectRecordsFirst: "请先勾选记录",
cnameDomain: "CNAME域名",
cnameDomainPlaceholder: "cname.handsfree.work",
cnameDomainHelper:
"需要一个右边DNS提供商注册的域名也可以将其他域名的dns服务器转移到这几家来。\nCNAME域名一旦确定不可修改建议使用一级子域名",
dnsProvider: "DNS提供商",
dnsProviderAuthorization: "DNS提供商授权",
setDefault: "设置默认",
confirmSetDefault: "确定要设置为默认吗?",
setAsDefault: "设为默认",
disabledLabel: "禁用",
confirmToggleStatus: "确定要{action}吗?",
};

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

@ -0,0 +1,71 @@
export default {
createCertPipeline: {
title: "创建证书申请流水线",
description: "演示证书申请任务如何配置",
items: {
tutorialTitle: "教程演示内容",
tutorialDesc1: "本教程演示如何自动申请证书并部署到Nginx上",
tutorialDesc2: "仅需3步全自动申请部署证书",
createTitle: "创建证书流水线",
createDesc: "点击添加证书流水线,填写证书申请信息",
successTitle: "流水线创建成功",
successDesc: "点击手动触发即可申请证书",
nextTitle: "接下来演示如何自动部署证书",
nextDesc: "如果您只需要申请证书,那么到这一步就可以了",
},
},
buttons: {
prev: "上一步",
next: "下一步",
},
addDeployTask: {
title: "添加部署证书任务",
description: "这里演示部署证书到Nginx",
items: {
addTaskTitle: "添加证书部署任务",
addTaskDesc1: "这里演示自动部署证书到nginx",
addTaskDesc2: "本系统提供海量部署插件,满足您的各种部署需求",
fillParamsTitle: "填写任务参数",
fillParamsDesc1: "填写主机上证书文件的路径",
fillParamsDesc2: "选择主机ssh登录授权",
activateCertTitle: "让新证书生效",
activateCertDesc1: "执行重启脚本",
activateCertDesc2: "让证书生效",
taskSuccessTitle: "部署任务添加成功",
taskSuccessDesc: "现在可以运行",
pluginsTitle: "本系统提供茫茫多的部署插件",
pluginsDesc: "您可以根据自身需求将证书部署到各种应用和平台",
},
},
runAndTestTask: {
runAndTestTitle: "运行与测试",
runAndTestDescription: "演示流水线运行,查看日志,成功后跳过等",
runTestOnce: "运行测试一下",
clickManualTriggerToTest: "点击手动触发按钮,即可测试运行",
viewLogs: "查看日志",
clickTaskToViewStatusAndLogs: "点击任务可以查看状态和日志",
howToTroubleshootFailure: "执行失败如何排查",
viewErrorLogs: "查看错误日志",
nginxContainerNotExistFix: "这里报的是nginx容器不存在修改命令改成正确的nginx容器名称即可",
executionSuccess: "执行成功",
retryAfterFix: "修改正确后,重新点击手动触发,重新运行一次,执行成功",
autoSkipAfterSuccess: "成功后自动跳过",
successSkipExplanation: "可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行",
viewCertDeploymentSuccess: "查看证书部署成功",
visitNginxToSeeCert: "访问nginx上的网站可以看到证书已经部署成功",
downloadCertManualDeploy: "还可以下载证书,手动部署",
downloadIfNoAutoDeployPlugin: "如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署",
},
scheduleAndEmailTask: {
title: "设置定时执行和邮件通知",
description: "自动运行",
setSchedule: "设置定时执行",
pipelineSuccessThenSchedule: "流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了",
recommendDailyRun: "推荐配置每天运行一次在到期前35天才会重新申请新证书并部署没到期前会自动跳过不会重复申请。",
setEmailNotification: "设置邮件通知",
suggestErrorAndRecoveryEmails: "建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)",
basicVersionNeedsMailServer: "(基础版需要配置邮件服务器)",
tutorialEndTitle: "教程结束",
thanksForWatching: "感谢观看,希望对你有所帮助",
}
}

View File

@ -0,0 +1,19 @@
import certd from './certd';
import authentication from './authentication';
import vip from './vip';
import tutorial from './tutorial';
import preferences from './preferences';
import ui from './ui';
import guide from './guide';
import common from './common';
export default {
certd,
authentication,
vip,
ui,
tutorial,
preferences,
guide,
common
};

View File

@ -0,0 +1,186 @@
export default {
"title": "偏好设置",
"subtitle": "自定义偏好设置 & 实时预览",
"resetTitle": "重置偏好设置",
"resetTip": "数据有变化,点击可进行重置",
"resetSuccess": "重置偏好设置成功",
"appearance": "外观",
"layout": "布局",
"content": "内容",
"other": "其它",
"wide": "流式",
"compact": "定宽",
"followSystem": "跟随系统",
"vertical": "垂直",
"verticalTip": "侧边垂直菜单模式",
"horizontal": "水平",
"horizontalTip": "水平菜单模式,菜单全部显示在顶部",
"twoColumn": "双列菜单",
"twoColumnTip": "垂直双列菜单模式",
"headerSidebarNav": "侧边导航",
"headerSidebarNavTip": "顶部通栏,侧边导航模式",
"headerTwoColumn": "混合双列",
"headerTwoColumnTip": "双列、水平菜单共存模式",
"mixedMenu": "混合垂直",
"mixedMenuTip": "垂直水平菜单共存",
"fullContent": "内容全屏",
"fullContentTip": "不显示任何菜单,只显示内容主体",
"normal": "常规",
"plain": "朴素",
"rounded": "圆润",
"copyPreferences": "复制偏好设置",
"copyPreferencesSuccessTitle": "复制成功",
"copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
"clearAndLogout": "清空缓存 & 退出登录",
"mode": "模式",
"general": "通用",
"language": "语言",
"dynamicTitle": "动态标题",
"watermark": "水印",
"checkUpdates": "定时检查更新",
"position": {
"title": "偏好设置位置",
"header": "顶栏",
"auto": "自动",
"fixed": "固定"
},
"sidebar": {
"title": "侧边栏",
"width": "宽度",
"visible": "显示侧边栏",
"collapsed": "折叠菜单",
"collapsedShowTitle": "折叠显示菜单名",
"autoActivateChild": "自动激活子菜单",
"autoActivateChildTip": "点击顶层菜单时,自动激活第一个子菜单或者上一次激活的子菜单",
"expandOnHover": "鼠标悬停展开",
"expandOnHoverTip": "鼠标在折叠区域悬浮时,`启用`则展开当前子菜单,`禁用`则展开整个侧边栏"
},
"tabbar": {
"title": "标签栏",
"enable": "启用标签栏",
"icon": "显示标签栏图标",
"showMore": "显示更多按钮",
"showMaximize": "显示最大化按钮",
"persist": "持久化标签页",
"maxCount": "最大标签数",
"maxCountTip": "每次打开新的标签时如果超过最大标签数,\n会自动关闭一个最先打开的标签\n设置为 0 则不限制",
"draggable": "启动拖拽排序",
"wheelable": "启用纵向滚轮响应",
"middleClickClose": "点击鼠标中键关闭标签页",
"wheelableTip": "开启后,标签栏区域可以响应滚轮的纵向滚动事件。\n关闭时只能响应系统的横向滚动事件需要按下Shift再滚动滚轮",
"styleType": {
"title": "标签页风格",
"chrome": "谷歌",
"card": "卡片",
"plain": "朴素",
"brisk": "轻快"
},
"contextMenu": {
"reload": "重新加载",
"close": "关闭",
"pin": "固定",
"unpin": "取消固定",
"closeLeft": "关闭左侧标签页",
"closeRight": "关闭右侧标签页",
"closeOther": "关闭其它标签页",
"closeAll": "关闭全部标签页",
"openInNewWindow": "在新窗口打开",
"maximize": "最大化",
"restoreMaximize": "还原"
}
},
"navigationMenu": {
"title": "导航菜单",
"style": "导航菜单风格",
"accordion": "侧边导航菜单手风琴模式",
"split": "导航菜单分离",
"splitTip": "开启时,侧边栏显示顶栏对应菜单的子菜单"
},
"breadcrumb": {
"title": "面包屑导航",
"enable": "开启面包屑导航",
"icon": "显示面包屑图标",
"home": "显示首页按钮",
"style": "面包屑风格",
"hideOnlyOne": "仅有一个时隐藏",
"background": "背景"
},
"animation": {
"title": "动画",
"loading": "页面切换 Loading",
"transition": "页面切换动画",
"progress": "页面切换进度条"
},
"theme": {
"title": "主题",
"radius": "圆角",
"light": "浅色",
"dark": "深色",
"darkSidebar": "深色侧边栏",
"darkHeader": "深色顶栏",
"weakMode": "色弱模式",
"grayMode": "灰色模式",
"builtin": {
"title": "内置主题",
"default": "默认",
"violet": "紫罗兰",
"pink": "樱花粉",
"rose": "玫瑰红",
"skyBlue": "天蓝色",
"deepBlue": "深蓝色",
"green": "浅绿色",
"deepGreen": "深绿色",
"orange": "橙黄色",
"yellow": "柠檬黄",
"zinc": "锌色灰",
"neutral": "中性色",
"slate": "石板灰",
"gray": "中灰色",
"custom": "自定义"
}
},
"header": {
"title": "顶栏",
"modeStatic": "静止",
"modeFixed": "固定",
"modeAuto": "自动隐藏和显示",
"modeAutoScroll": "滚动隐藏和显示",
"visible": "显示顶栏",
"menuAlign": "菜单位置",
"menuAlignStart": "左侧",
"menuAlignEnd": "右侧",
"menuAlignCenter": "居中"
},
"footer": {
"title": "底栏",
"visible": "显示底栏",
"fixed": "固定在底部"
},
"copyright": {
"title": "版权",
"enable": "启用版权",
"companyName": "公司名",
"companySiteLink": "公司主页",
"date": "日期",
"icp": "ICP 备案号",
"icpLink": "ICP 网站链接"
},
"shortcutKeys": {
"title": "快捷键",
"global": "全局",
"search": "全局搜索",
"logout": "退出登录",
"preferences": "偏好设置"
},
"widget": {
"title": "小部件",
"globalSearch": "启用全局搜索",
"fullscreen": "启用全屏",
"themeToggle": "启用主题切换",
"languageToggle": "启用语言切换",
"notification": "启用通知",
"sidebarToggle": "启用侧边栏切换",
"lockScreen": "启用锁屏",
"refresh": "启用刷新"
}
}

View File

@ -0,0 +1,3 @@
export default {
title: '使用教程',
}

View File

@ -0,0 +1,104 @@
export default {
"formRules": {
"required": "请输入{0}",
"selectRequired": "请选择{0}",
"minLength": "{0}至少{1}个字符",
"maxLength": "{0}最多{1}个字符",
"length": "{0}长度必须为{1}个字符",
"alreadyExists": "{0} `{1}` 已存在",
"startWith": "{0}必须以 {1} 开头",
"invalidURL": "请输入有效的链接"
},
"actionTitle": {
"edit": "修改{0}",
"create": "新增{0}",
"delete": "删除{0}",
"view": "查看{0}"
},
"actionMessage": {
"deleteConfirm": "确定删除 {0} 吗?",
"deleting": "正在删除 {0} ...",
"deleteSuccess": "{0} 删除成功",
"operationSuccess": "操作成功",
"operationFailed": "操作失败"
},
"placeholder": {
"input": "请输入",
"select": "请选择"
},
"captcha": {
"title": "请完成安全验证",
"sliderSuccessText": "验证通过",
"sliderDefaultText": "请按住滑块拖动",
"sliderRotateDefaultTip": "点击图片可刷新",
"sliderRotateFailTip": "验证失败",
"sliderRotateSuccessTip": "验证成功,耗时{0}秒",
"alt": "支持img标签src属性值",
"refreshAriaLabel": "刷新验证码",
"confirmAriaLabel": "确认选择",
"confirm": "确认",
"pointAriaLabel": "点击点",
"clickInOrder": "请依次点击"
},
"iconPicker": {
"placeholder": "选择一个图标",
"search": "搜索图标..."
},
"jsonViewer": {
"copy": "复制",
"copied": "已复制"
},
"fallback": {
"pageNotFound": "哎呀!未找到页面",
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
"forbidden": "哎呀!访问被拒绝",
"forbiddenDesc": "抱歉,您没有权限访问此页面。",
"internalError": "哎呀!出错了",
"internalErrorDesc": "抱歉,服务器遇到错误。",
"offline": "离线页面",
"offlineError": "哎呀!网络错误",
"offlineErrorDesc": "抱歉,无法连接到互联网,请检查您的网络连接并重试。",
"comingSoon": "即将推出",
"http": {
"requestTimeout": "请求超时,请稍后再试。",
"networkError": "网络异常,请检查您的网络连接后重试。",
"badRequest": "请求错误。请检查您的输入并重试。",
"unauthorized": "登录认证过期,请重新登录后继续。",
"forbidden": "禁止访问, 您没有权限访问此资源。",
"notFound": "未找到, 请求的资源不存在。",
"internalServerError": "内部服务器错误,请稍后再试。"
}
},
"widgets": {
"document": "文档",
"qa": "问题 & 帮助",
"setting": "设置",
"logoutTip": "是否退出登录?",
"viewAll": "查看所有消息",
"notifications": "通知",
"markAllAsRead": "全部标记为已读",
"clearNotifications": "清空",
"checkUpdatesTitle": "新版本可用",
"checkUpdatesDescription": "点击刷新以获取最新版本",
"search": {
"title": "搜索",
"searchNavigate": "搜索导航菜单",
"select": "选择",
"navigate": "导航",
"close": "关闭",
"noResults": "未找到搜索结果",
"noRecent": "没有搜索历史",
"recent": "搜索历史"
},
"lockScreen": {
"title": "锁定屏幕",
"screenButton": "锁定",
"password": "密码",
"placeholder": "请输入锁屏密码",
"unlock": "点击解锁",
"errorPasswordTip": "密码错误,请重新输入",
"backToLogin": "返回登录",
"entry": "进入系统"
}
}
}

View File

@ -0,0 +1,86 @@
export default {
comm: {
name: "{vipLabel}已开通",
title: "到期时间:{expire}",
nav: "{vipLabel}",
},
plus: {
name: "商业版功能",
title: "升级商业版,获取商业授权",
},
free: {
comm: {
name: "商业版功能",
title: "升级商业版,获取商业授权",
},
button: {
name: "专业版功能",
title: "升级专业版享受更多VIP特权",
},
nav: {
name: "基础版",
title: "升级专业版享受更多VIP特权",
},
},
enterCode: "请输入激活码",
successTitle: "激活成功",
successContent: "您已成功激活{vipLabel},有效期至:{expireDate}",
bindAccountTitle: "是否绑定袖手账号",
bindAccountContent: "绑定账号后可以避免License丢失强烈建议绑定",
congratulations_vip_trial: '恭喜,您已获得专业版{duration}天试用',
trial_modal_title: '7天专业版试用获取',
trial_modal_ok_text: '立即获取',
trial_modal_thanks: '感谢您对开源项目的支持',
trial_modal_click_confirm: '点击确认即可获取7天专业版试用',
get_7_day_pro_trial: "7天专业版试用获取",
star_now: "立即去Star",
please_help_star: "可以先请您帮忙点个star吗感谢感谢",
admin_only_operation: "仅限管理员操作",
enter_activation_code: "请输入激活码",
activate_pro_business: "激活专业版/商业版",
renew_business: "续期商业版",
renew_pro_upgrade_business: "续期专业版/升级商业版",
basic_edition: "基础版",
community_free_version: "社区免费版",
unlimited_certificate_application: "证书申请无限制",
unlimited_domain_count: "域名数量无限制",
unlimited_certificate_pipelines: "证书流水线数量无限制",
common_deployment_plugins: "常用的主机、云平台、cdn、宝塔、1Panel等部署插件",
email_webhook_notifications: "邮件、webhook通知方式",
professional_edition: "专业版",
open_source_support: "开源需要您的赞助支持",
vip_group_priority: "可加VIP群您的需求将优先实现",
unlimited_site_certificate_monitoring: "站点证书监控无限制",
more_notification_methods: "更多通知方式",
plugins_fully_open: "插件全开放,群辉等更多插件",
click_to_get_7_day_trial: "点击获取7天试用",
years: "年",
afdian_support_vip: '爱发电赞助“VIP会员”后获取一年期专业版激活码开源需要您的支持',
get_after_support: "爱发电赞助后获取",
business_edition: "商业版",
commercial_license: "商业授权,可对外运营",
all_pro_privileges: "拥有专业版所有特权",
allow_commercial_use_modify_logo_title: "允许商用可修改logo、标题",
data_statistics: "数据统计",
plugin_management: "插件管理",
unlimited_multi_users: "多用户无限制",
support_user_payment: "支持用户支付",
contact_author_for_trial: "请联系作者获取试用",
activate: "激活",
get_pro_code_after_support: '爱发电赞助“VIP会员”后获取一年期专业版激活码',
business_contact_author: "商业版请直接联系作者",
year: "年",
freee: "免费",
renew: "续期",
activate_immediately: "立刻激活",
current: "当前",
activated_expire_time: "已激活,到期时间:",
site_id: "站点ID",
invite_code_optional: "邀请码【选填】可额外获得专业版30天/商业版15天时长",
no_activation_code: "没有激活码?",
activation_code_one_use: "激活码使用过一次之后,不可再次使用,如果要更换站点,请",
bind_account: "绑定账号",
transfer_vip: ',然后"转移VIP"即可',
}

View File

@ -3,8 +3,7 @@ import App from "./App.vue";
// import Antd from "ant-design-vue";
import Antd from "./plugin/antdv-async/index";
import "./style/common.less";
import { loadMessages } from "./i18n";
import { i18n } from "/@/vben/locales";
import { i18n, loadLocaleMessages } from "/@/locales"
import components from "./components";
import router from "./router";
import plugin from "./plugin/";
@ -16,15 +15,15 @@ import { initPreferences } from "/@/vben/preferences";
// import "./components/code-editor/import-works";
// @ts-ignore
async function bootstrap() {
const app = createApp(App);
// app.use(Antd);
app.use(Antd);
await setupVben(app, { loadMessages, router });
app.use(router);
// app.use(i18n);
// app.use(store);
app.use(components);
app.use(plugin, { i18n });
const app = createApp(App);
// app.use(Antd);
app.use(Antd);
await setupVben(app, { loadLocaleMessages, router });
app.use(router);
// app.use(i18n);
// app.use(store);
app.use(components);
app.use(plugin, { i18n });
const envMode = util.env.MODE;
const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${envMode}`;

View File

@ -1,42 +1,44 @@
import LayoutBasic from "/@/layout/layout-basic.vue";
import type { RouteRecordRaw } from "vue-router";
import i18n from '/@/locales/i18n';
import { mergeRouteModules } from "/@/vben/utils";
const dynamicRouteFiles = import.meta.glob("./modules/**/*.ts*", {
eager: true,
eager: true,
});
/** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
export const frameworkResource = [
{
title: "框架",
name: "root",
path: "/",
redirect: "/index",
component: LayoutBasic,
meta: {
icon: "ion:accessibility",
hideInBreadcrumb: true,
},
children: [
{
title: "首页",
name: "index",
path: "/index",
component: "/framework/home/index.vue",
meta: {
fixedAside: true,
showOnHeader: false,
icon: "ion:home-outline",
auth: true,
},
},
// @ts-ignore
{
title: i18n.global.t("certd.framework.title"),
name: "root",
path: "/",
redirect: "/index",
component: LayoutBasic,
meta: {
icon: "ion:accessibility",
hideInBreadcrumb: true,
},
children: [
{
title: i18n.global.t("certd.framework.home"),
name: "index",
path: "/index",
component: "/framework/home/index.vue",
meta: {
fixedAside: true,
showOnHeader: false,
icon: "ion:home-outline",
auth: true,
},
},
// @ts-ignore
...dynamicRoutes,
],
},
...dynamicRoutes,
],
},
];
console.assert(frameworkResource.length === 1, "frameworkResource数组长度只能为1你只能配置framework路由的子路由");

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;

View File

@ -1,267 +1,256 @@
import { useSettingStore } from "/@/store/settings";
import aboutResource from "/@/router/source/modules/about";
import i18n from '/@/locales/i18n';
export const certdResources = [
{
title: "证书自动化",
name: "CertdRoot",
path: "/certd",
redirect: "/certd/pipeline",
meta: {
icon: "ion:key-outline",
auth: true,
order: 0,
},
children: [
{
title: "证书自动化流水线",
name: "PipelineManager",
path: "/certd/pipeline",
component: "/certd/pipeline/index.vue",
meta: {
icon: "ion:analytics-sharp",
keepAlive: true,
},
},
{
title: "编辑流水线",
name: "PipelineEdit",
path: "/certd/pipeline/detail",
component: "/certd/pipeline/detail.vue",
meta: {
isMenu: false,
},
},
{
title: "执行历史记录",
name: "PipelineHistory",
path: "/certd/history",
component: "/certd/history/index.vue",
meta: {
icon: "ion:timer-outline",
keepAlive: true,
},
},
{
title: "证书仓库",
name: "CertStore",
path: "/certd/monitor/cert",
component: "/certd/monitor/cert/index.vue",
meta: {
icon: "ion:shield-checkmark-outline",
auth: true,
isMenu: true,
keepAlive: true,
},
},
{
title: "站点证书监控",
name: "SiteCertMonitor",
path: "/certd/monitor/site",
component: "/certd/monitor/site/index.vue",
meta: {
icon: "ion:videocam-outline",
auth: true,
keepAlive: true,
},
},
{
title: "设置",
name: "MineSetting",
path: "/certd/setting",
redirect: "/certd/access",
meta: {
icon: "ion:settings-outline",
auth: true,
keepAlive: true,
},
children: [
{
title: "授权管理",
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
keepAlive: true,
},
},
{
title: "CNAME记录管理",
name: "CnameRecord",
path: "/certd/cname/record",
component: "/certd/cname/record/index.vue",
meta: {
icon: "ion:link-outline",
auth: true,
keepAlive: true,
},
},
{
title: "子域名托管设置",
name: "SubDomain",
path: "/certd/pipeline/subDomain",
component: "/certd/pipeline/sub-domain/index.vue",
meta: {
icon: "material-symbols:approval-delegation-outline",
auth: true,
keepAlive: true,
},
},
{
title: "流水线分组管理",
name: "PipelineGroupManager",
path: "/certd/pipeline/group",
component: "/certd/pipeline/group/index.vue",
meta: {
icon: "mdi:format-list-group",
auth: true,
keepAlive: true,
},
},
{
title: "开放接口密钥",
name: "OpenKey",
path: "/certd/open/openkey",
component: "/certd/open/openkey/index.vue",
meta: {
icon: "hugeicons:api",
auth: true,
keepAlive: true,
},
},
{
title: "通知设置",
name: "NotificationManager",
path: "/certd/notification",
component: "/certd/notification/index.vue",
meta: {
icon: "ion:megaphone-outline",
auth: true,
keepAlive: true,
},
},
{
title: "站点监控设置",
name: "SiteMonitorSetting",
path: "/certd/monitor/setting",
component: "/certd/monitor/site/setting/index.vue",
meta: {
icon: "ion:videocam-outline",
auth: true,
isMenu: true,
},
},
{
title: "认证安全设置",
name: "UserSecurity",
path: "/certd/mine/security",
component: "/certd/mine/security/index.vue",
meta: {
icon: "fluent:shield-keyhole-16-regular",
auth: true,
isMenu: true,
},
},
{
title: "账号信息",
name: "UserProfile",
path: "/certd/mine/user-profile",
component: "/certd/mine/user-profile.vue",
meta: {
icon: "ion:person-outline",
auth: true,
isMenu: false,
},
},
],
},
{
title: "套餐",
name: "SuiteProduct",
path: "/certd/suite",
redirect: "/certd/suite/mine",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm && settingStore.isSuiteEnabled;
},
icon: "ion:cart-outline",
auth: true,
},
children: [
{
title: "我的套餐",
name: "MySuite",
path: "/certd/suite/mine",
component: "/certd/suite/mine/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true,
},
},
{
title: "套餐购买",
name: "SuiteProductBuy",
path: "/certd/suite/buy",
component: "/certd/suite/buy.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:cart-outline",
auth: true,
},
},
{
title: "我的订单",
name: "MyTrade",
path: "/certd/trade",
component: "/certd/trade/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:bag-check-outline",
auth: true,
keepAlive: true,
},
},
{
title: "支付返回",
name: "PaymentReturn",
path: "/certd/payment/return/:type",
component: "/certd/payment/return.vue",
meta: {
icon: "ant-design:pay-circle-outlined",
auth: false,
isMenu: false,
},
},
],
},
// {
// title: "邮箱设置",
// name: "EmailSetting",
// path: "/sys/settings/email",
// component: "/sys/settings/email-setting.vue",
// meta: {
// icon: "ion:mail-outline",
// auth: true
// }
// },
],
},
{
title: i18n.global.t("certd.title"),
name: "CertdRoot",
path: "/certd",
redirect: "/certd/pipeline",
meta: {
icon: "ion:key-outline",
auth: true,
order: 0,
},
children: [
{
title: i18n.global.t("certd.pipeline"),
name: "PipelineManager",
path: "/certd/pipeline",
component: "/certd/pipeline/index.vue",
meta: {
icon: "ion:analytics-sharp",
keepAlive: true,
},
},
{
title: i18n.global.t("certd.pipelineEdit"),
name: "PipelineEdit",
path: "/certd/pipeline/detail",
component: "/certd/pipeline/detail.vue",
meta: {
isMenu: false,
},
},
{
title: i18n.global.t("certd.history"),
name: "PipelineHistory",
path: "/certd/history",
component: "/certd/history/index.vue",
meta: {
icon: "ion:timer-outline",
keepAlive: true,
},
},
{
title: i18n.global.t("certd.certStore"),
name: "CertStore",
path: "/certd/monitor/cert",
component: "/certd/monitor/cert/index.vue",
meta: {
icon: "ion:shield-checkmark-outline",
auth: true,
isMenu: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.siteMonitor"),
name: "SiteCertMonitor",
path: "/certd/monitor/site",
component: "/certd/monitor/site/index.vue",
meta: {
icon: "ion:videocam-outline",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.settings"),
name: "MineSetting",
path: "/certd/setting",
redirect: "/certd/access",
meta: {
icon: "ion:settings-outline",
auth: true,
keepAlive: true,
},
children: [
{
title: i18n.global.t("certd.accessManager"),
name: "AccessManager",
path: "/certd/access",
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.cnameRecord"),
name: "CnameRecord",
path: "/certd/cname/record",
component: "/certd/cname/record/index.vue",
meta: {
icon: "ion:link-outline",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.subDomain"),
name: "SubDomain",
path: "/certd/pipeline/subDomain",
component: "/certd/pipeline/sub-domain/index.vue",
meta: {
icon: "material-symbols:approval-delegation-outline",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.pipelineGroup"),
name: "PipelineGroupManager",
path: "/certd/pipeline/group",
component: "/certd/pipeline/group/index.vue",
meta: {
icon: "mdi:format-list-group",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.openKey"),
name: "OpenKey",
path: "/certd/open/openkey",
component: "/certd/open/openkey/index.vue",
meta: {
icon: "hugeicons:api",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.notification"),
name: "NotificationManager",
path: "/certd/notification",
component: "/certd/notification/index.vue",
meta: {
icon: "ion:megaphone-outline",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.siteMonitorSetting"),
name: "SiteMonitorSetting",
path: "/certd/monitor/setting",
component: "/certd/monitor/site/setting/index.vue",
meta: {
icon: "ion:videocam-outline",
auth: true,
isMenu: true,
},
},
{
title: i18n.global.t("certd.userSecurity"),
name: "UserSecurity",
path: "/certd/mine/security",
component: "/certd/mine/security/index.vue",
meta: {
icon: "fluent:shield-keyhole-16-regular",
auth: true,
isMenu: true,
},
},
{
title: i18n.global.t("certd.userProfile"),
name: "UserProfile",
path: "/certd/mine/user-profile",
component: "/certd/mine/user-profile.vue",
meta: {
icon: "ion:person-outline",
auth: true,
isMenu: false,
},
},
],
},
{
title: i18n.global.t("certd.suite"),
name: "SuiteProduct",
path: "/certd/suite",
redirect: "/certd/suite/mine",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm && settingStore.isSuiteEnabled;
},
icon: "ion:cart-outline",
auth: true,
},
children: [
{
title: i18n.global.t("certd.mySuite"),
name: "MySuite",
path: "/certd/suite/mine",
component: "/certd/suite/mine/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true,
},
},
{
title: i18n.global.t("certd.suiteBuy"),
name: "SuiteProductBuy",
path: "/certd/suite/buy",
component: "/certd/suite/buy.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:cart-outline",
auth: true,
},
},
{
title: i18n.global.t("certd.myTrade"),
name: "MyTrade",
path: "/certd/trade",
component: "/certd/trade/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:bag-check-outline",
auth: true,
keepAlive: true,
},
},
{
title: i18n.global.t("certd.paymentReturn"),
name: "PaymentReturn",
path: "/certd/payment/return/:type",
component: "/certd/payment/return.vue",
meta: {
icon: "ant-design:pay-circle-outlined",
auth: false,
isMenu: false,
},
},
],
},
],
},
];
export default certdResources;

View File

@ -1,256 +1,256 @@
import LayoutPass from "/@/layout/layout-pass.vue";
import { useSettingStore } from "/@/store/settings";
import aboutResource from "/@/router/source/modules/about";
import i18n from '/@/locales/i18n';
export const sysResources = [
{
title: "系统管理",
name: "SysRoot",
path: "/sys",
redirect: "/sys/settings",
meta: {
icon: "ion:settings-outline",
permission: "sys:settings:view",
order: 10,
},
children: [
{
title: "控制台",
name: "SysConsole",
path: "/sys/console",
component: "/sys/console/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:speedometer-outline",
permission: "sys:auth:user:view",
},
},
{
title: i18n.global.t('certd.sysResources.sysRoot'),
name: "SysRoot",
path: "/sys",
redirect: "/sys/settings",
meta: {
icon: "ion:settings-outline",
permission: "sys:settings:view",
order: 10,
},
children: [
{
title: i18n.global.t('certd.sysResources.sysConsole'),
name: "SysConsole",
path: "/sys/console",
component: "/sys/console/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:speedometer-outline",
permission: "sys:auth:user:view",
},
},
{
title: "系统设置",
name: "SysSettings",
path: "/sys/settings",
component: "/sys/settings/index.vue",
meta: {
icon: "ion:settings-outline",
permission: "sys:settings:view",
},
},
{
title: "CNAME服务设置",
name: "CnameSetting",
path: "/sys/cname/provider",
component: "/sys/cname/provider/index.vue",
meta: {
icon: "ion:earth-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "邮件服务器设置",
name: "EmailSetting",
path: "/sys/settings/email",
component: "/sys/settings/email/index.vue",
meta: {
permission: "sys:settings:view",
icon: "ion:mail-outline",
auth: true,
},
},
{
title: "站点个性化",
name: "SiteSetting",
path: "/sys/site",
component: "/sys/site/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:document-text-outline",
permission: "sys:settings:view",
},
},
{
title: "顶部菜单设置",
name: "HeaderMenus",
path: "/sys/settings/header-menus",
component: "/sys/settings/header-menus/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:menu",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "系统级授权",
name: "SysAccess",
path: "/sys/access",
component: "/sys/access/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:disc-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "插件管理",
name: "SysPlugin",
path: "/sys/plugin",
component: "/sys/plugin/index.vue",
meta: {
icon: "ion:extension-puzzle-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "编辑插件",
name: "SysPluginEdit",
path: "/sys/plugin/edit",
component: "/sys/plugin/edit.vue",
meta: {
isMenu: false,
icon: "ion:extension-puzzle",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "证书插件配置",
name: "SysPluginConfig",
path: "/sys/plugin/config",
component: "/sys/plugin/config.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:extension-puzzle",
permission: "sys:settings:view",
},
},
{
title: "账号绑定",
name: "AccountBind",
path: "/sys/account",
component: "/sys/account/index.vue",
meta: {
icon: "ion:golf-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: "权限管理",
name: "PermissionManager",
path: "/sys/authority/permission",
component: "/sys/authority/permission/index.vue",
meta: {
icon: "ion:list-outline",
//需要校验权限
permission: "sys:auth:per:view",
keepAlive: true,
},
},
{
title: "角色管理",
name: "RoleManager",
path: "/sys/authority/role",
component: "/sys/authority/role/index.vue",
meta: {
icon: "ion:people-outline",
permission: "sys:auth:role:view",
keepAlive: true,
},
},
{
title: "用户管理",
name: "UserManager",
path: "/sys/authority/user",
component: "/sys/authority/user/index.vue",
meta: {
icon: "ion:person-outline",
permission: "sys:auth:user:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.sysSettings'),
name: "SysSettings",
path: "/sys/settings",
component: "/sys/settings/index.vue",
meta: {
icon: "ion:settings-outline",
permission: "sys:settings:view",
},
},
{
title: i18n.global.t('certd.sysResources.cnameSetting'),
name: "CnameSetting",
path: "/sys/cname/provider",
component: "/sys/cname/provider/index.vue",
meta: {
icon: "ion:earth-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.emailSetting'),
name: "EmailSetting",
path: "/sys/settings/email",
component: "/sys/settings/email/index.vue",
meta: {
permission: "sys:settings:view",
icon: "ion:mail-outline",
auth: true,
},
},
{
title: i18n.global.t('certd.sysResources.siteSetting'),
name: "SiteSetting",
path: "/sys/site",
component: "/sys/site/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:document-text-outline",
permission: "sys:settings:view",
},
},
{
title: i18n.global.t('certd.sysResources.headerMenus'),
name: "HeaderMenus",
path: "/sys/settings/header-menus",
component: "/sys/settings/header-menus/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:menu",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.sysAccess'),
name: "SysAccess",
path: "/sys/access",
component: "/sys/access/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:disc-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.sysPlugin'),
name: "SysPlugin",
path: "/sys/plugin",
component: "/sys/plugin/index.vue",
meta: {
icon: "ion:extension-puzzle-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.sysPluginEdit'),
name: "SysPluginEdit",
path: "/sys/plugin/edit",
component: "/sys/plugin/edit.vue",
meta: {
isMenu: false,
icon: "ion:extension-puzzle",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.sysPluginConfig'),
name: "SysPluginConfig",
path: "/sys/plugin/config",
component: "/sys/plugin/config.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:extension-puzzle",
permission: "sys:settings:view",
},
},
{
title: i18n.global.t('certd.sysResources.accountBind'),
name: "AccountBind",
path: "/sys/account",
component: "/sys/account/index.vue",
meta: {
icon: "ion:golf-outline",
permission: "sys:settings:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.permissionManager'),
name: "PermissionManager",
path: "/sys/authority/permission",
component: "/sys/authority/permission/index.vue",
meta: {
icon: "ion:list-outline",
permission: "sys:auth:per:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.roleManager'),
name: "RoleManager",
path: "/sys/authority/role",
component: "/sys/authority/role/index.vue",
meta: {
icon: "ion:people-outline",
permission: "sys:auth:role:view",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.userManager'),
name: "UserManager",
path: "/sys/authority/user",
component: "/sys/authority/user/index.vue",
meta: {
icon: "ion:person-outline",
permission: "sys:auth:user:view",
keepAlive: true,
},
},
{
title: "套餐管理",
name: "SuiteManager",
path: "/sys/suite",
redirect: "/sys/suite/setting",
meta: {
icon: "ion:cart-outline",
permission: "sys:settings:edit",
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
keepAlive: true,
},
children: [
{
title: "套餐设置",
name: "SuiteSetting",
path: "/sys/suite/setting",
component: "/sys/suite/setting/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:cart",
permission: "sys:settings:edit",
},
},
{
title: "订单管理",
name: "OrderManager",
path: "/sys/suite/trade",
component: "/sys/suite/trade/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:bag-check",
permission: "sys:settings:edit",
keepAlive: true,
},
},
{
title: "用户套餐",
name: "UserSuites",
path: "/sys/suite/user-suite",
component: "/sys/suite/user-suite/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true,
keepAlive: true,
},
},
],
},
],
},
{
title: i18n.global.t('certd.sysResources.suiteManager'),
name: "SuiteManager",
path: "/sys/suite",
redirect: "/sys/suite/setting",
meta: {
icon: "ion:cart-outline",
permission: "sys:settings:edit",
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
keepAlive: true,
},
children: [
{
title: i18n.global.t('certd.sysResources.suiteSetting'),
name: "SuiteSetting",
path: "/sys/suite/setting",
component: "/sys/suite/setting/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:cart",
permission: "sys:settings:edit",
},
},
{
title: i18n.global.t('certd.sysResources.orderManager'),
name: "OrderManager",
path: "/sys/suite/trade",
component: "/sys/suite/trade/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:bag-check",
permission: "sys:settings:edit",
keepAlive: true,
},
},
{
title: i18n.global.t('certd.sysResources.userSuites'),
name: "UserSuites",
path: "/sys/suite/user-suite",
component: "/sys/suite/user-suite/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:gift-outline",
auth: true,
keepAlive: true,
},
},
],
},
],
},
];
export default sysResources;

View File

@ -3,159 +3,159 @@ import * as api from "./api.plugin";
import { DynamicType, FormItemProps } from "@fast-crud/fast-crud";
interface PluginState {
group?: PluginGroups;
group?: PluginGroups;
}
export type PluginGroup = {
key: string;
title: string;
desc?: string;
order: number;
icon: string;
plugins: any[];
key: string;
title: string;
desc?: string;
order: number;
icon: string;
plugins: any[];
};
export type PluginDefine = {
name: string;
title: string;
desc?: string;
shortcut: any;
input: {
[key: string]: DynamicType<FormItemProps>;
};
output: {
[key: string]: any;
};
name: string;
title: string;
desc?: string;
shortcut: any;
input: {
[key: string]: DynamicType<FormItemProps>;
};
output: {
[key: string]: any;
};
};
export class PluginGroups {
groups!: { [key: string]: PluginGroup };
map!: { [key: string]: PluginDefine };
constructor(groups: { [key: string]: PluginGroup }) {
this.groups = groups;
this.initGroup(groups);
this.initMap();
}
groups!: { [key: string]: PluginGroup };
map!: { [key: string]: PluginDefine };
constructor(groups: { [key: string]: PluginGroup }) {
this.groups = groups;
this.initGroup(groups);
this.initMap();
}
private initGroup(groups: { [p: string]: PluginGroup }) {
const all: PluginGroup = {
key: "all",
title: "全部",
order: 0,
plugins: [],
icon: "material-symbols:border-all-rounded",
};
for (const key in groups) {
all.plugins.push(...groups[key].plugins);
}
this.groups = {
all,
...groups,
};
}
private initGroup(groups: { [p: string]: PluginGroup }) {
const all: PluginGroup = {
key: "all",
title: t('certd.all'),
order: 0,
plugins: [],
icon: "material-symbols:border-all-rounded",
};
for (const key in groups) {
all.plugins.push(...groups[key].plugins);
}
this.groups = {
all,
...groups,
};
}
initMap() {
const map: { [key: string]: PluginDefine } = {};
for (const key in this.groups) {
const group = this.groups[key];
for (const plugin of group.plugins) {
map[plugin.name] = plugin;
}
}
this.map = map;
}
initMap() {
const map: { [key: string]: PluginDefine } = {};
for (const key in this.groups) {
const group = this.groups[key];
for (const plugin of group.plugins) {
map[plugin.name] = plugin;
}
}
this.map = map;
}
getGroups() {
return this.groups;
}
getGroups() {
return this.groups;
}
get(name: string) {
return this.map[name];
}
get(name: string) {
return this.map[name];
}
getPreStepOutputOptions({ pipeline, currentStageIndex, currentTaskIndex, currentStepIndex, currentTask }: any) {
const steps = this.collectionPreStepOutputs({
pipeline,
currentStageIndex,
currentTaskIndex,
currentStepIndex,
currentTask,
});
const options: any[] = [];
for (const step of steps) {
const stepDefine = this.get(step.type);
for (const key in stepDefine?.output) {
options.push({
value: `step.${step.id}.${key}`,
label: `${stepDefine.output[key].title}【from${step.title}`,
type: step.type,
});
}
}
return options;
}
getPreStepOutputOptions({ pipeline, currentStageIndex, currentTaskIndex, currentStepIndex, currentTask }: any) {
const steps = this.collectionPreStepOutputs({
pipeline,
currentStageIndex,
currentTaskIndex,
currentStepIndex,
currentTask,
});
const options: any[] = [];
for (const step of steps) {
const stepDefine = this.get(step.type);
for (const key in stepDefine?.output) {
options.push({
value: `step.${step.id}.${key}`,
label: `${stepDefine.output[key].title}【from${step.title}`,
type: step.type,
});
}
}
return options;
}
collectionPreStepOutputs({ pipeline, currentStageIndex, currentTaskIndex, currentStepIndex, currentTask }: any) {
const steps: any[] = [];
// 开始放step
for (let i = 0; i < currentStageIndex; i++) {
const stage = pipeline.stages[i];
for (const task of stage.tasks) {
for (const step of task.steps) {
steps.push(step);
}
}
}
//当前阶段之前的task
const currentStage = pipeline.stages[currentStageIndex];
for (let i = 0; i < currentTaskIndex; i++) {
const task = currentStage.tasks[i];
for (const step of task.steps) {
steps.push(step);
}
}
//放当前任务下的step
for (let i = 0; i < currentStepIndex; i++) {
const step = currentTask.steps[i];
steps.push(step);
}
return steps;
}
collectionPreStepOutputs({ pipeline, currentStageIndex, currentTaskIndex, currentStepIndex, currentTask }: any) {
const steps: any[] = [];
// 开始放step
for (let i = 0; i < currentStageIndex; i++) {
const stage = pipeline.stages[i];
for (const task of stage.tasks) {
for (const step of task.steps) {
steps.push(step);
}
}
}
//当前阶段之前的task
const currentStage = pipeline.stages[currentStageIndex];
for (let i = 0; i < currentTaskIndex; i++) {
const task = currentStage.tasks[i];
for (const step of task.steps) {
steps.push(step);
}
}
//放当前任务下的step
for (let i = 0; i < currentStepIndex; i++) {
const step = currentTask.steps[i];
steps.push(step);
}
return steps;
}
}
export const usePluginStore = defineStore({
id: "app.plugin",
state: (): PluginState => ({
group: null,
}),
actions: {
async reload() {
const groups = await api.GetGroups({});
this.group = new PluginGroups(groups);
},
async init() {
if (!this.group) {
await this.reload();
}
return this.group;
},
async getGroups(): Promise<PluginGroups> {
await this.init();
return this.group as PluginGroups;
},
async clear() {
this.group = null;
},
async getList(): Promise<PluginDefine[]> {
await this.init();
return this.group.groups.all.plugins;
},
async getPluginDefine(name: string): Promise<PluginDefine> {
await this.init();
return this.group.get(name);
},
async getPluginConfig(query: any) {
return await api.GetPluginConfig(query);
},
},
id: "app.plugin",
state: (): PluginState => ({
group: null,
}),
actions: {
async reload() {
const groups = await api.GetGroups({});
this.group = new PluginGroups(groups);
},
async init() {
if (!this.group) {
await this.reload();
}
return this.group;
},
async getGroups(): Promise<PluginGroups> {
await this.init();
return this.group as PluginGroups;
},
async clear() {
this.group = null;
},
async getList(): Promise<PluginDefine[]> {
await this.init();
return this.group.groups.all.plugins;
},
async getPluginDefine(name: string): Promise<PluginDefine> {
await this.init();
return this.group.get(name);
},
async getPluginConfig(query: any) {
return await api.GetPluginConfig(query);
},
},
});

View File

@ -2,7 +2,7 @@
import type { CaptchaPoint, PointSelectionCaptchaProps } from '../types';
import { RotateCw } from '/@/vben/icons';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { VbenButton, VbenIconButton } from '/@/vben/shadcn-ui';

View File

@ -3,7 +3,7 @@ import type { PointSelectionCaptchaCardProps } from '../types';
import { computed } from 'vue';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import {
Card,

View File

@ -7,7 +7,7 @@ import type {
import { reactive, unref, useTemplateRef, watch, watchEffect } from 'vue';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { cn } from '/@/vben/shared/utils';

View File

@ -8,7 +8,7 @@ import type {
import { computed, reactive, unref, useTemplateRef, watch } from 'vue';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { useTimeoutFn } from '@vueuse/core';

View File

@ -5,7 +5,7 @@ import { computed, ref, watch, watchEffect } from 'vue';
import { usePagination } from '/@/vben/hooks';
import { EmptyIcon, Grip, listIcons } from '/@/vben/icons';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import {
Button,

View File

@ -14,7 +14,7 @@ import { computed, useAttrs } from 'vue';
// @ts-ignore
import VueJsonViewer from 'vue-json-viewer';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { isBoolean } from '/@/vben/shared/utils';

View File

@ -6,7 +6,7 @@ import type { VbenFormSchema } from '/@/vben/form-ui';
import { computed, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton } from '/@/vben/shadcn-ui';

View File

@ -4,7 +4,7 @@ import type { VbenFormSchema } from '/@/vben/form-ui';
import { computed, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton } from '/@/vben/shadcn-ui';

View File

@ -8,7 +8,7 @@ import type { AuthenticationProps } from './types';
import { computed, onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton, VbenCheckbox } from '/@/vben/shadcn-ui';

View File

@ -6,7 +6,7 @@ import type { VbenFormSchema } from '/@/vben/form-ui';
import { computed, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton } from '/@/vben/shadcn-ui';

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { MdiGithub, MdiGoogle, MdiQqchat, MdiWechat } from '/@/vben/icons';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { VbenIconButton } from '/@/vben/shadcn-ui';

View File

@ -5,7 +5,7 @@ import { computed, defineAsyncComponent } from 'vue';
import { useRouter } from 'vue-router';
import { ArrowLeft, RotateCw } from '/@/vben/icons';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { VbenButton } from '/@/vben/shadcn-ui';

View File

@ -9,7 +9,7 @@ import "./styles";
import "./styles/antd/index.css";
import { useTitle } from "@vueuse/core";
import { setupI18n } from "/@/vben/locales";
import { setupI18n } from "../locales";
import { useSettingStore } from "/@/store/settings";
export async function setupVben(app: any, { loadMessages, router }: any) {

View File

@ -6,7 +6,7 @@ import type { MenuRecordRaw } from "../../types";
import { computed, useSlots, watch } from "vue";
import { useRefresh } from "../../hooks";
import { $t, i18n } from "../../locales";
import { $t, i18n } from "/@/locales";
import { preferences, updatePreferences, usePreferences } from "../../preferences";
import { useLockStore } from "../../stores";
import { cloneDeep, mapTree } from "../../utils";

View File

@ -9,7 +9,7 @@ import { useRoute, useRouter } from "vue-router";
import { useContentMaximize, useTabs } from "../../../hooks";
import { ArrowLeftToLine, ArrowRightLeft, ArrowRightToLine, ExternalLink, FoldHorizontal, Fullscreen, Minimize2, Pin, PinOff, RotateCw, X } from "../../../icons";
import { $t, useI18n } from "../../../locales";
import { $t, useI18n } from "/@/locales";
import { useAccessStore, useTabbarStore } from "../../../stores";
import { filterTree } from "../../../utils";

View File

@ -6,7 +6,7 @@ import type { IBreadcrumb } from "/@/vben//shadcn-ui";
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import { VbenBreadcrumbView } from "/@/vben//shadcn-ui";

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import { $t } from "../../../locales";
import { $t } from "/@/locales";
import { useVbenModal } from "../../../popup-ui";

View File

@ -4,7 +4,7 @@ import type { MenuRecordRaw } from "../../../types";
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { ArrowDown, ArrowUp, CornerDownLeft, MdiKeyboardEsc, Search } from "../../../icons";
import { $t } from "../../../locales";
import { $t } from "/@/locales";
import { isWindowsOs } from "../../../utils";
import { useVbenModal } from "../../../popup-ui";

View File

@ -5,7 +5,7 @@ import { nextTick, onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
import { SearchX, X } from "../../../icons";
import { $t } from "../../../locales";
import { $t } from "/@/locales";
import { mapTree, traverseTreeValues, uniqueByField } from "../../../utils";
import { VbenIcon, VbenScrollbar } from "../../../shadcn-ui";

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import type { SupportedLanguagesType } from "/@/vben/locales";
import type { SupportedLanguagesType } from "/@/locales";
import { SUPPORT_LANGUAGES } from "/@/vben/constants";
import { Languages } from "/@/vben/icons";
import { loadLocaleMessages } from "/@/vben/locales";
import { loadLocaleMessages } from "/@/locales";
import { preferences, updatePreferences } from "/@/vben/preferences";
import { VbenDropdownRadioMenu, VbenIconButton } from "/@/vben//shadcn-ui";

View File

@ -6,7 +6,7 @@ import type { VbenDropdownMenuItem } from "/@/vben//shadcn-ui";
import { computed } from "vue";
import { InspectionPanel, PanelLeft, PanelRight } from "/@/vben/icons";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import { preferences, updatePreferences, usePreferences } from "/@/vben/preferences";
import { VbenDropdownRadioMenu, VbenIconButton } from "/@/vben//shadcn-ui";

View File

@ -3,7 +3,7 @@ import type { Recordable } from "../../../types";
import { computed, reactive } from "vue";
import { $t } from "../../../locales";
import { $t } from "/@/locales";
import { useVbenForm, z } from "../../../form-ui";
import { useVbenModal } from "../../../popup-ui";

View File

@ -2,7 +2,7 @@
import { computed, reactive, ref } from "vue";
import { LockKeyhole } from "../../../icons";
import { $t, useI18n } from "../../../locales";
import { $t, useI18n } from "/@/locales";
import { storeToRefs, useLockStore } from "../../../stores";
import { useScrollLock } from "../../../composables";

View File

@ -2,7 +2,7 @@
import type { NotificationItem } from "./types";
import { Bell, MailCheck } from "/@/vben/icons";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import { VbenButton, VbenIconButton, VbenPopover, VbenScrollbar } from "/@/vben//shadcn-ui";

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import SwitchItem from "../switch-item.vue";

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { SUPPORT_LANGUAGES } from "/@/vben/constants";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import SelectItem from "../select-item.vue";
import SwitchItem from "../switch-item.vue";

View File

@ -3,7 +3,7 @@ import type { SelectOption } from "/@/vben/types";
import { computed } from "vue";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import SwitchItem from "../switch-item.vue";
import ToggleItem from "../toggle-item.vue";

View File

@ -3,7 +3,7 @@ import type { Component } from "vue";
import { computed } from "vue";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import { ContentCompact, ContentWide } from "../../icons";

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from "vue";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import InputItem from "../input-item.vue";
import SwitchItem from "../switch-item.vue";

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import SwitchItem from "../switch-item.vue";

View File

@ -5,7 +5,7 @@ import type {
SelectOption,
} from '/@/vben/types';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue';

View File

@ -6,107 +6,102 @@ import type { LayoutType } from '/@/vben/types';
import { computed } from 'vue';
import { CircleHelp } from '/@/vben/icons';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { VbenTooltip } from '/@/vben//shadcn-ui';
import {
FullContent,
HeaderMixedNav,
HeaderNav,
HeaderSidebarNav,
MixedNav,
SidebarMixedNav,
SidebarNav,
FullContent,
HeaderMixedNav,
HeaderNav,
HeaderSidebarNav,
MixedNav,
SidebarMixedNav,
SidebarNav,
} from '../../icons';
interface PresetItem {
name: string;
tip: string;
type: LayoutType;
name: string;
tip: string;
type: LayoutType;
}
defineOptions({
name: 'PreferenceLayout',
name: 'PreferenceLayout',
});
const modelValue = defineModel<LayoutType>({ default: 'sidebar-nav' });
const components: Record<LayoutType, Component> = {
'full-content': FullContent,
'header-nav': HeaderNav,
'mixed-nav': MixedNav,
'sidebar-mixed-nav': SidebarMixedNav,
'sidebar-nav': SidebarNav,
'header-mixed-nav': HeaderMixedNav,
'header-sidebar-nav': HeaderSidebarNav,
'full-content': FullContent,
'header-nav': HeaderNav,
'mixed-nav': MixedNav,
'sidebar-mixed-nav': SidebarMixedNav,
'sidebar-nav': SidebarNav,
'header-mixed-nav': HeaderMixedNav,
'header-sidebar-nav': HeaderSidebarNav,
};
const PRESET = computed((): PresetItem[] => [
{
name: $t('preferences.vertical'),
tip: $t('preferences.verticalTip'),
type: 'sidebar-nav',
},
{
name: $t('preferences.twoColumn'),
tip: $t('preferences.twoColumnTip'),
type: 'sidebar-mixed-nav',
},
{
name: $t('preferences.horizontal'),
tip: $t('preferences.horizontalTip'),
type: 'header-nav',
},
{
name: $t('preferences.headerSidebarNav'),
tip: $t('preferences.headerSidebarNavTip'),
type: 'header-sidebar-nav',
},
{
name: $t('preferences.mixedMenu'),
tip: $t('preferences.mixedMenuTip'),
type: 'mixed-nav',
},
{
name: $t('preferences.headerTwoColumn'),
tip: $t('preferences.headerTwoColumnTip'),
type: 'header-mixed-nav',
},
{
name: $t('preferences.fullContent'),
tip: $t('preferences.fullContentTip'),
type: 'full-content',
},
{
name: $t('preferences.vertical'),
tip: $t('preferences.verticalTip'),
type: 'sidebar-nav',
},
{
name: $t('preferences.twoColumn'),
tip: $t('preferences.twoColumnTip'),
type: 'sidebar-mixed-nav',
},
{
name: $t('preferences.horizontal'),
tip: $t('preferences.horizontalTip'),
type: 'header-nav',
},
{
name: $t('preferences.headerSidebarNav'),
tip: $t('preferences.headerSidebarNavTip'),
type: 'header-sidebar-nav',
},
{
name: $t('preferences.mixedMenu'),
tip: $t('preferences.mixedMenuTip'),
type: 'mixed-nav',
},
{
name: $t('preferences.headerTwoColumn'),
tip: $t('preferences.headerTwoColumnTip'),
type: 'header-mixed-nav',
},
{
name: $t('preferences.fullContent'),
tip: $t('preferences.fullContentTip'),
type: 'full-content',
},
]);
function activeClass(theme: string): string[] {
return theme === modelValue.value ? ['outline-box-active'] : [];
return theme === modelValue.value ? ['outline-box-active'] : [];
}
</script>
<template>
<div class="flex w-full flex-wrap gap-5">
<template v-for="theme in PRESET" :key="theme.name">
<div
class="flex w-[100px] cursor-pointer flex-col"
@click="modelValue = theme.type"
>
<div :class="activeClass(theme.type)" class="outline-box flex-center">
<component :is="components[theme.type]" />
</div>
<div
class="text-muted-foreground flex-center hover:text-foreground mt-2 text-center text-xs"
>
{{ theme.name }}
<VbenTooltip v-if="theme.tip" side="bottom">
<template #trigger>
<CircleHelp class="ml-1 size-3 cursor-help" />
</template>
{{ theme.tip }}
</VbenTooltip>
</div>
</div>
</template>
</div>
<div class="flex w-full flex-wrap gap-5">
<template v-for="theme in PRESET" :key="theme.name">
<div class="flex w-[100px] cursor-pointer flex-col" @click="modelValue = theme.type">
<div :class="activeClass(theme.type)" class="outline-box flex-center">
<component :is="components[theme.type]" />
</div>
<div class="text-muted-foreground flex-center hover:text-foreground mt-2 text-center text-xs">
{{ theme.name }}
<VbenTooltip v-if="theme.tip" side="bottom">
<template #trigger>
<CircleHelp class="ml-1 size-3 cursor-help" />
</template>
{{ theme.tip }}
</VbenTooltip>
</div>
</div>
</template>
</div>
</template>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { SelectOption } from '/@/vben/types';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import SwitchItem from '../switch-item.vue';
import ToggleItem from '../toggle-item.vue';

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { LayoutType } from '/@/vben/types';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import NumberFieldItem from '../number-field-item.vue';
import SwitchItem from '../switch-item.vue';

View File

@ -3,7 +3,7 @@ import type { SelectOption } from '/@/vben/types';
import { computed } from 'vue';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import NumberFieldItem from '../number-field-item.vue';
import SelectItem from '../select-item.vue';

View File

@ -3,7 +3,7 @@ import type { SelectOption } from '/@/vben/types';
import { computed } from 'vue';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue';

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { isWindowsOs } from '/@/vben/utils';
import SwitchItem from '../switch-item.vue';

View File

@ -5,7 +5,7 @@ import type { BuiltinThemeType } from '/@/vben/types';
import { computed, ref, watch } from 'vue';
import { UserRoundPen } from '/@/vben/icons';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import { BUILT_IN_THEME_PRESETS } from '/@/vben/preferences';
import { convertToHsl, TinyColor } from '/@/vben/utils';

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import SwitchItem from '../switch-item.vue';

View File

@ -4,7 +4,7 @@ import type { Component } from 'vue';
import type { ThemeModeType } from '/@/vben/types';
import { MoonStar, Sun, SunMoon } from '/@/vben/icons';
import { $t } from '/@/vben/locales';
import { $t } from '/@/locales';
import SwitchItem from '../switch-item.vue';

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { SupportedLanguagesType } from "/@/vben/locales";
import type { SupportedLanguagesType } from "/@/locales";
import type {
BreadcrumbStyleType,
BuiltinThemeType,
@ -17,7 +17,7 @@ import type { SegmentedItem } from "/@/vben//shadcn-ui";
import { computed, ref } from "vue";
import { Copy, RotateCw, X } from "/@/vben/icons";
import { $t, loadLocaleMessages } from "/@/vben/locales";
import { $t, loadLocaleMessages } from "/@/locales";
import { clearPreferencesCache, preferences, resetPreferences, usePreferences } from "/@/vben/preferences";
import { useVbenDrawer } from "/@/vben//popup-ui";

View File

@ -2,7 +2,7 @@
import { computed } from "vue";
import { Settings } from "/@/vben/icons";
import { $t, loadLocaleMessages } from "/@/vben/locales";
import { $t, loadLocaleMessages } from "/@/locales";
import { preferences, updatePreferences } from "/@/vben/preferences";
import { capitalizeFirstLetter } from "/@/vben/utils";

View File

@ -2,7 +2,7 @@
import type { ThemeModeType } from "/@/vben/types";
import { MoonStar, Sun, SunMoon } from "/@/vben/icons";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import { preferences, updatePreferences, usePreferences } from "/@/vben/preferences";
import { ToggleGroup, ToggleGroupItem, VbenTooltip } from "/@/vben//shadcn-ui";

View File

@ -7,7 +7,7 @@ import { computed, useTemplateRef, watch } from "vue";
import { useHoverToggle } from "/@/vben/hooks";
import { LockKeyhole, LogOut } from "/@/vben/icons";
import { $t } from "/@/vben/locales";
import { $t } from "/@/locales";
import { preferences, usePreferences } from "/@/vben/preferences";
import { useLockStore } from "/@/vben/stores";
import { isWindowsOs } from "/@/vben/utils";

View File

@ -1,128 +0,0 @@
import type { App } from "vue";
import type { Locale } from "vue-i18n";
import type { ImportLocaleFn, LoadMessageFn, LocaleSetupOptions, SupportedLanguagesType } from "./typing";
import { unref } from "vue";
import { createI18n } from "vue-i18n";
import { useSimpleLocale } from "/@/vben/composables";
const i18n = createI18n({
globalInjection: true,
legacy: false,
locale: "",
messages: {}
});
const modules = import.meta.glob("./langs/**/*.json");
const { setSimpleLocale } = useSimpleLocale();
const localesMap = loadLocalesMapFromDir(/\.\/langs\/([^/]+)\/(.*)\.json$/, modules);
let loadMessages: LoadMessageFn;
/**
* Load locale modules
* @param modules
*/
function loadLocalesMap(modules: Record<string, () => Promise<unknown>>) {
const localesMap: Record<Locale, ImportLocaleFn> = {};
for (const [path, loadLocale] of Object.entries(modules)) {
const key = path.match(/([\w-]*)\.(json)/)?.[1];
if (key) {
localesMap[key] = loadLocale as ImportLocaleFn;
}
}
return localesMap;
}
/**
* Load locale modules with directory structure
* @param regexp - Regular expression to match language and file names
* @param modules - The modules object containing paths and import functions
* @returns A map of locales to their corresponding import functions
*/
function loadLocalesMapFromDir(regexp: RegExp, modules: Record<string, () => Promise<unknown>>): Record<Locale, ImportLocaleFn> {
const localesRaw: Record<Locale, Record<string, () => Promise<unknown>>> = {};
const localesMap: Record<Locale, ImportLocaleFn> = {};
// Iterate over the modules to extract language and file names
for (const path in modules) {
const match = path.match(regexp);
if (match) {
const [_, locale, fileName] = match;
if (locale && fileName) {
if (!localesRaw[locale]) {
localesRaw[locale] = {};
}
if (modules[path]) {
localesRaw[locale][fileName] = modules[path];
}
}
}
}
// Convert raw locale data into async import functions
for (const [locale, files] of Object.entries(localesRaw)) {
localesMap[locale] = async () => {
const messages: Record<string, any> = {};
for (const [fileName, importFn] of Object.entries(files)) {
messages[fileName] = ((await importFn()) as any)?.default;
}
return { default: messages };
};
}
return localesMap;
}
/**
* Set i18n language
* @param locale
*/
function setI18nLanguage(locale: Locale) {
i18n.global.locale.value = locale;
document?.querySelector("html")?.setAttribute("lang", locale);
}
async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
const { defaultLocale = "zh-CN" } = options;
// app可以自行扩展一些第三方库和组件库的国际化
loadMessages = options.loadMessages || (async () => ({}));
app.use(i18n);
await loadLocaleMessages(defaultLocale);
// 在控制台打印警告
i18n.global.setMissingHandler((locale, key) => {
if (options.missingWarn && key.includes(".")) {
console.warn(`[intlify] Not found '${key}' key in '${locale}' locale messages.`);
}
});
}
/**
* Load locale messages
* @param lang
*/
async function loadLocaleMessages(lang: SupportedLanguagesType) {
if (unref(i18n.global.locale) === lang) {
return setI18nLanguage(lang);
}
setSimpleLocale(lang);
const message = await localesMap[lang]?.();
if (message?.default) {
i18n.global.setLocaleMessage(lang, message.default);
}
const mergeMessage = await loadMessages(lang);
i18n.global.mergeLocaleMessage(lang, mergeMessage);
return setI18nLanguage(lang);
}
export { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n };

View File

@ -1,56 +0,0 @@
{
"welcomeBack": "Welcome Back",
"pageTitle": "Plug-and-play Admin system",
"pageDesc": "Efficient, versatile frontend template",
"loginSuccess": "Login Successful",
"loginSuccessDesc": "Welcome Back",
"loginSubtitle": "Enter your account details to manage your projects",
"selectAccount": "Quick Select Account",
"username": "Username",
"password": "Password",
"usernameTip": "Please enter username",
"passwordErrorTip": "Password is incorrect",
"passwordTip": "Please enter password",
"verifyRequiredTip": "Please complete the verification first",
"rememberMe": "Remember Me",
"createAnAccount": "Create an Account",
"createAccount": "Create Account",
"alreadyHaveAccount": "Already have an account?",
"accountTip": "Don't have an account?",
"signUp": "Sign Up",
"signUpSubtitle": "Make managing your applications simple and fun",
"confirmPassword": "Confirm Password",
"confirmPasswordTip": "The passwords do not match",
"agree": "I agree to",
"privacyPolicy": "Privacy-policy",
"terms": "Terms",
"agreeTip": "Please agree to the Privacy Policy and Terms",
"goToLogin": "Login instead",
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols",
"forgetPassword": "Forget Password?",
"forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password",
"emailTip": "Please enter email",
"emailValidErrorTip": "The email format you entered is incorrect",
"sendResetLink": "Send Reset Link",
"email": "Email",
"qrcodeSubtitle": "Scan the QR code with your phone to login",
"qrcodePrompt": "Click 'Confirm' after scanning to complete login",
"qrcodeLogin": "QR Code Login",
"codeSubtitle": "Enter your phone number to start managing your project",
"code": "Security code",
"codeTip": "Security code required {0} characters",
"mobile": "Mobile",
"mobileLogin": "Mobile Login",
"mobileTip": "Please enter mobile number",
"mobileErrortip": "The phone number format is incorrect",
"sendCode": "Get Security code",
"sendText": "Resend in {0}s",
"thirdPartyLogin": "Or continue with",
"loginAgainTitle": "Please Log In Again",
"loginAgainSubTitle": "Your login session has expired. Please log in again to continue.",
"layout": {
"center": "Align Center",
"alignLeft": "Align Left",
"alignRight": "Align Right"
}
}

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

@ -1,186 +0,0 @@
{
"title": "Preferences",
"subtitle": "Customize Preferences & Preview in Real Time",
"resetTip": "Data has changed, click to reset",
"resetTitle": "Reset Preferences",
"resetSuccess": "Preferences reset successfully",
"appearance": "Appearance",
"layout": "Layout",
"content": "Content",
"other": "Other",
"wide": "Wide",
"compact": "Fixed",
"followSystem": "Follow System",
"vertical": "Vertical",
"verticalTip": "Side vertical menu mode",
"horizontal": "Horizontal",
"horizontalTip": "Horizontal menu mode, all menus displayed at the top",
"twoColumn": "Two Column",
"twoColumnTip": "Vertical Two Column Menu Mode",
"headerSidebarNav": "Header Vertical",
"headerSidebarNavTip": "Header Full Width, Sidebar Navigation Mode",
"headerTwoColumn": "Header Two Column",
"headerTwoColumnTip": "Header Navigation & Sidebar Two Column co-exists",
"mixedMenu": "Mixed Menu",
"mixedMenuTip": "Vertical & Horizontal Menu Co-exists",
"fullContent": "Full Content",
"fullContentTip": "Only display content body, hide all menus",
"normal": "Normal",
"plain": "Plain",
"rounded": "Rounded",
"copyPreferences": "Copy Preferences",
"copyPreferencesSuccessTitle": "Copy successful",
"copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
"clearAndLogout": "Clear Cache & Logout",
"mode": "Mode",
"general": "General",
"language": "Language",
"dynamicTitle": "Dynamic Title",
"watermark": "Watermark",
"checkUpdates": "Periodic update check",
"position": {
"title": "Preferences Postion",
"header": "Header",
"auto": "Auto",
"fixed": "Fixed"
},
"sidebar": {
"title": "Sidebar",
"width": "Width",
"visible": "Show Sidebar",
"collapsed": "Collpase Menu",
"collapsedShowTitle": "Show Menu Title",
"autoActivateChild": "Auto Activate SubMenu",
"autoActivateChildTip": "`Enabled` to automatically activate the submenu while click menu.",
"expandOnHover": "Expand On Hover",
"expandOnHoverTip": "When the mouse hovers over menu, \n `Enabled` to expand children menus \n `Disabled` to expand whole sidebar."
},
"tabbar": {
"title": "Tabbar",
"enable": "Enable Tab Bar",
"icon": "Show Tabbar Icon",
"showMore": "Show More Button",
"showMaximize": "Show Maximize Button",
"persist": "Persist Tabs",
"maxCount": "Max Count of Tabs",
"maxCountTip": "When the number of tabs exceeds the maximum,\nthe oldest tab will be closed.\n Set to 0 to disable count checking.",
"draggable": "Enable Draggable Sort",
"wheelable": "Support Mouse Wheel",
"middleClickClose": "Close Tab when Mouse Middle Button Click",
"wheelableTip": "When enabled, the Tabbar area responds to vertical scrolling events of the scroll wheel.",
"styleType": {
"title": "Tabs Style",
"chrome": "Chrome",
"card": "Card",
"plain": "Plain",
"brisk": "Brisk"
},
"contextMenu": {
"reload": "Reload",
"close": "Close",
"pin": "Pin",
"unpin": "Unpin",
"closeLeft": "Close Left Tabs",
"closeRight": "Close Right Tabs",
"closeOther": "Close Other Tabs",
"closeAll": "Close All Tabs",
"openInNewWindow": "Open in New Window",
"maximize": "Maximize",
"restoreMaximize": "Restore"
}
},
"navigationMenu": {
"title": "Navigation Menu",
"style": "Navigation Menu Style",
"accordion": "Sidebar Accordion Menu",
"split": "Navigation Menu Separation",
"splitTip": "When enabled, the sidebar displays the top bar's submenu"
},
"breadcrumb": {
"title": "Breadcrumb",
"home": "Show Home Button",
"enable": "Enable Breadcrumb",
"icon": "Show Breadcrumb Icon",
"background": "background",
"style": "Breadcrumb Style",
"hideOnlyOne": "Hidden when only one"
},
"animation": {
"title": "Animation",
"loading": "Page Loading",
"transition": "Page Transition",
"progress": "Page Progress"
},
"theme": {
"title": "Theme",
"radius": "Radius",
"light": "Light",
"dark": "Dark",
"darkSidebar": "Semi Dark Sidebar",
"darkHeader": "Semi Dark Header",
"weakMode": "Weak Mode",
"grayMode": "Gray Mode",
"builtin": {
"title": "Built-in",
"default": "Default",
"violet": "Violet",
"pink": "Pink",
"rose": "Rose",
"skyBlue": "Sky Blue",
"deepBlue": "Deep Blue",
"green": "Green",
"deepGreen": "Deep Green",
"orange": "Orange",
"yellow": "Yellow",
"zinc": "Zinc",
"neutral": "Neutral",
"slate": "Slate",
"gray": "Gray",
"custom": "Custom"
}
},
"header": {
"title": "Header",
"visible": "Show Header",
"modeStatic": "Static",
"modeFixed": "Fixed",
"modeAuto": "Auto hide & Show",
"modeAutoScroll": "Scroll to Hide & Show",
"menuAlign": "Menu Align",
"menuAlignStart": "Start",
"menuAlignEnd": "End",
"menuAlignCenter": "Center"
},
"footer": {
"title": "Footer",
"visible": "Show Footer",
"fixed": "Fixed at Bottom"
},
"copyright": {
"title": "Copyright",
"enable": "Enable Copyright",
"companyName": "Company Name",
"companySiteLink": "Company Site Link",
"date": "Date",
"icp": "ICP License Number",
"icpLink": "ICP Site Link"
},
"shortcutKeys": {
"title": "Shortcut Keys",
"global": "Global",
"search": "Global Search",
"logout": "Logout",
"preferences": "Preferences"
},
"widget": {
"title": "Widget",
"globalSearch": "Enable Global Search",
"fullscreen": "Enable Fullscreen",
"themeToggle": "Enable Theme Toggle",
"languageToggle": "Enable Language Toggle",
"notification": "Enable Notification",
"sidebarToggle": "Enable Sidebar Toggle",
"lockScreen": "Enable Lock Screen",
"refresh": "Enable Refresh"
}
}

View File

@ -1,104 +0,0 @@
{
"formRules": {
"required": "Please enter {0}",
"selectRequired": "Please select {0}",
"minLength": "{0} must be at least {1} characters",
"maxLength": "{0} can be at most {1} characters",
"length": "{0} must be {1} characters long",
"alreadyExists": "{0} `{1}` already exists",
"startWith": "{0} must start with `{1}`",
"invalidURL": "Please input a valid URL"
},
"actionTitle": {
"edit": "Modify {0}",
"create": "Create {0}",
"delete": "Delete {0}",
"view": "View {0}"
},
"actionMessage": {
"deleteConfirm": "Are you sure to delete {0}?",
"deleting": "Deleting {0} ...",
"deleteSuccess": "{0} deleted successfully",
"operationSuccess": "Operation succeeded",
"operationFailed": "Operation failed"
},
"placeholder": {
"input": "Please enter",
"select": "Please select"
},
"captcha": {
"title": "Please complete the security verification",
"sliderSuccessText": "Passed",
"sliderDefaultText": "Slider and drag",
"alt": "Supports img tag src attribute value",
"sliderRotateDefaultTip": "Click picture to refresh",
"sliderRotateFailTip": "Validation failed",
"sliderRotateSuccessTip": "Validation successful, time {0} seconds",
"refreshAriaLabel": "Refresh captcha",
"confirmAriaLabel": "Confirm selection",
"confirm": "Confirm",
"pointAriaLabel": "Click point",
"clickInOrder": "Please click in order"
},
"iconPicker": {
"placeholder": "Select an icon",
"search": "Search icon..."
},
"jsonViewer": {
"copy": "Copy",
"copied": "Copied"
},
"fallback": {
"pageNotFound": "Oops! Page Not Found",
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
"forbidden": "Oops! Access Denied",
"forbiddenDesc": "Sorry, but you don't have permission to access this page.",
"internalError": "Oops! Something Went Wrong",
"internalErrorDesc": "Sorry, but the server encountered an error.",
"offline": "Offline Page",
"offlineError": "Oops! Network Error",
"offlineErrorDesc": "Sorry, can't connect to the internet. Check your connection.",
"comingSoon": "Coming Soon",
"http": {
"requestTimeout": "The request timed out. Please try again later.",
"networkError": "A network error occurred. Please check your internet connection and try again.",
"badRequest": "Bad Request. Please check your input and try again.",
"unauthorized": "Unauthorized. Please log in to continue.",
"forbidden": "Forbidden. You do not have permission to access this resource.",
"notFound": "Not Found. The requested resource could not be found.",
"internalServerError": "Internal Server Error. Something went wrong on our end. Please try again later."
}
},
"widgets": {
"document": "Document",
"qa": "Q&A",
"setting": "Settings",
"logoutTip": "Do you want to logout?",
"viewAll": "View All Messages",
"notifications": "Notifications",
"markAllAsRead": "Make All as Read",
"clearNotifications": "Clear",
"checkUpdatesTitle": "New Version Available",
"checkUpdatesDescription": "Click to refresh and get the latest version",
"search": {
"title": "Search",
"searchNavigate": "Search Navigation",
"select": "Select",
"navigate": "Navigate",
"close": "Close",
"noResults": "No Search Results Found",
"noRecent": "No Search History",
"recent": "Search History"
},
"lockScreen": {
"title": "Lock Screen",
"screenButton": "Locking",
"password": "Password",
"placeholder": "Please enter password",
"unlock": "Click to unlock",
"errorPasswordTip": "Password error, please re-enter",
"backToLogin": "Back to login",
"entry": "Enter the system"
}
}
}

View File

@ -1,56 +0,0 @@
{
"welcomeBack": "欢迎回来",
"pageTitle": "开箱即用的大型中后台管理系统",
"pageDesc": "工程化、高性能、跨组件库的前端模版",
"loginSuccess": "登录成功",
"loginSuccessDesc": "欢迎回来",
"loginSubtitle": "请输入您的帐户信息以开始管理您的项目",
"selectAccount": "快速选择账号",
"username": "账号",
"password": "密码",
"usernameTip": "请输入用户名",
"passwordTip": "请输入密码",
"verifyRequiredTip": "请先完成验证",
"passwordErrorTip": "密码错误",
"rememberMe": "记住账号",
"createAnAccount": "创建一个账号",
"createAccount": "创建账号",
"alreadyHaveAccount": "已经有账号了?",
"accountTip": "还没有账号?",
"signUp": "注册",
"signUpSubtitle": "让您的应用程序管理变得简单而有趣",
"confirmPassword": "确认密码",
"confirmPasswordTip": "两次输入的密码不一致",
"agree": "我同意",
"privacyPolicy": "隐私政策",
"terms": "条款",
"agreeTip": "请同意隐私政策和条款",
"goToLogin": "去登录",
"passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号",
"forgetPassword": "忘记密码?",
"forgetPasswordSubtitle": "输入您的电子邮件,我们将向您发送重置密码的连接",
"emailTip": "请输入邮箱",
"emailValidErrorTip": "你输入的邮箱格式不正确",
"sendResetLink": "发送重置链接",
"email": "邮箱",
"qrcodeSubtitle": "请用手机扫描二维码登录",
"qrcodePrompt": "扫码后点击 '确认',即可完成登录",
"qrcodeLogin": "扫码登录",
"codeSubtitle": "请输入您的手机号码以开始管理您的项目",
"code": "验证码",
"codeTip": "请输入{0}位验证码",
"mobile": "手机号码",
"mobileTip": "请输入手机号",
"mobileErrortip": "手机号码格式错误",
"mobileLogin": "手机号登录",
"sendCode": "获取验证码",
"sendText": "{0}秒后重新获取",
"thirdPartyLogin": "其他登录方式",
"loginAgainTitle": "重新登录",
"loginAgainSubTitle": "您的登录状态已过期,请重新登录以继续。",
"layout": {
"center": "居中",
"alignLeft": "居左",
"alignRight": "居右"
}
}

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

@ -1,186 +0,0 @@
{
"title": "偏好设置",
"subtitle": "自定义偏好设置 & 实时预览",
"resetTitle": "重置偏好设置",
"resetTip": "数据有变化,点击可进行重置",
"resetSuccess": "重置偏好设置成功",
"appearance": "外观",
"layout": "布局",
"content": "内容",
"other": "其它",
"wide": "流式",
"compact": "定宽",
"followSystem": "跟随系统",
"vertical": "垂直",
"verticalTip": "侧边垂直菜单模式",
"horizontal": "水平",
"horizontalTip": "水平菜单模式,菜单全部显示在顶部",
"twoColumn": "双列菜单",
"twoColumnTip": "垂直双列菜单模式",
"headerSidebarNav": "侧边导航",
"headerSidebarNavTip": "顶部通栏,侧边导航模式",
"headerTwoColumn": "混合双列",
"headerTwoColumnTip": "双列、水平菜单共存模式",
"mixedMenu": "混合垂直",
"mixedMenuTip": "垂直水平菜单共存",
"fullContent": "内容全屏",
"fullContentTip": "不显示任何菜单,只显示内容主体",
"normal": "常规",
"plain": "朴素",
"rounded": "圆润",
"copyPreferences": "复制偏好设置",
"copyPreferencesSuccessTitle": "复制成功",
"copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
"clearAndLogout": "清空缓存 & 退出登录",
"mode": "模式",
"general": "通用",
"language": "语言",
"dynamicTitle": "动态标题",
"watermark": "水印",
"checkUpdates": "定时检查更新",
"position": {
"title": "偏好设置位置",
"header": "顶栏",
"auto": "自动",
"fixed": "固定"
},
"sidebar": {
"title": "侧边栏",
"width": "宽度",
"visible": "显示侧边栏",
"collapsed": "折叠菜单",
"collapsedShowTitle": "折叠显示菜单名",
"autoActivateChild": "自动激活子菜单",
"autoActivateChildTip": "点击顶层菜单时,自动激活第一个子菜单或者上一次激活的子菜单",
"expandOnHover": "鼠标悬停展开",
"expandOnHoverTip": "鼠标在折叠区域悬浮时,`启用`则展开当前子菜单,`禁用`则展开整个侧边栏"
},
"tabbar": {
"title": "标签栏",
"enable": "启用标签栏",
"icon": "显示标签栏图标",
"showMore": "显示更多按钮",
"showMaximize": "显示最大化按钮",
"persist": "持久化标签页",
"maxCount": "最大标签数",
"maxCountTip": "每次打开新的标签时如果超过最大标签数,\n会自动关闭一个最先打开的标签\n设置为 0 则不限制",
"draggable": "启动拖拽排序",
"wheelable": "启用纵向滚轮响应",
"middleClickClose": "点击鼠标中键关闭标签页",
"wheelableTip": "开启后,标签栏区域可以响应滚轮的纵向滚动事件。\n关闭时只能响应系统的横向滚动事件需要按下Shift再滚动滚轮",
"styleType": {
"title": "标签页风格",
"chrome": "谷歌",
"card": "卡片",
"plain": "朴素",
"brisk": "轻快"
},
"contextMenu": {
"reload": "重新加载",
"close": "关闭",
"pin": "固定",
"unpin": "取消固定",
"closeLeft": "关闭左侧标签页",
"closeRight": "关闭右侧标签页",
"closeOther": "关闭其它标签页",
"closeAll": "关闭全部标签页",
"openInNewWindow": "在新窗口打开",
"maximize": "最大化",
"restoreMaximize": "还原"
}
},
"navigationMenu": {
"title": "导航菜单",
"style": "导航菜单风格",
"accordion": "侧边导航菜单手风琴模式",
"split": "导航菜单分离",
"splitTip": "开启时,侧边栏显示顶栏对应菜单的子菜单"
},
"breadcrumb": {
"title": "面包屑导航",
"enable": "开启面包屑导航",
"icon": "显示面包屑图标",
"home": "显示首页按钮",
"style": "面包屑风格",
"hideOnlyOne": "仅有一个时隐藏",
"background": "背景"
},
"animation": {
"title": "动画",
"loading": "页面切换 Loading",
"transition": "页面切换动画",
"progress": "页面切换进度条"
},
"theme": {
"title": "主题",
"radius": "圆角",
"light": "浅色",
"dark": "深色",
"darkSidebar": "深色侧边栏",
"darkHeader": "深色顶栏",
"weakMode": "色弱模式",
"grayMode": "灰色模式",
"builtin": {
"title": "内置主题",
"default": "默认",
"violet": "紫罗兰",
"pink": "樱花粉",
"rose": "玫瑰红",
"skyBlue": "天蓝色",
"deepBlue": "深蓝色",
"green": "浅绿色",
"deepGreen": "深绿色",
"orange": "橙黄色",
"yellow": "柠檬黄",
"zinc": "锌色灰",
"neutral": "中性色",
"slate": "石板灰",
"gray": "中灰色",
"custom": "自定义"
}
},
"header": {
"title": "顶栏",
"modeStatic": "静止",
"modeFixed": "固定",
"modeAuto": "自动隐藏和显示",
"modeAutoScroll": "滚动隐藏和显示",
"visible": "显示顶栏",
"menuAlign": "菜单位置",
"menuAlignStart": "左侧",
"menuAlignEnd": "右侧",
"menuAlignCenter": "居中"
},
"footer": {
"title": "底栏",
"visible": "显示底栏",
"fixed": "固定在底部"
},
"copyright": {
"title": "版权",
"enable": "启用版权",
"companyName": "公司名",
"companySiteLink": "公司主页",
"date": "日期",
"icp": "ICP 备案号",
"icpLink": "ICP 网站链接"
},
"shortcutKeys": {
"title": "快捷键",
"global": "全局",
"search": "全局搜索",
"logout": "退出登录",
"preferences": "偏好设置"
},
"widget": {
"title": "小部件",
"globalSearch": "启用全局搜索",
"fullscreen": "启用全屏",
"themeToggle": "启用主题切换",
"languageToggle": "启用语言切换",
"notification": "启用通知",
"sidebarToggle": "启用侧边栏切换",
"lockScreen": "启用锁屏",
"refresh": "启用刷新"
}
}

View File

@ -1,104 +0,0 @@
{
"formRules": {
"required": "请输入{0}",
"selectRequired": "请选择{0}",
"minLength": "{0}至少{1}个字符",
"maxLength": "{0}最多{1}个字符",
"length": "{0}长度必须为{1}个字符",
"alreadyExists": "{0} `{1}` 已存在",
"startWith": "{0}必须以 {1} 开头",
"invalidURL": "请输入有效的链接"
},
"actionTitle": {
"edit": "修改{0}",
"create": "新增{0}",
"delete": "删除{0}",
"view": "查看{0}"
},
"actionMessage": {
"deleteConfirm": "确定删除 {0} 吗?",
"deleting": "正在删除 {0} ...",
"deleteSuccess": "{0} 删除成功",
"operationSuccess": "操作成功",
"operationFailed": "操作失败"
},
"placeholder": {
"input": "请输入",
"select": "请选择"
},
"captcha": {
"title": "请完成安全验证",
"sliderSuccessText": "验证通过",
"sliderDefaultText": "请按住滑块拖动",
"sliderRotateDefaultTip": "点击图片可刷新",
"sliderRotateFailTip": "验证失败",
"sliderRotateSuccessTip": "验证成功,耗时{0}秒",
"alt": "支持img标签src属性值",
"refreshAriaLabel": "刷新验证码",
"confirmAriaLabel": "确认选择",
"confirm": "确认",
"pointAriaLabel": "点击点",
"clickInOrder": "请依次点击"
},
"iconPicker": {
"placeholder": "选择一个图标",
"search": "搜索图标..."
},
"jsonViewer": {
"copy": "复制",
"copied": "已复制"
},
"fallback": {
"pageNotFound": "哎呀!未找到页面",
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
"forbidden": "哎呀!访问被拒绝",
"forbiddenDesc": "抱歉,您没有权限访问此页面。",
"internalError": "哎呀!出错了",
"internalErrorDesc": "抱歉,服务器遇到错误。",
"offline": "离线页面",
"offlineError": "哎呀!网络错误",
"offlineErrorDesc": "抱歉,无法连接到互联网,请检查您的网络连接并重试。",
"comingSoon": "即将推出",
"http": {
"requestTimeout": "请求超时,请稍后再试。",
"networkError": "网络异常,请检查您的网络连接后重试。",
"badRequest": "请求错误。请检查您的输入并重试。",
"unauthorized": "登录认证过期,请重新登录后继续。",
"forbidden": "禁止访问, 您没有权限访问此资源。",
"notFound": "未找到, 请求的资源不存在。",
"internalServerError": "内部服务器错误,请稍后再试。"
}
},
"widgets": {
"document": "文档",
"qa": "问题 & 帮助",
"setting": "设置",
"logoutTip": "是否退出登录?",
"viewAll": "查看所有消息",
"notifications": "通知",
"markAllAsRead": "全部标记为已读",
"clearNotifications": "清空",
"checkUpdatesTitle": "新版本可用",
"checkUpdatesDescription": "点击刷新以获取最新版本",
"search": {
"title": "搜索",
"searchNavigate": "搜索导航菜单",
"select": "选择",
"navigate": "导航",
"close": "关闭",
"noResults": "未找到搜索结果",
"noRecent": "没有搜索历史",
"recent": "搜索历史"
},
"lockScreen": {
"title": "锁定屏幕",
"screenButton": "锁定",
"password": "密码",
"placeholder": "请输入锁屏密码",
"unlock": "点击解锁",
"errorPasswordTip": "密码错误,请重新输入",
"backToLogin": "返回登录",
"entry": "进入系统"
}
}
}

View File

@ -2,141 +2,143 @@
import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useI18n } from "vue-i18n";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { crudBinding } = crudExpose;
const { props, ctx, api } = context;
const lastResRef = ref();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await context.api.GetList(query);
};
const editRequest = async (req: EditReq) => {
const { form, row } = req;
form.id = row.id;
form.type = props.type;
const res = await context.api.UpdateObj(form);
lastResRef.value = res;
return res;
};
const delRequest = async (req: DelReq) => {
const { row } = req;
return await context.api.DelObj(row.id);
};
const { t } = useI18n();
const { crudBinding } = crudExpose;
const { props, ctx, api } = context;
const lastResRef = ref();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await context.api.GetList(query);
};
const editRequest = async (req: EditReq) => {
const { form, row } = req;
form.id = row.id;
form.type = props.type;
const res = await context.api.UpdateObj(form);
lastResRef.value = res;
return res;
};
const delRequest = async (req: DelReq) => {
const { row } = req;
return await context.api.DelObj(row.id);
};
const addRequest = async (req: AddReq) => {
const { form } = req;
form.type = props.type;
const res = await context.api.AddObj(form);
lastResRef.value = res;
return res;
};
const addRequest = async (req: AddReq) => {
const { form } = req;
form.type = props.type;
const res = await context.api.AddObj(form);
lastResRef.value = res;
return res;
};
const selectedRowKey = ref([props.modelValue]);
const selectedRowKey = ref([props.modelValue]);
const onSelectChange = (changed: any) => {
selectedRowKey.value = changed;
ctx.emit("update:modelValue", changed[0]);
};
const onSelectChange = (changed: any) => {
selectedRowKey.value = changed;
ctx.emit("update:modelValue", changed[0]);
};
const typeRef = ref("aliyun");
context.typeRef = typeRef;
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
commonColumnsDefine.type.form.component.disabled = true;
return {
typeRef,
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
toolbar: {
show: false,
},
search: {
show: false,
},
form: {
wrapper: {
width: "1050px",
},
},
rowHandle: {
width: 200,
},
table: {
scroll: {
x: 800,
},
rowSelection: {
type: "radio",
selectedRowKeys: selectedRowKey,
onChange: onSelectChange,
},
customRow: (record: any) => {
return {
onClick: () => {
onSelectChange([record.id]);
}, // 点击行
};
},
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50,
},
form: {
show: false,
},
},
name: {
title: "名称",
search: {
show: true,
},
type: ["text"],
form: {
rules: [{ required: true, message: "请填写名称" }],
helper: "随便填,当多个相同类型的授权时,便于区分",
},
column: {
width: 200,
},
},
from: {
title: "级别",
type: "dict-select",
dict: dict({
data: [
{ label: "系统", value: "sys" },
{ label: "用户", value: "user" },
],
}),
search: {
show: false,
},
form: {
show: false,
},
column: {
width: 100,
align: "center",
component: {
color: "auto",
},
order: 10,
},
valueBuilder: ({ row, key, value }) => {
row[key] = row.userId > 0 ? "user" : "sys";
},
},
...commonColumnsDefine,
},
},
};
const typeRef = ref("aliyun");
context.typeRef = typeRef;
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
commonColumnsDefine.type.form.component.disabled = true;
return {
typeRef,
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
toolbar: {
show: false,
},
search: {
show: false,
},
form: {
wrapper: {
width: "1050px",
},
},
rowHandle: {
width: 200,
},
table: {
scroll: {
x: 800,
},
rowSelection: {
type: "radio",
selectedRowKeys: selectedRowKey,
onChange: onSelectChange,
},
customRow: (record: any) => {
return {
onClick: () => {
onSelectChange([record.id]);
}, // 点击行
};
},
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 50,
},
form: {
show: false,
},
},
name: {
title: t("certd.name"),
search: {
show: true,
},
type: ["text"],
form: {
rules: [{ required: true, message: t("certd.pleaseEnterName") }],
helper: t("certd.nameHelper"),
},
column: {
width: 200,
},
},
from: {
title: t("certd.level"),
type: "dict-select",
dict: dict({
data: [
{ label: t("certd.system"), value: "sys" },
{ label: t("certd.usera"), value: "user" },
],
}),
search: {
show: false,
},
form: {
show: false,
},
column: {
width: 100,
align: "center",
component: {
color: "auto",
},
order: 10,
},
valueBuilder: ({ row, key, value }) => {
row[key] = row.userId > 0 ? "user" : "sys";
},
},
...commonColumnsDefine,
},
},
};
}

View File

@ -1,13 +1,13 @@
<template>
<fs-page>
<template #header>
<div class="title">
授权管理
<span class="sub">管理第三方系统授权信息</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
<fs-page>
<template #header>
<div class="title">
{{ t("certd.authorizationManagement") }}
<span class="sub">{{ t("certd.manageThirdPartyAuth") }}</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page>
</template>
<script lang="ts">
@ -15,25 +15,29 @@ import { defineComponent, onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createAccessApi } from "/@/views/certd/access/api";
import { useI18n } from "vue-i18n";
export default defineComponent({
name: "AccessManager",
setup() {
const api = createAccessApi("user");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
name: "AccessManager",
setup() {
const { t } = useI18n();
const api = createAccessApi("user");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
return {
crudBinding,
crudRef,
};
},
return {
crudBinding,
crudRef,
t
};
},
});
</script>

View File

@ -8,271 +8,272 @@ import { useSettingStore } from "/@/store/settings";
import { message } from "ant-design-vue";
import CnameTip from "/@/components/plugins/cert/domains-verify-plan-editor/cname-tip.vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
const res = await api.AddObj(form);
return res;
};
const addRequest = async ({ form }: AddReq) => {
const res = await api.AddObj(form);
return res;
};
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
const dictRef = dict({
data: [
{ label: "待设置CNAME", value: "cname", color: "warning" },
{ label: "验证中", value: "validating", color: "blue" },
{ label: "验证成功", value: "valid", color: "green" },
{ label: "验证失败", value: "failed", color: "red" },
{ label: "验证超时", value: "timeout", color: "red" },
],
});
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys,
},
},
},
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
tabs: {
name: "status",
show: true,
},
rowHandle: {
minWidth: 200,
fixed: "right",
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 80,
},
form: {
show: false,
},
},
domain: {
title: "被代理域名",
type: "text",
search: {
show: true,
},
editForm: {
component: {
disabled: true,
},
},
},
hostRecord: {
title: "主机记录",
type: "text",
form: {
show: false,
},
column: {
width: 250,
cellRender: ({ value }) => {
return <fs-copyable v-model={value} />;
},
},
},
recordValue: {
title: "请设置CNAME",
type: "copyable",
form: {
show: false,
},
column: {
width: 500,
},
},
cnameProviderId: {
title: "CNAME服务",
type: "dict-select",
dict: dict({
url: "/cname/provider/list",
value: "id",
label: "domain",
}),
form: {
component: {
onDictChange: ({ form, dict }: any) => {
if (!form.cnameProviderId) {
const list = dict.data.filter((item: any) => {
return !item.disabled;
});
let item = list.find((item: any) => item.isDefault);
if (!item && list.length > 0) {
item = list[0];
}
if (item) {
form.cnameProviderId = item.id;
}
}
},
renderLabel(item: any) {
if (item.title) {
return `${item.domain}<${item.title}>`;
} else {
return item.domain;
}
},
},
helper: {
render() {
const closeForm = () => {
crudExpose.getFormWrapperRef().close();
};
return (
<div>
CNAME
<router-link to={"/sys/cname/provider"} onClick={closeForm}>
CNAME
</router-link>
</div>
);
},
},
},
column: {
width: 120,
align: "center",
cellRender({ value }) {
if (value < 0) {
return <a-tag color={"green"}>CNAME</a-tag>;
} else {
return <a-tag color={"blue"}>CNAME</a-tag>;
}
},
},
},
status: {
title: "状态",
type: "dict-select",
dict: dictRef,
addForm: {
show: false,
},
column: {
width: 120,
align: "center",
cellRender({ value, row }) {
return (
<div class={"flex flex-center"}>
<fs-values-format modelValue={value} dict={dictRef}></fs-values-format>
{row.error && (
<a-tooltip title={row.error}>
<fs-icon class={"ml-5 color-red"} icon="ion:warning-outline"></fs-icon>
</a-tooltip>
)}
</div>
);
},
},
},
triggerValidate: {
title: "验证",
type: "text",
form: {
show: false,
},
column: {
conditionalRenderDisabled: true,
width: 130,
align: "center",
cellRender({ row, value }) {
if (row.status === "valid") {
return "-";
}
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
const dictRef = dict({
data: [
{ label: t('certd.pending_cname_setup'), value: "cname", color: "warning" },
{ label: t('certd.validating'), value: "validating", color: "blue" },
{ label: t('certd.validation_successful'), value: "valid", color: "green" },
{ label: t('certd.validation_failed'), value: "failed", color: "red" },
{ label: t('certd.validation_timed_out'), value: "timeout", color: "red" },
],
});
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys,
},
},
},
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
tabs: {
name: "status",
show: true,
},
rowHandle: {
minWidth: 200,
fixed: "right",
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 80,
},
form: {
show: false,
},
},
domain: {
title: t('certd.proxied_domain'),
type: "text",
search: {
show: true,
},
editForm: {
component: {
disabled: true,
},
},
},
hostRecord: {
title: t('certd.host_record'),
type: "text",
form: {
show: false,
},
column: {
width: 250,
cellRender: ({ value }) => {
return <fs-copyable v-model={value} />;
},
},
},
recordValue: {
title: t('certd.please_set_cname'),
type: "copyable",
form: {
show: false,
},
column: {
width: 500,
},
},
cnameProviderId: {
title: t('certd.cname_service'),
type: "dict-select",
dict: dict({
url: "/cname/provider/list",
value: "id",
label: "domain",
}),
form: {
component: {
onDictChange: ({ form, dict }: any) => {
if (!form.cnameProviderId) {
const list = dict.data.filter((item: any) => {
return !item.disabled;
});
let item = list.find((item: any) => item.isDefault);
if (!item && list.length > 0) {
item = list[0];
}
if (item) {
form.cnameProviderId = item.id;
}
}
},
renderLabel(item: any) {
if (item.title) {
return `${item.domain}<${item.title}>`;
} else {
return item.domain;
}
},
},
helper: {
render() {
const closeForm = () => {
crudExpose.getFormWrapperRef().close();
};
return (
<div>
{t('certd.default_public_cname')}
<router-link to={"/sys/cname/provider"} onClick={closeForm}>
{t('certd.customize_cname')}
</router-link>
</div>
);
},
},
},
column: {
width: 120,
align: "center",
cellRender({ value }) {
if (value < 0) {
return <a-tag color={"green"}>{t('certd.public_cname')}</a-tag>;
} else {
return <a-tag color={"blue"}>{t('certd.custom_cname')}</a-tag>;
}
},
},
},
status: {
title: t('certd.fields.status'),
type: "dict-select",
dict: dictRef,
addForm: {
show: false,
},
column: {
width: 120,
align: "center",
cellRender({ value, row }) {
return (
<div class={"flex flex-center"}>
<fs-values-format modelValue={value} dict={dictRef}></fs-values-format>
{row.error && (
<a-tooltip title={row.error}>
<fs-icon class={"ml-5 color-red"} icon="ion:warning-outline"></fs-icon>
</a-tooltip>
)}
</div>
);
},
},
},
triggerValidate: {
title: t('certd.validate'),
type: "text",
form: {
show: false,
},
column: {
conditionalRenderDisabled: true,
width: 130,
align: "center",
cellRender({ row, value }) {
if (row.status === "valid") {
return "-";
}
async function doVerify() {
row._validating_ = true;
try {
const res = await api.DoVerify(row.id);
if (res === true) {
message.success("验证成功");
row.status = "valid";
} else if (res === false) {
message.success("验证超时");
row.status = "timeout";
} else {
message.success("开始验证,请耐心等待");
}
await crudExpose.doRefresh();
} catch (e: any) {
console.error(e);
message.error(e.message);
} finally {
row._validating_ = false;
}
}
return (
<div>
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
</a-button>
<CnameTip record={row} />
</div>
);
},
},
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false,
},
column: {
show: true,
},
},
},
},
};
async function doVerify() {
row._validating_ = true;
try {
const res = await api.DoVerify(row.id);
if (res === true) {
message.success(t('certd.validation_successful'));
row.status = "valid";
} else if (res === false) {
message.success(t('certd.validation_timed_out'));
row.status = "timeout";
} else {
message.success(t('certd.validation_started'));
}
await crudExpose.doRefresh();
} catch (e: any) {
console.error(e);
message.error(e.message);
} finally {
row._validating_ = false;
}
}
return (
<div>
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
{t('certd.click_to_validate')}
</a-button>
<CnameTip record={row} />
</div>
);
},
},
},
createTime: {
title: t('certd.create_time'),
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: t('certd.update_time'),
type: "datetime",
form: {
show: false,
},
column: {
show: true,
},
},
},
},
};
}

View File

@ -1,59 +1,66 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">
CNAME记录管理
<span class="sub">
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">CNAME功能原理及使用说明</a>
</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
<fs-page class="page-cert">
<template #header>
<div class="title">
{{ t('certd.cnameRecord') }}
<span class="sub">
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">
{{ t('certd.cname_feature_guide') }}
</a>
</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip :title="t('certd.batch_delete')">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</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 { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
defineOptions({
name: "CnameRecord",
name: "CnameRecord",
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: "确认",
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error("请先勾选记录");
}
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: t('certd.confirm'),
content: t('certd.confirm_delete_count', { count: selectedRowKeys.value.length }),
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info(t('certd.delete_successful'));
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error(t('certd.please_select_records'));
}
};
//
onMounted(() => {
crudExpose.doRefresh();
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
await crudExpose.doRefresh();
});
</script>
<style lang="less"></style>

View File

@ -8,209 +8,209 @@ import { useSettingStore } from "/@/store/settings";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
const res = await api.AddObj(form);
return res;
};
const addRequest = async ({ form }: AddReq) => {
const res = await api.AddObj(form);
return res;
};
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys,
},
},
},
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
actionbar: {
buttons: {
add: {
show: false,
},
},
},
search: {
formItem: {
labelCol: {
style: {
// width: "100px"
},
},
wrapperCol: {
style: {
width: "50%",
},
},
},
},
rowHandle: {
minWidth: 200,
fixed: "right",
buttons: {
edit: {
show: false,
},
},
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100,
},
form: {
show: false,
},
},
userId: {
title: "用户Id",
type: "number",
search: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
}),
},
form: {
show: false,
},
column: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
}),
width: 100,
},
},
pipelineId: {
title: "流水线Id",
type: "number",
search: {
show: true,
},
form: {
show: false,
},
column: {
width: 100,
},
},
pipelineTitle: {
title: "流水线名称",
type: "text",
search: {
show: true,
},
column: {
width: 300,
tooltip: true,
ellipsis: true,
cellRender: ({ row, value }) => {
return <router-link to={{ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: false, historyId: row.id } }}>{value}</router-link>;
},
},
},
triggerType: {
title: "触发类型",
type: "dict-select",
search: {
show: true,
},
dict: dict({
data: [
{ value: "user", label: "手动执行" },
{ value: "timer", label: "定时执行" },
],
}),
form: {
show: false,
value: "custom",
},
column: {
sorter: true,
width: 90,
align: "center",
show: true,
component: {
color: "auto",
},
},
},
status: {
title: "状态",
type: "dict-select",
search: {
show: true,
},
dict: dict({
data: statusUtil.getOptions(),
}),
form: {
show: false,
},
column: {
sorter: true,
width: 120,
align: "center",
},
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false,
},
column: {
show: true,
},
},
},
},
};
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys,
},
},
},
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
actionbar: {
buttons: {
add: {
show: false,
},
},
},
search: {
formItem: {
labelCol: {
style: {
// width: "100px"
},
},
wrapperCol: {
style: {
width: "50%",
},
},
},
},
rowHandle: {
minWidth: 200,
fixed: "right",
buttons: {
edit: {
show: false,
},
},
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100,
},
form: {
show: false,
},
},
userId: {
title: t("certd.fields.userId"),
type: "number",
search: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
}),
},
form: {
show: false,
},
column: {
show: computed(() => {
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
}),
width: 100,
},
},
pipelineId: {
title: t("certd.fields.pipelineId"),
type: "number",
search: {
show: true,
},
form: {
show: false,
},
column: {
width: 100,
},
},
pipelineTitle: {
title: t('certd.fields.pipelineName'),
type: "text",
search: {
show: true,
},
column: {
width: 300,
tooltip: true,
ellipsis: true,
cellRender: ({ row, value }) => {
return <router-link to={{ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: false, historyId: row.id } }}>{value}</router-link>;
},
},
},
triggerType: {
title: t("certd.fields.triggerType"),
type: "dict-select",
search: {
show: true,
},
dict: dict({
data: [
{ value: "user", label: t("certd.triggerTypes.manual") },
{ value: "timer", label: t("certd.triggerTypes.timer") },
],
}),
form: {
show: false,
value: "custom",
},
column: {
sorter: true,
width: 90,
align: "center",
show: true,
component: {
color: "auto",
},
},
},
status: {
title: t("certd.fields.status"),
type: "dict-select",
search: {
show: true,
},
dict: dict({
data: statusUtil.getOptions(),
}),
form: {
show: false,
},
column: {
sorter: true,
width: 120,
align: "center",
},
},
createTime: {
title: t("certd.fields.createTime"),
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: t("certd.fields.updateTime"),
type: "datetime",
form: {
show: false,
},
column: {
show: true,
},
},
},
},
};
}

View File

@ -1,11 +1,11 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">流水线执行记录</div>
<div class="title">{{ t("certd.pipelineExecutionRecords") }}</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<a-tooltip :title="t('certd.batchDelete')">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
@ -13,12 +13,16 @@
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
defineOptions({
name: "PipelineHistory",
@ -29,17 +33,17 @@ const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: "确认",
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
title: t("certd.confirm"),
content: t("certd.confirmBatchDeleteContent", { count: selectedRowKeys.value.length }),
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info("删除成功");
message.info(t("certd.deleteSuccess"));
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error("请先勾选记录");
message.error(t("certd.pleaseSelectRecords"));
}
};

View File

@ -1,105 +1,114 @@
<template>
<a-button v-if="showButton" type="primary" @click="open"></a-button>
<a-button v-if="showButton" type="primary" @click="open">
{{ $t("authentication.changePasswordButton") }}
</a-button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
import * as api from "/@/views/certd/mine/api";
import { notification } from "ant-design-vue";
import { useUserStore } from "/@/store/user";
defineProps<{
showButton: boolean;
showButton: boolean;
}>();
let passwordFormRef = ref();
const validatePass1 = async (rule: any, value: any) => {
if (value === "") {
throw new Error("请输入密码");
}
const formData = passwordFormRef.value.getFormData();
if (formData.confirmNewPassword !== "") {
passwordFormRef.value.formRef.formRef.validateFields(["confirmNewPassword"]);
}
if (formData.password === formData.newPassword) {
throw new Error("新密码不能和旧密码相同");
}
if (value === "") {
throw new Error(t("authentication.enterPassword"));
}
const formData = passwordFormRef.value.getFormData();
if (formData.confirmNewPassword !== "") {
passwordFormRef.value.formRef.formRef.validateFields(["confirmNewPassword"]);
}
if (formData.password === formData.newPassword) {
throw new Error(t("authentication.newPasswordNotSameOld"));
}
};
const validatePass2 = async (rule: any, value: any) => {
if (value === "") {
throw new Error("请再次输入密码");
} else if (value !== passwordFormRef.value.getFormData().newPassword) {
throw new Error("两次输入密码不一致!");
}
if (value === "") {
throw new Error(t("authentication.enterPasswordAgain"));
} else if (value !== passwordFormRef.value.getFormData().newPassword) {
throw new Error(t("authentication.passwordsNotMatch"));
}
};
const userStore = useUserStore();
const { openDialog } = useFormWrapper();
const { buildFormOptions } = useColumns();
const passwordFormOptions: CrudOptions = {
form: {
col: {
span: 24,
},
wrapper: {
title: "修改密码",
width: "500px",
},
async doSubmit({ form }) {
await api.changePassword(form);
//
await userStore.loadUserInfo();
},
async afterSubmit() {
notification.success({ message: "修改成功" });
},
},
columns: {
password: {
title: "旧密码",
type: "password",
form: {
rules: [{ required: true, message: "请输入旧密码" }],
},
},
newPassword: {
title: "新密码",
type: "password",
form: {
rules: [
{ required: true, message: "请输入确认密码" },
//@ts-ignore
{ validator: validatePass1, trigger: "blur" },
],
},
},
confirmNewPassword: {
title: "确认新密码",
type: "password",
form: {
rules: [
{ required: true, message: "请输入确认密码" },
//@ts-ignore
{ validator: validatePass2, trigger: "blur" },
],
},
},
},
form: {
col: {
span: 24,
},
wrapper: {
title: t("authentication.title"),
width: "500px",
},
async doSubmit({ form }) {
await api.changePassword(form);
//
await userStore.loadUserInfo();
},
async afterSubmit() {
notification.success({ message: t("authentication.successMessage") });
},
},
columns: {
password: {
title: t("authentication.oldPassword"),
type: "password",
form: {
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
},
},
newPassword: {
title: t("authentication.newPassword"),
type: "password",
form: {
rules: [
{ required: true, message: t("authentication.newPasswordRequired") },
//@ts-ignore
{ validator: validatePass1, trigger: "blur" },
],
},
},
confirmNewPassword: {
title: t("authentication.confirmNewPassword"),
type: "password",
form: {
rules: [
{
required: true,
message: t("authentication.confirmNewPasswordRequired"),
},
//@ts-ignore
{ validator: validatePass2, trigger: "blur" },
],
},
},
},
};
async function open(opts: { password: "" }) {
const formOptions = buildFormOptions(passwordFormOptions);
formOptions.newInstance = true; //
passwordFormRef.value = await openDialog(formOptions);
passwordFormRef.value.setFormData({
password: opts.password,
});
console.log(passwordFormRef.value);
const formOptions = buildFormOptions(passwordFormOptions);
formOptions.newInstance = true; //
passwordFormRef.value = await openDialog(formOptions);
passwordFormRef.value.setFormData({
password: opts.password,
});
console.log(passwordFormRef.value);
}
const scope = ref({
open: open,
open: open,
});
defineExpose(scope.value);

View File

@ -1,73 +1,82 @@
<template>
<fs-page class="page-user-settings page-two-factor">
<template #header>
<div class="title">认证安全设置</div>
</template>
<div class="user-settings-form settings-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
<a-form-item label="2FA多重验证登录" :name="['authenticator', 'enabled']">
<div class="flex mt-5">
<a-switch v-model:checked="formState.authenticator.enabled" :disabled="!settingsStore.isPlus" @change="onAuthenticatorEnabledChanged" />
<fs-page class="page-user-settings page-two-factor">
<template #header>
<div class="title">{{ t("certd.securitySettings") }}</div>
</template>
<a-button
v-if="formState.authenticator.enabled && formState.authenticator.verified"
:disabled="authenticatorOpenRef || !settingsStore.isPlus"
size="small"
class="ml-5"
type="primary"
@click="authenticatorForm.open = true"
>
重新绑定
</a-button>
<div class="user-settings-form settings-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }"
autocomplete="off">
<a-form-item :label="t('certd.twoFactorAuth')" :name="['authenticator', 'enabled']">
<div class="flex mt-5">
<a-switch v-model:checked="formState.authenticator.enabled" :disabled="!settingsStore.isPlus"
@change="onAuthenticatorEnabledChanged" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<a-button v-if="formState.authenticator.enabled && formState.authenticator.verified"
:disabled="authenticatorOpenRef || !settingsStore.isPlus" size="small" class="ml-5"
type="primary" @click="authenticatorForm.open = true">
{{ t('certd.rebind') }}
</a-button>
<div class="helper">是否开启多重验证登录</div>
</a-form-item>
<a-form-item v-if="authenticatorOpenRef" label="绑定设备" class="authenticator-config">
<h3 class="font-bold m-5">1. 安装任意一款支持Authenticator的验证APP比如</h3>
<div class="ml-20">
<ul>
<li>
<a-tooltip title="如果报没有找到谷歌服务的错误您可以安装KK谷歌助手">
<a href="https://appgallery.huawei.com/app/C100262999" target="_blank"> Microsoft Authenticator</a>
</a-tooltip>
</li>
<li>
<a href="https://sj.qq.com/appdetail/com.tencent.authenticator" target="_blank">腾讯身份验证器</a>
</li>
<li>
<a href="https://www.synology.cn/zh-cn/dsm/feature/authentication" target="_blank">群晖身份验证器</a>
</li>
<li>
<a-tooltip title="如果报没有找到谷歌服务的错误您可以安装KK谷歌助手">
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" target="_blank">Google Authenticator</a>
</a-tooltip>
</li>
<li>
<a href="https://play.google.com/store/apps/details?id=com.authy.authy" target="_blank">Authy</a>
</li>
</ul>
</div>
<h3 class="font-bold m-10">2. 扫描二维码添加账号</h3>
<div v-if="authenticatorForm.qrcodeSrc" class="qrcode">
<div class="ml-20">
<img class="full-w" :src="authenticatorForm.qrcodeSrc" />
</div>
</div>
<h3 class="font-bold m-10">3. 输入验证码</h3>
<div class="ml-20">
<a-input v-model:value="authenticatorForm.verifyCode" placeholder="请输入验证码" />
</div>
<div class="ml-20 flex mt-10">
<loading-button type="primary" html-type="button" :click="doAuthenticatorSave">确认</loading-button>
<a-button class="ml-1" @click="authenticatorForm.open = false">取消</a-button>
</div>
</a-form-item>
</a-form>
</div>
</fs-page>
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">{{ t('certd.twoFactorAuthHelper') }}</div>
</a-form-item>
<a-form-item v-if="authenticatorOpenRef" :label="t('certd.bindDevice')" class="authenticator-config">
<h3 class="font-bold m-5">{{ t('certd.step1') }}</h3>
<div class="ml-20">
<ul>
<li>
<a-tooltip :title="t('certd.tooltipGoogleServiceError')">
<a href="https://appgallery.huawei.com/app/C100262999" target="_blank">Microsoft
Authenticator</a>
</a-tooltip>
</li>
<li>
<a href="https://sj.qq.com/appdetail/com.tencent.authenticator"
target="_blank">腾讯身份验证器</a>
</li>
<li>
<a href="https://www.synology.cn/zh-cn/dsm/feature/authentication"
target="_blank">群晖身份验证器</a>
</li>
<li>
<a-tooltip :title="t('certd.tooltipGoogleServiceError')">
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2"
target="_blank">Google Authenticator</a>
</a-tooltip>
</li>
<li>
<a href="https://play.google.com/store/apps/details?id=com.authy.authy"
target="_blank">Authy</a>
</li>
</ul>
</div>
<h3 class="font-bold m-10">{{ t('certd.step2') }}</h3>
<div v-if="authenticatorForm.qrcodeSrc" class="qrcode">
<div class="ml-20">
<img class="full-w" :src="authenticatorForm.qrcodeSrc" />
</div>
</div>
<h3 class="font-bold m-10">{{ t('certd.step3') }}</h3>
<div class="ml-20">
<a-input v-model:value="authenticatorForm.verifyCode"
:placeholder="t('certd.inputVerifyCode')" />
</div>
<div class="ml-20 flex mt-10">
<loading-button type="primary" html-type="button" :click="doAuthenticatorSave">{{
t('certd.confirm')
}}</loading-button>
<a-button class="ml-1" @click="authenticatorForm.open = false">{{ t('certd.cancel')
}}</a-button>
</div>
</a-form-item>
</a-form>
</div>
</fs-page>
</template>
<script setup lang="tsx">
@ -77,87 +86,92 @@ import { UserTwoFactorSetting } from "./api";
import { Modal, notification } from "ant-design-vue";
import { merge } from "lodash-es";
import { useSettingStore } from "/@/store/settings";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const settingsStore = useSettingStore();
defineOptions({
name: "UserSecurity",
name: "UserSecurity",
});
const formState = reactive<Partial<UserTwoFactorSetting>>({
authenticator: {
enabled: false,
verified: false,
},
authenticator: {
enabled: false,
verified: false,
},
});
const authenticatorForm = reactive({
qrcodeSrc: "",
verifyCode: "",
open: false,
qrcodeSrc: "",
verifyCode: "",
open: false,
});
const authenticatorOpenRef = computed(() => {
return formState.authenticator.enabled && (authenticatorForm.open || !formState.authenticator.verified);
return formState.authenticator.enabled && (authenticatorForm.open || !formState.authenticator.verified);
});
watch(
() => {
return authenticatorOpenRef.value;
},
async open => {
if (open) {
//base64
authenticatorForm.qrcodeSrc = await api.TwoFactorAuthenticatorGet();
} else {
authenticatorForm.qrcodeSrc = "";
authenticatorForm.verifyCode = "";
}
}
() => {
return authenticatorOpenRef.value;
},
async open => {
if (open) {
//base64
authenticatorForm.qrcodeSrc = await api.TwoFactorAuthenticatorGet();
} else {
authenticatorForm.qrcodeSrc = "";
authenticatorForm.verifyCode = "";
}
}
);
async function loadUserSettings() {
const data: any = await api.TwoFactorSettingsGet();
merge(formState, data);
const data: any = await api.TwoFactorSettingsGet();
merge(formState, data);
}
loadUserSettings();
const doAuthenticatorSave = async (form: any) => {
await api.TwoFactorAuthenticatorSave({
verifyCode: authenticatorForm.verifyCode,
});
notification.success({
message: "保存成功",
});
authenticatorForm.open = false;
formState.authenticator.verified = true;
await api.TwoFactorAuthenticatorSave({
verifyCode: authenticatorForm.verifyCode,
});
notification.success({
message: t("certd.saveSuccess"),
});
authenticatorForm.open = false;
formState.authenticator.verified = true;
};
function onAuthenticatorEnabledChanged(value: any) {
if (!value) {
//
if (formState.authenticator.verified) {
Modal.confirm({
title: "确认",
content: `确定要关闭多重验证登录吗?`,
async onOk() {
await api.TwoFactorAuthenticatorOff();
notification.success({
message: "关闭成功",
});
loadUserSettings();
},
onCancel() {
formState.authenticator.enabled = true;
},
});
}
}
if (!value) {
//
if (formState.authenticator.verified) {
Modal.confirm({
title: t("certd.confirm"),
content: t("certd.confirmDisable2FA"),
async onOk() {
await api.TwoFactorAuthenticatorOff();
notification.success({
message: t("certd.disabledSuccess"),
});
loadUserSettings();
},
onCancel() {
formState.authenticator.enabled = true;
},
});
}
}
}
</script>
<style lang="less">
.page-user-settings {
.user-settings-form {
width: 600px;
margin: 20px;
}
.user-settings-form {
width: 600px;
margin: 20px;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More