27 changed files with 1987 additions and 0 deletions
@ -0,0 +1,19 @@ |
|||
# @vben/hooks |
|||
|
|||
用于多个 `app` 公用的 hook,继承了 `@vben/hooks` 的所有能力。业务上有通用 hooks 可以放在这里。 |
|||
|
|||
## 用法 |
|||
|
|||
### 添加依赖 |
|||
|
|||
```bash |
|||
# 进入目标应用目录,例如 apps/xxxx-app |
|||
# cd apps/xxxx-app |
|||
pnpm add @vben/hooks |
|||
``` |
|||
|
|||
### 使用 |
|||
|
|||
```ts |
|||
import { useNamespace } from '@vben/hooks'; |
|||
``` |
|||
@ -0,0 +1,33 @@ |
|||
{ |
|||
"name": "@vben/hooks", |
|||
"version": "5.6.0", |
|||
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
|||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
|||
"directory": "packages/effects/hooks" |
|||
}, |
|||
"license": "MIT", |
|||
"type": "module", |
|||
"sideEffects": [ |
|||
"**/*.css" |
|||
], |
|||
"exports": { |
|||
".": { |
|||
"types": "./src/index.ts", |
|||
"default": "./src/index.ts" |
|||
} |
|||
}, |
|||
"dependencies": { |
|||
"@vben-core/composables": "workspace:*", |
|||
"@vben/preferences": "workspace:*", |
|||
"@vben/stores": "workspace:*", |
|||
"@vben/types": "workspace:*", |
|||
"@vben/utils": "workspace:*", |
|||
"@vueuse/core": "catalog:", |
|||
"vue": "catalog:", |
|||
"vue-router": "catalog:", |
|||
"watermark-js-plus": "catalog:" |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
export * from './use-app-config'; |
|||
export * from './use-content-maximize'; |
|||
export * from './use-design-tokens'; |
|||
export * from './use-hover-toggle'; |
|||
export * from './use-pagination'; |
|||
export * from './use-refresh'; |
|||
export * from './use-tabs'; |
|||
export * from './use-watermark'; |
|||
export * from '@vben-core/composables'; |
|||
@ -0,0 +1,38 @@ |
|||
import type { |
|||
ApplicationConfig, |
|||
VbenAdminProAppConfigRaw, |
|||
} from '@vben/types/global'; |
|||
|
|||
/** |
|||
* 由 vite-inject-app-config 注入的全局配置 |
|||
*/ |
|||
export function useAppConfig( |
|||
env: Record<string, any>, |
|||
isProduction: boolean, |
|||
): ApplicationConfig { |
|||
// 生产环境下,直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量
|
|||
const config = isProduction |
|||
? window._VBEN_ADMIN_PRO_APP_CONF_ |
|||
: (env as VbenAdminProAppConfigRaw); |
|||
|
|||
const { |
|||
VITE_GLOB_API_URL, |
|||
VITE_GLOB_JAVA_API_URL, |
|||
VITE_GLOB_AUTH_DINGDING_CORP_ID, |
|||
VITE_GLOB_AUTH_DINGDING_CLIENT_ID, |
|||
} = config; |
|||
|
|||
const applicationConfig: ApplicationConfig = { |
|||
apiURL: VITE_GLOB_API_URL, |
|||
javaURL: VITE_GLOB_JAVA_API_URL, |
|||
auth: {}, |
|||
}; |
|||
if (VITE_GLOB_AUTH_DINGDING_CORP_ID && VITE_GLOB_AUTH_DINGDING_CLIENT_ID) { |
|||
applicationConfig.auth.dingding = { |
|||
clientId: VITE_GLOB_AUTH_DINGDING_CLIENT_ID, |
|||
corpId: VITE_GLOB_AUTH_DINGDING_CORP_ID, |
|||
}; |
|||
} |
|||
|
|||
return applicationConfig; |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
import { updatePreferences, usePreferences } from '@vben/preferences'; |
|||
/** |
|||
* 主体区域最大化 |
|||
*/ |
|||
export function useContentMaximize() { |
|||
const { contentIsMaximize } = usePreferences(); |
|||
|
|||
function toggleMaximize() { |
|||
const isMaximize = contentIsMaximize.value; |
|||
|
|||
updatePreferences({ |
|||
header: { |
|||
hidden: !isMaximize, |
|||
}, |
|||
sidebar: { |
|||
hidden: !isMaximize, |
|||
}, |
|||
}); |
|||
} |
|||
return { |
|||
contentIsMaximize, |
|||
toggleMaximize, |
|||
}; |
|||
} |
|||
@ -0,0 +1,321 @@ |
|||
import { reactive, watch } from 'vue'; |
|||
|
|||
import { preferences, usePreferences } from '@vben/preferences'; |
|||
import { convertToRgb, updateCSSVariables } from '@vben/utils'; |
|||
|
|||
/** |
|||
* 用于适配各个框架的设计系统 |
|||
*/ |
|||
|
|||
export function useAntdDesignTokens() { |
|||
const rootStyles = getComputedStyle(document.documentElement); |
|||
|
|||
const tokens = reactive({ |
|||
borderRadius: '' as any, |
|||
colorBgBase: '', |
|||
colorBgContainer: '', |
|||
colorBgElevated: '', |
|||
colorBgLayout: '', |
|||
colorBgMask: '', |
|||
colorBorder: '', |
|||
colorBorderSecondary: '', |
|||
colorError: '', |
|||
colorInfo: '', |
|||
colorPrimary: '', |
|||
colorSuccess: '', |
|||
colorTextBase: '', |
|||
colorWarning: '', |
|||
zIndexPopupBase: 2000, // 调整基础弹层层级,避免下拉等组件被弹窗或者最大化状态下的表格遮挡
|
|||
}); |
|||
|
|||
const getCssVariableValue = (variable: string, isColor: boolean = true) => { |
|||
const value = rootStyles.getPropertyValue(variable); |
|||
return isColor ? `hsl(${value})` : value; |
|||
}; |
|||
|
|||
watch( |
|||
() => preferences.theme, |
|||
() => { |
|||
tokens.colorPrimary = getCssVariableValue('--primary'); |
|||
|
|||
tokens.colorInfo = getCssVariableValue('--primary'); |
|||
|
|||
tokens.colorError = getCssVariableValue('--destructive'); |
|||
|
|||
tokens.colorWarning = getCssVariableValue('--warning'); |
|||
|
|||
tokens.colorSuccess = getCssVariableValue('--success'); |
|||
|
|||
tokens.colorTextBase = getCssVariableValue('--foreground'); |
|||
|
|||
getCssVariableValue('--primary-foreground'); |
|||
|
|||
tokens.colorBorderSecondary = tokens.colorBorder = |
|||
getCssVariableValue('--border'); |
|||
|
|||
tokens.colorBgElevated = getCssVariableValue('--popover'); |
|||
|
|||
tokens.colorBgContainer = getCssVariableValue('--card'); |
|||
|
|||
tokens.colorBgBase = getCssVariableValue('--background'); |
|||
|
|||
const radius = Number.parseFloat(getCssVariableValue('--radius', false)); |
|||
// 1rem = 16px
|
|||
tokens.borderRadius = radius * 16; |
|||
|
|||
tokens.colorBgLayout = getCssVariableValue('--background-deep'); |
|||
tokens.colorBgMask = getCssVariableValue('--overlay'); |
|||
}, |
|||
{ immediate: true }, |
|||
); |
|||
|
|||
return { |
|||
tokens, |
|||
}; |
|||
} |
|||
|
|||
export function useNaiveDesignTokens() { |
|||
const rootStyles = getComputedStyle(document.documentElement); |
|||
|
|||
const commonTokens = reactive({ |
|||
baseColor: '', |
|||
bodyColor: '', |
|||
borderColor: '', |
|||
borderRadius: '', |
|||
cardColor: '', |
|||
dividerColor: '', |
|||
errorColor: '', |
|||
errorColorHover: '', |
|||
errorColorPressed: '', |
|||
errorColorSuppl: '', |
|||
invertedColor: '', |
|||
modalColor: '', |
|||
popoverColor: '', |
|||
primaryColor: '', |
|||
primaryColorHover: '', |
|||
primaryColorPressed: '', |
|||
primaryColorSuppl: '', |
|||
successColor: '', |
|||
successColorHover: '', |
|||
successColorPressed: '', |
|||
successColorSuppl: '', |
|||
tableColor: '', |
|||
textColorBase: '', |
|||
warningColor: '', |
|||
warningColorHover: '', |
|||
warningColorPressed: '', |
|||
warningColorSuppl: '', |
|||
}); |
|||
|
|||
const getCssVariableValue = (variable: string, isColor: boolean = true) => { |
|||
const value = rootStyles.getPropertyValue(variable); |
|||
return isColor ? convertToRgb(`hsl(${value})`) : value; |
|||
}; |
|||
|
|||
watch( |
|||
() => preferences.theme, |
|||
() => { |
|||
commonTokens.primaryColor = getCssVariableValue('--primary'); |
|||
commonTokens.primaryColorHover = getCssVariableValue('--primary-600'); |
|||
commonTokens.primaryColorPressed = getCssVariableValue('--primary-700'); |
|||
commonTokens.primaryColorSuppl = getCssVariableValue('--primary-800'); |
|||
|
|||
commonTokens.errorColor = getCssVariableValue('--destructive'); |
|||
commonTokens.errorColorHover = getCssVariableValue('--destructive-600'); |
|||
commonTokens.errorColorPressed = getCssVariableValue('--destructive-700'); |
|||
commonTokens.errorColorSuppl = getCssVariableValue('--destructive-800'); |
|||
|
|||
commonTokens.warningColor = getCssVariableValue('--warning'); |
|||
commonTokens.warningColorHover = getCssVariableValue('--warning-600'); |
|||
commonTokens.warningColorPressed = getCssVariableValue('--warning-700'); |
|||
commonTokens.warningColorSuppl = getCssVariableValue('--warning-800'); |
|||
|
|||
commonTokens.successColor = getCssVariableValue('--success'); |
|||
commonTokens.successColorHover = getCssVariableValue('--success-600'); |
|||
commonTokens.successColorPressed = getCssVariableValue('--success-700'); |
|||
commonTokens.successColorSuppl = getCssVariableValue('--success-800'); |
|||
|
|||
commonTokens.textColorBase = getCssVariableValue('--foreground'); |
|||
|
|||
commonTokens.baseColor = getCssVariableValue('--primary-foreground'); |
|||
|
|||
commonTokens.dividerColor = commonTokens.borderColor = |
|||
getCssVariableValue('--border'); |
|||
|
|||
commonTokens.modalColor = commonTokens.popoverColor = |
|||
getCssVariableValue('--popover'); |
|||
|
|||
commonTokens.tableColor = commonTokens.cardColor = |
|||
getCssVariableValue('--card'); |
|||
|
|||
commonTokens.bodyColor = getCssVariableValue('--background'); |
|||
commonTokens.invertedColor = getCssVariableValue('--background-deep'); |
|||
|
|||
commonTokens.borderRadius = getCssVariableValue('--radius', false); |
|||
}, |
|||
{ immediate: true }, |
|||
); |
|||
return { |
|||
commonTokens, |
|||
}; |
|||
} |
|||
|
|||
export function useElementPlusDesignTokens() { |
|||
const { isDark } = usePreferences(); |
|||
const rootStyles = getComputedStyle(document.documentElement); |
|||
|
|||
const getCssVariableValueRaw = (variable: string) => { |
|||
return rootStyles.getPropertyValue(variable); |
|||
}; |
|||
|
|||
const getCssVariableValue = (variable: string, isColor: boolean = true) => { |
|||
const value = getCssVariableValueRaw(variable); |
|||
return isColor ? convertToRgb(`hsl(${value})`) : value; |
|||
}; |
|||
|
|||
watch( |
|||
() => preferences.theme, |
|||
() => { |
|||
const background = getCssVariableValue('--background'); |
|||
const border = getCssVariableValue('--border'); |
|||
const accent = getCssVariableValue('--accent'); |
|||
|
|||
const variables: Record<string, string> = { |
|||
'--el-bg-color': background, |
|||
'--el-bg-color-overlay': getCssVariableValue('--popover'), |
|||
'--el-bg-color-page': getCssVariableValue('--background-deep'), |
|||
'--el-border-color': border, |
|||
'--el-border-color-dark': border, |
|||
'--el-border-color-extra-light': border, |
|||
'--el-border-color-hover': accent, |
|||
'--el-border-color-light': border, |
|||
'--el-border-color-lighter': border, |
|||
|
|||
'--el-border-radius-base': getCssVariableValue('--radius', false), |
|||
'--el-color-danger': getCssVariableValue('--destructive-500'), |
|||
'--el-color-danger-dark-2': isDark.value |
|||
? getCssVariableValue('--destructive-400') |
|||
: getCssVariableValue('--destructive-600'), |
|||
'--el-color-danger-light-3': isDark.value |
|||
? getCssVariableValue('--destructive-600') |
|||
: getCssVariableValue('--destructive-400'), |
|||
'--el-color-danger-light-5': isDark.value |
|||
? getCssVariableValue('--destructive-700') |
|||
: getCssVariableValue('--destructive-300'), |
|||
'--el-color-danger-light-7': isDark.value |
|||
? getCssVariableValue('--destructive-800') |
|||
: getCssVariableValue('--destructive-200'), |
|||
'--el-color-danger-light-8': isDark.value |
|||
? getCssVariableValue('--destructive-900') |
|||
: getCssVariableValue('--destructive-100'), |
|||
'--el-color-danger-light-9': isDark.value |
|||
? getCssVariableValue('--destructive-950') |
|||
: getCssVariableValue('--destructive-50'), |
|||
|
|||
'--el-color-error': getCssVariableValue('--destructive-500'), |
|||
'--el-color-error-dark-2': isDark.value |
|||
? getCssVariableValue('--destructive-400') |
|||
: getCssVariableValue('--destructive-600'), |
|||
'--el-color-error-light-3': isDark.value |
|||
? getCssVariableValue('--destructive-600') |
|||
: getCssVariableValue('--destructive-400'), |
|||
'--el-color-error-light-5': isDark.value |
|||
? getCssVariableValue('--destructive-700') |
|||
: getCssVariableValue('--destructive-300'), |
|||
'--el-color-error-light-7': isDark.value |
|||
? getCssVariableValue('--destructive-800') |
|||
: getCssVariableValue('--destructive-200'), |
|||
'--el-color-error-light-8': isDark.value |
|||
? getCssVariableValue('--destructive-900') |
|||
: getCssVariableValue('--destructive-100'), |
|||
'--el-color-error-light-9': isDark.value |
|||
? getCssVariableValue('--destructive-950') |
|||
: getCssVariableValue('--destructive-50'), |
|||
|
|||
'--el-color-info-light-5': border, |
|||
'--el-color-info-light-8': border, |
|||
'--el-color-info-light-9': getCssVariableValue('--info'), // getCssVariableValue('--secondary'),
|
|||
|
|||
'--el-color-primary': getCssVariableValue('--primary-500'), |
|||
'--el-color-primary-dark-2': isDark.value |
|||
? getCssVariableValue('--primary-400') |
|||
: getCssVariableValue('--primary-600'), |
|||
'--el-color-primary-light-3': isDark.value |
|||
? getCssVariableValue('--primary-600') |
|||
: getCssVariableValue('--primary-400'), |
|||
'--el-color-primary-light-5': isDark.value |
|||
? getCssVariableValue('--primary-700') |
|||
: getCssVariableValue('--primary-300'), |
|||
'--el-color-primary-light-7': isDark.value |
|||
? getCssVariableValue('--primary-800') |
|||
: getCssVariableValue('--primary-200'), |
|||
'--el-color-primary-light-8': isDark.value |
|||
? getCssVariableValue('--primary-900') |
|||
: getCssVariableValue('--primary-100'), |
|||
'--el-color-primary-light-9': isDark.value |
|||
? getCssVariableValue('--primary-950') |
|||
: getCssVariableValue('--primary-50'), |
|||
|
|||
'--el-color-success': getCssVariableValue('--success-500'), |
|||
'--el-color-success-dark-2': isDark.value |
|||
? getCssVariableValue('--success-400') |
|||
: getCssVariableValue('--success-600'), |
|||
'--el-color-success-light-3': isDark.value |
|||
? getCssVariableValue('--success-600') |
|||
: getCssVariableValue('--success-400'), |
|||
'--el-color-success-light-5': isDark.value |
|||
? getCssVariableValue('--success-700') |
|||
: getCssVariableValue('--success-300'), |
|||
'--el-color-success-light-7': isDark.value |
|||
? getCssVariableValue('--success-800') |
|||
: getCssVariableValue('--success-200'), |
|||
'--el-color-success-light-8': isDark.value |
|||
? getCssVariableValue('--success-900') |
|||
: getCssVariableValue('--success-100'), |
|||
'--el-color-success-light-9': isDark.value |
|||
? getCssVariableValue('--success-950') |
|||
: getCssVariableValue('--success-50'), |
|||
|
|||
'--el-color-warning': getCssVariableValue('--warning-500'), |
|||
'--el-color-warning-dark-2': isDark.value |
|||
? getCssVariableValue('--warning-400') |
|||
: getCssVariableValue('--warning-600'), |
|||
'--el-color-warning-light-3': isDark.value |
|||
? getCssVariableValue('--warning-600') |
|||
: getCssVariableValue('--warning-400'), |
|||
'--el-color-warning-light-5': isDark.value |
|||
? getCssVariableValue('--warning-700') |
|||
: getCssVariableValue('--warning-300'), |
|||
'--el-color-warning-light-7': isDark.value |
|||
? getCssVariableValue('--warning-800') |
|||
: getCssVariableValue('--warning-200'), |
|||
'--el-color-warning-light-8': isDark.value |
|||
? getCssVariableValue('--warning-900') |
|||
: getCssVariableValue('--warning-100'), |
|||
'--el-color-warning-light-9': isDark.value |
|||
? getCssVariableValue('--warning-950') |
|||
: getCssVariableValue('--warning-50'), |
|||
|
|||
'--el-fill-color': getCssVariableValue('--accent'), |
|||
'--el-fill-color-blank': background, |
|||
'--el-fill-color-light': getCssVariableValue('--accent'), |
|||
'--el-fill-color-lighter': getCssVariableValue('--accent-lighter'), |
|||
|
|||
'--el-fill-color-dark': getCssVariableValue('--accent-dark'), |
|||
'--el-fill-color-darker': getCssVariableValue('--accent-darker'), |
|||
|
|||
// 解决ElLoading背景色问题
|
|||
'--el-mask-color': isDark.value |
|||
? 'rgba(0,0,0,.8)' |
|||
: 'rgba(255,255,255,.9)', |
|||
|
|||
'--el-text-color-primary': getCssVariableValue('--foreground'), |
|||
|
|||
'--el-text-color-regular': getCssVariableValue('--foreground'), |
|||
}; |
|||
|
|||
updateCSSVariables(variables, `__vben_design_styles__`); |
|||
}, |
|||
{ immediate: true }, |
|||
); |
|||
} |
|||
@ -0,0 +1,163 @@ |
|||
import type { Arrayable, MaybeElementRef } from '@vueuse/core'; |
|||
|
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { computed, effectScope, onUnmounted, ref, unref, watch } from 'vue'; |
|||
|
|||
import { isFunction } from '@vben/utils'; |
|||
|
|||
import { useElementHover } from '@vueuse/core'; |
|||
|
|||
interface HoverDelayOptions { |
|||
/** 鼠标进入延迟时间 */ |
|||
enterDelay?: (() => number) | number; |
|||
/** 鼠标离开延迟时间 */ |
|||
leaveDelay?: (() => number) | number; |
|||
} |
|||
|
|||
const DEFAULT_LEAVE_DELAY = 500; // 鼠标离开延迟时间,默认为 500ms
|
|||
const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0(立即响应)
|
|||
|
|||
/** |
|||
* 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false |
|||
* @param refElement 所有需要检测的元素。支持单个元素、元素数组或响应式引用的元素数组。如果鼠标在任何一个元素内部都会返回 true |
|||
* @param delay 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象 |
|||
* @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用 |
|||
*/ |
|||
export function useHoverToggle( |
|||
refElement: Arrayable<MaybeElementRef> | Ref<HTMLElement[] | null>, |
|||
delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY, |
|||
) { |
|||
// 兼容旧版本API
|
|||
const normalizedOptions: HoverDelayOptions = |
|||
typeof delay === 'number' || isFunction(delay) |
|||
? { enterDelay: DEFAULT_ENTER_DELAY, leaveDelay: delay } |
|||
: { |
|||
enterDelay: DEFAULT_ENTER_DELAY, |
|||
leaveDelay: DEFAULT_LEAVE_DELAY, |
|||
...delay, |
|||
}; |
|||
|
|||
const value = ref(false); |
|||
const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>(); |
|||
const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>(); |
|||
const hoverScopes = ref<ReturnType<typeof effectScope>[]>([]); |
|||
|
|||
// 使用计算属性包装 refElement,使其响应式变化
|
|||
const refs = computed(() => { |
|||
const raw = unref(refElement); |
|||
if (raw === null) return []; |
|||
return Array.isArray(raw) ? raw : [raw]; |
|||
}); |
|||
// 存储所有 hover 状态
|
|||
const isHovers = ref<Array<Ref<boolean>>>([]); |
|||
|
|||
// 更新 hover 监听的函数
|
|||
function updateHovers() { |
|||
// 停止并清理之前的作用域
|
|||
hoverScopes.value.forEach((scope) => scope.stop()); |
|||
hoverScopes.value = []; |
|||
|
|||
isHovers.value = refs.value.map((refEle) => { |
|||
if (!refEle) { |
|||
return ref(false); |
|||
} |
|||
const eleRef = computed(() => { |
|||
const ele = unref(refEle); |
|||
return ele instanceof Element ? ele : (ele?.$el as Element); |
|||
}); |
|||
|
|||
// 为每个元素创建独立的作用域
|
|||
const scope = effectScope(); |
|||
const hoverRef = scope.run(() => useElementHover(eleRef)) || ref(false); |
|||
hoverScopes.value.push(scope); |
|||
|
|||
return hoverRef; |
|||
}); |
|||
} |
|||
|
|||
// 监听元素数量变化,避免过度执行
|
|||
const elementsCount = computed(() => { |
|||
const raw = unref(refElement); |
|||
if (raw === null) return 0; |
|||
return Array.isArray(raw) ? raw.length : 1; |
|||
}); |
|||
|
|||
// 初始设置
|
|||
updateHovers(); |
|||
|
|||
// 只在元素数量变化时重新设置监听器
|
|||
const stopWatcher = watch(elementsCount, updateHovers, { deep: false }); |
|||
|
|||
const isOutsideAll = computed(() => isHovers.value.every((v) => !v.value)); |
|||
|
|||
function clearTimers() { |
|||
if (enterTimer.value) { |
|||
clearTimeout(enterTimer.value); |
|||
enterTimer.value = undefined; |
|||
} |
|||
if (leaveTimer.value) { |
|||
clearTimeout(leaveTimer.value); |
|||
leaveTimer.value = undefined; |
|||
} |
|||
} |
|||
|
|||
function setValueDelay(val: boolean) { |
|||
clearTimers(); |
|||
|
|||
if (val) { |
|||
// 鼠标进入
|
|||
const enterDelay = normalizedOptions.enterDelay ?? DEFAULT_ENTER_DELAY; |
|||
const delayTime = isFunction(enterDelay) ? enterDelay() : enterDelay; |
|||
|
|||
if (delayTime <= 0) { |
|||
value.value = true; |
|||
} else { |
|||
enterTimer.value = setTimeout(() => { |
|||
value.value = true; |
|||
enterTimer.value = undefined; |
|||
}, delayTime); |
|||
} |
|||
} else { |
|||
// 鼠标离开
|
|||
const leaveDelay = normalizedOptions.leaveDelay ?? DEFAULT_LEAVE_DELAY; |
|||
const delayTime = isFunction(leaveDelay) ? leaveDelay() : leaveDelay; |
|||
|
|||
if (delayTime <= 0) { |
|||
value.value = false; |
|||
} else { |
|||
leaveTimer.value = setTimeout(() => { |
|||
value.value = false; |
|||
leaveTimer.value = undefined; |
|||
}, delayTime); |
|||
} |
|||
} |
|||
} |
|||
|
|||
const hoverWatcher = watch( |
|||
isOutsideAll, |
|||
(val) => { |
|||
setValueDelay(!val); |
|||
}, |
|||
{ immediate: true }, |
|||
); |
|||
|
|||
const controller = { |
|||
enable() { |
|||
hoverWatcher.resume(); |
|||
}, |
|||
disable() { |
|||
hoverWatcher.pause(); |
|||
}, |
|||
}; |
|||
|
|||
onUnmounted(() => { |
|||
clearTimers(); |
|||
// 停止监听器
|
|||
stopWatcher(); |
|||
// 停止所有剩余的作用域
|
|||
hoverScopes.value.forEach((scope) => scope.stop()); |
|||
}); |
|||
|
|||
return [value, controller] as [typeof value, typeof controller]; |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { computed, ref, unref, watch } from 'vue'; |
|||
|
|||
/** |
|||
* Paginates an array of items |
|||
* @param list The array to paginate |
|||
* @param pageNo The current page number (1-based) |
|||
* @param pageSize Number of items per page |
|||
* @returns Paginated array slice |
|||
* @throws {Error} If pageNo or pageSize are invalid |
|||
*/ |
|||
function pagination<T = any>(list: T[], pageNo: number, pageSize: number): T[] { |
|||
if (pageNo < 1) throw new Error('Page number must be positive'); |
|||
if (pageSize < 1) throw new Error('Page size must be positive'); |
|||
|
|||
const offset = (pageNo - 1) * Number(pageSize); |
|||
const ret = |
|||
offset + pageSize >= list.length |
|||
? list.slice(offset) |
|||
: list.slice(offset, offset + pageSize); |
|||
return ret; |
|||
} |
|||
|
|||
export function usePagination<T = any>( |
|||
list: Ref<T[]>, |
|||
pageSize: number, |
|||
totalChangeToFirstPage = true, |
|||
) { |
|||
const currentPage = ref(1); |
|||
const pageSizeRef = ref(pageSize); |
|||
|
|||
const totalPages = computed(() => |
|||
Math.ceil(unref(list).length / unref(pageSizeRef)), |
|||
); |
|||
|
|||
const paginationList = computed(() => { |
|||
return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); |
|||
}); |
|||
|
|||
const total = computed(() => { |
|||
return unref(list).length; |
|||
}); |
|||
|
|||
if (totalChangeToFirstPage) { |
|||
watch(total, () => { |
|||
setCurrentPage(1); |
|||
}); |
|||
} |
|||
|
|||
function setCurrentPage(page: number) { |
|||
if (page === 1 && unref(totalPages) === 0) { |
|||
currentPage.value = 1; |
|||
} else { |
|||
if (page < 1 || page > unref(totalPages)) { |
|||
throw new Error('Invalid page number'); |
|||
} |
|||
currentPage.value = page; |
|||
} |
|||
} |
|||
|
|||
function setPageSize(pageSize: number) { |
|||
if (pageSize < 1) { |
|||
throw new Error('Page size must be positive'); |
|||
} |
|||
pageSizeRef.value = pageSize; |
|||
// Reset to first page to prevent invalid state
|
|||
currentPage.value = 1; |
|||
} |
|||
|
|||
return { setCurrentPage, total, setPageSize, paginationList, currentPage }; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
import { useRouter } from 'vue-router'; |
|||
|
|||
import { useTabbarStore } from '@vben/stores'; |
|||
|
|||
export function useRefresh() { |
|||
const router = useRouter(); |
|||
const tabbarStore = useTabbarStore(); |
|||
|
|||
async function refresh() { |
|||
await tabbarStore.refresh(router); |
|||
} |
|||
|
|||
return { |
|||
refresh, |
|||
}; |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
import type { ComputedRef } from 'vue'; |
|||
import type { RouteLocationNormalized } from 'vue-router'; |
|||
|
|||
import { useRoute, useRouter } from 'vue-router'; |
|||
|
|||
import { useTabbarStore } from '@vben/stores'; |
|||
|
|||
export function useTabs() { |
|||
const router = useRouter(); |
|||
const route = useRoute(); |
|||
const tabbarStore = useTabbarStore(); |
|||
|
|||
async function closeLeftTabs(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.closeLeftTabs(tab || route); |
|||
} |
|||
|
|||
async function closeAllTabs() { |
|||
await tabbarStore.closeAllTabs(router); |
|||
} |
|||
|
|||
async function closeRightTabs(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.closeRightTabs(tab || route); |
|||
} |
|||
|
|||
async function closeOtherTabs(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.closeOtherTabs(tab || route); |
|||
} |
|||
|
|||
async function closeCurrentTab(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.closeTab(tab || route, router); |
|||
} |
|||
|
|||
async function pinTab(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.pinTab(tab || route); |
|||
} |
|||
|
|||
async function unpinTab(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.unpinTab(tab || route); |
|||
} |
|||
|
|||
async function toggleTabPin(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.toggleTabPin(tab || route); |
|||
} |
|||
|
|||
async function refreshTab(name?: string) { |
|||
await tabbarStore.refresh(name || router); |
|||
} |
|||
|
|||
async function openTabInNewWindow(tab?: RouteLocationNormalized) { |
|||
await tabbarStore.openTabInNewWindow(tab || route); |
|||
} |
|||
|
|||
async function closeTabByKey(key: string) { |
|||
await tabbarStore.closeTabByKey(key, router); |
|||
} |
|||
|
|||
/** |
|||
* 设置当前标签页的标题 |
|||
* |
|||
* @description 支持设置静态标题字符串或动态计算标题 |
|||
* @description 动态标题会在每次渲染时重新计算,适用于多语言或状态相关的标题 |
|||
* |
|||
* @param title - 标题内容 |
|||
* - 静态标题: 直接传入字符串 |
|||
* - 动态标题: 传入 ComputedRef |
|||
* |
|||
* @example |
|||
* // 静态标题
|
|||
* setTabTitle('标签页') |
|||
* |
|||
* // 动态标题(多语言)
|
|||
* setTabTitle(computed(() => t('page.title'))) |
|||
*/ |
|||
async function setTabTitle(title: ComputedRef<string> | string) { |
|||
tabbarStore.setUpdateTime(); |
|||
await tabbarStore.setTabTitle(route, title); |
|||
} |
|||
|
|||
async function resetTabTitle() { |
|||
tabbarStore.setUpdateTime(); |
|||
await tabbarStore.resetTabTitle(route); |
|||
} |
|||
|
|||
/** |
|||
* 获取操作是否禁用 |
|||
* @param tab |
|||
*/ |
|||
function getTabDisableState(tab: RouteLocationNormalized = route) { |
|||
const tabs = tabbarStore.getTabs; |
|||
const affixTabs = tabbarStore.affixTabs; |
|||
const index = tabs.findIndex((item) => item.path === tab.path); |
|||
|
|||
const disabled = tabs.length <= 1; |
|||
|
|||
const { meta } = tab; |
|||
const affixTab = meta?.affixTab ?? false; |
|||
const isCurrentTab = route.path === tab.path; |
|||
|
|||
// 当前处于最左侧或者减去固定标签页的数量等于0
|
|||
const disabledCloseLeft = |
|||
index === 0 || index - affixTabs.length <= 0 || !isCurrentTab; |
|||
|
|||
const disabledCloseRight = !isCurrentTab || index === tabs.length - 1; |
|||
|
|||
const disabledCloseOther = |
|||
disabled || !isCurrentTab || tabs.length - affixTabs.length <= 1; |
|||
return { |
|||
disabledCloseAll: disabled, |
|||
disabledCloseCurrent: !!affixTab || disabled, |
|||
disabledCloseLeft, |
|||
disabledCloseOther, |
|||
disabledCloseRight, |
|||
disabledRefresh: !isCurrentTab, |
|||
}; |
|||
} |
|||
|
|||
return { |
|||
closeAllTabs, |
|||
closeCurrentTab, |
|||
closeLeftTabs, |
|||
closeOtherTabs, |
|||
closeRightTabs, |
|||
closeTabByKey, |
|||
getTabDisableState, |
|||
openTabInNewWindow, |
|||
pinTab, |
|||
refreshTab, |
|||
resetTabTitle, |
|||
setTabTitle, |
|||
toggleTabPin, |
|||
unpinTab, |
|||
}; |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
import type { Watermark, WatermarkOptions } from 'watermark-js-plus'; |
|||
|
|||
import { nextTick, onUnmounted, readonly, ref } from 'vue'; |
|||
|
|||
const watermark = ref<Watermark>(); |
|||
const unmountedHooked = ref<boolean>(false); |
|||
const cachedOptions = ref<Partial<WatermarkOptions>>({ |
|||
advancedStyle: { |
|||
colorStops: [ |
|||
{ |
|||
color: 'gray', |
|||
offset: 0, |
|||
}, |
|||
{ |
|||
color: 'gray', |
|||
offset: 1, |
|||
}, |
|||
], |
|||
type: 'linear', |
|||
}, |
|||
// fontSize: '20px',
|
|||
content: '', |
|||
contentType: 'multi-line-text', |
|||
globalAlpha: 0.25, |
|||
gridLayoutOptions: { |
|||
cols: 2, |
|||
gap: [20, 20], |
|||
matrix: [ |
|||
[1, 0], |
|||
[0, 1], |
|||
], |
|||
rows: 2, |
|||
}, |
|||
height: 200, |
|||
layout: 'grid', |
|||
rotate: 30, |
|||
width: 160, |
|||
}); |
|||
|
|||
export function useWatermark() { |
|||
async function initWatermark(options: Partial<WatermarkOptions>) { |
|||
const { Watermark } = await import('watermark-js-plus'); |
|||
|
|||
cachedOptions.value = { |
|||
...cachedOptions.value, |
|||
...options, |
|||
}; |
|||
watermark.value = new Watermark(cachedOptions.value); |
|||
await watermark.value?.create(); |
|||
} |
|||
|
|||
async function updateWatermark(options: Partial<WatermarkOptions>) { |
|||
if (watermark.value) { |
|||
await nextTick(); |
|||
await watermark.value?.changeOptions({ |
|||
...cachedOptions.value, |
|||
...options, |
|||
}); |
|||
} else { |
|||
await initWatermark(options); |
|||
} |
|||
} |
|||
|
|||
function destroyWatermark() { |
|||
if (watermark.value) { |
|||
watermark.value.destroy(); |
|||
watermark.value = undefined; |
|||
} |
|||
} |
|||
|
|||
// 只在第一次调用时注册卸载钩子,防止重复注册以致于在路由切换时销毁了水印
|
|||
if (!unmountedHooked.value) { |
|||
unmountedHooked.value = true; |
|||
onUnmounted(() => { |
|||
destroyWatermark(); |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
destroyWatermark, |
|||
updateWatermark, |
|||
watermark: readonly(watermark), |
|||
}; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/web.json", |
|||
"compilerOptions": { |
|||
"types": ["vite/client", "@vben/types/global"] |
|||
}, |
|||
"include": ["src"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
{ |
|||
"name": "@vben/locales", |
|||
"version": "5.6.0", |
|||
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
|||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
|||
"directory": "packages/locales" |
|||
}, |
|||
"license": "MIT", |
|||
"type": "module", |
|||
"sideEffects": [ |
|||
"**/*.css" |
|||
], |
|||
"exports": { |
|||
".": { |
|||
"types": "./src/index.ts", |
|||
"default": "./src/index.ts" |
|||
} |
|||
}, |
|||
"dependencies": { |
|||
"@intlify/core-base": "catalog:", |
|||
"@vben-core/composables": "workspace:*", |
|||
"vue": "catalog:", |
|||
"vue-i18n": "catalog:" |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
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-core/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, |
|||
}; |
|||
@ -0,0 +1,30 @@ |
|||
import { |
|||
i18n, |
|||
loadLocaleMessages, |
|||
loadLocalesMap, |
|||
loadLocalesMapFromDir, |
|||
setupI18n, |
|||
} from './i18n'; |
|||
|
|||
const $t = i18n.global.t; |
|||
const $te = i18n.global.te; |
|||
|
|||
export { |
|||
$t, |
|||
$te, |
|||
i18n, |
|||
loadLocaleMessages, |
|||
loadLocalesMap, |
|||
loadLocalesMapFromDir, |
|||
setupI18n, |
|||
}; |
|||
export { |
|||
type ImportLocaleFn, |
|||
type LocaleSetupOptions, |
|||
type SupportedLanguagesType, |
|||
} from './typing'; |
|||
export type { CompileError } from '@intlify/core-base'; |
|||
|
|||
export { useI18n } from 'vue-i18n'; |
|||
|
|||
export type { Locale } from 'vue-i18n'; |
|||
@ -0,0 +1,65 @@ |
|||
{ |
|||
"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", |
|||
"wechatLogin": "Wechat Login", |
|||
"qqLogin": "QQ Login", |
|||
"githubLogin": "Github Login", |
|||
"googleLogin": "Google Login", |
|||
"dingdingLogin": "Dingding 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", |
|||
"weChat": "WeChat", |
|||
"qq": "QQ", |
|||
"gitHub": "GitHub", |
|||
"google": "Google", |
|||
"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" |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
{ |
|||
"back": "Back", |
|||
"backToHome": "Back To Home", |
|||
"login": "Login", |
|||
"logout": "Logout", |
|||
"prompt": "Prompt", |
|||
"cancel": "Cancel", |
|||
"confirm": "Confirm", |
|||
"reset": "Reset", |
|||
"noData": "No Data", |
|||
"refresh": "Refresh", |
|||
"loadingMenu": "Loading Menu", |
|||
"query": "Search", |
|||
"search": "Search", |
|||
"enabled": "Enabled", |
|||
"disabled": "Disabled", |
|||
"edit": "Edit", |
|||
"delete": "Delete", |
|||
"create": "Create", |
|||
"yes": "Yes", |
|||
"no": "No", |
|||
"showSearchPanel": "Show search panel", |
|||
"hideSearchPanel": "Hide search panel" |
|||
} |
|||
@ -0,0 +1,201 @@ |
|||
{ |
|||
"title": "Preferences", |
|||
"subtitle": "Customize Preferences & Preview in Real Time", |
|||
"enableStickyPreferencesNavigationBar": "Enable sticky preferences navigation bar", |
|||
"disableStickyPreferencesNavigationBar": "Disable sticky preferences navigation bar", |
|||
"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", |
|||
"enableCopyPreferences": "Show copy preferences button", |
|||
"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", |
|||
"watermarkContent": "Please input Watermark content", |
|||
"checkUpdates": "Periodic update check", |
|||
"position": { |
|||
"title": "Preferences Postion", |
|||
"header": "Header", |
|||
"auto": "Auto", |
|||
"fixed": "Fixed" |
|||
}, |
|||
"sidebar": { |
|||
"buttons": "Show Buttons", |
|||
"buttonFixed": "Fixed", |
|||
"buttonCollapsed": "Collapsed", |
|||
"title": "Sidebar", |
|||
"width": "Width", |
|||
"visible": "Show Sidebar", |
|||
"draggable": "Drag Sidebar Menu", |
|||
"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", |
|||
"visitHistory": "Visit History", |
|||
"visitHistoryTip": "When enabled, the tab bar records tab visit history. \nClosing the current tab will automatically select the last opened tab.", |
|||
"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", |
|||
"fontSize": "Font Size", |
|||
"fontSizeTip": "Adjust global font size with real-time preview", |
|||
"light": "Light", |
|||
"dark": "Dark", |
|||
"darkSidebar": "Semi Dark Sidebar", |
|||
"darkSidebarTip": "It can be enabled when the theme is light, and the layout is neither 'Horizontal' nor 'Full Content'.", |
|||
"darkSidebarSub": "Semi Dark Sidebar Sub", |
|||
"darkSidebarSubTip": "It can be enabled when the theme is light, the semi dark sidebar is enabled, and the layout uses 'Two-Column' menu mode.", |
|||
"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" |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"updatePassword": "Update Password", |
|||
"updateBasicProfile": "Update Basic Profile" |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
{ |
|||
"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", |
|||
"sizeLimit": "The file size cannot exceed {0}MB", |
|||
"previewWarning": "Unable to open the file, there is no available URL or preview address" |
|||
}, |
|||
"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", |
|||
"upload": "Click to upload" |
|||
}, |
|||
"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", |
|||
"sliderTranslateDefaultTip": "Click picture to refresh", |
|||
"sliderRotateFailTip": "Validation failed", |
|||
"sliderRotateSuccessTip": "Validation successful, time {0} seconds", |
|||
"sliderTranslateFailTip": "Validation failed", |
|||
"sliderTranslateSuccessTip": "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" |
|||
}, |
|||
"crop": { |
|||
"title": "Image Cropping", |
|||
"titleTip": "Cropping Ratio {0}", |
|||
"confirm": "Crop", |
|||
"cancel": "Cancel cropping", |
|||
"errorTip": "Cropping error" |
|||
}, |
|||
"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" |
|||
}, |
|||
"timezone": { |
|||
"setTimezone": "Set Timezone", |
|||
"setSuccess": "Timezone set successfully" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
{ |
|||
"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": "扫码登录", |
|||
"wechatLogin": "微信登录", |
|||
"qqLogin": "QQ登录", |
|||
"githubLogin": "Github登录", |
|||
"googleLogin": "Google登录", |
|||
"dingdingLogin": "钉钉登录", |
|||
"codeSubtitle": "请输入您的手机号码以开始管理您的项目", |
|||
"code": "验证码", |
|||
"codeTip": "请输入{0}位验证码", |
|||
"mobile": "手机号码", |
|||
"mobileTip": "请输入手机号", |
|||
"mobileErrortip": "手机号码格式错误", |
|||
"mobileLogin": "手机号登录", |
|||
"sendCode": "获取验证码", |
|||
"sendText": "{0}秒后重新获取", |
|||
"thirdPartyLogin": "其他登录方式", |
|||
"weChat": "微信", |
|||
"qq": "QQ", |
|||
"gitHub": "GitHub", |
|||
"google": "Google", |
|||
"loginAgainTitle": "重新登录", |
|||
"loginAgainSubTitle": "您的登录状态已过期,请重新登录以继续。", |
|||
"layout": { |
|||
"center": "居中", |
|||
"alignLeft": "居左", |
|||
"alignRight": "居右" |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
{ |
|||
"back": "返回", |
|||
"backToHome": "返回首页", |
|||
"login": "登录", |
|||
"logout": "退出登录", |
|||
"prompt": "提示", |
|||
"cancel": "取消", |
|||
"confirm": "确认", |
|||
"reset": "重置", |
|||
"noData": "暂无数据", |
|||
"refresh": "刷新", |
|||
"loadingMenu": "加载菜单中", |
|||
"query": "查询", |
|||
"search": "搜索", |
|||
"enabled": "已启用", |
|||
"disabled": "已禁用", |
|||
"edit": "修改", |
|||
"delete": "删除", |
|||
"create": "新增", |
|||
"yes": "是", |
|||
"no": "否", |
|||
"showSearchPanel": "显示搜索面板", |
|||
"hideSearchPanel": "隐藏搜索面板" |
|||
} |
|||
@ -0,0 +1,201 @@ |
|||
{ |
|||
"title": "偏好设置", |
|||
"subtitle": "自定义偏好设置 & 实时预览", |
|||
"enableStickyPreferencesNavigationBar": "开启首选项导航栏吸顶效果", |
|||
"disableStickyPreferencesNavigationBar": "关闭首选项导航栏吸顶效果", |
|||
"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": "复制偏好设置", |
|||
"enableCopyPreferences": "显示复制偏好设置按钮", |
|||
"copyPreferencesSuccessTitle": "复制成功", |
|||
"copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖", |
|||
"clearAndLogout": "清空缓存 & 退出登录", |
|||
"mode": "模式", |
|||
"general": "通用", |
|||
"language": "语言", |
|||
"dynamicTitle": "动态标题", |
|||
"watermark": "水印", |
|||
"watermarkContent": "请输入水印文案", |
|||
"checkUpdates": "定时检查更新", |
|||
"position": { |
|||
"title": "偏好设置位置", |
|||
"header": "顶栏", |
|||
"auto": "自动", |
|||
"fixed": "固定" |
|||
}, |
|||
"sidebar": { |
|||
"buttons": "显示按钮", |
|||
"buttonFixed": "固定按钮", |
|||
"buttonCollapsed": "折叠按钮", |
|||
"title": "侧边栏", |
|||
"width": "宽度", |
|||
"visible": "显示侧边栏", |
|||
"draggable": "侧边栏菜单拖拽", |
|||
"collapsed": "折叠菜单", |
|||
"collapsedShowTitle": "折叠显示菜单名", |
|||
"autoActivateChild": "自动激活子菜单", |
|||
"autoActivateChildTip": "点击顶层菜单时,自动激活第一个子菜单或者上一次激活的子菜单", |
|||
"expandOnHover": "鼠标悬停展开", |
|||
"expandOnHoverTip": "鼠标在折叠区域悬浮时,`启用`则展开当前子菜单,`禁用`则展开整个侧边栏" |
|||
}, |
|||
"tabbar": { |
|||
"title": "标签栏", |
|||
"enable": "启用标签栏", |
|||
"icon": "显示标签栏图标", |
|||
"showMore": "显示更多按钮", |
|||
"showMaximize": "显示最大化按钮", |
|||
"persist": "持久化标签页", |
|||
"visitHistory": "访问历史记录", |
|||
"visitHistoryTip": "开启后,标签栏会记录标签访问历史\n关闭当前标签,会自动选中上一个打开的标签", |
|||
"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": "圆角", |
|||
"fontSize": "字体大小", |
|||
"fontSizeTip": "调整全局字体大小,实时预览效果", |
|||
"light": "浅色", |
|||
"dark": "深色", |
|||
"darkSidebar": "深色侧边栏", |
|||
"darkSidebarTip": "当主题为浅色,布局不为水平菜单或不为内容全屏时可开启", |
|||
"darkSidebarSub": "深色侧边栏子栏", |
|||
"darkSidebarSubTip": "当主题为浅色,开启深色侧边栏且布局使用双列菜单模式时可开启", |
|||
"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": "启用刷新" |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"updatePassword": "更新密码", |
|||
"updateBasicProfile": "更新基本信息" |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
{ |
|||
"formRules": { |
|||
"required": "请输入{0}", |
|||
"selectRequired": "请选择{0}", |
|||
"minLength": "{0}至少{1}个字符", |
|||
"maxLength": "{0}最多{1}个字符", |
|||
"length": "{0}长度必须为{1}个字符", |
|||
"alreadyExists": "{0} `{1}` 已存在", |
|||
"startWith": "{0}必须以 {1} 开头", |
|||
"invalidURL": "请输入有效的链接", |
|||
"sizeLimit": "文件大小不能超过 {0}MB", |
|||
"previewWarning": "无法打开文件,没有可用的URL或预览地址" |
|||
}, |
|||
"actionTitle": { |
|||
"edit": "修改{0}", |
|||
"create": "新增{0}", |
|||
"delete": "删除{0}", |
|||
"view": "查看{0}" |
|||
}, |
|||
"actionMessage": { |
|||
"deleteConfirm": "确定删除 {0} 吗?", |
|||
"deleting": "正在删除 {0} ...", |
|||
"deleteSuccess": "{0} 删除成功", |
|||
"operationSuccess": "操作成功", |
|||
"operationFailed": "操作失败" |
|||
}, |
|||
"placeholder": { |
|||
"input": "请输入", |
|||
"select": "请选择", |
|||
"upload": "点击上传" |
|||
}, |
|||
"captcha": { |
|||
"title": "请完成安全验证", |
|||
"sliderSuccessText": "验证通过", |
|||
"sliderDefaultText": "请按住滑块拖动", |
|||
"sliderRotateDefaultTip": "点击图片可刷新", |
|||
"sliderTranslateDefaultTip": "点击图片可刷新", |
|||
"sliderRotateFailTip": "验证失败", |
|||
"sliderRotateSuccessTip": "验证成功,耗时{0}秒", |
|||
"sliderTranslateFailTip": "验证失败", |
|||
"sliderTranslateSuccessTip": "验证成功,耗时{0}秒", |
|||
"alt": "支持img标签src属性值", |
|||
"refreshAriaLabel": "刷新验证码", |
|||
"confirmAriaLabel": "确认选择", |
|||
"confirm": "确认", |
|||
"pointAriaLabel": "点击点", |
|||
"clickInOrder": "请依次点击" |
|||
}, |
|||
"iconPicker": { |
|||
"placeholder": "选择一个图标", |
|||
"search": "搜索图标..." |
|||
}, |
|||
"jsonViewer": { |
|||
"copy": "复制", |
|||
"copied": "已复制" |
|||
}, |
|||
"crop": { |
|||
"title": "图片裁剪", |
|||
"titleTip": "裁剪比例 {0}", |
|||
"confirm": "裁剪", |
|||
"cancel": "取消裁剪", |
|||
"errorTip": "裁剪错误" |
|||
}, |
|||
"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": "进入系统" |
|||
}, |
|||
"timezone": { |
|||
"setTimezone": "设置时区", |
|||
"setSuccess": "时区设置成功" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
export type SupportedLanguagesType = 'en-US' | 'zh-CN'; |
|||
|
|||
export type ImportLocaleFn = () => Promise<{ default: Record<string, string> }>; |
|||
|
|||
export type LoadMessageFn = ( |
|||
lang: SupportedLanguagesType, |
|||
) => Promise<Record<string, string> | undefined>; |
|||
|
|||
export interface LocaleSetupOptions { |
|||
/** |
|||
* Default language |
|||
* @default zh-CN |
|||
*/ |
|||
defaultLocale?: SupportedLanguagesType; |
|||
/** |
|||
* Load message function |
|||
* @param lang |
|||
* @returns |
|||
*/ |
|||
loadMessages?: LoadMessageFn; |
|||
/** |
|||
* Whether to warn when the key is not found |
|||
*/ |
|||
missingWarn?: boolean; |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/web.json", |
|||
"include": ["src"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
Loading…
Reference in new issue