Browse Source

init

master
zhanglei 2 weeks ago
parent
commit
526fd1044c
  1. 19
      vue-vben-admin/packages/effects/hooks/README.md
  2. 33
      vue-vben-admin/packages/effects/hooks/package.json
  3. 9
      vue-vben-admin/packages/effects/hooks/src/index.ts
  4. 38
      vue-vben-admin/packages/effects/hooks/src/use-app-config.ts
  5. 24
      vue-vben-admin/packages/effects/hooks/src/use-content-maximize.ts
  6. 321
      vue-vben-admin/packages/effects/hooks/src/use-design-tokens.ts
  7. 163
      vue-vben-admin/packages/effects/hooks/src/use-hover-toggle.ts
  8. 72
      vue-vben-admin/packages/effects/hooks/src/use-pagination.ts
  9. 16
      vue-vben-admin/packages/effects/hooks/src/use-refresh.ts
  10. 133
      vue-vben-admin/packages/effects/hooks/src/use-tabs.ts
  11. 84
      vue-vben-admin/packages/effects/hooks/src/use-watermark.ts
  12. 9
      vue-vben-admin/packages/effects/hooks/tsconfig.json
  13. 28
      vue-vben-admin/packages/locales/package.json
  14. 147
      vue-vben-admin/packages/locales/src/i18n.ts
  15. 30
      vue-vben-admin/packages/locales/src/index.ts
  16. 65
      vue-vben-admin/packages/locales/src/langs/en-US/authentication.json
  17. 24
      vue-vben-admin/packages/locales/src/langs/en-US/common.json
  18. 201
      vue-vben-admin/packages/locales/src/langs/en-US/preferences.json
  19. 4
      vue-vben-admin/packages/locales/src/langs/en-US/profile.json
  20. 121
      vue-vben-admin/packages/locales/src/langs/en-US/ui.json
  21. 65
      vue-vben-admin/packages/locales/src/langs/zh-CN/authentication.json
  22. 24
      vue-vben-admin/packages/locales/src/langs/zh-CN/common.json
  23. 201
      vue-vben-admin/packages/locales/src/langs/zh-CN/preferences.json
  24. 4
      vue-vben-admin/packages/locales/src/langs/zh-CN/profile.json
  25. 121
      vue-vben-admin/packages/locales/src/langs/zh-CN/ui.json
  26. 25
      vue-vben-admin/packages/locales/src/typing.ts
  27. 6
      vue-vben-admin/packages/locales/tsconfig.json

19
vue-vben-admin/packages/effects/hooks/README.md

@ -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';
```

33
vue-vben-admin/packages/effects/hooks/package.json

@ -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:"
}
}

9
vue-vben-admin/packages/effects/hooks/src/index.ts

@ -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';

38
vue-vben-admin/packages/effects/hooks/src/use-app-config.ts

@ -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;
}

24
vue-vben-admin/packages/effects/hooks/src/use-content-maximize.ts

@ -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,
};
}

321
vue-vben-admin/packages/effects/hooks/src/use-design-tokens.ts

@ -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 },
);
}

163
vue-vben-admin/packages/effects/hooks/src/use-hover-toggle.ts

@ -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];
}

72
vue-vben-admin/packages/effects/hooks/src/use-pagination.ts

@ -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 };
}

16
vue-vben-admin/packages/effects/hooks/src/use-refresh.ts

@ -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,
};
}

133
vue-vben-admin/packages/effects/hooks/src/use-tabs.ts

@ -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,
};
}

84
vue-vben-admin/packages/effects/hooks/src/use-watermark.ts

@ -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),
};
}

9
vue-vben-admin/packages/effects/hooks/tsconfig.json

@ -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"]
}

28
vue-vben-admin/packages/locales/package.json

@ -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:"
}
}

147
vue-vben-admin/packages/locales/src/i18n.ts

@ -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,
};

30
vue-vben-admin/packages/locales/src/index.ts

@ -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';

65
vue-vben-admin/packages/locales/src/langs/en-US/authentication.json

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

24
vue-vben-admin/packages/locales/src/langs/en-US/common.json

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

201
vue-vben-admin/packages/locales/src/langs/en-US/preferences.json

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

4
vue-vben-admin/packages/locales/src/langs/en-US/profile.json

@ -0,0 +1,4 @@
{
"updatePassword": "Update Password",
"updateBasicProfile": "Update Basic Profile"
}

121
vue-vben-admin/packages/locales/src/langs/en-US/ui.json

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

65
vue-vben-admin/packages/locales/src/langs/zh-CN/authentication.json

@ -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": "居右"
}
}

24
vue-vben-admin/packages/locales/src/langs/zh-CN/common.json

@ -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": "隐藏搜索面板"
}

201
vue-vben-admin/packages/locales/src/langs/zh-CN/preferences.json

@ -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": "启用刷新"
}
}

4
vue-vben-admin/packages/locales/src/langs/zh-CN/profile.json

@ -0,0 +1,4 @@
{
"updatePassword": "更新密码",
"updateBasicProfile": "更新基本信息"
}

121
vue-vben-admin/packages/locales/src/langs/zh-CN/ui.json

@ -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": "时区设置成功"
}
}
}

25
vue-vben-admin/packages/locales/src/typing.ts

@ -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;
}

6
vue-vben-admin/packages/locales/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}
Loading…
Cancel
Save