fix: 修复 Admin tsconfig 类型校验

This commit is contained in:
sunlei 2026-05-18 22:11:35 +08:00
parent 51464d85d5
commit c82ffa2b6c
19 changed files with 59 additions and 36 deletions

View File

@ -100,6 +100,16 @@ const PreviewGroup = defineAsyncComponent(() =>
import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup), import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup),
); );
type CropperExpose = {
getCropImage: (
format?: 'image/jpeg' | 'image/png',
quality?: number,
outputType?: 'base64' | 'blob',
targetWidth?: number,
targetHeight?: number,
) => Promise<Blob | string | undefined>;
};
const withDefaultPlaceholder = <T extends Component>( const withDefaultPlaceholder = <T extends Component>(
component: T, component: T,
type: 'input' | 'select', type: 'input' | 'select',
@ -293,7 +303,7 @@ const withPreviewUpload = () => {
let objectUrl: null | string = null; let objectUrl: null | string = null;
const open = ref<boolean>(true); const open = ref<boolean>(true);
const cropperRef = ref<InstanceType<typeof VCropper> | null>(null); const cropperRef = ref<CropperExpose | null>(null);
const closeModal = () => { const closeModal = () => {
open.value = false; open.value = false;

View File

@ -2,7 +2,6 @@
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web-app.json", "extends": "@vben/tsconfig/web-app.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".",
"paths": { "paths": {
"#/*": ["./src/*"] "#/*": ["./src/*"]
} }

View File

@ -1,6 +1,6 @@
import { defineConfig } from '@vben/vite-config'; import { defineConfig } from '@vben/vite-config';
export default defineConfig(async () => { const config = defineConfig(async () => {
return { return {
application: {}, application: {},
vite: { vite: {
@ -17,4 +17,6 @@ export default defineConfig(async () => {
}, },
}, },
}; };
}); }) as unknown;
export default config;

View File

@ -8,7 +8,6 @@
"moduleDetection": "force", "moduleDetection": "force",
"experimentalDecorators": true, "experimentalDecorators": true,
"baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",

View File

@ -5,7 +5,6 @@
"compilerOptions": { "compilerOptions": {
"composite": false, "composite": false,
"lib": ["ESNext"], "lib": ["ESNext"],
"baseUrl": "./",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"types": ["node"], "types": ["node"],
"noImplicitAny": true "noImplicitAny": true

View File

@ -15,6 +15,7 @@
"base.json", "base.json",
"library.json", "library.json",
"node.json", "node.json",
"types",
"web-app.json", "web-app.json",
"web.json" "web.json"
], ],

View File

@ -0,0 +1,10 @@
/// <reference types="vite/client" />
declare module '*.vue' {
const component: import('vue').DefineComponent<
Record<string, unknown>,
Record<string, unknown>,
Record<string, unknown>
>;
export default component;
}

View File

@ -3,6 +3,6 @@
"display": "Web Application", "display": "Web Application",
"extends": "./web.json", "extends": "./web.json",
"compilerOptions": { "compilerOptions": {
"types": ["vite/client", "@vben/types/global"] "types": ["@vben/tsconfig/types/vben-web", "@vben/types/global"]
} }
} }

View File

@ -8,7 +8,7 @@
"lib": ["ESNext", "DOM", "DOM.Iterable"], "lib": ["ESNext", "DOM", "DOM.Iterable"],
"useDefineForClassFields": true, "useDefineForClassFields": true,
"moduleResolution": "bundler", "moduleResolution": "bundler",
"types": ["vite/client"], "types": ["@vben/tsconfig/types/vben-web"],
"declaration": false "declaration": false
} }
} }

View File

@ -35,7 +35,8 @@ async function loadConditionPlugins(conditionPlugins: ConditionPlugin[]) {
const plugins: PluginOption[] = []; const plugins: PluginOption[] = [];
for (const conditionPlugin of conditionPlugins) { for (const conditionPlugin of conditionPlugins) {
if (conditionPlugin.condition) { if (conditionPlugin.condition) {
const realPlugins = await conditionPlugin.plugins(); // 第三方插件可能绑定到不同的 Vite 类型实例,运行时统一收敛为 Vite PluginOption。
const realPlugins = (await conditionPlugin.plugins()) as PluginOption[];
plugins.push(...realPlugins); plugins.push(...realPlugins);
} }
} }

View File

@ -14,7 +14,7 @@ async function viteVxeTableImportsPlugin(): Promise<PluginOption> {
}), }),
], ],
}), }),
]; ] as unknown as PluginOption;
} }
export { viteVxeTableImportsPlugin }; export { viteVxeTableImportsPlugin };

