Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
commit
e02b5590dd
|
@ -1 +1 @@
|
||||||
20.14.0
|
22.1.0
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.cursorBlinking": "expand",
|
"editor.cursorBlinking": "expand",
|
||||||
"editor.largeFileOptimizations": false,
|
"editor.largeFileOptimizations": true,
|
||||||
"editor.accessibilitySupport": "off",
|
"editor.accessibilitySupport": "off",
|
||||||
"editor.cursorSmoothCaretAnimation": "on",
|
"editor.cursorSmoothCaretAnimation": "on",
|
||||||
"editor.guides.bracketPairs": "active",
|
"editor.guides.bracketPairs": "active",
|
||||||
|
@ -91,6 +91,7 @@
|
||||||
"**/bower_components": true,
|
"**/bower_components": true,
|
||||||
"**/.turbo": true,
|
"**/.turbo": true,
|
||||||
"**/.idea": true,
|
"**/.idea": true,
|
||||||
|
"**/.vitepress": true,
|
||||||
"**/tmp": true,
|
"**/tmp": true,
|
||||||
"**/.git": true,
|
"**/.git": true,
|
||||||
"**/.svn": true,
|
"**/.svn": true,
|
||||||
|
@ -112,6 +113,8 @@
|
||||||
"**/yarn.lock": true
|
"**/yarn.lock": true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
|
||||||
|
|
||||||
// search
|
// search
|
||||||
"search.searchEditor.singleClickBehaviour": "peekDefinition",
|
"search.searchEditor.singleClickBehaviour": "peekDefinition",
|
||||||
"search.followSymlinks": false,
|
"search.followSymlinks": false,
|
||||||
|
|
|
@ -3,149 +3,328 @@ import type { ConfigEnv, PluginOption, UserConfig } from 'vite';
|
||||||
import type { PluginOptions } from 'vite-plugin-dts';
|
import type { PluginOptions } from 'vite-plugin-dts';
|
||||||
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
|
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImportMap 配置接口
|
||||||
|
* @description 用于配置模块导入映射,支持自定义导入路径和范围
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* {
|
||||||
|
* imports: {
|
||||||
|
* 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'
|
||||||
|
* },
|
||||||
|
* scopes: {
|
||||||
|
* 'https://site.com/': {
|
||||||
|
* 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
interface IImportMap {
|
interface IImportMap {
|
||||||
|
/** 模块导入映射 */
|
||||||
imports?: Record<string, string>;
|
imports?: Record<string, string>;
|
||||||
|
/** 作用域特定的导入映射 */
|
||||||
scopes?: {
|
scopes?: {
|
||||||
[scope: string]: Record<string, string>;
|
[scope: string]: Record<string, string>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印插件配置选项
|
||||||
|
* @description 用于配置控制台打印信息
|
||||||
|
*/
|
||||||
interface PrintPluginOptions {
|
interface PrintPluginOptions {
|
||||||
/**
|
/**
|
||||||
* 打印的数据
|
* 打印的数据映射
|
||||||
|
* @description 键值对形式的数据,将在控制台打印
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* {
|
||||||
|
* 'App Version': '1.0.0',
|
||||||
|
* 'Build Time': '2024-01-01'
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
infoMap?: Record<string, string | undefined>;
|
infoMap?: Record<string, string | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nitro Mock 插件配置选项
|
||||||
|
* @description 用于配置 Nitro Mock 服务器的行为
|
||||||
|
*/
|
||||||
interface NitroMockPluginOptions {
|
interface NitroMockPluginOptions {
|
||||||
/**
|
/**
|
||||||
* mock server 包名
|
* Mock 服务器包名
|
||||||
|
* @default '@vbenjs/nitro-mock'
|
||||||
*/
|
*/
|
||||||
mockServerPackage?: string;
|
mockServerPackage?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mock 服务端口
|
* Mock 服务端口
|
||||||
|
* @default 3000
|
||||||
*/
|
*/
|
||||||
port?: number;
|
port?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mock 日志是否打印
|
* 是否打印 Mock 日志
|
||||||
|
* @default false
|
||||||
*/
|
*/
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归档插件配置选项
|
||||||
|
* @description 用于配置构建产物的压缩归档
|
||||||
|
*/
|
||||||
interface ArchiverPluginOptions {
|
interface ArchiverPluginOptions {
|
||||||
/**
|
/**
|
||||||
* 输出文件名
|
* 输出文件名
|
||||||
* @default dist
|
* @default 'dist'
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string;
|
||||||
/**
|
/**
|
||||||
* 输出目录
|
* 输出目录
|
||||||
* @default .
|
* @default '.'
|
||||||
*/
|
*/
|
||||||
outputDir?: string;
|
outputDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* importmap 插件配置
|
* ImportMap 插件配置
|
||||||
|
* @description 用于配置模块的 CDN 导入
|
||||||
*/
|
*/
|
||||||
interface ImportmapPluginOptions {
|
interface ImportmapPluginOptions {
|
||||||
/**
|
/**
|
||||||
* CDN 供应商
|
* CDN 供应商
|
||||||
* @default jspm.io
|
* @default 'jspm.io'
|
||||||
|
* @description 支持 esm.sh 和 jspm.io 两种 CDN 供应商
|
||||||
*/
|
*/
|
||||||
defaultProvider?: 'esm.sh' | 'jspm.io';
|
defaultProvider?: 'esm.sh' | 'jspm.io';
|
||||||
/** importmap 配置 */
|
/**
|
||||||
|
* ImportMap 配置数组
|
||||||
|
* @description 配置需要从 CDN 导入的包
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* [
|
||||||
|
* { name: 'vue' },
|
||||||
|
* { name: 'pinia', range: '^2.0.0' }
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
importmap?: Array<{ name: string; range?: string }>;
|
importmap?: Array<{ name: string; range?: string }>;
|
||||||
/** 手动配置importmap */
|
/**
|
||||||
|
* 手动配置 ImportMap
|
||||||
|
* @description 自定义 ImportMap 配置
|
||||||
|
*/
|
||||||
inputMap?: IImportMap;
|
inputMap?: IImportMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于判断是否需要加载插件
|
* 条件插件配置
|
||||||
|
* @description 用于根据条件动态加载插件
|
||||||
*/
|
*/
|
||||||
interface ConditionPlugin {
|
interface ConditionPlugin {
|
||||||
// 判断条件
|
/**
|
||||||
|
* 判断条件
|
||||||
|
* @description 当条件为 true 时加载插件
|
||||||
|
*/
|
||||||
condition?: boolean;
|
condition?: boolean;
|
||||||
// 插件对象
|
/**
|
||||||
|
* 插件对象
|
||||||
|
* @description 返回插件数组或 Promise
|
||||||
|
*/
|
||||||
plugins: () => PluginOption[] | PromiseLike<PluginOption[]>;
|
plugins: () => PluginOption[] | PromiseLike<PluginOption[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用插件配置选项
|
||||||
|
* @description 所有插件共用的基础配置
|
||||||
|
*/
|
||||||
interface CommonPluginOptions {
|
interface CommonPluginOptions {
|
||||||
/** 是否开启devtools */
|
/**
|
||||||
|
* 是否开启开发工具
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
devtools?: boolean;
|
devtools?: boolean;
|
||||||
/** 环境变量 */
|
/**
|
||||||
|
* 环境变量
|
||||||
|
* @description 自定义环境变量
|
||||||
|
*/
|
||||||
env?: Record<string, any>;
|
env?: Record<string, any>;
|
||||||
/** 是否注入metadata */
|
/**
|
||||||
|
* 是否注入元数据
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
injectMetadata?: boolean;
|
injectMetadata?: boolean;
|
||||||
/** 是否构建模式 */
|
/**
|
||||||
|
* 是否为构建模式
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
isBuild?: boolean;
|
isBuild?: boolean;
|
||||||
/** 构建模式 */
|
/**
|
||||||
|
* 构建模式
|
||||||
|
* @default 'development'
|
||||||
|
*/
|
||||||
mode?: string;
|
mode?: string;
|
||||||
/** 开启依赖分析 */
|
/**
|
||||||
|
* 是否开启依赖分析
|
||||||
|
* @default false
|
||||||
|
* @description 使用 rollup-plugin-visualizer 分析依赖
|
||||||
|
*/
|
||||||
visualizer?: boolean | PluginVisualizerOptions;
|
visualizer?: boolean | PluginVisualizerOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用插件配置选项
|
||||||
|
* @description 用于配置应用构建时的插件选项
|
||||||
|
*/
|
||||||
interface ApplicationPluginOptions extends CommonPluginOptions {
|
interface ApplicationPluginOptions extends CommonPluginOptions {
|
||||||
/** 开启后,会在打包dist同级生成dist.zip */
|
/**
|
||||||
|
* 是否开启压缩归档
|
||||||
|
* @default false
|
||||||
|
* @description 开启后会在打包目录生成 zip 文件
|
||||||
|
*/
|
||||||
archiver?: boolean;
|
archiver?: boolean;
|
||||||
/** 压缩归档插件配置 */
|
/**
|
||||||
|
* 压缩归档插件配置
|
||||||
|
* @description 配置压缩归档的行为
|
||||||
|
*/
|
||||||
archiverPluginOptions?: ArchiverPluginOptions;
|
archiverPluginOptions?: ArchiverPluginOptions;
|
||||||
/** 开启 gzip|brotli 压缩 */
|
/**
|
||||||
|
* 是否开启压缩
|
||||||
|
* @default false
|
||||||
|
* @description 支持 gzip 和 brotli 压缩
|
||||||
|
*/
|
||||||
compress?: boolean;
|
compress?: boolean;
|
||||||
/** 压缩类型 */
|
/**
|
||||||
|
* 压缩类型
|
||||||
|
* @default ['gzip']
|
||||||
|
* @description 可选的压缩类型
|
||||||
|
*/
|
||||||
compressTypes?: ('brotli' | 'gzip')[];
|
compressTypes?: ('brotli' | 'gzip')[];
|
||||||
/** 在构建的时候抽离配置文件 */
|
/**
|
||||||
|
* 是否抽离配置文件
|
||||||
|
* @default false
|
||||||
|
* @description 在构建时抽离配置文件
|
||||||
|
*/
|
||||||
extraAppConfig?: boolean;
|
extraAppConfig?: boolean;
|
||||||
/** 是否开启html插件 */
|
/**
|
||||||
|
* 是否开启 HTML 插件
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
html?: boolean;
|
html?: boolean;
|
||||||
/** 是否开启i18n */
|
/**
|
||||||
|
* 是否开启国际化
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
i18n?: boolean;
|
i18n?: boolean;
|
||||||
/** 是否开启 importmap CDN */
|
/**
|
||||||
|
* 是否开启 ImportMap CDN
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
importmap?: boolean;
|
importmap?: boolean;
|
||||||
/** importmap 插件配置 */
|
/**
|
||||||
|
* ImportMap 插件配置
|
||||||
|
*/
|
||||||
importmapOptions?: ImportmapPluginOptions;
|
importmapOptions?: ImportmapPluginOptions;
|
||||||
/** 是否注入app loading */
|
/**
|
||||||
|
* 是否注入应用加载动画
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
injectAppLoading?: boolean;
|
injectAppLoading?: boolean;
|
||||||
/** 是否注入全局scss */
|
/**
|
||||||
|
* 是否注入全局 SCSS
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
injectGlobalScss?: boolean;
|
injectGlobalScss?: boolean;
|
||||||
/** 是否注入版权信息 */
|
/**
|
||||||
|
* 是否注入版权信息
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
license?: boolean;
|
license?: boolean;
|
||||||
/** 是否开启nitro mock */
|
/**
|
||||||
|
* 是否开启 Nitro Mock
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
nitroMock?: boolean;
|
nitroMock?: boolean;
|
||||||
/** nitro mock 插件配置 */
|
/**
|
||||||
|
* Nitro Mock 插件配置
|
||||||
|
*/
|
||||||
nitroMockOptions?: NitroMockPluginOptions;
|
nitroMockOptions?: NitroMockPluginOptions;
|
||||||
/** 开启控制台自定义打印 */
|
/**
|
||||||
|
* 是否开启控制台打印
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
print?: boolean;
|
print?: boolean;
|
||||||
/** 打印插件配置 */
|
/**
|
||||||
|
* 打印插件配置
|
||||||
|
*/
|
||||||
printInfoMap?: PrintPluginOptions['infoMap'];
|
printInfoMap?: PrintPluginOptions['infoMap'];
|
||||||
/** 是否开启pwa */
|
/**
|
||||||
|
* 是否开启 PWA
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
pwa?: boolean;
|
pwa?: boolean;
|
||||||
/** pwa 插件配置 */
|
/**
|
||||||
|
* PWA 插件配置
|
||||||
|
*/
|
||||||
pwaOptions?: Partial<PwaPluginOptions>;
|
pwaOptions?: Partial<PwaPluginOptions>;
|
||||||
/** 是否开启vxe-table懒加载 */
|
/**
|
||||||
|
* 是否开启 VXE Table 懒加载
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
vxeTableLazyImport?: boolean;
|
vxeTableLazyImport?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 库插件配置选项
|
||||||
|
* @description 用于配置库构建时的插件选项
|
||||||
|
*/
|
||||||
interface LibraryPluginOptions extends CommonPluginOptions {
|
interface LibraryPluginOptions extends CommonPluginOptions {
|
||||||
/** 开启 dts 输出 */
|
/**
|
||||||
|
* 是否开启 DTS 输出
|
||||||
|
* @default true
|
||||||
|
* @description 生成 TypeScript 类型声明文件
|
||||||
|
*/
|
||||||
dts?: boolean | PluginOptions;
|
dts?: boolean | PluginOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置选项类型
|
||||||
|
*/
|
||||||
type ApplicationOptions = ApplicationPluginOptions;
|
type ApplicationOptions = ApplicationPluginOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 库配置选项类型
|
||||||
|
*/
|
||||||
type LibraryOptions = LibraryPluginOptions;
|
type LibraryOptions = LibraryPluginOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置定义函数类型
|
||||||
|
* @description 用于定义应用构建配置
|
||||||
|
*/
|
||||||
type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{
|
type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{
|
||||||
|
/** 应用插件配置 */
|
||||||
application?: ApplicationOptions;
|
application?: ApplicationOptions;
|
||||||
|
/** Vite 配置 */
|
||||||
vite?: UserConfig;
|
vite?: UserConfig;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 库配置定义函数类型
|
||||||
|
* @description 用于定义库构建配置
|
||||||
|
*/
|
||||||
type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{
|
type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{
|
||||||
|
/** 库插件配置 */
|
||||||
library?: LibraryOptions;
|
library?: LibraryOptions;
|
||||||
|
/** Vite 配置 */
|
||||||
vite?: UserConfig;
|
vite?: UserConfig;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置定义类型
|
||||||
|
* @description 应用或库的配置定义
|
||||||
|
*/
|
||||||
type DefineConfig = DefineApplicationOptions | DefineLibraryOptions;
|
type DefineConfig = DefineApplicationOptions | DefineLibraryOptions;
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
"node": ">=20.10.0",
|
"node": ">=20.10.0",
|
||||||
"pnpm": ">=9.12.0"
|
"pnpm": ">=9.12.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.9",
|
"packageManager": "pnpm@10.10.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
|
|
|
@ -2,13 +2,18 @@ import type { FormRenderProps } from '../types';
|
||||||
|
|
||||||
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
|
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
|
import {
|
||||||
|
breakpointsTailwind,
|
||||||
|
useBreakpoints,
|
||||||
|
useElementVisibility,
|
||||||
|
} from '@vueuse/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态计算行数
|
* 动态计算行数
|
||||||
*/
|
*/
|
||||||
export function useExpandable(props: FormRenderProps) {
|
export function useExpandable(props: FormRenderProps) {
|
||||||
const wrapperRef = useTemplateRef<HTMLElement>('wrapperRef');
|
const wrapperRef = useTemplateRef<HTMLElement>('wrapperRef');
|
||||||
|
const isVisible = useElementVisibility(wrapperRef);
|
||||||
const rowMapping = ref<Record<number, number>>({});
|
const rowMapping = ref<Record<number, number>>({});
|
||||||
// 是否已经计算过一次
|
// 是否已经计算过一次
|
||||||
const isCalculated = ref(false);
|
const isCalculated = ref(false);
|
||||||
|
@ -31,6 +36,7 @@ export function useExpandable(props: FormRenderProps) {
|
||||||
() => props.showCollapseButton,
|
() => props.showCollapseButton,
|
||||||
() => breakpoints.active().value,
|
() => breakpoints.active().value,
|
||||||
() => props.schema?.length,
|
() => props.schema?.length,
|
||||||
|
() => isVisible.value,
|
||||||
],
|
],
|
||||||
async ([val]) => {
|
async ([val]) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
|
|
|
@ -82,17 +82,17 @@ const {
|
||||||
zIndex,
|
zIndex,
|
||||||
} = usePriorityValues(props, state);
|
} = usePriorityValues(props, state);
|
||||||
|
|
||||||
watch(
|
// watch(
|
||||||
() => showLoading.value,
|
// () => showLoading.value,
|
||||||
(v) => {
|
// (v) => {
|
||||||
if (v && wrapperRef.value) {
|
// if (v && wrapperRef.value) {
|
||||||
wrapperRef.value.scrollTo({
|
// wrapperRef.value.scrollTo({
|
||||||
// behavior: 'smooth',
|
// // behavior: 'smooth',
|
||||||
top: 0,
|
// top: 0,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
function interactOutside(e: Event) {
|
function interactOutside(e: Event) {
|
||||||
if (!closeOnClickModal.value || submitting.value) {
|
if (!closeOnClickModal.value || submitting.value) {
|
||||||
|
@ -266,19 +266,13 @@ const getForceMount = computed(() => {
|
||||||
ref="wrapperRef"
|
ref="wrapperRef"
|
||||||
:class="
|
:class="
|
||||||
cn('relative flex-1 overflow-y-auto p-3', contentClass, {
|
cn('relative flex-1 overflow-y-auto p-3', contentClass, {
|
||||||
'overflow-hidden': showLoading,
|
'pointer-events-none': showLoading || submitting,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<VbenLoading
|
|
||||||
v-if="showLoading || submitting"
|
|
||||||
class="size-full"
|
|
||||||
spinning
|
|
||||||
/>
|
|
||||||
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
<VbenLoading v-if="showLoading || submitting" spinning />
|
||||||
<SheetFooter
|
<SheetFooter
|
||||||
v-if="showFooter"
|
v-if="showFooter"
|
||||||
:class="
|
:class="
|
||||||
|
|
|
@ -123,17 +123,17 @@ watch(
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
// watch(
|
||||||
() => [showLoading.value, submitting.value],
|
// () => [showLoading.value, submitting.value],
|
||||||
([l, s]) => {
|
// ([l, s]) => {
|
||||||
if ((s || l) && wrapperRef.value) {
|
// if ((s || l) && wrapperRef.value) {
|
||||||
wrapperRef.value.scrollTo({
|
// wrapperRef.value.scrollTo({
|
||||||
// behavior: 'smooth',
|
// // behavior: 'smooth',
|
||||||
top: 0,
|
// top: 0,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
function handleFullscreen() {
|
function handleFullscreen() {
|
||||||
props.modalApi?.setState((prev) => {
|
props.modalApi?.setState((prev) => {
|
||||||
|
@ -274,18 +274,13 @@ function handleClosed() {
|
||||||
ref="wrapperRef"
|
ref="wrapperRef"
|
||||||
:class="
|
:class="
|
||||||
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
|
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
|
||||||
'overflow-hidden': showLoading || submitting,
|
'pointer-events-none': showLoading || submitting,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<VbenLoading
|
|
||||||
v-if="showLoading || submitting"
|
|
||||||
class="size-full h-auto min-h-full"
|
|
||||||
spinning
|
|
||||||
/>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
<VbenLoading v-if="showLoading || submitting" spinning />
|
||||||
<VbenIconButton
|
<VbenIconButton
|
||||||
v-if="fullscreenButton"
|
v-if="fullscreenButton"
|
||||||
class="hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-10 top-3 hidden size-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
class="hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-10 top-3 hidden size-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
||||||
|
|
|
@ -42,7 +42,15 @@ async function generateAccessible(
|
||||||
delete route.component;
|
delete route.component;
|
||||||
}
|
}
|
||||||
// 根据router name判断,如果路由已经存在,则不再添加
|
// 根据router name判断,如果路由已经存在,则不再添加
|
||||||
if (!names?.includes(route.name)) {
|
if (names?.includes(route.name)) {
|
||||||
|
// 找到已存在的路由索引并更新,不更新会造成切换用户时,一级目录未更新,homePath 在二级目录导致的404问题
|
||||||
|
const index = root.children?.findIndex(
|
||||||
|
(item) => item.name === route.name,
|
||||||
|
);
|
||||||
|
if (index !== undefined && index !== -1 && root.children) {
|
||||||
|
root.children[index] = route;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
root.children?.push(route);
|
root.children?.push(route);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -12,7 +12,8 @@ defineOptions({
|
||||||
name: 'Page',
|
name: 'Page',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { autoContentHeight = false } = defineProps<PageProps>();
|
const { autoContentHeight = false, heightOffset = 0 } =
|
||||||
|
defineProps<PageProps>();
|
||||||
|
|
||||||
const headerHeight = ref(0);
|
const headerHeight = ref(0);
|
||||||
const footerHeight = ref(0);
|
const footerHeight = ref(0);
|
||||||
|
@ -26,7 +27,7 @@ const docRef = useTemplateRef<HTMLDivElement>('docRef');
|
||||||
const contentStyle = computed<StyleValue>(() => {
|
const contentStyle = computed<StyleValue>(() => {
|
||||||
if (autoContentHeight) {
|
if (autoContentHeight) {
|
||||||
return {
|
return {
|
||||||
height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px - ${docHeight.value}px)`,
|
height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px - ${docHeight.value}px - ${typeof heightOffset === 'number' ? `${heightOffset}px` : heightOffset})`,
|
||||||
overflowY: shouldAutoHeight.value ? 'auto' : 'unset',
|
overflowY: shouldAutoHeight.value ? 'auto' : 'unset',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,4 +8,10 @@ export interface PageProps {
|
||||||
autoContentHeight?: boolean;
|
autoContentHeight?: boolean;
|
||||||
headerClass?: string;
|
headerClass?: string;
|
||||||
footerClass?: string;
|
footerClass?: string;
|
||||||
|
/**
|
||||||
|
* Custom height offset value (in pixels) to adjust content area sizing
|
||||||
|
* when used with autoContentHeight
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
heightOffset?: number;
|
||||||
}
|
}
|
||||||
|
|
1305
pnpm-lock.yaml
1305
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -30,13 +30,13 @@ catalog:
|
||||||
'@intlify/unplugin-vue-i18n': ^6.0.8
|
'@intlify/unplugin-vue-i18n': ^6.0.8
|
||||||
'@jspm/generator': ^2.5.1
|
'@jspm/generator': ^2.5.1
|
||||||
'@manypkg/get-packages': ^2.2.2
|
'@manypkg/get-packages': ^2.2.2
|
||||||
'@nolebase/vitepress-plugin-git-changelog': ^2.16.0
|
'@nolebase/vitepress-plugin-git-changelog': ^2.17.0
|
||||||
'@playwright/test': ^1.52.0
|
'@playwright/test': ^1.52.0
|
||||||
'@pnpm/workspace.read-manifest': ^1000.1.4
|
'@pnpm/workspace.read-manifest': ^1000.1.4
|
||||||
'@stylistic/stylelint-plugin': ^3.1.2
|
'@stylistic/stylelint-plugin': ^3.1.2
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.16
|
'@tailwindcss/typography': ^0.5.16
|
||||||
'@tanstack/vue-query': ^5.74.6
|
'@tanstack/vue-query': ^5.74.7
|
||||||
'@tanstack/vue-store': ^0.7.0
|
'@tanstack/vue-store': ^0.7.0
|
||||||
'@tinymce/tinymce-vue': ^6.1.0
|
'@tinymce/tinymce-vue': ^6.1.0
|
||||||
'@form-create/ant-design-vue': ^3.2.22
|
'@form-create/ant-design-vue': ^3.2.22
|
||||||
|
@ -114,7 +114,7 @@ catalog:
|
||||||
find-up: ^7.0.0
|
find-up: ^7.0.0
|
||||||
get-port: ^7.1.0
|
get-port: ^7.1.0
|
||||||
globals: ^16.0.0
|
globals: ^16.0.0
|
||||||
h3: ^1.15.1
|
h3: ^1.15.3
|
||||||
happy-dom: ^17.4.4
|
happy-dom: ^17.4.4
|
||||||
html-minifier-terser: ^7.2.0
|
html-minifier-terser: ^7.2.0
|
||||||
husky: ^9.1.7
|
husky: ^9.1.7
|
||||||
|
@ -129,7 +129,7 @@ catalog:
|
||||||
lucide-vue-next: ^0.503.0
|
lucide-vue-next: ^0.503.0
|
||||||
medium-zoom: ^1.1.0
|
medium-zoom: ^1.1.0
|
||||||
naive-ui: ^2.41.0
|
naive-ui: ^2.41.0
|
||||||
nitropack: ^2.11.9
|
nitropack: ^2.11.11
|
||||||
nprogress: ^0.2.0
|
nprogress: ^0.2.0
|
||||||
ora: ^8.2.0
|
ora: ^8.2.0
|
||||||
pinia: ^3.0.2
|
pinia: ^3.0.2
|
||||||
|
@ -180,7 +180,7 @@ catalog:
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
vite-plugin-lazy-import: ^1.0.7
|
vite-plugin-lazy-import: ^1.0.7
|
||||||
vite-plugin-pwa: ^1.0.0
|
vite-plugin-pwa: ^1.0.0
|
||||||
vite-plugin-vue-devtools: ^7.7.5
|
vite-plugin-vue-devtools: ^7.7.6
|
||||||
vitepress: ^1.6.3
|
vitepress: ^1.6.3
|
||||||
vitepress-plugin-group-icons: ^1.5.2
|
vitepress-plugin-group-icons: ^1.5.2
|
||||||
vitest: ^3.1.2
|
vitest: ^3.1.2
|
||||||
|
@ -193,7 +193,7 @@ catalog:
|
||||||
vue-tippy: ^6.7.0
|
vue-tippy: ^6.7.0
|
||||||
vue-tsc: 2.1.10
|
vue-tsc: 2.1.10
|
||||||
vxe-pc-ui: ^4.5.14
|
vxe-pc-ui: ^4.5.14
|
||||||
vxe-table: ^4.12.5
|
vxe-table: ^4.13.14
|
||||||
watermark-js-plus: ^1.6.0
|
watermark-js-plus: ^1.6.0
|
||||||
zod: ^3.24.3
|
zod: ^3.24.3
|
||||||
zod-defaults: ^0.1.3
|
zod-defaults: ^0.1.3
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
import { promises as fs } from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join, normalize } from 'node:path';
|
||||||
|
|
||||||
const rootDir = process.cwd();
|
const rootDir = process.cwd();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归查找并删除目标目录
|
* 递归查找并删除目标目录
|
||||||
* @param {string} currentDir - 当前遍历的目录路径
|
* @param {string} currentDir - 当前遍历的目录路径
|
||||||
|
* @param {string[]} targets - 要删除的目标列表
|
||||||
*/
|
*/
|
||||||
async function cleanTargetsRecursively(currentDir, targets) {
|
async function cleanTargetsRecursively(currentDir, targets) {
|
||||||
const items = await fs.readdir(currentDir);
|
const items = await fs.readdir(currentDir);
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
try {
|
try {
|
||||||
const itemPath = join(currentDir, item);
|
const itemPath = normalize(join(currentDir, item));
|
||||||
|
const stat = await fs.lstat(itemPath);
|
||||||
|
|
||||||
if (targets.includes(item)) {
|
if (targets.includes(item)) {
|
||||||
// 匹配到目标目录或文件时直接删除
|
// 匹配到目标目录或文件时直接删除
|
||||||
await fs.rm(itemPath, { force: true, recursive: true });
|
await fs.rm(itemPath, { force: true, recursive: true });
|
||||||
console.log(`Deleted: ${itemPath}`);
|
console.log(`Deleted: ${itemPath}`);
|
||||||
}
|
} else if (stat.isDirectory()) {
|
||||||
const stat = await fs.lstat(itemPath);
|
// 只对目录进行递归处理
|
||||||
if (stat.isDirectory()) {
|
|
||||||
await cleanTargetsRecursively(itemPath, targets);
|
await cleanTargetsRecursively(itemPath, targets);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
// console.error(
|
console.error(
|
||||||
// `Error handling item ${item} in ${currentDir}: ${error.message}`,
|
`Error handling item ${item} in ${currentDir}: ${error.message}`,
|
||||||
// );
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,9 +35,9 @@ async function cleanTargetsRecursively(currentDir, targets) {
|
||||||
(async function startCleanup() {
|
(async function startCleanup() {
|
||||||
// 要删除的目录及文件名称
|
// 要删除的目录及文件名称
|
||||||
const targets = ['node_modules', 'dist', '.turbo', 'dist.zip'];
|
const targets = ['node_modules', 'dist', '.turbo', 'dist.zip'];
|
||||||
|
|
||||||
const deleteLockFile = process.argv.includes('--del-lock');
|
const deleteLockFile = process.argv.includes('--del-lock');
|
||||||
const cleanupTargets = [...targets];
|
const cleanupTargets = [...targets];
|
||||||
|
|
||||||
if (deleteLockFile) {
|
if (deleteLockFile) {
|
||||||
cleanupTargets.push('pnpm-lock.yaml');
|
cleanupTargets.push('pnpm-lock.yaml');
|
||||||
}
|
}
|
||||||
|
@ -46,8 +48,9 @@ async function cleanTargetsRecursively(currentDir, targets) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await cleanTargetsRecursively(rootDir, cleanupTargets);
|
await cleanTargetsRecursively(rootDir, cleanupTargets);
|
||||||
console.log('Cleanup process completed.');
|
console.log('Cleanup process completed successfully.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Unexpected error during cleanup: ${error.message}`);
|
console.error(`Unexpected error during cleanup: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20-slim AS builder
|
FROM node:22-slim AS builder
|
||||||
|
|
||||||
# --max-old-space-size
|
# --max-old-space-size
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
@ -13,6 +13,7 @@ WORKDIR /app
|
||||||
# copy package.json and pnpm-lock.yaml to workspace
|
# copy package.json and pnpm-lock.yaml to workspace
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||||
RUN pnpm run build --filter=\!./docs
|
RUN pnpm run build --filter=\!./docs
|
||||||
|
|
||||||
|
@ -20,12 +21,17 @@ RUN echo "Builder Success 🎉"
|
||||||
|
|
||||||
FROM nginx:stable-alpine AS production
|
FROM nginx:stable-alpine AS production
|
||||||
|
|
||||||
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf
|
# 配置 nginx
|
||||||
|
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf \
|
||||||
|
&& rm -rf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# 复制构建产物
|
||||||
COPY --from=builder /app/playground/dist /usr/share/nginx/html
|
COPY --from=builder /app/playground/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# 复制 nginx 配置
|
||||||
COPY --from=builder /app/scripts/deploy/nginx.conf /etc/nginx/nginx.conf
|
COPY --from=builder /app/scripts/deploy/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# start nginx
|
# 启动 nginx
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
|
@ -1,3 +1,59 @@
|
||||||
# @vben/turbo-run
|
# @vben/turbo-run
|
||||||
|
|
||||||
turbo-run is a command line tool that allows you to run multiple commands in parallel.
|
`turbo-run` 是一个命令行工具,允许你在多个包中并行运行命令。它提供了一个交互式的界面,让你可以选择要运行命令的包。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 🚀 交互式选择要运行的包
|
||||||
|
- 📦 支持 monorepo 项目结构
|
||||||
|
- 🔍 自动检测可用的命令
|
||||||
|
- 🎯 精确过滤目标包
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add -D @vben/turbo-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
基本语法:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo-run [script]
|
||||||
|
```
|
||||||
|
|
||||||
|
例如,如果你想运行 `dev` 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo-run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
工具会自动检测哪些包有 `dev` 命令,并提供一个交互式界面让你选择要运行的包。
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
假设你的项目中有以下包:
|
||||||
|
|
||||||
|
- `@vben/app`
|
||||||
|
- `@vben/admin`
|
||||||
|
- `@vben/website`
|
||||||
|
|
||||||
|
当你运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo-run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
工具会:
|
||||||
|
|
||||||
|
1. 检测哪些包有 `dev` 命令
|
||||||
|
2. 显示一个交互式选择界面
|
||||||
|
3. 让你选择要运行命令的包
|
||||||
|
4. 使用 `pnpm --filter` 在选定的包中运行命令
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 确保你的项目使用 pnpm 作为包管理器
|
||||||
|
- 确保目标包在 `package.json` 中定义了相应的脚本命令
|
||||||
|
- 该工具需要在 monorepo 项目的根目录下运行
|
||||||
|
|
|
@ -25,7 +25,7 @@ export async function run(options: RunOptions) {
|
||||||
|
|
||||||
let selectPkg: string | symbol;
|
let selectPkg: string | symbol;
|
||||||
if (selectPkgs.length > 1) {
|
if (selectPkgs.length > 1) {
|
||||||
selectPkg = await select<any, string>({
|
selectPkg = await select<string>({
|
||||||
message: `Select the app you need to run [${command}]:`,
|
message: `Select the app you need to run [${command}]:`,
|
||||||
options: selectPkgs.map((item) => ({
|
options: selectPkgs.map((item) => ({
|
||||||
label: item?.packageJson.name,
|
label: item?.packageJson.name,
|
||||||
|
|
|
@ -1,3 +1,56 @@
|
||||||
# @vben/vsh
|
# @vben/vsh
|
||||||
|
|
||||||
shell 脚本工具集合
|
一个 Shell 脚本工具集合,用于 Vue Vben Admin 项目的开发和管理。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 🚀 基于 Node.js 的现代化 Shell 工具
|
||||||
|
- 📦 支持模块化开发和按需加载
|
||||||
|
- 🔍 提供依赖检查和分析功能
|
||||||
|
- 🔄 支持循环依赖扫描
|
||||||
|
- 📝 提供包发布检查功能
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 pnpm 安装
|
||||||
|
pnpm add -D @vben/vsh
|
||||||
|
|
||||||
|
# 或者使用 npm
|
||||||
|
npm install -D @vben/vsh
|
||||||
|
|
||||||
|
# 或者使用 yarn
|
||||||
|
yarn add -D @vben/vsh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 全局安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 全局安装
|
||||||
|
pnpm add -g @vben/vsh
|
||||||
|
|
||||||
|
# 使用 vsh 命令
|
||||||
|
vsh [command]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 本地使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在 package.json 中添加脚本
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"vsh": "vsh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 运行命令
|
||||||
|
pnpm vsh [command]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 命令列表
|
||||||
|
|
||||||
|
- `vsh check-deps`: 检查项目依赖
|
||||||
|
- `vsh scan-circular`: 扫描循环依赖
|
||||||
|
- `vsh publish-check`: 检查包发布配置
|
||||||
|
|
|
@ -4,9 +4,12 @@ import { extname } from 'node:path';
|
||||||
|
|
||||||
import { getStagedFiles } from '@vben/node-utils';
|
import { getStagedFiles } from '@vben/node-utils';
|
||||||
|
|
||||||
import { circularDepsDetect, printCircles } from 'circular-dependency-scanner';
|
import { circularDepsDetect } from 'circular-dependency-scanner';
|
||||||
|
|
||||||
const IGNORE_DIR = [
|
// 默认配置
|
||||||
|
const DEFAULT_CONFIG = {
|
||||||
|
allowedExtensions: ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx', '.vue'],
|
||||||
|
ignoreDirs: [
|
||||||
'dist',
|
'dist',
|
||||||
'.turbo',
|
'.turbo',
|
||||||
'output',
|
'output',
|
||||||
|
@ -16,39 +19,93 @@ const IGNORE_DIR = [
|
||||||
'packages/effects/request/src/',
|
'packages/effects/request/src/',
|
||||||
'packages/@core/ui-kit/menu-ui/src/',
|
'packages/@core/ui-kit/menu-ui/src/',
|
||||||
'packages/@core/ui-kit/popup-ui/src/',
|
'packages/@core/ui-kit/popup-ui/src/',
|
||||||
].join(',');
|
],
|
||||||
|
threshold: 0, // 循环依赖的阈值
|
||||||
|
} as const;
|
||||||
|
|
||||||
const IGNORE = [`**/{${IGNORE_DIR}}/**`];
|
// 类型定义
|
||||||
|
type CircularDependencyResult = string[];
|
||||||
|
|
||||||
|
interface CheckCircularConfig {
|
||||||
|
allowedExtensions?: string[];
|
||||||
|
ignoreDirs?: string[];
|
||||||
|
threshold?: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface CommandOptions {
|
interface CommandOptions {
|
||||||
|
config?: CheckCircularConfig;
|
||||||
staged: boolean;
|
staged: boolean;
|
||||||
verbose: boolean;
|
verbose: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkCircular({ staged, verbose }: CommandOptions) {
|
// 缓存机制
|
||||||
|
const cache = new Map<string, CircularDependencyResult[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化循环依赖的输出
|
||||||
|
* @param circles - 循环依赖结果
|
||||||
|
*/
|
||||||
|
function formatCircles(circles: CircularDependencyResult[]): void {
|
||||||
|
if (circles.length === 0) {
|
||||||
|
console.log('✅ No circular dependencies found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('⚠️ Circular dependencies found:');
|
||||||
|
circles.forEach((circle, index) => {
|
||||||
|
console.log(`\nCircular dependency #${index + 1}:`);
|
||||||
|
circle.forEach((file) => console.log(` → ${file}`));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查项目中的循环依赖
|
||||||
|
* @param options - 检查选项
|
||||||
|
* @param options.staged - 是否只检查暂存区文件
|
||||||
|
* @param options.verbose - 是否显示详细信息
|
||||||
|
* @param options.config - 自定义配置
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
async function checkCircular({
|
||||||
|
config = {},
|
||||||
|
staged,
|
||||||
|
verbose,
|
||||||
|
}: CommandOptions): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 合并配置
|
||||||
|
const finalConfig = {
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成忽略模式
|
||||||
|
const ignorePattern = `**/{${finalConfig.ignoreDirs.join(',')}}/**`;
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`;
|
||||||
|
if (cache.has(cacheKey)) {
|
||||||
|
const cachedResults = cache.get(cacheKey);
|
||||||
|
if (cachedResults) {
|
||||||
|
verbose && formatCircles(cachedResults);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测循环依赖
|
||||||
const results = await circularDepsDetect({
|
const results = await circularDepsDetect({
|
||||||
absolute: staged,
|
absolute: staged,
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
ignore: IGNORE,
|
ignore: [ignorePattern],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (staged) {
|
if (staged) {
|
||||||
let files = await getStagedFiles();
|
let files = await getStagedFiles();
|
||||||
|
const allowedExtensions = new Set(finalConfig.allowedExtensions);
|
||||||
const allowedExtensions = new Set([
|
|
||||||
'.cjs',
|
|
||||||
'.js',
|
|
||||||
'.jsx',
|
|
||||||
'.mjs',
|
|
||||||
'.ts',
|
|
||||||
'.tsx',
|
|
||||||
'.vue',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 过滤文件列表
|
// 过滤文件列表
|
||||||
files = files.filter((file) => allowedExtensions.has(extname(file)));
|
files = files.filter((file) => allowedExtensions.has(extname(file)));
|
||||||
|
|
||||||
const circularFiles: string[][] = [];
|
const circularFiles: CircularDependencyResult[] = [];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
|
@ -58,23 +115,56 @@ async function checkCircular({ staged, verbose }: CommandOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verbose && printCircles(circularFiles);
|
|
||||||
|
// 更新缓存
|
||||||
|
cache.set(cacheKey, circularFiles);
|
||||||
|
verbose && formatCircles(circularFiles);
|
||||||
} else {
|
} else {
|
||||||
verbose && printCircles(results);
|
// 更新缓存
|
||||||
|
cache.set(cacheKey, results);
|
||||||
|
verbose && formatCircles(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果发现循环依赖,只输出警告信息
|
||||||
|
if (results.length > 0) {
|
||||||
|
console.log(
|
||||||
|
'\n⚠️ Warning: Circular dependencies found, please check and fix',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'❌ Error checking circular dependencies:',
|
||||||
|
error instanceof Error ? error.message : error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineCheckCircularCommand(cac: CAC) {
|
/**
|
||||||
|
* 定义检查循环依赖的命令
|
||||||
|
* @param cac - CAC实例
|
||||||
|
*/
|
||||||
|
function defineCheckCircularCommand(cac: CAC): void {
|
||||||
cac
|
cac
|
||||||
.command('check-circular')
|
.command('check-circular')
|
||||||
.option(
|
.option('--staged', 'Only check staged files')
|
||||||
'--staged',
|
.option('--verbose', 'Show detailed information')
|
||||||
'Whether it is the staged commit mode, in which mode, if there is a circular dependency, an alarm will be given.',
|
.option('--threshold <number>', 'Threshold for circular dependencies', {
|
||||||
)
|
default: 0,
|
||||||
.usage(`Analysis of project circular dependencies.`)
|
})
|
||||||
.action(async ({ staged }) => {
|
.option('--ignore-dirs <dirs>', 'Directories to ignore, comma separated')
|
||||||
await checkCircular({ staged, verbose: true });
|
.usage('Analyze project circular dependencies')
|
||||||
|
.action(async ({ ignoreDirs, staged, threshold, verbose }) => {
|
||||||
|
const config: CheckCircularConfig = {
|
||||||
|
threshold: Number(threshold),
|
||||||
|
...(ignoreDirs && { ignoreDirs: ignoreDirs.split(',') }),
|
||||||
|
};
|
||||||
|
|
||||||
|
await checkCircular({
|
||||||
|
config,
|
||||||
|
staged,
|
||||||
|
verbose: verbose ?? true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { defineCheckCircularCommand };
|
export { type CheckCircularConfig, defineCheckCircularCommand };
|
||||||
|
|
|
@ -4,30 +4,9 @@ import { getPackages } from '@vben/node-utils';
|
||||||
|
|
||||||
import depcheck from 'depcheck';
|
import depcheck from 'depcheck';
|
||||||
|
|
||||||
async function runDepcheck() {
|
// 默认配置
|
||||||
const { packages } = await getPackages();
|
const DEFAULT_CONFIG = {
|
||||||
await Promise.all(
|
// 需要忽略的依赖匹配
|
||||||
packages.map(async (pkg) => {
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
'@vben/backend-mock',
|
|
||||||
'@vben/commitlint-config',
|
|
||||||
'@vben/eslint-config',
|
|
||||||
'@vben/lint-staged-config',
|
|
||||||
'@vben/node-utils',
|
|
||||||
'@vben/prettier-config',
|
|
||||||
'@vben/stylelint-config',
|
|
||||||
'@vben/tailwind-config',
|
|
||||||
'@vben/tsconfig',
|
|
||||||
'@vben/vite-config',
|
|
||||||
'@vben/vite-config',
|
|
||||||
'@vben/vsh',
|
|
||||||
].includes(pkg.packageJson.name)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unused = await depcheck(pkg.dir, {
|
|
||||||
ignoreMatches: [
|
ignoreMatches: [
|
||||||
'vite',
|
'vite',
|
||||||
'vitest',
|
'vitest',
|
||||||
|
@ -38,11 +17,52 @@ async function runDepcheck() {
|
||||||
'@types/*',
|
'@types/*',
|
||||||
'@vben-core/design',
|
'@vben-core/design',
|
||||||
],
|
],
|
||||||
|
// 需要忽略的包
|
||||||
|
ignorePackages: [
|
||||||
|
'@vben/backend-mock',
|
||||||
|
'@vben/commitlint-config',
|
||||||
|
'@vben/eslint-config',
|
||||||
|
'@vben/lint-staged-config',
|
||||||
|
'@vben/node-utils',
|
||||||
|
'@vben/prettier-config',
|
||||||
|
'@vben/stylelint-config',
|
||||||
|
'@vben/tailwind-config',
|
||||||
|
'@vben/tsconfig',
|
||||||
|
'@vben/vite-config',
|
||||||
|
'@vben/vsh',
|
||||||
|
],
|
||||||
|
// 需要忽略的文件模式
|
||||||
ignorePatterns: ['dist', 'node_modules', 'public'],
|
ignorePatterns: ['dist', 'node_modules', 'public'],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
interface DepcheckResult {
|
||||||
|
dependencies: string[];
|
||||||
|
devDependencies: string[];
|
||||||
|
missing: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DepcheckConfig {
|
||||||
|
ignoreMatches?: string[];
|
||||||
|
ignorePackages?: string[];
|
||||||
|
ignorePatterns?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PackageInfo {
|
||||||
|
dir: string;
|
||||||
|
packageJson: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理依赖检查结果
|
||||||
|
* @param unused - 依赖检查结果
|
||||||
|
*/
|
||||||
|
function cleanDepcheckResult(unused: DepcheckResult): void {
|
||||||
// 删除file:前缀的依赖提示,该依赖是本地依赖
|
// 删除file:前缀的依赖提示,该依赖是本地依赖
|
||||||
Reflect.deleteProperty(unused.missing, 'file:');
|
Reflect.deleteProperty(unused.missing, 'file:');
|
||||||
|
|
||||||
|
// 清理路径依赖
|
||||||
Object.keys(unused.missing).forEach((key) => {
|
Object.keys(unused.missing).forEach((key) => {
|
||||||
unused.missing[key] = (unused.missing[key] || []).filter(
|
unused.missing[key] = (unused.missing[key] || []).filter(
|
||||||
(item: string) => !item.startsWith('/'),
|
(item: string) => !item.startsWith('/'),
|
||||||
|
@ -51,35 +71,125 @@ async function runDepcheck() {
|
||||||
Reflect.deleteProperty(unused.missing, key);
|
Reflect.deleteProperty(unused.missing, key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
/**
|
||||||
Object.keys(unused.missing).length === 0 &&
|
* 格式化依赖检查结果
|
||||||
unused.dependencies.length === 0 &&
|
* @param pkgName - 包名
|
||||||
unused.devDependencies.length === 0
|
* @param unused - 依赖检查结果
|
||||||
) {
|
*/
|
||||||
|
function formatDepcheckResult(pkgName: string, unused: DepcheckResult): void {
|
||||||
|
const hasIssues =
|
||||||
|
Object.keys(unused.missing).length > 0 ||
|
||||||
|
unused.dependencies.length > 0 ||
|
||||||
|
unused.devDependencies.length > 0;
|
||||||
|
|
||||||
|
if (!hasIssues) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error(
|
|
||||||
'\n',
|
|
||||||
pkg.packageJson.name,
|
|
||||||
'\n missing:',
|
|
||||||
unused.missing,
|
|
||||||
'\n dependencies:',
|
|
||||||
unused.dependencies,
|
|
||||||
'\n devDependencies:',
|
|
||||||
unused.devDependencies,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function defineDepcheckCommand(cac: CAC) {
|
console.log('\n📦 Package:', pkgName);
|
||||||
cac
|
|
||||||
.command('check-dep')
|
if (Object.keys(unused.missing).length > 0) {
|
||||||
.usage(`Analysis of project circular dependencies.`)
|
console.log('❌ Missing dependencies:');
|
||||||
.action(async () => {
|
Object.entries(unused.missing).forEach(([dep, files]) => {
|
||||||
await runDepcheck();
|
console.log(` - ${dep}:`);
|
||||||
|
files.forEach((file) => console.log(` → ${file}`));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { defineDepcheckCommand };
|
if (unused.dependencies.length > 0) {
|
||||||
|
console.log('⚠️ Unused dependencies:');
|
||||||
|
unused.dependencies.forEach((dep) => console.log(` - ${dep}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unused.devDependencies.length > 0) {
|
||||||
|
console.log('⚠️ Unused devDependencies:');
|
||||||
|
unused.devDependencies.forEach((dep) => console.log(` - ${dep}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行依赖检查
|
||||||
|
* @param config - 配置选项
|
||||||
|
*/
|
||||||
|
async function runDepcheck(config: DepcheckConfig = {}): Promise<void> {
|
||||||
|
try {
|
||||||
|
const finalConfig = {
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { packages } = await getPackages();
|
||||||
|
|
||||||
|
let hasIssues = false;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
packages.map(async (pkg: PackageInfo) => {
|
||||||
|
// 跳过需要忽略的包
|
||||||
|
if (finalConfig.ignorePackages.includes(pkg.packageJson.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unused = await depcheck(pkg.dir, {
|
||||||
|
ignoreMatches: finalConfig.ignoreMatches,
|
||||||
|
ignorePatterns: finalConfig.ignorePatterns,
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanDepcheckResult(unused);
|
||||||
|
|
||||||
|
const pkgHasIssues =
|
||||||
|
Object.keys(unused.missing).length > 0 ||
|
||||||
|
unused.dependencies.length > 0 ||
|
||||||
|
unused.devDependencies.length > 0;
|
||||||
|
|
||||||
|
if (pkgHasIssues) {
|
||||||
|
hasIssues = true;
|
||||||
|
formatDepcheckResult(pkg.packageJson.name, unused);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasIssues) {
|
||||||
|
console.log('\n✅ Dependency check completed, no issues found');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'❌ Dependency check failed:',
|
||||||
|
error instanceof Error ? error.message : error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义依赖检查命令
|
||||||
|
* @param cac - CAC实例
|
||||||
|
*/
|
||||||
|
function defineDepcheckCommand(cac: CAC): void {
|
||||||
|
cac
|
||||||
|
.command('check-dep')
|
||||||
|
.option(
|
||||||
|
'--ignore-packages <packages>',
|
||||||
|
'Packages to ignore, comma separated',
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'--ignore-matches <matches>',
|
||||||
|
'Dependency patterns to ignore, comma separated',
|
||||||
|
)
|
||||||
|
.option(
|
||||||
|
'--ignore-patterns <patterns>',
|
||||||
|
'File patterns to ignore, comma separated',
|
||||||
|
)
|
||||||
|
.usage('Analyze project dependencies')
|
||||||
|
.action(async ({ ignoreMatches, ignorePackages, ignorePatterns }) => {
|
||||||
|
const config: DepcheckConfig = {
|
||||||
|
...(ignorePackages && { ignorePackages: ignorePackages.split(',') }),
|
||||||
|
...(ignoreMatches && { ignoreMatches: ignoreMatches.split(',') }),
|
||||||
|
...(ignorePatterns && { ignorePatterns: ignorePatterns.split(',') }),
|
||||||
|
};
|
||||||
|
|
||||||
|
await runDepcheck(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { defineDepcheckCommand, type DepcheckConfig };
|
||||||
|
|
|
@ -2,40 +2,73 @@ import { colors, consola } from '@vben/node-utils';
|
||||||
|
|
||||||
import { cac } from 'cac';
|
import { cac } from 'cac';
|
||||||
|
|
||||||
|
import { version } from '../package.json';
|
||||||
import { defineCheckCircularCommand } from './check-circular';
|
import { defineCheckCircularCommand } from './check-circular';
|
||||||
import { defineDepcheckCommand } from './check-dep';
|
import { defineDepcheckCommand } from './check-dep';
|
||||||
import { defineCodeWorkspaceCommand } from './code-workspace';
|
import { defineCodeWorkspaceCommand } from './code-workspace';
|
||||||
import { defineLintCommand } from './lint';
|
import { defineLintCommand } from './lint';
|
||||||
import { definePubLintCommand } from './publint';
|
import { definePubLintCommand } from './publint';
|
||||||
|
|
||||||
|
// 命令描述
|
||||||
|
const COMMAND_DESCRIPTIONS = {
|
||||||
|
'check-circular': 'Check for circular dependencies',
|
||||||
|
'check-dep': 'Check for unused dependencies',
|
||||||
|
'code-workspace': 'Manage VS Code workspace settings',
|
||||||
|
lint: 'Run linting on the project',
|
||||||
|
publint: 'Check package.json files for publishing standards',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize and run the CLI
|
||||||
|
*/
|
||||||
|
async function main(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const vsh = cac('vsh');
|
const vsh = cac('vsh');
|
||||||
|
|
||||||
// vsh lint
|
// Register commands
|
||||||
defineLintCommand(vsh);
|
defineLintCommand(vsh);
|
||||||
|
|
||||||
// vsh publint
|
|
||||||
definePubLintCommand(vsh);
|
definePubLintCommand(vsh);
|
||||||
|
|
||||||
// vsh code-workspace
|
|
||||||
defineCodeWorkspaceCommand(vsh);
|
defineCodeWorkspaceCommand(vsh);
|
||||||
|
|
||||||
// vsh check-circular
|
|
||||||
defineCheckCircularCommand(vsh);
|
defineCheckCircularCommand(vsh);
|
||||||
|
|
||||||
// vsh check-dep
|
|
||||||
defineDepcheckCommand(vsh);
|
defineDepcheckCommand(vsh);
|
||||||
|
|
||||||
// Invalid command
|
// Handle invalid commands
|
||||||
vsh.on('command:*', () => {
|
vsh.on('command:*', ([cmd]) => {
|
||||||
consola.error(colors.red('Invalid command!'));
|
consola.error(
|
||||||
|
colors.red(`Invalid command: ${cmd}`),
|
||||||
|
'\n',
|
||||||
|
colors.yellow('Available commands:'),
|
||||||
|
'\n',
|
||||||
|
Object.entries(COMMAND_DESCRIPTIONS)
|
||||||
|
.map(([cmd, desc]) => ` ${colors.cyan(cmd)} - ${desc}`)
|
||||||
|
.join('\n'),
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
vsh.usage('vsh');
|
// Set up CLI
|
||||||
|
vsh.usage('vsh <command> [options]');
|
||||||
vsh.help();
|
vsh.help();
|
||||||
|
vsh.version(version);
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
vsh.parse();
|
vsh.parse();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
consola.error(error);
|
consola.error(
|
||||||
|
colors.red('An unexpected error occurred:'),
|
||||||
|
'\n',
|
||||||
|
error instanceof Error ? error.message : error,
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the CLI
|
||||||
|
main().catch((error) => {
|
||||||
|
consola.error(
|
||||||
|
colors.red('Failed to start CLI:'),
|
||||||
|
'\n',
|
||||||
|
error instanceof Error ? error.message : error,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue