First translation step

pull/436/head
Lorenzo 2025-06-25 20:09:29 +02:00
parent f1a25b21a6
commit 3ab99647aa
90 changed files with 2827 additions and 2378 deletions

View File

@ -8,8 +8,6 @@
</template> </template>
<script lang="ts" setup> <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 { computed, onMounted, provide, ref } from "vue";
import "dayjs/locale/zh-cn"; import "dayjs/locale/zh-cn";
import "dayjs/locale/en"; import "dayjs/locale/en";
@ -27,26 +25,6 @@ defineOptions({
}); });
const [modal, contextHolder] = Modal.useModal(); const [modal, contextHolder] = Modal.useModal();
provide("modal", modal); 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(); const { isDark } = usePreferences();
@ -65,13 +43,5 @@ const tokenTheme = computed(() => {
token: tokens, token: tokens,
}; };
}); });
//
// const resourceStore = useResourceStore();
// resourceStore.init();
// const pageStore = usePageStore();
// pageStore.init();
// const settingStore = useSettingStore();
// settingStore.init();
</script> </script>

View File

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

View File

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

View File

@ -1,5 +1,9 @@
<template> <template>
<div v-if="!settingStore.isComm || userStore.isAdmin" class="layout-vip isPlus" @click="openUpgrade"> <div
v-if="!settingStore.isComm || userStore.isAdmin"
class="layout-vip isPlus"
@click="openUpgrade"
>
<contextHolder /> <contextHolder />
<fs-icon icon="mingcute:vip-1-line" :title="text.title" /> <fs-icon icon="mingcute:vip-1-line" :title="text.title" />
@ -20,6 +24,9 @@ import { useSettingStore } from "/@/store/settings";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { mitter } from "/@/utils/util.mitt"; import { mitter } from "/@/utils/util.mitt";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const props = withDefaults( const props = withDefaults(
@ -39,56 +46,56 @@ const text = computed<Text>(() => {
const map = { const map = {
isComm: { isComm: {
comm: { comm: {
name: `${vipLabel}已开通`, name: t("vip.comm.name", { vipLabel }),
title: "到期时间:" + expireTime.value, title: t("vip.comm.title", { expire: expireTime.value }),
}, },
button: { button: {
name: `${vipLabel}已开通`, name: t("vip.comm.name", { vipLabel }),
title: "到期时间:" + expireTime.value, title: t("vip.comm.title", { expire: expireTime.value }),
}, },
icon: { icon: {
name: "", name: "",
title: `${vipLabel}已开通`, title: t("vip.comm.name", { vipLabel }),
}, },
nav: { nav: {
name: `${vipLabel}`, name: t("vip.comm.nav", { vipLabel }),
title: "到期时间:" + expireTime.value, title: t("vip.comm.title", { expire: expireTime.value }),
}, },
}, },
isPlus: { isPlus: {
comm: { comm: {
name: "商业版功能", name: t("vip.plus.name"),
title: "升级商业版,获取商业授权", title: t("vip.plus.title"),
}, },
button: { button: {
name: `${vipLabel}已开通`, name: t("vip.comm.name", { vipLabel }),
title: "到期时间:" + expireTime.value, title: t("vip.comm.title", { expire: expireTime.value }),
}, },
icon: { icon: {
name: "", name: "",
title: `${vipLabel}已开通`, title: t("vip.comm.name", { vipLabel }),
}, },
nav: { nav: {
name: `${vipLabel}`, name: t("vip.comm.nav", { vipLabel }),
title: "到期时间:" + expireTime.value, title: t("vip.comm.title", { expire: expireTime.value }),
}, },
}, },
free: { free: {
comm: { comm: {
name: "商业版功能", name: t("vip.free.comm.name"),
title: "升级商业版,获取商业授权", title: t("vip.free.comm.title"),
}, },
button: { button: {
name: "专业版功能", name: t("vip.free.button.name"),
title: "升级专业版享受更多VIP特权", title: t("vip.free.button.title"),
}, },
icon: { icon: {
name: "", name: "",
title: "专业版功能", title: t("vip.free.button.name"),
}, },
nav: { nav: {
name: "基础版", name: t("vip.free.nav.name"),
title: "升级专业版享受更多VIP特权", title: t("vip.free.nav.title"),
}, },
}, },
}; };
@ -101,6 +108,7 @@ const text = computed<Text>(() => {
} }
}); });
const expireTime = computed(() => { const expireTime = computed(() => {
if (settingStore.isPlus) { if (settingStore.isPlus) {
return dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD"); return dayjs(settingStore.plusInfo.expireTime).format("YYYY-MM-DD");

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> <template>
<a-dropdown> <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> <template #overlay>
<a-menu> <a-menu>
<a-menu-item> <a-menu-item>
<div @click="goUserProfile"></div> <div @click="goUserProfile">{{ t('user.profile') }}</div>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<div @click="doLogout"></div> <div @click="doLogout">{{ t('user.logout') }}</div>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</template> </template>

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,80 @@
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!",
}

View File

@ -0,0 +1,153 @@
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",
},
};

View File

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

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,25 @@
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",
},
},
}

View File

@ -0,0 +1,81 @@
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: "两次输入密码不一致!",
}

View File

@ -0,0 +1,159 @@
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: "首页",
},
certd: {
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: "定时执行",
},
};