View File

@ -1,7 +1,6 @@
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'; import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
import type { import type {
ConfigEnv, ConfigEnv,
PluginOption,
UserConfig, UserConfig,
UserConfigFnPromise, UserConfigFnPromise,
} from 'vite'; } from 'vite';
@ -138,7 +137,7 @@ interface ConditionPlugin {
* *
* @description Promise * @description Promise
*/ */
plugins: () => PluginOption[] | PromiseLike<PluginOption[]>; plugins: () => PromiseLike<unknown[]> | unknown[];
} }
/** /**

View File

@ -4,6 +4,7 @@ import { FormApi } from '../src/form-api';
describe('formApi', () => { describe('formApi', () => {
let formApi: FormApi; let formApi: FormApi;
const componentRefMap = () => new Map<string, unknown>();
beforeEach(() => { beforeEach(() => {
formApi = new FormApi(); formApi = new FormApi();
@ -41,7 +42,7 @@ describe('formApi', () => {
values: { name: 'test' }, values: { name: 'test' },
}; };
await formApi.mount(formActions); formApi.mount(formActions, componentRefMap());
expect(formApi.isMounted).toBe(true); expect(formApi.isMounted).toBe(true);
expect(formApi.form).toEqual(formActions); expect(formApi.form).toEqual(formActions);
}); });
@ -52,7 +53,7 @@ describe('formApi', () => {
values: { name: 'test' }, values: { name: 'test' },
}; };
await formApi.mount(formActions); formApi.mount(formActions, componentRefMap());
const values = await formApi.getValues(); const values = await formApi.getValues();
expect(values).toEqual({ name: 'test' }); expect(values).toEqual({ name: 'test' });
}); });
@ -65,7 +66,7 @@ describe('formApi', () => {
values: { name: 'test' }, values: { name: 'test' },
}; };
await formApi.mount(formActions); formApi.mount(formActions, componentRefMap());
await formApi.setFieldValue('name', 'new value'); await formApi.setFieldValue('name', 'new value');
expect(setFieldValueMock).toHaveBeenCalledWith( expect(setFieldValueMock).toHaveBeenCalledWith(
'name', 'name',
@ -82,7 +83,7 @@ describe('formApi', () => {
values: { name: 'test' }, values: { name: 'test' },
}; };
await formApi.mount(formActions); formApi.mount(formActions, componentRefMap());
await formApi.resetForm(); await formApi.resetForm();
expect(resetFormMock).toHaveBeenCalled(); expect(resetFormMock).toHaveBeenCalled();
}); });
@ -100,7 +101,7 @@ describe('formApi', () => {
}; };
formApi.setState(state); formApi.setState(state);
await formApi.mount(formActions); formApi.mount(formActions, componentRefMap());
const result = await formApi.submitForm(); const result = await formApi.submitForm();
expect(formActions.submitForm).toHaveBeenCalled(); expect(formActions.submitForm).toHaveBeenCalled();
@ -120,7 +121,7 @@ describe('formApi', () => {
validate: validateMock, validate: validateMock,
}; };
await formApi.mount(formActions); formApi.mount(formActions, componentRefMap());
const isValid = await formApi.validate(); const isValid = await formApi.validate();
expect(validateMock).toHaveBeenCalled(); expect(validateMock).toHaveBeenCalled();
expect(isValid).toBe(true); expect(isValid).toBe(true);

View File

@ -2,7 +2,6 @@
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json", "extends": "@vben/tsconfig/web.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".",
"paths": { "paths": {
"@vben-core/shadcn-ui/*": ["./src/*"] "@vben-core/shadcn-ui/*": ["./src/*"]
} }

View File

@ -1,9 +1,8 @@
import type { TabsProps } from './types'; import type { TabsProps } from './types';
import type { ComponentPublicInstance } from 'vue';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
type DomElement = Element | null | undefined; type DomElement = Element | null | undefined;
@ -12,7 +11,7 @@ export function useTabsViewScroll(props: TabsProps) {
let resizeObserver: null | ResizeObserver = null; let resizeObserver: null | ResizeObserver = null;
let mutationObserver: MutationObserver | null = null; let mutationObserver: MutationObserver | null = null;
let tabItemCount = 0; let tabItemCount = 0;
const scrollbarRef = ref<InstanceType<typeof VbenScrollbar> | null>(null); const scrollbarRef = ref<ComponentPublicInstance | null>(null);
const scrollViewportEl = ref<DomElement>(null); const scrollViewportEl = ref<DomElement>(null);
const showScrollButton = ref(false); const showScrollButton = ref(false);
const scrollIsAtLeft = ref(true); const scrollIsAtLeft = ref(true);

View File

@ -1,3 +1,5 @@
import type { AxiosResponse } from 'axios';
import axios from 'axios'; import axios from 'axios';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { afterEach, beforeEach, describe, expect, it } from 'vitest';
@ -92,7 +94,8 @@ describe('requestClient', () => {
mock.onGet('/test/download').reply(200, mockFileContent); mock.onGet('/test/download').reply(200, mockFileContent);
const res = await requestClient.download('/test/download'); const res =
await requestClient.download<AxiosResponse<Blob>>('/test/download');
expect(res.data).toBeInstanceOf(Blob); expect(res.data).toBeInstanceOf(Blob);
}); });

View File

@ -38,15 +38,15 @@ const mockRoutes = [
describe('hasAuthority', () => { describe('hasAuthority', () => {
it('should return true if there is no authority defined', () => { it('should return true if there is no authority defined', () => {
expect(hasAuthority(mockRoutes[2], ['admin'])).toBe(true); expect(hasAuthority(mockRoutes[2]!, ['admin'])).toBe(true);
}); });
it('should return true if the user has the required authority', () => { it('should return true if the user has the required authority', () => {
expect(hasAuthority(mockRoutes[0], ['admin'])).toBe(true); expect(hasAuthority(mockRoutes[0]!, ['admin'])).toBe(true);
}); });
it('should return false if the user does not have the required authority', () => { it('should return false if the user does not have the required authority', () => {
expect(hasAuthority(mockRoutes[1], ['user'])).toBe(false); expect(hasAuthority(mockRoutes[1]!, ['user'])).toBe(false);
}); });
}); });

View File

@ -27,12 +27,8 @@ function generateMenus(
// 获取最终的路由路径 // 获取最终的路由路径
const path = finalRoutesMap[route.name as string] ?? route.path ?? ''; const path = finalRoutesMap[route.name as string] ?? route.path ?? '';
const { const { name: routeName, redirect, children = [] } = route;
meta = {} as RouteMeta, const meta = (route.meta ?? {}) as unknown as Partial<RouteMeta>;
name: routeName,
redirect,
children = [],
} = route;
const { const {
activeIcon, activeIcon,
badge, badge,

View File

@ -1,5 +1,7 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import type { RouteMeta } from '@vben-core/typings';
import { filterTree, mapTree } from '@vben-core/shared/utils'; import { filterTree, mapTree } from '@vben-core/shared/utils';
/** /**
@ -34,7 +36,8 @@ async function generateRoutesByFrontend(
* @param access * @param access
*/ */
function hasAuthority(route: RouteRecordRaw, access: string[]) { function hasAuthority(route: RouteRecordRaw, access: string[]) {
const authority = route.meta?.authority; const meta = route.meta as Partial<RouteMeta> | undefined;
const authority = meta?.authority;
if (!authority) { if (!authority) {
return true; return true;
} }
@ -48,10 +51,12 @@ function hasAuthority(route: RouteRecordRaw, access: string[]) {
* @param route * @param route
*/ */
function menuHasVisibleWithForbidden(route: RouteRecordRaw) { function menuHasVisibleWithForbidden(route: RouteRecordRaw) {
const meta = route.meta as Partial<RouteMeta> | undefined;
return ( return (
!!route.meta?.authority && !!meta?.authority &&
Reflect.has(route.meta || {}, 'menuVisibleWithForbidden') && Reflect.has(meta || {}, 'menuVisibleWithForbidden') &&
!!route.meta?.menuVisibleWithForbidden !!meta?.menuVisibleWithForbidden
); );
} }