diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts
index 90ff7a45..d3e8be0a 100644
--- a/apps/web-antd/src/adapter/component/index.ts
+++ b/apps/web-antd/src/adapter/component/index.ts
@@ -167,6 +167,7 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
+
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
component: Cascader,
fieldNames: { label: 'label', value: 'value', children: 'children' },
@@ -174,34 +175,20 @@ async function initComponentAdapter() {
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
}),
- ApiSelect: withDefaultPlaceholder(
- {
- ...ApiComponent,
- name: 'ApiSelect',
- },
- 'select',
- {
- component: Select,
- loadingSlot: 'suffixIcon',
- visibleEvent: 'onDropdownVisibleChange',
- modelPropName: 'value',
- },
- ),
- ApiTreeSelect: withDefaultPlaceholder(
- {
- ...ApiComponent,
- name: 'ApiTreeSelect',
- },
- 'select',
- {
- component: TreeSelect,
- fieldNames: { label: 'label', value: 'value', children: 'children' },
- loadingSlot: 'suffixIcon',
- modelPropName: 'value',
- optionsPropName: 'treeData',
- visibleEvent: 'onVisibleChange',
- },
- ),
+ ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
+ component: Select,
+ loadingSlot: 'suffixIcon',
+ modelPropName: 'value',
+ visibleEvent: 'onVisibleChange',
+ }),
+ ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
+ component: TreeSelect,
+ fieldNames: { label: 'label', value: 'value', children: 'children' },
+ loadingSlot: 'suffixIcon',
+ modelPropName: 'value',
+ optionsPropName: 'treeData',
+ visibleEvent: 'onVisibleChange',
+ }),
AutoComplete,
Cascader: withDefaultPlaceholder(Cascader, 'select'),
Checkbox,
diff --git a/apps/web-antd/src/views/_core/profile/password-setting.vue b/apps/web-antd/src/views/_core/profile/password-setting.vue
index b246bc37..e5609c0b 100644
--- a/apps/web-antd/src/views/_core/profile/password-setting.vue
+++ b/apps/web-antd/src/views/_core/profile/password-setting.vue
@@ -1,14 +1,12 @@
{
'**/*.sh',
'**/*.ttf',
'**/*.woff',
+ '**/.github',
+ '**/lefthook.yml',
],
},
];
diff --git a/internal/lint-configs/eslint-config/src/configs/index.ts b/internal/lint-configs/eslint-config/src/configs/index.ts
index c0284efb..50f8e1cd 100644
--- a/internal/lint-configs/eslint-config/src/configs/index.ts
+++ b/internal/lint-configs/eslint-config/src/configs/index.ts
@@ -8,6 +8,7 @@ export * from './jsdoc';
export * from './jsonc';
export * from './node';
export * from './perfectionist';
+export * from './pnpm';
export * from './prettier';
export * from './regexp';
export * from './test';
@@ -15,3 +16,4 @@ export * from './turbo';
export * from './typescript';
export * from './unicorn';
export * from './vue';
+export * from './yaml';
diff --git a/internal/lint-configs/eslint-config/src/configs/jsonc.ts b/internal/lint-configs/eslint-config/src/configs/jsonc.ts
index 4072e4cd..4b92ff44 100644
--- a/internal/lint-configs/eslint-config/src/configs/jsonc.ts
+++ b/internal/lint-configs/eslint-config/src/configs/jsonc.ts
@@ -48,6 +48,7 @@ export async function jsonc(): Promise {
},
sortTsconfig(),
sortPackageJson(),
+ sortCspellJson(),
];
}
@@ -130,6 +131,21 @@ function sortPackageJson(): Linter.Config {
};
}
+function sortCspellJson(): Linter.Config {
+ return {
+ files: ['**/cspell.json', '**/.cspell.json'],
+ rules: {
+ 'jsonc/sort-array-values': [
+ 'error',
+ {
+ order: { type: 'asc' },
+ pathPattern: '^words$|^ignorePaths$',
+ },
+ ],
+ },
+ };
+}
+
function sortTsconfig(): Linter.Config {
return {
files: [
diff --git a/internal/lint-configs/eslint-config/src/configs/pnpm.ts b/internal/lint-configs/eslint-config/src/configs/pnpm.ts
new file mode 100644
index 00000000..a3b28a7f
--- /dev/null
+++ b/internal/lint-configs/eslint-config/src/configs/pnpm.ts
@@ -0,0 +1,41 @@
+import type { Linter } from 'eslint';
+
+import { interopDefault } from '../util';
+
+export async function pnpm(): Promise {
+ const [pluginPnpm, parserPnpm, parserJsonc] = await Promise.all([
+ interopDefault(import('eslint-plugin-pnpm')),
+ interopDefault(import('yaml-eslint-parser')),
+ interopDefault(import('jsonc-eslint-parser')),
+ ] as const);
+
+ return [
+ {
+ files: ['package.json', '**/package.json'],
+ languageOptions: {
+ parser: parserJsonc,
+ },
+ plugins: {
+ pnpm: pluginPnpm,
+ },
+ rules: {
+ 'pnpm/json-enforce-catalog': 'error',
+ 'pnpm/json-prefer-workspace-settings': 'error',
+ 'pnpm/json-valid-catalog': 'error',
+ },
+ },
+ {
+ files: ['pnpm-workspace.yaml'],
+ languageOptions: {
+ parser: parserPnpm,
+ },
+ plugins: {
+ pnpm: pluginPnpm,
+ },
+ rules: {
+ 'pnpm/yaml-no-duplicate-catalog-item': 'error',
+ 'pnpm/yaml-no-unused-catalog-item': 'error',
+ },
+ },
+ ];
+}
diff --git a/internal/lint-configs/eslint-config/src/configs/yaml.ts b/internal/lint-configs/eslint-config/src/configs/yaml.ts
new file mode 100644
index 00000000..55aa94d5
--- /dev/null
+++ b/internal/lint-configs/eslint-config/src/configs/yaml.ts
@@ -0,0 +1,87 @@
+import type { Linter } from 'eslint';
+
+import { interopDefault } from '../util';
+
+export async function yaml(): Promise {
+ const [pluginYaml, parserYaml] = await Promise.all([
+ interopDefault(import('eslint-plugin-yml')),
+ interopDefault(import('yaml-eslint-parser')),
+ ] as const);
+
+ return [
+ {
+ files: ['**/*.y?(a)ml'],
+ plugins: {
+ yaml: pluginYaml as any,
+ },
+ languageOptions: {
+ parser: parserYaml,
+ },
+ rules: {
+ 'style/spaced-comment': 'off',
+
+ 'yaml/block-mapping': 'error',
+ 'yaml/block-sequence': 'error',
+ 'yaml/no-empty-key': 'error',
+ 'yaml/no-empty-sequence-entry': 'error',
+ 'yaml/no-irregular-whitespace': 'error',
+ 'yaml/plain-scalar': 'error',
+
+ 'yaml/vue-custom-block/no-parsing-error': 'error',
+
+ 'yaml/block-mapping-question-indicator-newline': 'error',
+ 'yaml/block-sequence-hyphen-indicator-newline': 'error',
+ 'yaml/flow-mapping-curly-newline': 'error',
+ 'yaml/flow-mapping-curly-spacing': 'error',
+ 'yaml/flow-sequence-bracket-newline': 'error',
+ 'yaml/flow-sequence-bracket-spacing': 'error',
+ 'yaml/indent': ['error', 2],
+ 'yaml/key-spacing': 'error',
+ 'yaml/no-tab-indent': 'error',
+ 'yaml/quotes': [
+ 'error',
+ {
+ avoidEscape: true,
+ prefer: 'single',
+ },
+ ],
+ 'yaml/spaced-comment': 'error',
+ },
+ },
+ {
+ files: ['pnpm-workspace.yaml'],
+ rules: {
+ 'yaml/sort-keys': [
+ 'error',
+ {
+ order: [
+ 'packages',
+ 'overrides',
+ 'patchedDependencies',
+ 'hoistPattern',
+ 'catalog',
+ 'catalogs',
+
+ 'allowedDeprecatedVersions',
+ 'allowNonAppliedPatches',
+ 'configDependencies',
+ 'ignoredBuiltDependencies',
+ 'ignoredOptionalDependencies',
+ 'neverBuiltDependencies',
+ 'onlyBuiltDependencies',
+ 'onlyBuiltDependenciesFile',
+ 'packageExtensions',
+ 'peerDependencyRules',
+ 'supportedArchitectures',
+ ],
+ pathPattern: '^$',
+ },
+ {
+ order: { type: 'asc' },
+ pathPattern: '.*',
+ },
+ ],
+ },
+ },
+ ];
+}
diff --git a/internal/lint-configs/eslint-config/src/index.ts b/internal/lint-configs/eslint-config/src/index.ts
index c9f08bd5..d3bbf575 100644
--- a/internal/lint-configs/eslint-config/src/index.ts
+++ b/internal/lint-configs/eslint-config/src/index.ts
@@ -11,6 +11,7 @@ import {
jsonc,
node,
perfectionist,
+ pnpm,
prettier,
regexp,
test,
@@ -18,6 +19,7 @@ import {
typescript,
unicorn,
vue,
+ yaml,
} from './configs';
import { customConfig } from './custom-config';
@@ -48,6 +50,8 @@ async function defineConfig(config: FlatConfig[] = []) {
regexp(),
command(),
turbo(),
+ yaml(),
+ pnpm(),
...customConfig,
...config,
];
diff --git a/internal/lint-configs/eslint-config/tsconfig.json b/internal/lint-configs/eslint-config/tsconfig.json
index dbd3bcc8..b2ec3b61 100644
--- a/internal/lint-configs/eslint-config/tsconfig.json
+++ b/internal/lint-configs/eslint-config/tsconfig.json
@@ -1,9 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
- "compilerOptions": {
- "moduleResolution": "bundler"
- },
"include": ["src"],
"exclude": ["node_modules"]
}
diff --git a/internal/lint-configs/prettier-config/package.json b/internal/lint-configs/prettier-config/package.json
index 65e8b8f8..16ef11ed 100644
--- a/internal/lint-configs/prettier-config/package.json
+++ b/internal/lint-configs/prettier-config/package.json
@@ -1,6 +1,6 @@
{
"name": "@vben/prettier-config",
- "version": "5.0.0",
+ "version": "5.5.9",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
diff --git a/internal/lint-configs/stylelint-config/index.mjs b/internal/lint-configs/stylelint-config/index.mjs
index 08ac8238..47cd12d8 100644
--- a/internal/lint-configs/stylelint-config/index.mjs
+++ b/internal/lint-configs/stylelint-config/index.mjs
@@ -75,6 +75,7 @@ export default {
'import-notation': null,
'media-feature-range-notation': null,
'named-grid-areas-no-invalid': null,
+ 'nesting-selector-no-missing-scoping-root': null,
'no-descending-specificity': null,
'no-empty-source': null,
'order/order': [
diff --git a/internal/tailwind-config/tsconfig.json b/internal/tailwind-config/tsconfig.json
index dbd3bcc8..b2ec3b61 100644
--- a/internal/tailwind-config/tsconfig.json
+++ b/internal/tailwind-config/tsconfig.json
@@ -1,9 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
- "compilerOptions": {
- "moduleResolution": "bundler"
- },
"include": ["src"],
"exclude": ["node_modules"]
}
diff --git a/internal/tsconfig/node.json b/internal/tsconfig/node.json
index 31ce8f18..01bde7a7 100644
--- a/internal/tsconfig/node.json
+++ b/internal/tsconfig/node.json
@@ -6,6 +6,7 @@
"composite": false,
"lib": ["ESNext"],
"baseUrl": "./",
+ "moduleResolution": "bundler",
"types": ["node"],
"noImplicitAny": true
}
diff --git a/internal/vite-config/src/config/index.ts b/internal/vite-config/src/config/index.ts
index d04a84a8..b8c770a9 100644
--- a/internal/vite-config/src/config/index.ts
+++ b/internal/vite-config/src/config/index.ts
@@ -1,4 +1,4 @@
-import type { DefineConfig } from '../typing';
+import type { DefineConfig, VbenViteConfig } from '../typing';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
@@ -12,7 +12,7 @@ export * from './library';
function defineConfig(
userConfigPromise?: DefineConfig,
type: 'application' | 'auto' | 'library' = 'auto',
-) {
+): VbenViteConfig {
let projectType = type;
// 根据包是否存在 index.html,自动判断类型
diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts
index f730bdb9..2dd04830 100644
--- a/internal/vite-config/src/typing.ts
+++ b/internal/vite-config/src/typing.ts
@@ -1,5 +1,10 @@
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
-import type { ConfigEnv, PluginOption, UserConfig } from 'vite';
+import type {
+ ConfigEnv,
+ PluginOption,
+ UserConfig,
+ UserConfigFnPromise,
+} from 'vite';
import type { PluginOptions } from 'vite-plugin-dts';
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
@@ -327,6 +332,8 @@ type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{
*/
type DefineConfig = DefineApplicationOptions | DefineLibraryOptions;
+type VbenViteConfig = Promise | UserConfig | UserConfigFnPromise;
+
export type {
ApplicationPluginOptions,
ArchiverPluginOptions,
@@ -340,4 +347,5 @@ export type {
LibraryPluginOptions,
NitroMockPluginOptions,
PrintPluginOptions,
+ VbenViteConfig,
};
diff --git a/package.json b/package.json
index b59d1c30..0e5fd90e 100644
--- a/package.json
+++ b/package.json
@@ -92,28 +92,8 @@
"vue-tsc": "catalog:"
},
"engines": {
- "node": ">=20.12.0",
+ "node": ">=20.19.0",
"pnpm": ">=10.0.0"
},
- "packageManager": "pnpm@10.22.0",
- "pnpm": {
- "peerDependencyRules": {
- "allowedVersions": {
- "eslint": "*"
- }
- },
- "overrides": {
- "@ast-grep/napi": "catalog:",
- "@ctrl/tinycolor": "catalog:",
- "clsx": "catalog:",
- "esbuild": "0.25.3",
- "jiti": "catalog:",
- "pinia": "catalog:",
- "vue": "catalog:"
- },
- "neverBuiltDependencies": [
- "canvas",
- "node-gyp"
- ]
- }
+ "packageManager": "pnpm@10.28.1"
}
diff --git a/packages/@core/base/design/src/css/global.css b/packages/@core/base/design/src/css/global.css
index dc154fc7..cd78a23f 100644
--- a/packages/@core/base/design/src/css/global.css
+++ b/packages/@core/base/design/src/css/global.css
@@ -18,9 +18,9 @@
font-size: var(--font-size-base, 16px);
font-variation-settings: normal;
+ font-synthesis-weight: none;
line-height: 1.15;
text-size-adjust: 100%;
- font-synthesis-weight: none;
scroll-behavior: smooth;
text-rendering: optimizelegibility;
-webkit-tap-highlight-color: transparent;
diff --git a/packages/@core/base/shared/package.json b/packages/@core/base/shared/package.json
index 2e453400..159ba3cc 100644
--- a/packages/@core/base/shared/package.json
+++ b/packages/@core/base/shared/package.json
@@ -93,9 +93,6 @@
},
"devDependencies": {
"@types/lodash.clonedeep": "catalog:",
- "@types/lodash.get": "catalog:",
- "@types/lodash.isequal": "catalog:",
- "@types/lodash.set": "catalog:",
"@types/nprogress": "catalog:"
}
}
diff --git a/packages/@core/base/shared/src/utils/__tests__/dom.test.ts b/packages/@core/base/shared/src/utils/__tests__/dom.test.ts
index df51268a..ffc5b49f 100644
--- a/packages/@core/base/shared/src/utils/__tests__/dom.test.ts
+++ b/packages/@core/base/shared/src/utils/__tests__/dom.test.ts
@@ -116,11 +116,11 @@ describe('getElementVisibleRect', () => {
} as HTMLElement;
expect(getElementVisibleRect(element)).toEqual({
- bottom: 800,
+ bottom: 0,
height: 0,
- left: 1100,
- right: 1000,
- top: 900,
+ left: 0,
+ right: 0,
+ top: 0,
width: 0,
});
});
diff --git a/packages/@core/base/shared/src/utils/dom.ts b/packages/@core/base/shared/src/utils/dom.ts
index 69617176..35a7e5ff 100644
--- a/packages/@core/base/shared/src/utils/dom.ts
+++ b/packages/@core/base/shared/src/utils/dom.ts
@@ -41,6 +41,18 @@ export function getElementVisibleRect(
const left = Math.max(rect.left, 0);
const right = Math.min(rect.right, viewWidth);
+ // 如果元素完全不可见,则返回一个空的矩形
+ if (top >= viewHeight || bottom <= 0 || left >= viewWidth || right <= 0) {
+ return {
+ bottom: 0,
+ height: 0,
+ left: 0,
+ right: 0,
+ top: 0,
+ width: 0,
+ };
+ }
+
return {
bottom,
height: Math.max(0, bottom - top),
diff --git a/packages/@core/composables/src/__tests__/use-sortable.test.ts b/packages/@core/composables/src/__tests__/use-sortable.test.ts
index e7ba1f13..3524143a 100644
--- a/packages/@core/composables/src/__tests__/use-sortable.test.ts
+++ b/packages/@core/composables/src/__tests__/use-sortable.test.ts
@@ -29,9 +29,9 @@ describe('useSortable', () => {
await initializeSortable();
// Import sortablejs to access the mocked create function
- const Sortable = await import(
- 'sortablejs/modular/sortable.complete.esm.js'
- );
+ const Sortable =
+ // @ts-expect-error - This is a dynamic import
+ await import('sortablejs/modular/sortable.complete.esm.js');
// Verify that Sortable.create was called with the correct parameters
expect(Sortable.default.create).toHaveBeenCalledTimes(1);
diff --git a/packages/@core/preferences/src/preferences.ts b/packages/@core/preferences/src/preferences.ts
index 93487d2c..1dfd530c 100644
--- a/packages/@core/preferences/src/preferences.ts
+++ b/packages/@core/preferences/src/preferences.ts
@@ -63,8 +63,9 @@ class PreferenceManager {
/**
* 初始化偏好设置
- * @param namespace - 命名空间,用于隔离不同应用的配置
- * @param overrides - 要覆盖的偏好设置
+ * @param options - 初始化配置项
+ * @param options.namespace - 命名空间,用于隔离不同应用的配置
+ * @param options.overrides - 要覆盖的偏好设置
*/
initPreferences = async ({ namespace, overrides }: InitialOptions) => {
// 防止重复初始化
diff --git a/packages/@core/preferences/src/use-preferences.ts b/packages/@core/preferences/src/use-preferences.ts
index 94f28bef..b08eac19 100644
--- a/packages/@core/preferences/src/use-preferences.ts
+++ b/packages/@core/preferences/src/use-preferences.ts
@@ -135,7 +135,7 @@ function usePreferences() {
});
/**
- * @zh_CN 登录注册页面布局是否为左侧
+ * @zh_CN 登录注册页面布局是否为右侧
*/
const authPanelRight = computed(() => {
return appPreferences.value.authPageLayout === 'panel-right';
diff --git a/packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts b/packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts
index 9881db14..cec74818 100644
--- a/packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts
+++ b/packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts
@@ -82,10 +82,8 @@ export default function useDependencies(
// 2. 判断show,如果show为false,则隐藏
if (isFunction(show)) {
isShow.value = !!(await show(formValues, formApi));
- if (!isShow.value) return;
} else if (isBoolean(show)) {
isShow.value = show;
- if (!isShow.value) return;
}
if (isFunction(componentProps)) {
diff --git a/packages/@core/ui-kit/form-ui/src/form-render/form.vue b/packages/@core/ui-kit/form-ui/src/form-render/form.vue
index 8a47b508..73422b64 100644
--- a/packages/@core/ui-kit/form-ui/src/form-render/form.vue
+++ b/packages/@core/ui-kit/form-ui/src/form-render/form.vue
@@ -53,7 +53,11 @@ const wrapperClass = computed(() => {
provideFormRenderProps(props);
-const { isCalculated, keepFormItemIndex, wrapperRef } = useExpandable(props);
+const {
+ isCalculated,
+ keepFormItemIndex,
+ wrapperRef: _wrapperRef,
+} = useExpandable(props);
const shapes = computed(() => {
const resultShapes: FormShape[] = [];
@@ -170,7 +174,7 @@ const computedSchema = computed(
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/effects/common-ui/src/components/cropper/index.ts b/packages/effects/common-ui/src/components/cropper/index.ts
new file mode 100644
index 00000000..58a8f0ab
--- /dev/null
+++ b/packages/effects/common-ui/src/components/cropper/index.ts
@@ -0,0 +1 @@
+export { default as VCropper } from './cropper.vue';
diff --git a/packages/effects/common-ui/src/components/index.ts b/packages/effects/common-ui/src/components/index.ts
index ccbe6b52..13fc3c7a 100644
--- a/packages/effects/common-ui/src/components/index.ts
+++ b/packages/effects/common-ui/src/components/index.ts
@@ -3,6 +3,7 @@ export * from './captcha';
export * from './code-mirror';
export * from './col-page';
export * from './count-to';
+export * from './cropper';
export * from './ellipsis-text';
export * from './icon-picker';
export * from './json-preview';
diff --git a/packages/effects/common-ui/src/ui/profile/base-setting.vue b/packages/effects/common-ui/src/ui/profile/base-setting.vue
index 1ef1c1a0..15fc97ba 100644
--- a/packages/effects/common-ui/src/ui/profile/base-setting.vue
+++ b/packages/effects/common-ui/src/ui/profile/base-setting.vue
@@ -5,6 +5,8 @@ import type { VbenFormSchema } from '@vben-core/form-ui';
import { computed, reactive } from 'vue';
+import { $t } from '@vben/locales';
+
import { useVbenForm } from '@vben-core/form-ui';
import { VbenButton } from '@vben-core/shadcn-ui';
@@ -50,7 +52,7 @@ defineExpose({
- 更新基本信息
+ {{ $t('profile.updateBasicProfile') }}
diff --git a/packages/effects/common-ui/src/ui/profile/password-setting.vue b/packages/effects/common-ui/src/ui/profile/password-setting.vue
index 1d90e086..06b9b3d1 100644
--- a/packages/effects/common-ui/src/ui/profile/password-setting.vue
+++ b/packages/effects/common-ui/src/ui/profile/password-setting.vue
@@ -5,6 +5,8 @@ import type { VbenFormSchema } from '@vben-core/form-ui';
import { computed, reactive } from 'vue';
+import { $t } from '@vben/locales';
+
import { useVbenForm } from '@vben-core/form-ui';
import { VbenButton } from '@vben-core/shadcn-ui';
@@ -23,6 +25,7 @@ const emit = defineEmits<{
const [Form, formApi] = useVbenForm(
reactive({
commonConfig: {
+ labelWidth: 130,
// 所有表单项
componentProps: {
class: 'w-full',
@@ -50,7 +53,7 @@ defineExpose({
- 更新密码
+ {{ $t('profile.updatePassword') }}
diff --git a/packages/effects/layouts/src/basic/layout.vue b/packages/effects/layouts/src/basic/layout.vue
index 6263fbdf..45b97833 100644
--- a/packages/effects/layouts/src/basic/layout.vue
+++ b/packages/effects/layouts/src/basic/layout.vue
@@ -14,7 +14,7 @@ import {
updatePreferences,
usePreferences,
} from '@vben/preferences';
-import { useAccessStore } from '@vben/stores';
+import { useAccessStore, useTabbarStore, useTimezoneStore } from '@vben/stores';
import { cloneDeep, mapTree } from '@vben/utils';
import { VbenAdminLayout } from '@vben-core/layout-ui';
@@ -52,6 +52,7 @@ const {
theme,
} = usePreferences();
const accessStore = useAccessStore();
+const timezoneStore = useTimezoneStore();
const { refresh } = useRefresh();
const sidebarTheme = computed(() => {
@@ -187,9 +188,19 @@ watch(
},
);
+const tabbarStore = useTabbarStore();
+
+function refreshAll() {
+ tabbarStore.cachedTabs.clear();
+ refresh();
+}
+
// 语言更新后,刷新页面
// i18n.global.locale会在preference.app.locale变更之后才会更新,因此watchpreference.app.locale是不合适的,刷新页面时可能语言配置尚未完全加载完成
-watch(i18n.global.locale, refresh, { flush: 'post' });
+watch(i18n.global.locale, refreshAll, { flush: 'post' });
+
+// 时区更新后,刷新页面
+watch(() => timezoneStore.timezone, refreshAll, { flush: 'post' });
const slots: SetupContext['slots'] = useSlots();
const headerSlots = computed(() => {
diff --git a/packages/effects/plugins/src/echarts/use-echarts.ts b/packages/effects/plugins/src/echarts/use-echarts.ts
index 469ce58e..47c5af93 100644
--- a/packages/effects/plugins/src/echarts/use-echarts.ts
+++ b/packages/effects/plugins/src/echarts/use-echarts.ts
@@ -92,7 +92,8 @@ function useEcharts(chartRef: Ref) {
return;
}
useTimeoutFn(() => {
- if (!chartInstance) {
+ if (!chartInstance || chartInstance?.getDom() !== el) {
+ chartInstance?.dispose();
const instance = initCharts();
if (!instance) return;
}
@@ -104,6 +105,36 @@ function useEcharts(chartRef: Ref) {
});
};
+ const updateDate = (
+ option: EChartsOption,
+ notMerge = false, // false = 合并(保留动画),true = 完全替换
+ lazyUpdate = false, // true 时不立即重绘,适合短时间内多次调用
+ ): Promise => {
+ return new Promise((resolve) => {
+ nextTick(() => {
+ if (!chartInstance) {
+ // 还没初始化 → 当作首次渲染
+ renderEcharts(option).then(resolve);
+ return;
+ }
+
+ // 合并你原有的全局配置(比如 backgroundColor)
+ const finalOption = {
+ ...option,
+ ...getOptions.value,
+ };
+
+ chartInstance.setOption(finalOption, {
+ notMerge,
+ lazyUpdate,
+ // silent: true, // 如果追求极致性能可开启(关闭所有事件)
+ });
+
+ resolve(chartInstance);
+ });
+ });
+ };
+
function resize(withAnimation = true) {
const el = getChartEl();
if (isElHidden(el)) {
@@ -141,6 +172,7 @@ function useEcharts(chartRef: Ref) {
return {
renderEcharts,
resize,
+ updateDate,
getChartInstance: () => chartInstance,
};
}
diff --git a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
index c1a73260..da828049 100644
--- a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
+++ b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
@@ -320,6 +320,7 @@ async function init() {
'[Vben Vxe Table]: The formConfig in the grid is not supported, please use the `formOptions` props',
);
}
+ // @ts-ignore
props.api?.setState?.({ gridOptions: defaultGridOptions });
// form 由 vben-form 代替,所以需要保证query相关事件可以拿到参数
extendProxyOptions(props.api, defaultGridOptions, () =>
diff --git a/packages/locales/src/langs/en-US/profile.json b/packages/locales/src/langs/en-US/profile.json
new file mode 100644
index 00000000..1c17e2ec
--- /dev/null
+++ b/packages/locales/src/langs/en-US/profile.json
@@ -0,0 +1,4 @@
+{
+ "updatePassword": "Update Password",
+ "updateBasicProfile": "Update Basic Profile"
+}
diff --git a/packages/locales/src/langs/en-US/ui.json b/packages/locales/src/langs/en-US/ui.json
index 31b3325f..4085b262 100644
--- a/packages/locales/src/langs/en-US/ui.json
+++ b/packages/locales/src/langs/en-US/ui.json
@@ -54,6 +54,13 @@
"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.",
diff --git a/packages/locales/src/langs/zh-CN/profile.json b/packages/locales/src/langs/zh-CN/profile.json
new file mode 100644
index 00000000..a38908c5
--- /dev/null
+++ b/packages/locales/src/langs/zh-CN/profile.json
@@ -0,0 +1,4 @@
+{
+ "updatePassword": "更新密码",
+ "updateBasicProfile": "更新基本信息"
+}
diff --git a/packages/locales/src/langs/zh-CN/ui.json b/packages/locales/src/langs/zh-CN/ui.json
index 383ae88c..f324182f 100644
--- a/packages/locales/src/langs/zh-CN/ui.json
+++ b/packages/locales/src/langs/zh-CN/ui.json
@@ -54,6 +54,13 @@
"copy": "复制",
"copied": "已复制"
},
+ "crop": {
+ "title": "图片裁剪",
+ "titleTip": "裁剪比例 {0}",
+ "confirm": "裁剪",
+ "cancel": "取消裁剪",
+ "errorTip": "裁剪错误"
+ },
"fallback": {
"pageNotFound": "哎呀!未找到页面",
"pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。",
diff --git a/playground/src/adapter/component/index.ts b/playground/src/adapter/component/index.ts
index b0e43c9a..80d990b7 100644
--- a/playground/src/adapter/component/index.ts
+++ b/playground/src/adapter/component/index.ts
@@ -3,6 +3,8 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
+/* eslint-disable vue/one-component-per-file */
+
import type {
UploadChangeParam,
UploadFile,
@@ -15,6 +17,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import {
+ computed,
defineAsyncComponent,
defineComponent,
h,
@@ -24,12 +27,17 @@ import {
watch,
} from 'vue';
-import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
+import {
+ ApiComponent,
+ globalShareState,
+ IconPicker,
+ VCropper,
+} from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { isEmpty } from '@vben/utils';
-import { message, notification } from 'ant-design-vue';
+import { message, Modal, notification } from 'ant-design-vue';
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
@@ -99,7 +107,6 @@ const withDefaultPlaceholder = (
$t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
- // const publicApi: Recordable = {};
expose(
new Proxy(
{},
@@ -109,14 +116,6 @@ const withDefaultPlaceholder = (
},
),
);
- // const instance = getCurrentInstance();
- // instance?.proxy?.$nextTick(() => {
- // for (const key in innerRef.value) {
- // if (typeof innerRef.value[key] === 'function') {
- // publicApi[key] = innerRef.value[key];
- // }
- // }
- // });
return () =>
h(
component,
@@ -128,6 +127,33 @@ const withDefaultPlaceholder = (
};
const withPreviewUpload = () => {
+ // 检查是否为图片文件的辅助函数
+ const isImageFile = (file: UploadFile): boolean => {
+ const imageExtensions = new Set([
+ 'bmp',
+ 'gif',
+ 'jpeg',
+ 'jpg',
+ 'png',
+ 'svg',
+ 'webp',
+ ]);
+ if (file.url) {
+ try {
+ const pathname = new URL(file.url, 'http://localhost').pathname;
+ const ext = pathname.split('.').pop()?.toLowerCase();
+ return ext ? imageExtensions.has(ext) : false;
+ } catch {
+ const ext = file.url?.split('.').pop()?.toLowerCase();
+ return ext ? imageExtensions.has(ext) : false;
+ }
+ }
+ if (!file.type) {
+ const ext = file.name?.split('.').pop()?.toLowerCase();
+ return ext ? imageExtensions.has(ext) : false;
+ }
+ return file.type.startsWith('image/');
+ };
// 创建默认的上传按钮插槽
const createDefaultSlotsWithUpload = (
listType: string,
@@ -162,27 +188,6 @@ const withPreviewUpload = () => {
visible: Ref,
fileList: Ref,
) => {
- // 检查是否为图片文件的辅助函数
- const isImageFile = (file: UploadFile): boolean => {
- const imageExtensions = new Set([
- 'bmp',
- 'gif',
- 'jpeg',
- 'jpg',
- 'png',
- 'webp',
- ]);
- if (file.url) {
- const ext = file.url?.split('.').pop()?.toLowerCase();
- return ext ? imageExtensions.has(ext) : false;
- }
- if (!file.type) {
- const ext = file.name?.split('.').pop()?.toLowerCase();
- return ext ? imageExtensions.has(ext) : false;
- }
- return file.type.startsWith('image/');
- };
-
// 如果当前文件不是图片,直接打开
if (!isImageFile(file)) {
if (file.url) {
@@ -268,6 +273,100 @@ const withPreviewUpload = () => {
render(h(PreviewWrapper), container);
};
+
+ // 图片裁剪操作
+ const cropImage = (file: File, aspectRatio: string | undefined) => {
+ return new Promise((resolve, reject) => {
+ const container: HTMLElement | null = document.createElement('div');
+ document.body.append(container);
+
+ // 用于追踪组件是否已卸载
+ let isUnmounted = false;
+ let objectUrl: null | string = null;
+
+ const open = ref(true);
+ const cropperRef = ref | null>(null);
+
+ const closeModal = () => {
+ open.value = false;
+ // 延迟清理,确保动画完成
+ setTimeout(() => {
+ if (!isUnmounted && container) {
+ if (objectUrl) {
+ URL.revokeObjectURL(objectUrl);
+ }
+ isUnmounted = true;
+ render(null, container);
+ container.remove();
+ }
+ }, 300);
+ };
+
+ const CropperWrapper = {
+ setup() {
+ return () => {
+ if (isUnmounted) return null;
+ if (!objectUrl) {
+ objectUrl = URL.createObjectURL(file);
+ }
+ return h(
+ Modal,
+ {
+ open: open.value,
+ title: h('div', {}, [
+ $t('ui.crop.title'),
+ h(
+ 'span',
+ {
+ class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
+ },
+ $t('ui.crop.titleTip', [aspectRatio]),
+ ),
+ ]),
+ centered: true,
+ width: 548,
+ keyboard: false,
+ maskClosable: false,
+ closable: false,
+ cancelText: $t('common.cancel'),
+ okText: $t('ui.crop.confirm'),
+ destroyOnClose: true,
+ onOk: async () => {
+ const cropper = cropperRef.value;
+ if (!cropper) {
+ reject(new Error('Cropper not found'));
+ closeModal();
+ return;
+ }
+ try {
+ const dataUrl = await cropper.getCropImage();
+ resolve(dataUrl);
+ } catch {
+ reject(new Error($t('ui.crop.errorTip')));
+ } finally {
+ closeModal();
+ }
+ },
+ onCancel() {
+ resolve('');
+ closeModal();
+ },
+ },
+ () =>
+ h(VCropper, {
+ ref: (ref: any) => (cropperRef.value = ref),
+ img: objectUrl as string,
+ aspectRatio,
+ }),
+ );
+ };
+ },
+ };
+
+ render(h(CropperWrapper), container);
+ });
+ };
+
return defineComponent({
name: Upload.name,
emits: ['update:modelValue'],
@@ -285,16 +384,51 @@ const withPreviewUpload = () => {
attrs?.fileList || attrs?.['file-list'] || [],
);
- const handleBeforeUpload = (file: UploadFile) => {
- if (attrs.maxSize && (file.size || 0) / 1024 / 1024 > attrs.maxSize) {
- message.error($t('ui.formRules.sizeLimit', [attrs.maxSize]));
+ const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);
+ const aspectRatio = computed(
+ () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
+ );
+
+ const handleBeforeUpload = async (
+ file: UploadFile,
+ originFileList: Array,
+ ) => {
+ if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
+ message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
file.status = 'removed';
return false;
}
+ // 多选或者非图片不唤起裁剪框
+ if (
+ attrs.crop &&
+ !attrs.multiple &&
+ originFileList[0] &&
+ isImageFile(file)
+ ) {
+ file.status = 'removed';
+ // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取
+ const blob = await cropImage(originFileList[0], aspectRatio.value);
+ return new Promise((resolve, reject) => {
+ if (!blob) {
+ return reject(new Error($t('ui.crop.errorTip')));
+ }
+ resolve(blob);
+ });
+ }
+
return attrs.beforeUpload?.(file) ?? true;
};
- const handleChange = async (event: UploadChangeParam) => {
+ const handleChange = (event: UploadChangeParam) => {
+ try {
+ // 行内写法 handleChange: (event) => {}
+ attrs.handleChange?.(event);
+ // template写法 @handle-change="(event) => {}"
+ attrs.onHandleChange?.(event);
+ } catch (error) {
+ // Avoid breaking internal v-model sync on user handler errors
+ console.error(error);
+ }
fileList.value = event.fileList.filter(
(file) => file.status !== 'removed',
);
diff --git a/playground/src/locales/langs/en-US/examples.json b/playground/src/locales/langs/en-US/examples.json
index 42a42d54..05035487 100644
--- a/playground/src/locales/langs/en-US/examples.json
+++ b/playground/src/locales/langs/en-US/examples.json
@@ -23,6 +23,7 @@
"upload-error": "Partial file upload failed",
"upload-urls": "Urls after file upload",
"file": "file",
+ "crop-image": "Crop image",
"upload-image": "Click to upload image"
},
"vxeTable": {
@@ -75,5 +76,8 @@
},
"function": {
"contentMenu": "Content Menu"
+ },
+ "cropper": {
+ "title": "Cropper"
}
}
diff --git a/playground/src/locales/langs/zh-CN/examples.json b/playground/src/locales/langs/zh-CN/examples.json
index 8808b5a9..3b0d934c 100644
--- a/playground/src/locales/langs/zh-CN/examples.json
+++ b/playground/src/locales/langs/zh-CN/examples.json
@@ -26,6 +26,7 @@
"upload-error": "部分文件上传失败",
"upload-urls": "文件上传后的网址",
"file": "文件",
+ "crop-image": "裁剪图片",
"upload-image": "点击上传图片"
},
"vxeTable": {
@@ -75,5 +76,8 @@
},
"function": {
"contentMenu": "上下文菜单"
+ },
+ "cropper": {
+ "title": "图片裁剪"
}
}
diff --git a/playground/src/router/guard.ts b/playground/src/router/guard.ts
index 514bd2eb..167a84d1 100644
--- a/playground/src/router/guard.ts
+++ b/playground/src/router/guard.ts
@@ -108,9 +108,9 @@ function setupAccessGuard(router: Router) {
let redirectPath: string;
if (from.query.redirect) {
redirectPath = from.query.redirect as string;
- } else if (to.path === preferences.app.defaultHomePath) {
+ } else if (to.fullPath === preferences.app.defaultHomePath) {
redirectPath = preferences.app.defaultHomePath;
- } else if (userInfo.homePath && to.path === userInfo.homePath) {
+ } else if (userInfo.homePath && to.fullPath === userInfo.homePath) {
redirectPath = userInfo.homePath;
} else {
redirectPath = to.fullPath;
diff --git a/playground/src/router/routes/modules/demos.ts b/playground/src/router/routes/modules/demos.ts
index 3df4f0ad..b8d27ef5 100644
--- a/playground/src/router/routes/modules/demos.ts
+++ b/playground/src/router/routes/modules/demos.ts
@@ -157,9 +157,7 @@ const routes: RouteRecordRaw[] = [
name: 'HideChildrenInMenuDemo',
path: '',
component: () =>
- import(
- '#/views/demos/features/hide-menu-children/parent.vue'
- ),
+ import('#/views/demos/features/hide-menu-children/parent.vue'),
meta: {
// hideInMenu: true,
title: $t('demos.features.hideChildrenInMenu'),
@@ -169,9 +167,7 @@ const routes: RouteRecordRaw[] = [
name: 'HideChildrenInMenuChildrenDemo',
path: '/demos/features/hide-menu-children/children',
component: () =>
- import(
- '#/views/demos/features/hide-menu-children/children.vue'
- ),
+ import('#/views/demos/features/hide-menu-children/children.vue'),
meta: {
activePath: '/demos/features/hide-menu-children',
title: $t('demos.features.hideChildrenInMenu'),
@@ -247,9 +243,7 @@ const routes: RouteRecordRaw[] = [
name: 'RequestParamsSerializerDemo',
path: '/demos/features/request-params-serializer',
component: () =>
- import(
- '#/views/demos/features/request-params-serializer/index.vue'
- ),
+ import('#/views/demos/features/request-params-serializer/index.vue'),
meta: {
icon: 'lucide:git-pull-request-arrow',
title: $t('demos.features.requestParamsSerializer'),
diff --git a/playground/src/router/routes/modules/examples.ts b/playground/src/router/routes/modules/examples.ts
index 5361fcc8..017b2c22 100644
--- a/playground/src/router/routes/modules/examples.ts
+++ b/playground/src/router/routes/modules/examples.ts
@@ -337,6 +337,15 @@ const routes: RouteRecordRaw[] = [
title: $t('examples.function.contentMenu'),
},
},
+ {
+ name: 'CropperDemo',
+ path: '/examples/cropper',
+ component: () => import('#/views/examples/cropper/index.vue'),
+ meta: {
+ icon: 'mdi:crop',
+ title: $t('examples.cropper.title'),
+ },
+ },
],
},
];
diff --git a/playground/src/store/auth.ts b/playground/src/store/auth.ts
index 4adeb76e..b50ee772 100644
--- a/playground/src/store/auth.ts
+++ b/playground/src/store/auth.ts
@@ -78,15 +78,22 @@ export const useAuthStore = defineStore('auth', () => {
};
}
+ const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环.
+
async function logout(redirect: boolean = true) {
+ if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回.
+ isLoggingOut.value = true; // 设置 标识
+
try {
await logoutApi();
} catch {
// 不做任何处理
- }
+ } finally {
+ isLoggingOut.value = false; // 重置 标识
- resetAllStores();
- accessStore.setLoginExpired(false);
+ resetAllStores();
+ accessStore.setLoginExpired(false);
+ }
// 回登录页带上当前路由地址
await router.replace({
diff --git a/playground/src/views/_core/profile/password-setting.vue b/playground/src/views/_core/profile/password-setting.vue
index b246bc37..e5609c0b 100644
--- a/playground/src/views/_core/profile/password-setting.vue
+++ b/playground/src/views/_core/profile/password-setting.vue
@@ -1,14 +1,12 @@
{
},
];
};
-
一共四个菜单(刷新、关闭当前、关闭其他、关闭所有)
-
-
+
+
diff --git a/playground/src/views/examples/cropper/index.vue b/playground/src/views/examples/cropper/index.vue
new file mode 100644
index 00000000..09ba10c2
--- /dev/null
+++ b/playground/src/views/examples/cropper/index.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![裁剪预览]()
+
+
+
+
+
+
diff --git a/playground/src/views/examples/form/basic.vue b/playground/src/views/examples/form/basic.vue
index 7cb08522..52d04319 100644
--- a/playground/src/views/examples/form/basic.vue
+++ b/playground/src/views/examples/form/basic.vue
@@ -348,6 +348,15 @@ const [BaseForm, baseFormApi] = useVbenForm({
showUploadList: true,
// 上传列表的内建样式,支持四种基本样式 text, picture, picture-card 和 picture-circle
listType: 'picture-card',
+ // onChange事件已被重写,如需自定义请在此基础上扩展
+ handleChange: ({ file }: { file: UploadFile }) => {
+ const { name, status } = file;
+ if (status === 'done') {
+ message.success(`${name} ${$t('examples.form.upload-success')}`);
+ } else if (status === 'error') {
+ message.error(`${name} ${$t('examples.form.upload-fail')}`);
+ }
+ },
},
fieldName: 'files',
label: $t('examples.form.file'),
@@ -358,6 +367,28 @@ const [BaseForm, baseFormApi] = useVbenForm({
},
rules: 'selectRequired',
},
+ {
+ component: 'Upload',
+ componentProps: {
+ accept: '.png,.jpg,.jpeg',
+ customRequest: upload_file,
+ maxCount: 1,
+ maxSize: 2,
+ listType: 'picture-card',
+ // 是否启用图片裁剪(多选或者非图片不唤起裁剪框)
+ crop: true,
+ // 裁剪比例
+ aspectRatio: '1:1',
+ },
+ fieldName: 'cropImage',
+ label: $t('examples.form.crop-image'),
+ renderComponentContent: () => {
+ return {
+ default: () => $t('examples.form.upload-image'),
+ };
+ },
+ rules: 'selectRequired',
+ },
],
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
@@ -365,13 +396,20 @@ const [BaseForm, baseFormApi] = useVbenForm({
function onSubmit(values: Record) {
const files = toRaw(values.files) as UploadFile[];
+ const cropImage = (toRaw(values.cropImage) ?? []) as UploadFile[];
const doneFiles = files.filter((file) => file.status === 'done');
const failedFiles = files.filter((file) => file.status !== 'done');
+ const doneCrop = cropImage.filter((file) => file.status === 'done');
+ const failedCrop = cropImage.filter((file) => file.status !== 'done');
const msg = [
...doneFiles.map((file) => file.response?.url || file.url),
...failedFiles.map((file) => file.name),
].join(', ');
+ const msgCrop = [
+ ...doneCrop.map((file) => file.response?.url || file.url),
+ ...failedCrop.map((file) => file.name),
+ ].join(', ');
if (failedFiles.length === 0) {
message.success({
@@ -383,8 +421,19 @@ function onSubmit(values: Record) {
});
return;
}
+ if (doneCrop.length > 0 && failedCrop.length === 0) {
+ message.success({
+ content: `${$t('examples.form.upload-urls')}: ${msgCrop}`,
+ });
+ } else if (failedCrop.length > 0) {
+ message.error({
+ content: `${$t('examples.form.upload-error')}: ${msgCrop}`,
+ });
+ return;
+ }
// 如果需要可提交前替换为需要的urls
values.files = doneFiles.map((file) => file.response?.url || file.url);
+ values.cropImage = doneCrop.map((file) => file.response?.url || file.url);
message.success({
content: `form values: ${JSON.stringify(values)}`,
});
diff --git a/playground/src/views/examples/vxe-table/basic.vue b/playground/src/views/examples/vxe-table/basic.vue
index d2047246..a4e9aa86 100644
--- a/playground/src/views/examples/vxe-table/basic.vue
+++ b/playground/src/views/examples/vxe-table/basic.vue
@@ -43,6 +43,7 @@ const gridEvents: VxeGridListeners = {
},
};
+// @ts-ignore
const [Grid, gridApi] = useVbenVxeGrid({
// 放开注释查看表单组件的类型
// formOptions: {
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 26fd2cfd..b88ba033 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -13,185 +13,191 @@ packages:
- docs
- playground
+overrides:
+ '@ast-grep/napi': 'catalog:'
+ '@ctrl/tinycolor': 'catalog:'
+ clsx: 'catalog:'
+ esbuild: 'catalog:'
+ jiti: 'catalog:'
+ pinia: 'catalog:'
+ vue: 'catalog:'
+
catalog:
'@ast-grep/napi': ^0.39.9
- '@changesets/changelog-github': ^0.5.1
- '@changesets/cli': ^2.29.7
+ '@changesets/changelog-github': ^0.5.2
+ '@changesets/cli': ^2.29.8
'@changesets/git': ^3.0.4
'@clack/prompts': ^0.11.0
'@commitlint/cli': ^19.8.1
'@commitlint/config-conventional': ^19.8.1
- '@ctrl/tinycolor': ^4.1.0
- '@eslint/js': ^9.39.1
+ '@ctrl/tinycolor': ^4.2.0
+ '@eslint/js': ^9.39.2
'@faker-js/faker': ^9.9.0
- '@iconify/json': ^2.2.406
+ '@iconify/json': ^2.2.431
'@iconify/tailwind': ^1.2.0
'@iconify/vue': ^5.0.0
- '@intlify/core-base': ^11.1.7
+ '@intlify/core-base': ^11.2.8
'@intlify/unplugin-vue-i18n': ^6.0.8
- '@jspm/generator': ^2.6.2
- '@manypkg/get-packages': ^3.0.0
- '@nolebase/vitepress-plugin-git-changelog': ^2.18.0
- '@playwright/test': ^1.56.1
- '@pnpm/workspace.read-manifest': ^1000.2.6
- '@stylistic/stylelint-plugin': ^3.1.3
+ '@jspm/generator': ^2.9.0
+ '@manypkg/get-packages': ^3.1.0
+ '@nolebase/vitepress-plugin-git-changelog': ^2.18.2
+ '@playwright/test': ^1.58.0
+ '@pnpm/workspace.read-manifest': ^1000.2.10
+ '@stylistic/stylelint-plugin': ^4.0.1
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
- '@tailwindcss/typography': ^0.5.16
- '@tanstack/vue-query': ^5.91.0
+ '@tailwindcss/typography': ^0.5.19
+ '@tanstack/vue-query': ^5.92.8
'@tanstack/vue-store': ^0.8.0
- '@types/archiver': ^6.0.3
+ '@types/archiver': ^6.0.4
'@types/eslint': ^9.6.1
'@types/html-minifier-terser': ^7.0.2
'@types/json-bigint': ^1.0.4
'@types/jsonwebtoken': ^9.0.10
'@types/lodash.clonedeep': ^4.5.9
- '@types/lodash.get': ^4.4.9
- '@types/lodash.isequal': ^4.5.8
- '@types/lodash.set': ^4.3.9
- '@types/node': ^22.18.12
+ '@types/node': ^24.10.9
'@types/nprogress': ^0.2.3
'@types/postcss-import': ^14.0.3
- '@types/qrcode': ^1.5.5
+ '@types/qrcode': ^1.5.6
'@types/qs': ^6.14.0
- '@types/sortablejs': ^1.15.8
- '@typescript-eslint/eslint-plugin': ^8.46.4
- '@typescript-eslint/parser': ^8.46.4
+ '@types/sortablejs': ^1.15.9
+ '@typescript-eslint/eslint-plugin': ^8.53.1
+ '@typescript-eslint/parser': ^8.53.1
'@vee-validate/zod': ^4.15.1
- '@vite-pwa/vitepress': ^1.0.0
- '@vitejs/plugin-vue': ^6.0.1
- '@vitejs/plugin-vue-jsx': ^5.1.1
- '@vue/reactivity': ^3.5.17
- '@vue/shared': ^3.5.24
+ '@vite-pwa/vitepress': ^1.1.0
+ '@vitejs/plugin-vue': ^6.0.3
+ '@vitejs/plugin-vue-jsx': ^5.1.3
+ '@vue/shared': ^3.5.27
'@vue/test-utils': ^2.4.6
- '@vueuse/core': ^13.4.0
- '@vueuse/integrations': ^14.0.0
+ '@vueuse/core': ^14.1.0
+ '@vueuse/integrations': ^14.1.0
'@vueuse/motion': ^3.0.3
ant-design-vue: ^4.2.6
archiver: ^7.0.1
- autoprefixer: ^10.4.22
- axios: ^1.10.0
+ autoprefixer: ^10.4.23
+ axios: ^1.13.2
axios-mock-adapter: ^2.1.0
cac: ^6.7.14
- chalk: ^5.4.1
- cheerio: ^1.1.0
+ chalk: ^5.6.2
+ cheerio: ^1.1.2
circular-dependency-scanner: ^2.3.0
class-variance-authority: ^0.7.1
clsx: ^2.1.1
- commitlint-plugin-function-rules: ^4.1.1
+ commitlint-plugin-function-rules: ^4.3.1
consola: ^3.4.2
- cross-env: ^7.0.3
- cspell: ^8.19.4
- cssnano: ^7.0.7
- cz-git: ^1.11.2
- czg: ^1.11.1
- dayjs: ^1.11.13
+ cross-env: ^10.1.0
+ cspell: ^9.6.0
+ cssnano: ^7.1.2
+ cz-git: ^1.12.0
+ czg: ^1.12.0
+ dayjs: ^1.11.19
defu: ^6.1.4
depcheck: ^1.4.7
dotenv: ^16.6.1
echarts: ^6.0.0
- element-plus: ^2.10.2
- eslint: ^9.39.1
- eslint-config-turbo: ^2.6.1
- eslint-plugin-command: ^3.3.1
+ element-plus: ^2.13.1
+ es-toolkit: ^1.44.0
+ esbuild: ^0.25.12
+ eslint: ^9.39.2
+ eslint-config-turbo: ^2.7.5
+ eslint-plugin-command: ^3.4.0
eslint-plugin-eslint-comments: ^3.2.0
eslint-plugin-import-x: ^4.16.1
- eslint-plugin-jsdoc: ^61.2.1
+ eslint-plugin-jsdoc: ^61.7.1
eslint-plugin-jsonc: ^2.21.0
- eslint-plugin-n: ^17.23.1
+ eslint-plugin-n: ^17.23.2
eslint-plugin-no-only-tests: ^3.3.0
eslint-plugin-perfectionist: ^4.15.1
- eslint-plugin-prettier: ^5.5.4
+ eslint-plugin-pnpm: ^1.5.0
+ eslint-plugin-prettier: ^5.5.5
eslint-plugin-regexp: ^2.10.0
eslint-plugin-unicorn: ^62.0.0
eslint-plugin-unused-imports: ^4.3.0
eslint-plugin-vitest: ^0.5.4
- eslint-plugin-vue: ^10.5.1
- execa: ^9.6.0
+ eslint-plugin-vue: ^10.7.0
+ eslint-plugin-yml: ^1.19.1
+ execa: ^9.6.1
find-up: ^7.0.0
get-port: ^7.1.0
- globals: ^16.3.0
- h3: ^1.15.3
+ globals: ^16.5.0
+ h3: ^1.15.5
happy-dom: ^17.6.3
html-minifier-terser: ^7.2.0
is-ci: ^4.1.0
jiti: ^2.6.1
json-bigint: ^1.0.0
- jsonc-eslint-parser: ^2.4.1
- jsonwebtoken: ^9.0.2
- lefthook: 1.11.12
+ jsonc-eslint-parser: ^2.4.2
+ jsonwebtoken: ^9.0.3
+ lefthook: ^2.0.15
lodash.clonedeep: ^4.5.0
- lodash.get: ^4.4.2
- lodash.isequal: ^4.5.0
- lodash.set: ^4.3.2
lucide-vue-next: ^0.553.0
medium-zoom: ^1.1.0
- naive-ui: ^2.42.0
- nitropack: ^2.11.13
+ naive-ui: ^2.43.2
+ nitropack: ^2.13.1
nprogress: ^0.2.0
ora: ^8.2.0
- pinia: ^3.0.3
- pinia-plugin-persistedstate: ^4.4.1
- pkg-types: ^2.2.0
- playwright: ^1.56.1
+ pinia: ^3.0.4
+ pinia-plugin-persistedstate: ^4.7.1
+ pkg-types: ^2.3.0
+ playwright: ^1.58.0
postcss: ^8.5.6
postcss-antd-fixes: ^0.2.0
- postcss-html: ^1.8.0
+ postcss-html: ^1.8.1
postcss-import: ^16.1.1
- postcss-preset-env: ^10.2.4
+ postcss-preset-env: ^10.6.1
postcss-scss: ^4.0.9
- prettier: ^3.6.2
- prettier-plugin-tailwindcss: ^0.7.1
- publint: ^0.3.12
+ prettier: ^3.8.1
+ prettier-plugin-tailwindcss: ^0.7.2
+ publint: ^0.3.17
qrcode: ^1.5.4
- qs: ^6.14.0
- reka-ui: ^2.6.0
+ qs: ^6.14.1
+ reka-ui: ^2.7.0
resolve.exports: ^2.0.3
- rimraf: ^6.1.0
- rollup: ^4.44.1
+ rimraf: ^6.1.2
+ rollup: ^4.56.0
rollup-plugin-visualizer: ^5.14.0
- sass: ^1.94.0
+ sass: ^1.97.3
secure-ls: ^2.0.0
sortablejs: ^1.15.6
- stylelint: ^16.21.0
- stylelint-config-recess-order: ^6.1.0
+ stylelint: ^16.26.1
+ stylelint-config-recess-order: ^7.6.0
stylelint-config-recommended: ^17.0.0
- stylelint-config-recommended-scss: ^14.1.0
+ stylelint-config-recommended-scss: ^16.0.2
stylelint-config-recommended-vue: ^1.6.1
- stylelint-config-standard: ^38.0.0
- stylelint-order: ^7.0.0
+ stylelint-config-standard: ^39.0.1
+ stylelint-order: ^7.0.1
stylelint-prettier: ^5.0.3
- stylelint-scss: ^6.12.1
+ stylelint-scss: ^6.14.0
tailwind-merge: ^2.6.0
- tailwindcss: ^3.4.18
+ tailwindcss: ^3.4.19
tailwindcss-animate: ^1.0.7
- tdesign-vue-next: ^1.17.1
+ tdesign-vue-next: ^1.18.0
theme-colors: ^0.1.0
tippy.js: ^6.3.7
- turbo: ^2.6.1
+ turbo: ^2.7.6
typescript: ^5.9.3
unbuild: ^3.6.1
- unplugin-element-plus: ^0.11.1
+ unplugin-element-plus: ^0.11.2
vee-validate: ^4.15.1
- vite: ^7.2.2
+ vite: ^7.3.1
vite-plugin-compression: ^0.5.1
vite-plugin-dts: ^4.5.4
vite-plugin-html: ^3.2.2
vite-plugin-lazy-import: ^1.0.7
- vite-plugin-pwa: ^1.0.1
- vite-plugin-vue-devtools: ^8.0.3
- vitepress: ^1.6.3
- vitepress-plugin-group-icons: ^1.6.1
+ vite-plugin-pwa: ^1.2.0
+ vite-plugin-vue-devtools: ^8.0.5
+ vitepress: ^1.6.4
+ vitepress-plugin-group-icons: ^1.7.1
vitest: ^3.2.4
- vue: ^3.5.24
+ vue: ^3.5.27
vue-eslint-parser: ^10.2.0
- vue-i18n: ^11.1.7
+ vue-i18n: ^11.2.8
vue-json-viewer: ^3.0.4
- vue-router: ^4.5.1
+ vue-router: ^4.6.4
vue-tippy: ^6.7.1
- vue-tsc: ^3.1.4
- vxe-pc-ui: 4.10.36
- vxe-table: ^4.16.11
- watermark-js-plus: ^1.6.2
- zod: ^3.25.67
+ vue-tsc: ^3.2.3
+ vxe-pc-ui: ^4.12.10
+ vxe-table: ^4.17.46
+ watermark-js-plus: ^1.6.3
+ yaml-eslint-parser: ^1.3.2
+ zod: ^3.25.76
zod-defaults: 0.1.3
- es-toolkit: ^1.41.0
diff --git a/vitest.config.ts b/vitest.config.ts
index a10b5fa3..a5ecd3d6 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -6,6 +6,14 @@ export default defineConfig({
plugins: [Vue(), VueJsx()],
test: {
environment: 'happy-dom',
- exclude: [...configDefaults.exclude, '**/e2e/**'],
+ exclude: [
+ ...configDefaults.exclude,
+ '**/e2e/**',
+ '**/dist/**',
+ '**/.{idea,git,cache,output,temp}/**',
+ '**/node_modules/**',
+ '**/{stylelint,eslint}.config.*',
+ '.prettierrc.mjs',
+ ],
},
});
diff --git a/vitest.workspace.ts b/vitest.workspace.ts
deleted file mode 100644
index f00d6f68..00000000
--- a/vitest.workspace.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { defineWorkspace } from 'vitest/config';
-
-export default defineWorkspace(['vitest.config.ts']);