View File

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

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,25 @@
export default {
comm: {
name: "{vipLabel}已开通",
title: "到期时间:{expire}",
nav: "{vipLabel}",
},
plus: {
name: "商业版功能",
title: "升级商业版,获取商业授权",
},
free: {
comm: {
name: "商业版功能",
title: "升级商业版,获取商业授权",
},
button: {
name: "专业版功能",
title: "升级专业版享受更多VIP特权",
},
nav: {
name: "基础版",
title: "升级专业版享受更多VIP特权",
},
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import { computed, useAttrs } from 'vue';
// @ts-ignore // @ts-ignore
import VueJsonViewer from 'vue-json-viewer'; import VueJsonViewer from 'vue-json-viewer';
import { $t } from '/@/vben/locales'; import { $t } from '/@/locales';
import { isBoolean } from '/@/vben/shared/utils'; 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 { computed, reactive } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales'; import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui'; import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton } from '/@/vben/shadcn-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 { computed, reactive } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales'; import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui'; import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton } from '/@/vben/shadcn-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 { computed, onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales'; import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui'; import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton, VbenCheckbox } from '/@/vben/shadcn-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 { computed, reactive } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { $t } from '/@/vben/locales'; import { $t } from '/@/locales';
import { useVbenForm } from '/@/vben/form-ui'; import { useVbenForm } from '/@/vben/form-ui';
import { VbenButton } from '/@/vben/shadcn-ui'; import { VbenButton } from '/@/vben/shadcn-ui';

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import "./styles";
import "./styles/antd/index.css"; import "./styles/antd/index.css";
import { useTitle } from "@vueuse/core"; import { useTitle } from "@vueuse/core";
import { setupI18n } from "/@/vben/locales"; import { setupI18n } from "../locales";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
export async function setupVben(app: any, { loadMessages, router }: any) { 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 { computed, useSlots, watch } from "vue";
import { useRefresh } from "../../hooks"; import { useRefresh } from "../../hooks";
import { $t, i18n } from "../../locales"; import { $t, i18n } from "/@/locales";
import { preferences, updatePreferences, usePreferences } from "../../preferences"; import { preferences, updatePreferences, usePreferences } from "../../preferences";
import { useLockStore } from "../../stores"; import { useLockStore } from "../../stores";
import { cloneDeep, mapTree } from "../../utils"; import { cloneDeep, mapTree } from "../../utils";

View File

@ -9,7 +9,7 @@ import { useRoute, useRouter } from "vue-router";
import { useContentMaximize, useTabs } from "../../../hooks"; import { useContentMaximize, useTabs } from "../../../hooks";
import { ArrowLeftToLine, ArrowRightLeft, ArrowRightToLine, ExternalLink, FoldHorizontal, Fullscreen, Minimize2, Pin, PinOff, RotateCw, X } from "../../../icons"; 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 { useAccessStore, useTabbarStore } from "../../../stores";
import { filterTree } from "../../../utils"; import { filterTree } from "../../../utils";

View File

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

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue"; import { onMounted, onUnmounted, ref } from "vue";
import { $t } from "../../../locales"; import { $t } from "/@/locales";
import { useVbenModal } from "../../../popup-ui"; 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 { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { ArrowDown, ArrowUp, CornerDownLeft, MdiKeyboardEsc, Search } from "../../../icons"; import { ArrowDown, ArrowUp, CornerDownLeft, MdiKeyboardEsc, Search } from "../../../icons";
import { $t } from "../../../locales"; import { $t } from "/@/locales";
import { isWindowsOs } from "../../../utils"; import { isWindowsOs } from "../../../utils";
import { useVbenModal } from "../../../popup-ui"; 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 { useRouter } from "vue-router";
import { SearchX, X } from "../../../icons"; import { SearchX, X } from "../../../icons";
import { $t } from "../../../locales"; import { $t } from "/@/locales";
import { mapTree, traverseTreeValues, uniqueByField } from "../../../utils"; import { mapTree, traverseTreeValues, uniqueByField } from "../../../utils";
import { VbenIcon, VbenScrollbar } from "../../../shadcn-ui"; import { VbenIcon, VbenScrollbar } from "../../../shadcn-ui";

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SupportedLanguagesType } from "/@/vben/locales"; import type { SupportedLanguagesType } from "/@/locales";
import { SUPPORT_LANGUAGES } from "/@/vben/constants"; import { SUPPORT_LANGUAGES } from "/@/vben/constants";
import { Languages } from "/@/vben/icons"; import { Languages } from "/@/vben/icons";
import { loadLocaleMessages } from "/@/vben/locales"; import { loadLocaleMessages } from "/@/locales";
import { preferences, updatePreferences } from "/@/vben/preferences"; import { preferences, updatePreferences } from "/@/vben/preferences";
import { VbenDropdownRadioMenu, VbenIconButton } from "/@/vben//shadcn-ui"; 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 { computed } from "vue";
import { InspectionPanel, PanelLeft, PanelRight } from "/@/vben/icons"; import { InspectionPanel, PanelLeft, PanelRight } from "/@/vben/icons";
import { $t } from "/@/vben/locales"; import { $t } from "/@/locales";
import { preferences, updatePreferences, usePreferences } from "/@/vben/preferences"; import { preferences, updatePreferences, usePreferences } from "/@/vben/preferences";
import { VbenDropdownRadioMenu, VbenIconButton } from "/@/vben//shadcn-ui"; import { VbenDropdownRadioMenu, VbenIconButton } from "/@/vben//shadcn-ui";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { $t } from '/@/vben/locales'; import { $t } from '/@/locales';
import { isWindowsOs } from '/@/vben/utils'; import { isWindowsOs } from '/@/vben/utils';
import SwitchItem from '../switch-item.vue'; 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 { computed, ref, watch } from 'vue';
import { UserRoundPen } from '/@/vben/icons'; import { UserRoundPen } from '/@/vben/icons';
import { $t } from '/@/vben/locales'; import { $t } from '/@/locales';
import { BUILT_IN_THEME_PRESETS } from '/@/vben/preferences'; import { BUILT_IN_THEME_PRESETS } from '/@/vben/preferences';
import { convertToHsl, TinyColor } from '/@/vben/utils'; import { convertToHsl, TinyColor } from '/@/vben/utils';

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
import type { ThemeModeType } from "/@/vben/types"; import type { ThemeModeType } from "/@/vben/types";
import { MoonStar, Sun, SunMoon } from "/@/vben/icons"; import { MoonStar, Sun, SunMoon } from "/@/vben/icons";
import { $t } from "/@/vben/locales"; import { $t } from "/@/locales";
import { preferences, updatePreferences, usePreferences } from "/@/vben/preferences"; import { preferences, updatePreferences, usePreferences } from "/@/vben/preferences";
import { ToggleGroup, ToggleGroupItem, VbenTooltip } from "/@/vben//shadcn-ui"; 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 { useHoverToggle } from "/@/vben/hooks";
import { LockKeyhole, LogOut } from "/@/vben/icons"; import { LockKeyhole, LogOut } from "/@/vben/icons";
import { $t } from "/@/vben/locales"; import { $t } from "/@/locales";
import { preferences, usePreferences } from "/@/vben/preferences"; import { preferences, usePreferences } from "/@/vben/preferences";
import { useLockStore } from "/@/vben/stores"; import { useLockStore } from "/@/vben/stores";
import { isWindowsOs } from "/@/vben/utils"; 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,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,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

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
<template> <template>
<fs-page class="page-cert"> <fs-page class="page-cert">
<template #header> <template #header>
<div class="title">我的流水线</div> <div class="title">{{ t("certd.myPipelines") }}</div>
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> <fs-crud ref="crudRef" v-bind="crudBinding">
<div v-if="selectedRowKeys.length > 0" class="batch-actions"> <div v-if="selectedRowKeys.length > 0" class="batch-actions">
<div class="batch-actions-inner"> <div class="batch-actions-inner">
<span> 已选择 {{ selectedRowKeys.length }} </span> <span>{{ t("certd.selectedCount", { count: selectedRowKeys.length }) }}</span>
<fs-button icon="ion:trash-outline" class="color-red" type="link" text="批量删除" @click="batchDelete"></fs-button> <fs-button icon="ion:trash-outline" class="color-red" type="link" :text="t('certd.batchDelete')" @click="batchDelete"></fs-button>
<fs-button icon="icon-park-outline:replay-music" class="need-plus" type="link" text="强制重新运行" @click="batchRerun"></fs-button> <fs-button icon="icon-park-outline:replay-music" class="need-plus" type="link" :text="t('certd.batchForceRerun')" @click="batchRerun"></fs-button>
<change-group class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-group> <change-group class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-group>
<change-notification class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-notification> <change-notification class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-notification>
<change-trigger class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-trigger> <change-trigger class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-trigger>
@ -16,12 +16,13 @@
</div> </div>
<template #actionbar-right> </template> <template #actionbar-right> </template>
<template #form-bottom> <template #form-bottom>
<div>申请证书</div> <div>{{ t("certd.applyCertificate") }}</div>
</template> </template>
</fs-crud> </fs-crud>
</fs-page> </fs-page>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onActivated, onMounted, ref } from "vue"; import { onActivated, onMounted, ref } from "vue";
import { dict, useFs } from "@fast-crud/fast-crud"; import { dict, useFs } from "@fast-crud/fast-crud";
@ -31,6 +32,9 @@ import ChangeGroup from "./components/change-group.vue";
import ChangeTrigger from "./components/change-trigger.vue"; import ChangeTrigger from "./components/change-trigger.vue";
import { Modal, notification } from "ant-design-vue"; import { Modal, notification } from "ant-design-vue";
import * as api from "./api"; import * as api from "./api";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import ChangeNotification from "/@/views/certd/pipeline/components/change-notification.vue"; import ChangeNotification from "/@/views/certd/pipeline/components/change-notification.vue";
defineOptions({ defineOptions({

View File

@ -1,32 +1,46 @@
<template> <template>
<a-modal v-model:open="openRef" class="order-modal" title="订单确认" @ok="orderCreate"> <a-modal v-model:open="openRef" class="order-modal" :title="$t('order.confirmTitle')" @ok="orderCreate">
<div v-if="product" class="order-box"> <div v-if="product" class="order-box">
<div class="flex-o mt-5"><span class="label">套餐</span>{{ product.title }}</div>
<div class="flex-o mt-5"><span class="label">说明</span>{{ product.intro }}</div>
<div class="flex-o mt-5"> <div class="flex-o mt-5">
<span class="label">规格</span> <span class="label">{{$t('order.package')}}</span>{{ product.title }}
</div>
<div class="flex-o mt-5">
<span class="label">{{$t('order.description')}}</span>{{ product.intro }}
</div>
<div class="flex-o mt-5">
<span class="label">{{$t('order.specifications')}}</span>
<span class="flex-o flex-wrap"> <span class="flex-o flex-wrap">
<span class="flex-o">流水线<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" unit="条" /></span> <span class="flex-o">
<span class="flex-o">域名<suite-value class="ml-5" :model-value="product.content.maxDomainCount" unit="个" /></span> {{$t('order.pipeline')}}<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" unit="{{$t('order.unit.pieces')}}" />
<span class="flex-o">部署次数<suite-value class="ml-5" :model-value="product.content.maxDeployCount" unit="次" /></span> </span>
<span class="flex-o">
{{$t('order.domain')}}<suite-value class="ml-5" :model-value="product.content.maxDomainCount" unit="{{$t('order.unit.count')}}" />
</span>
<span class="flex-o">
{{$t('order.deployTimes')}}<suite-value class="ml-5" :model-value="product.content.maxDeployCount" unit="{{$t('order.unit.times')}}" />
</span>
</span> </span>
</div> </div>
<div class="flex-o mt-5"> <div class="flex-o mt-5">
<span class="label">时长</span> <span class="label">{{$t('order.duration')}}</span>
<duration-value v-model="formRef.duration"></duration-value> <duration-value v-model="formRef.duration"></duration-value>
</div> </div>
<div class="flex-o mt-5"><span class="label">价格</span> <price-input :edit="false" :model-value="durationSelected.price"></price-input></div> <div class="flex-o mt-5">
<span class="label">{{$t('order.price')}}</span>
<price-input :edit="false" :model-value="durationSelected.price"></price-input>
</div>
<div class="flex-o mt-5"> <div class="flex-o mt-5">
<span class="label">支付方式</span> <span class="label">{{$t('order.paymentMethod')}}</span>
<div v-if="durationSelected.price === 0"></div> <div v-if="durationSelected.price === 0">{{$t('order.free')}}</div>
<fs-dict-select v-else v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select> <fs-dict-select v-else v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select>
</div> </div>
</div> </div>
</a-modal> </a-modal>
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import { ref } from "vue"; import { ref } from "vue";
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api"; import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";

View File

@ -11,19 +11,21 @@
<div class="text"> <div class="text">
<div class="left"> <div class="left">
<div> <div>
<span>您好{{ userInfo.nickName || userInfo.username }} 欢迎使用 {{ siteInfo.title }}</span> <span>{{ t('certd.dashboard.greeting', { name: userInfo.nickName || userInfo.username, site: siteInfo.title }) }}</span>
</div> </div>
<div class="flex-o flex-wrap profile-badges"> <div class="flex-o flex-wrap profile-badges">
<a-tooltip :title="deltaTimeTip"> <a-tooltip :title="deltaTimeTip">
<a-badge :dot="deltaTimeWarning"> <a-badge :dot="deltaTimeWarning">
<a-tag :color="deltaTimeWarning ? 'red' : 'green'" class="flex-inline pointer"> <fs-icon icon="ion:time-outline"></fs-icon> {{ now }}</a-tag> <a-tag :color="deltaTimeWarning ? 'red' : 'green'" class="flex-inline pointer">
<fs-icon icon="ion:time-outline"></fs-icon> {{ now }}
</a-tag>
</a-badge> </a-badge>
</a-tooltip> </a-tooltip>
<template v-if="userStore.isAdmin"> <template v-if="userStore.isAdmin">
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-badge :dot="hasNewVersion"> <a-badge :dot="hasNewVersion">
<a-tag color="blue" class="flex-inline pointer mr-0" :title="'最新版本:' + latestVersion" @click="openUpgradeUrl()"> <a-tag color="blue" class="flex-inline pointer mr-0" :title="t('certd.dashboard.latestVersion', { version: latestVersion })" @click="openUpgradeUrl()">
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon> <fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
v{{ version }} v{{ version }}
</a-tag> </a-tag>
@ -37,7 +39,7 @@
</template> </template>
<template v-if="settingsStore.isPlus && settingsStore.sysPublic.userValidTimeEnabled === true && userInfo.validTime"> <template v-if="settingsStore.isPlus && settingsStore.sysPublic.userValidTimeEnabled === true && userInfo.validTime">
<a-divider type="vertical" /> <a-divider type="vertical" />
<valid-time-format class="flex-o" prefix="账户有效期:" :model-value="userInfo.validTime" /> <valid-time-format class="flex-o" :prefix="t('certd.dashboard.validUntil')" :model-value="userInfo.validTime" />
</template> </template>
</div> </div>
</div> </div>
@ -46,9 +48,9 @@
<div class="suggest hidden md:block"> <div class="suggest hidden md:block">
<tutorial-button class="flex-center mt-2"> <tutorial-button class="flex-center mt-2">
<a-tooltip title="点击查看详细教程"> <a-tooltip :title="t('certd.dashboard.tutorialTooltip')">
<a-tag color="blue" class="flex-center"> <a-tag color="blue" class="flex-center">
仅需3步全自动申请部署证书 {{ t('certd.dashboard.tutorialText') }}
<fs-icon class="font-size-16 ml-5" icon="mingcute:question-line"></fs-icon> <fs-icon class="font-size-16 ml-5" icon="mingcute:question-line"></fs-icon>
</a-tag> </a-tag>
</a-tooltip> </a-tooltip>
@ -59,9 +61,10 @@
<div v-if="!settingStore.isComm" class="warning"> <div v-if="!settingStore.isComm" class="warning">
<a-alert type="warning" show-icon> <a-alert type="warning" show-icon>
<template #message> <template #message>
证书和授权为敏感信息不要使用来历不明的在线Certd服务和镜像以免泄露请务必私有化部署使用认准官方版本发布渠道 {{ t('certd.dashboard.alertMessage') }}
<a class="ml-5 flex-inline" href="https://gitee.com/certd/certd" target="_blank">gitee</a> <a class="ml-5 flex-inline" href="https://github.com/certd/certd" target="_blank">github</a> <a class="ml-5 flex-inline" href="https://gitee.com/certd/certd" target="_blank">gitee</a>
<a class="ml-5 flex-inline" href="https://certd.docmirror.cn" target="_blank">帮助文档</a> <a class="ml-5 flex-inline" href="https://github.com/certd/certd" target="_blank">github</a>
<a class="ml-5 flex-inline" href="https://certd.docmirror.cn" target="_blank">{{ t('certd.dashboard.helpDoc') }}</a>
</template> </template>
</a-alert> </a-alert>
</div> </div>
@ -69,30 +72,32 @@
<div class="statistic-data m-20"> <div class="statistic-data m-20">
<a-row :gutter="20" class="flex-wrap"> <a-row :gutter="20" class="flex-wrap">
<a-col :md="6" :xs="24"> <a-col :md="6" :xs="24">
<statistic-card title="证书流水线数量" :count="count.pipelineCount"> <statistic-card :title="t('certd.dashboard.pipelineCount')" :count="count.pipelineCount">
<template v-if="count.pipelineCount === 0" #default> <template v-if="count.pipelineCount === 0" #default>
<div class="flex-center flex-1 flex-col"> <div class="flex-center flex-1 flex-col">
<div style="font-size: 18px; font-weight: 700">您还没有证书流水线</div> <div style="font-size: 18px; font-weight: 700">{{ t('certd.dashboard.noPipeline') }}</div>
<fs-button type="primary" class="mt-10" icon="ion:add-circle-outline" @click="goPipeline"></fs-button> <fs-button type="primary" class="mt-10" icon="ion:add-circle-outline" @click="goPipeline">{{ t('certd.dashboard.createNow') }}</fs-button>
</div> </div>
</template> </template>
<template #footer> <template #footer>
<router-link to="/certd/pipeline" class="flex"><fs-icon icon="ion:settings-outline" class="mr-5 fs-16" /> 管理流水线</router-link> <router-link to="/certd/pipeline" class="flex">
<fs-icon icon="ion:settings-outline" class="mr-5 fs-16" /> {{ t('certd.dashboard.managePipeline') }}
</router-link>
</template> </template>
</statistic-card> </statistic-card>
</a-col> </a-col>
<a-col :md="6" :xs="24"> <a-col :md="6" :xs="24">
<statistic-card title="流水线状态" :footer="false"> <statistic-card :title="t('certd.dashboard.pipelineStatus')" :footer="false">
<pie-count v-if="count.pipelineStatusCount" :data="count.pipelineStatusCount"></pie-count> <pie-count v-if="count.pipelineStatusCount" :data="count.pipelineStatusCount"></pie-count>
</statistic-card> </statistic-card>
</a-col> </a-col>
<a-col :md="6" :xs="24"> <a-col :md="6" :xs="24">
<statistic-card title="最近运行统计" :footer="false"> <statistic-card :title="t('certd.dashboard.recentRun')" :footer="false">
<day-count v-if="count.historyCountPerDay" :data="count.historyCountPerDay" title="运行次数"></day-count> <day-count v-if="count.historyCountPerDay" :data="count.historyCountPerDay" :title="t('certd.dashboard.runCount')"></day-count>
</statistic-card> </statistic-card>
</a-col> </a-col>
<a-col :md="6" :xs="24"> <a-col :md="6" :xs="24">
<statistic-card title="最快到期证书"> <statistic-card :title="t('certd.dashboard.expiringCerts')">
<expiring-list v-if="count.expiringList" :data="count.expiringList"></expiring-list> <expiring-list v-if="count.expiringList" :data="count.expiringList"></expiring-list>
</statistic-card> </statistic-card>
</a-col> </a-col>
@ -102,7 +107,8 @@
<div v-if="pluginGroups" class="plugin-list"> <div v-if="pluginGroups" class="plugin-list">
<a-card> <a-card>
<template #title> <template #title>
已支持的部署任务总览 <a-tag color="green">{{ pluginGroups.groups.all.plugins.length }}</a-tag> {{ t('certd.dashboard.supportedTasks') }}
<a-tag color="green">{{ pluginGroups.groups.all.plugins.length }}</a-tag>
</template> </template>
<a-row :gutter="10"> <a-row :gutter="10">
<a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :xl="4" :md="6" :xs="24"> <a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :xl="4" :md="6" :xs="24">
@ -130,6 +136,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { FsIcon } from "@fast-crud/fast-crud"; import { FsIcon } from "@fast-crud/fast-crud";
import SimpleSteps from "/@/components/tutorial/simple-steps.vue"; import SimpleSteps from "/@/components/tutorial/simple-steps.vue";
@ -148,6 +155,8 @@ import { UserInfoRes } from "/@/store/user/api.user";
import { GetStatisticCount } from "/@/views/framework/home/dashboard/api"; import { GetStatisticCount } from "/@/views/framework/home/dashboard/api";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import * as api from "./api"; import * as api from "./api";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import { usePluginStore } from "/@/store/plugin"; import { usePluginStore } from "/@/store/plugin";
defineOptions({ defineOptions({
name: "DashboardUser", name: "DashboardUser",

View File

@ -2,7 +2,10 @@
<fs-page class="home—index bg-neutral-100 dark:bg-black"> <fs-page class="home—index bg-neutral-100 dark:bg-black">
<!-- <page-content />--> <!-- <page-content />-->
<dashboard-user /> <dashboard-user />
<change-password-button ref="changePasswordButtonRef" :show-button="false"></change-password-button> <change-password-button
ref="changePasswordButtonRef"
:show-button="false"
></change-password-button>
</fs-page> </fs-page>
</template> </template>
@ -12,20 +15,23 @@ import { useUserStore } from "/@/store/user";
import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue"; import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const changePasswordButtonRef = ref(); const changePasswordButtonRef = ref();
onMounted(() => { onMounted(() => {
if (userStore.getUserInfo.isWeak === true) { if (userStore.getUserInfo.isWeak === true) {
Modal.info({ Modal.info({
title: "修改密码", title: t("passwordForm.title"),
content: "为了您的账户安全,请立即修改密码", content: t("passwordForm.weakPasswordWarning"),
onOk: () => { onOk: () => {
changePasswordButtonRef.value.open({ changePasswordButtonRef.value.open({
password: "123456", password: "123456",
}); });
}, },
okText: "立即修改", okText: t("passwordForm.changeNow"),
}); });
} }
}); });

View File

@ -1,74 +1,96 @@
<template> <template>
<div class="main login-page"> <div class="main login-page">
<a-form v-if="!twoFactor.loginId" ref="formRef" class="user-layout-login" name="custom-validation" :model="formState" v-bind="layout" @finish="handleFinish" @finish-failed="handleFinishFailed"> <a-form v-if="!twoFactor.loginId" ref="formRef" class="user-layout-login" name="custom-validation"
<!-- <div class="login-title">登录</div>--> :model="formState" v-bind="layout" @finish="handleFinish" @finish-failed="handleFinishFailed">
<a-tabs v-model:active-key="formState.loginType" :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }"> <!-- <div class="login-title">登录</div>-->
<a-tab-pane key="password" tab="密码登录" :disabled="sysPublicSettings.passwordLoginEnabled !== true"> <a-tabs v-model:active-key="formState.loginType"
<template v-if="formState.loginType === 'password'"> :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
<!-- <div class="login-title">登录</div>--> <a-tab-pane key="password" :tab="$t('authentication.passwordTab')"
<a-form-item required has-feedback name="username" :rules="rules.username"> :disabled="sysPublicSettings.passwordLoginEnabled !== true">
<a-input v-model:value="formState.username" placeholder="请输入用户名/邮箱/手机号" autocomplete="off"> <template v-if="formState.loginType === 'password'">
<template #prefix> <!-- <div class="login-title">登录</div>-->
<fs-icon icon="ion:phone-portrait-outline"></fs-icon> <a-form-item required has-feedback name="username" :rules="rules.username">
</template> <a-input v-model:value="formState.username"
</a-input> :placeholder="$t('authentication.usernamePlaceholder')" autocomplete="off">
</a-form-item> <template #prefix>
<a-form-item has-feedback name="password" :rules="rules.password"> <fs-icon icon="ion:phone-portrait-outline"></fs-icon>
<a-input-password v-model:value="formState.password" placeholder="请输入密码" autocomplete="off"> </template>
<template #prefix> </a-input>
<fs-icon icon="ion:lock-closed-outline"></fs-icon> </a-form-item>
</template> <a-form-item has-feedback name="password" :rules="rules.password">
</a-input-password> <a-input-password v-model:value="formState.password"
</a-form-item> :placeholder="$t('authentication.passwordPlaceholder')" autocomplete="off">
</template> <template #prefix>
</a-tab-pane> <fs-icon icon="ion:lock-closed-outline"></fs-icon>
<a-tab-pane key="sms" tab="短信验证码登录" :disabled="sysPublicSettings.smsLoginEnabled !== true"> </template>
<template v-if="formState.loginType === 'sms'"> </a-input-password>
<a-form-item has-feedback name="mobile" :rules="rules.mobile"> </a-form-item>
<a-input v-model:value="formState.mobile" placeholder="请输入手机号" autocomplete="off"> </template>
<template #prefix> </a-tab-pane>
<fs-icon icon="ion:phone-portrait-outline"></fs-icon> <a-tab-pane key="sms" :tab="$t('authentication.smsTab')"
</template> :disabled="sysPublicSettings.smsLoginEnabled !== true">
</a-input> <template v-if="formState.loginType === 'sms'">
</a-form-item> <a-form-item has-feedback name="mobile" :rules="rules.mobile">
<a-form-item has-feedback name="imgCode"> <a-input v-model:value="formState.mobile"
<image-code v-model:value="formState.imgCode" v-model:random-str="formState.randomStr"></image-code> :placeholder="$t('authentication.mobilePlaceholder')" autocomplete="off">
</a-form-item> <template #prefix>
<fs-icon icon="ion:phone-portrait-outline"></fs-icon>
</template>
</a-input>
</a-form-item>
<a-form-item name="smsCode" :rules="rules.smsCode"> <a-form-item has-feedback name="imgCode">
<sms-code v-model:value="formState.smsCode" :img-code="formState.imgCode" :mobile="formState.mobile" :phone-code="formState.phoneCode" :random-str="formState.randomStr" /> <image-code v-model:value="formState.imgCode"
</a-form-item> v-model:random-str="formState.randomStr"></image-code>
</template> </a-form-item>
</a-tab-pane>
</a-tabs>
<a-form-item>
<a-button type="primary" size="large" html-type="submit" :loading="loading" class="login-button">登录</a-button>
<div v-if="!settingStore.isComm" class="mt-2"><a href="https://certd.docmirror.cn/guide/use/forgotpasswd/" target="_blank"></a></div> <a-form-item name="smsCode" :rules="rules.smsCode">
</a-form-item> <sms-code v-model:value="formState.smsCode" :img-code="formState.imgCode"
:mobile="formState.mobile" :phone-code="formState.phoneCode"
:random-str="formState.randomStr" />
</a-form-item>
</template>
</a-tab-pane>
</a-tabs>
<a-form-item>
<a-button type="primary" size="large" html-type="submit" :loading="loading" class="login-button">
{{ $t('authentication.loginButton') }}
</a-button>
<a-form-item class="user-login-other"> <div v-if="!settingStore.isComm" class="mt-2">
<router-link v-if="hasRegisterTypeEnabled()" class="register" :to="{ name: 'register' }"> </router-link> <a href="https://certd.docmirror.cn/guide/use/forgotpasswd/" target="_blank">
</a-form-item> {{ $t('authentication.forgotAdminPassword') }}
</a-form> </a>
<a-form v-else ref="twoFactorFormRef" class="user-layout-login" :model="twoFactor" v-bind="layout"> </div>
<div class="mb-10 flex flex-center">请打开您的Authenticator APP获取动态验证码</div> </a-form-item>
<a-form-item name="verifyCode">
<a-input ref="verifyCodeInputRef" v-model:value="twoFactor.verifyCode" placeholder="请输入动态验证码" allow-clear @keydown.enter="handleTwoFactorSubmit">
<template #prefix>
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
</template>
</a-input>
</a-form-item>
<a-form-item>
<loading-button type="primary" size="large" html-type="button" class="login-button" :click="handleTwoFactorSubmit">OTP验证登录</loading-button>
</a-form-item>
<a-form-item class="user-login-other"> <a-form-item class="user-login-other">
<a class="register" @click="twoFactor.loginId = null"> 返回 </a> <router-link v-if="hasRegisterTypeEnabled()" class="register" :to="{ name: 'register' }">
</a-form-item> {{ $t('authentication.registerLink') }}
</a-form> </router-link>
</div> </a-form-item>
</a-form>
<a-form v-else ref="twoFactorFormRef" class="user-layout-login" :model="twoFactor" v-bind="layout">
<div class="mb-10 flex flex-center">请打开您的Authenticator APP获取动态验证码</div>
<a-form-item name="verifyCode">
<a-input ref="verifyCodeInputRef" v-model:value="twoFactor.verifyCode" placeholder="请输入动态验证码"
allow-clear @keydown.enter="handleTwoFactorSubmit">
<template #prefix>
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
</template>
</a-input>
</a-form-item>
<a-form-item>
<loading-button type="primary" size="large" html-type="button" class="login-button"
:click="handleTwoFactorSubmit">OTP验证登录</loading-button>
</a-form-item>
<a-form-item class="user-login-other">
<a class="register" @click="twoFactor.loginId = null"> 返回 </a>
</a-form-item>
</a-form>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, nextTick, reactive, ref, toRaw } from "vue"; import { defineComponent, nextTick, reactive, ref, toRaw } from "vue";
@ -79,200 +101,208 @@ import ImageCode from "/@/views/framework/login/image-code.vue";
import SmsCode from "/@/views/framework/login/sms-code.vue"; import SmsCode from "/@/views/framework/login/sms-code.vue";
export default defineComponent({ export default defineComponent({
name: "LoginPage", name: "LoginPage",
components: { SmsCode, ImageCode }, components: { SmsCode, ImageCode },
setup() { setup() {
const verifyCodeInputRef = ref(); const verifyCodeInputRef = ref();
const loading = ref(false); const loading = ref(false);
const userStore = useUserStore(); const userStore = useUserStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const formRef = ref(); const formRef = ref();
const formState = reactive({ const formState = reactive({
username: "", username: "",
phoneCode: "86", phoneCode: "86",
mobile: "", mobile: "",
password: "", password: "",
loginType: "password", //password loginType: "password", //password
imgCode: "", imgCode: "",
smsCode: "", smsCode: "",
randomStr: "", randomStr: "",
}); });
const rules = { const rules = {
mobile: [ mobile: [
{ {
required: true, required: true,
message: "请输入手机号", message: "请输入手机号",
}, },
], ],
username: [ username: [
{ {
required: true, required: true,
message: "请输入用户名", message: "请输入用户名",
}, },
], ],
password: [ password: [
{ {
required: true, required: true,
message: "请输入登录密码", message: "请输入登录密码",
}, },
], ],
smsCode: [ smsCode: [
{ {
required: true, required: true,
message: "请输入短信验证码", message: "请输入短信验证码",
}, },
], ],
}; };
const layout = { const layout = {
labelCol: { labelCol: {
span: 0, span: 0,
}, },
wrapperCol: { wrapperCol: {
span: 24, span: 24,
}, },
}; };
const twoFactor = reactive({ const twoFactor = reactive({
loginId: "", loginId: "",
verifyCode: "", verifyCode: "",
}); });
const handleTwoFactorSubmit = async () => { const handleTwoFactorSubmit = async () => {
await userStore.loginByTwoFactor(twoFactor); await userStore.loginByTwoFactor(twoFactor);
}; };
const handleFinish = async (values: any) => { const handleFinish = async (values: any) => {
loading.value = true; loading.value = true;
try { try {
const loginType = formState.loginType; const loginType = formState.loginType;
await userStore.login(loginType, toRaw(formState)); await userStore.login(loginType, toRaw(formState));
} catch (e: any) { } catch (e: any) {
//@ts-ignore //@ts-ignore
if (e.code === 10020) { if (e.code === 10020) {
// //
//@ts-ignore //@ts-ignore
twoFactor.loginId = e.data; twoFactor.loginId = e.data;
await nextTick(); await nextTick();
verifyCodeInputRef.value.focus(); verifyCodeInputRef.value.focus();
} else { } else {
throw e; throw e;
} }
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
const handleFinishFailed = (errors: any) => { const handleFinishFailed = (errors: any) => {
utils.logger.log(errors); utils.logger.log(errors);
}; };
const resetForm = () => { const resetForm = () => {
formRef.value.resetFields(); formRef.value.resetFields();
}; };
const isLoginError = ref(); const isLoginError = ref();
const sysPublicSettings = settingStore.getSysPublic; const sysPublicSettings = settingStore.getSysPublic;
function hasRegisterTypeEnabled() { function hasRegisterTypeEnabled() {
return sysPublicSettings.registerEnabled && (sysPublicSettings.usernameRegisterEnabled || sysPublicSettings.emailRegisterEnabled); return sysPublicSettings.registerEnabled && (sysPublicSettings.usernameRegisterEnabled || sysPublicSettings.emailRegisterEnabled);
} }
return { return {
loading, loading,
formState, formState,
formRef, formRef,
rules, rules,
layout, layout,
handleFinishFailed, handleFinishFailed,
handleFinish, handleFinish,
resetForm, resetForm,
isLoginError, isLoginError,
sysPublicSettings, sysPublicSettings,
hasRegisterTypeEnabled, hasRegisterTypeEnabled,
twoFactor, twoFactor,
handleTwoFactorSubmit, handleTwoFactorSubmit,
verifyCodeInputRef, verifyCodeInputRef,
settingStore, settingStore,
}; };
}, },
}); });
</script> </script>
<style lang="less"> <style lang="less">
@import "../../../style/theme/index.less"; @import "../../../style/theme/index.less";
.login-page.main { .login-page.main {
//margin: 20px !important; //margin: 20px !important;
margin-bottom: 100px; margin-bottom: 100px;
.user-layout-login {
//label {
// font-size: 14px;
//}
.login-title { .user-layout-login {
color: @primary-color; //label {
font-size: 18px; // font-size: 14px;
text-align: center; //}
margin: 20px;
}
.getCaptcha {
display: block;
width: 100%;
}
.image-code { .login-title {
height: 34px; color: @primary-color;
} font-size: 18px;
.input-right { text-align: center;
width: 160px; margin: 20px;
margin-left: 10px; }
}
.forge-password {
font-size: 14px;
}
button.login-button { .getCaptcha {
padding: 0 15px; display: block;
font-size: 16px; width: 100%;
width: 100%; }
}
.user-login-other { .image-code {
text-align: left; height: 34px;
margin-top: 30px; }
margin-bottom: 30px;
//line-height: 22px;
.item-icon { .input-right {
font-size: 24px; width: 160px;
color: rgba(0, 0, 0, 0.2); margin-left: 10px;
margin-left: 16px; }
vertical-align: middle;
cursor: pointer;
transition: color 0.3s;
&:hover { .forge-password {
color: @primary-color; font-size: 14px;
} }
}
.register { button.login-button {
float: right; padding: 0 15px;
} font-size: 16px;
} width: 100%;
.fs-icon { }
color: rgba(0, 0, 0, 0.45);
margin-right: 4px; .user-login-other {
} text-align: left;
.ant-input-affix-wrapper { margin-top: 30px;
line-height: 1.8 !important; margin-bottom: 30px;
font-size: 14px !important; //line-height: 22px;
> * {
line-height: 1.8 !important; .item-icon {
font-size: 14px !important; font-size: 24px;
} color: rgba(0, 0, 0, 0.2);
} margin-left: 16px;
} vertical-align: middle;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: @primary-color;
}
}
.register {
float: right;
}
}
.fs-icon {
color: rgba(0, 0, 0, 0.45);
margin-right: 4px;
}
.ant-input-affix-wrapper {
line-height: 1.8 !important;
font-size: 14px !important;
>* {
line-height: 1.8 !important;
font-size: 14px !important;
}
}
}
} }
</style> </style>