Merge pull request !337 from xingyu/vite8
pull/339/head^2
xingyu 2026-03-23 08:57:12 +00:00 committed by Gitee
commit ca39b8d0c9
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
204 changed files with 5850 additions and 4637 deletions

View File

@ -2,6 +2,8 @@
"recommendations": [
// Vue 3
"Vue.volar",
// eslint VS Code
"dbaeumer.vscode-eslint",
// oxlint VS Code
"oxc.oxc-vscode",
// Visual Studio Code Stylelint
@ -10,6 +12,8 @@
"oxc.oxc-vscode",
// dotenv
"mikestead.dotenv",
// YAML ESLint pnpm-workspace.yaml
"redhat.vscode-yaml",
//
"streetsidesoftware.code-spell-checker",
// Tailwind CSS VS Code

View File

@ -1,5 +1,5 @@
{
"tailwindCSS.experimental.configFile": "packages/@core/base/design/src/css/global.css",
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/theme.css",
"tailwindCSS.lint.suggestCanonicalClasses": "ignore",
// workbench
"workbench.list.smoothScrolling": true,
@ -41,7 +41,9 @@
"oxc.typeAware": true,
"oxc.configPath": "oxlint.config.ts",
"oxc.fmt.configPath": "oxfmt.config.ts",
"eslint.useFlatConfig": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.oxc": "explicit",
"source.fixAll.stylelint": "explicit",
"source.organizeImports": "never"
@ -171,7 +173,7 @@
"emmet.triggerExpansionOnTab": false,
"errorLens.enabledDiagnosticLevels": ["warning", "error"],
"errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"],
"errorLens.excludeBySource": ["cSpell", "Grammarly"],
"stylelint.enable": true,
"stylelint.packageManager": "pnpm",

View File

@ -14,6 +14,7 @@ import type {
import type { Component, Ref } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Sortable } from '@vben/hooks';
import type { Recordable } from '@vben/types';
import {
@ -21,6 +22,9 @@ import {
defineAsyncComponent,
defineComponent,
h,
nextTick,
onMounted,
onUnmounted,
ref,
render,
unref,
@ -33,6 +37,7 @@ import {
IconPicker,
VCropper,
} from '@vben/common-ui';
import { useSortable } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { isEmpty } from '@vben/utils';
@ -132,260 +137,261 @@ const withDefaultPlaceholder = <T extends Component>(
});
};
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;
}
const IMAGE_EXTENSIONS = new Set([
'bmp',
'gif',
'jpeg',
'jpg',
'png',
'svg',
'webp',
]);
/**
*
*/
function isImageFile(file: UploadFile): boolean {
if (file.url) {
try {
const pathname = new URL(file.url, 'http://localhost').pathname;
const ext = pathname.split('.').pop()?.toLowerCase();
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
} catch {
const ext = file.url?.split('.').pop()?.toLowerCase();
return ext ? IMAGE_EXTENSIONS.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 (!file.type) {
const ext = file.name?.split('.').pop()?.toLowerCase();
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
}
return file.type.startsWith('image/');
}
/**
*
*/
function createDefaultUploadSlots(listType: string, placeholder: string) {
if (listType === 'picture-card') {
return { default: () => placeholder };
}
return {
default: () =>
h(
Button,
{
icon: h(IconifyIcon, {
icon: 'ant-design:upload-outlined',
class: 'mb-1 size-4',
}),
},
() => placeholder,
),
};
// 创建默认的上传按钮插槽
const createDefaultSlotsWithUpload = (
listType: string,
placeholder: string,
) => {
switch (listType) {
case 'picture-card': {
return {
default: () => placeholder,
};
}
default: {
return {
default: () =>
h(
Button,
{
icon: h(IconifyIcon, {
icon: 'ant-design:upload-outlined',
class: 'mb-1 size-4',
}),
}
/**
* Base64
*/
function getBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () => resolve(reader.result as string));
reader.addEventListener('error', reject);
});
}
/**
*
*/
async function previewImage(
file: UploadFile,
visible: Ref<boolean>,
fileList: Ref<UploadProps['fileList']>,
) {
// 非图片文件直接打开链接
if (!isImageFile(file)) {
const url = file.url || file.preview;
if (url) {
window.open(url, '_blank');
} else {
message.error($t('ui.formRules.previewWarning'));
}
return;
}
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
Image,
PreviewGroup,
]);
// 过滤图片文件并生成预览
const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f));
for (const imgFile of imageFiles) {
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
imgFile.preview = await getBase64(imgFile.originFileObj);
}
}
const container = document.createElement('div');
document.body.append(container);
let isUnmounted = false;
const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid);
const PreviewWrapper = {
setup() {
return () => {
if (isUnmounted) return null;
return h(
PreviewGroupComponent,
{
class: 'hidden',
preview: {
visible: visible.value,
current: currentIndex,
onVisibleChange: (value: boolean) => {
visible.value = value;
if (!value) {
setTimeout(() => {
if (!isUnmounted && container) {
isUnmounted = true;
render(null, container);
container.remove();
}
}, 300);
}
},
() => placeholder,
},
},
() =>
imageFiles.map((imgFile) =>
h(ImageComponent, {
key: imgFile.uid,
src: imgFile.url || imgFile.preview,
}),
),
};
}
}
);
};
},
};
// 构建预览图片组
const previewImage = async (
file: UploadFile,
visible: Ref<boolean>,
fileList: Ref<UploadProps['fileList']>,
) => {
// 如果当前文件不是图片,直接打开
if (!isImageFile(file)) {
if (file.url) {
window.open(file.url, '_blank');
} else if (file.preview) {
window.open(file.preview, '_blank');
} else {
message.error($t('ui.formRules.previewWarning'));
}
return;
}
// 对于图片文件,继续使用预览组
const [ImageComponent, PreviewGroupComponent] = await Promise.all([
Image,
PreviewGroup,
]);
render(h(PreviewWrapper), container);
}
const getBase64 = (file: File) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () => resolve(reader.result));
reader.addEventListener('error', (error) => reject(error));
});
};
// 从fileList中过滤出所有图片文件
const imageFiles = (unref(fileList) || []).filter((element) =>
isImageFile(element),
);
// 为所有没有预览地址的图片生成预览
for (const imgFile of imageFiles) {
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
}
}
const container: HTMLElement | null = document.createElement('div');
/**
*
*/
function cropImage(file: File, aspectRatio: string | undefined) {
return new Promise<Blob | string | undefined>((resolve, reject) => {
const container = document.createElement('div');
document.body.append(container);
// 用于追踪组件是否已卸载
let isUnmounted = false;
let objectUrl: null | string = null;
const PreviewWrapper = {
const open = ref<boolean>(true);
const cropperRef = ref<InstanceType<typeof VCropper> | 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(
PreviewGroupComponent,
Modal,
{
class: 'hidden',
preview: {
visible: visible.value,
// 设置初始显示的图片索引
current: imageFiles.findIndex((f) => f.uid === file.uid),
onVisibleChange: (value: boolean) => {
visible.value = value;
if (!value) {
// 延迟清理,确保动画完成
setTimeout(() => {
if (!isUnmounted && container) {
isUnmounted = true;
render(null, container);
container.remove();
}
}, 300);
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();
if (dataUrl) {
resolve(dataUrl);
} else {
reject(new Error($t('ui.crop.errorTip')));
}
},
} catch {
reject(new Error($t('ui.crop.errorTip')));
} finally {
closeModal();
}
},
onCancel() {
resolve('');
closeModal();
},
},
() =>
// 渲染所有图片文件
imageFiles.map((imgFile) =>
h(ImageComponent, {
key: imgFile.uid,
src: imgFile.url || imgFile.preview,
}),
),
h(VCropper, {
ref: (ref: any) => (cropperRef.value = ref),
img: objectUrl as string,
aspectRatio,
}),
);
};
},
};
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<boolean>(true);
const cropperRef = ref<InstanceType<typeof VCropper> | 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);
});
};
render(h(CropperWrapper), container);
});
}
/**
*
*/
const withPreviewUpload = () => {
return defineComponent({
name: Upload.name,
emits: ['update:modelValue'],
setup: (
setup(
props: any,
{ attrs, slots, emit }: { attrs: any; emit: any; slots: any },
) => {
) {
const previewVisible = ref<boolean>(false);
const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`);
const placeholder = attrs?.placeholder || $t('ui.placeholder.upload');
const listType = attrs?.listType || attrs?.['list-type'] || 'text';
const fileList = ref<UploadProps['fileList']>(
attrs?.fileList || attrs?.['file-list'] || [],
);
@ -399,12 +405,14 @@ const withPreviewUpload = () => {
file: UploadFile,
originFileList: Array<File>,
) => {
// 文件大小限制
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 &&
@ -412,14 +420,11 @@ const withPreviewUpload = () => {
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);
});
if (!blob) {
throw new Error($t('ui.crop.errorTip'));
}
return blob;
}
return attrs.beforeUpload?.(file) ?? true;
@ -427,12 +432,9 @@ const withPreviewUpload = () => {
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(
@ -449,21 +451,88 @@ const withPreviewUpload = () => {
await previewImage(file, previewVisible, fileList);
};
const renderUploadButton = (): any => {
const isDisabled = attrs.disabled;
// 如果禁用,不渲染上传按钮
if (isDisabled) {
return null;
}
// 否则渲染默认上传按钮
const renderUploadButton = () => {
if (attrs.disabled) return null;
return isEmpty(slots)
? createDefaultSlotsWithUpload(listType, placeholder)
? createDefaultUploadSlots(listType, placeholder)
: slots;
};
// 可以监听到表单API设置的值
// 拖拽排序
const draggable = computed(
() => (attrs.draggable ?? false) && !attrs.disabled,
);
const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
const sortableInstance = ref<null | Sortable>(null);
const styleId = `upload-drag-style-${uploadId}`;
function injectDragStyle() {
if (!document.querySelector(`[id="${styleId}"]`)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
[data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; }
[data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
`;
document.head.append(style);
}
}
function removeDragStyle() {
document.querySelector(`[id="${styleId}"]`)?.remove();
}
async function initSortable(retryCount = 0) {
if (!draggable.value) return;
injectDragStyle();
await nextTick();
await new Promise((resolve) => setTimeout(resolve, 100));
const container = document.querySelector(
`[data-upload-id="${uploadId}"] .ant-upload-list`,
) as HTMLElement;
if (!container) {
if (retryCount < 5) {
setTimeout(() => initSortable(retryCount + 1), 200);
}
return;
}
const { initializeSortable } = useSortable(container, {
animation: 300,
delay: 400,
delayOnTouchOnly: true,
filter:
'.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
if (
oldIndex === undefined ||
newIndex === undefined ||
oldIndex === newIndex
) {
return;
}
const list = [...(fileList.value || [])];
const [movedItem] = list.splice(oldIndex, 1);
if (movedItem) {
list.splice(newIndex, 0, movedItem);
fileList.value = list;
}
attrs.onDragSort?.(oldIndex, newIndex);
emit('update:modelValue', fileList.value);
},
});
sortableInstance.value = await initializeSortable();
}
// 监听表单值变化
watch(
() => attrs.modelValue,
(res) => {
@ -471,18 +540,28 @@ const withPreviewUpload = () => {
},
);
onMounted(initSortable);
onUnmounted(() => {
sortableInstance.value?.destroy();
removeDragStyle();
});
return () =>
h(
Upload,
{
...props,
...attrs,
fileList: fileList.value,
beforeUpload: handleBeforeUpload,
onChange: handleChange,
onPreview: handlePreview,
},
renderUploadButton(),
'div',
{ 'data-upload-id': uploadId, class: 'w-full' },
h(
Upload,
{
...props,
...attrs,
fileList: fileList.value,
beforeUpload: handleBeforeUpload,
onChange: handleChange,
onPreview: handlePreview,
},
renderUploadButton() as any,
),
);
},
});

View File

@ -3,7 +3,7 @@ import { isFunction, isObject } from '@vben/utils';
const WILDCARD = '*';
function CamundaModdleExtension(eventBus) {
// eslint-disable-next-line unicorn/no-this-assignment
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
const self = this;
eventBus.on('moddleCopy.canCopyProperty', (context) => {

View File

@ -64,12 +64,12 @@ const [Form, formApi] = useVbenForm({
// ================= =================
/** SKU 扩展类型 */
interface SkuExtension extends MallSpuApi.Sku {
interface SkuExtension extends MallSpuApi.Sku {
productConfig: MallDiscountActivityApi.DiscountProduct;
}
/** SPU 扩展类型 */
interface SpuExtension extends MallSpuApi.Spu {
interface SpuExtension extends MallSpuApi.Spu {
skus?: SkuExtension[];
}

View File

@ -50,7 +50,7 @@ function openRightMessage(item: MallKefuConversationApi.Conversation) {
/** 获得消息类型 */
const getConversationDisplayText = computed(
() => (lastMessageContentType: number, lastMessageContent: string) => {
() => (lastMessageContentType: number, lastMessageContent: string) => {
switch (lastMessageContentType) {
case KeFuMessageContentTypeEnum.IMAGE: {
return '[图片消息]';

View File

@ -21,7 +21,7 @@ import ProductBrowsingHistory from './product-browsing-history.vue';
const activeTab = ref<string>('会员信息');
const tabActivation = computed(() => (tab: string) => activeTab.value === tab);
const tabActivation = computed(() => (tab: string) => activeTab.value === tab);
/** tab 切换 */
const productBrowsingHistoryRef =

View File

@ -51,7 +51,7 @@ const loadHistory = ref(false); // 加载历史消息
/** 获悉消息内容 */
const getMessageContent = computed(
() => (item: any) => jsonParse(item.content),
() => (item: any) => jsonParse(item.content),
);
/** 获得消息列表 */

View File

@ -25,7 +25,7 @@ const emits = defineEmits<{
}>();
/** 选择赠送的优惠类型拓展 */
interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate {
interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate {
giveCount?: number;
}

View File

@ -16,7 +16,7 @@
},
"type": "module",
"scripts": {
"build": "pnpm vite build --mode production",
"#build": "pnpm vite build --mode production",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"preview": "vite preview",

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

View File

@ -3,7 +3,7 @@ import { isFunction, isObject } from '@vben/utils';
const WILDCARD = '*';
function CamundaModdleExtension(eventBus) {
// eslint-disable-next-line unicorn/no-this-assignment
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
const self = this;
eventBus.on('moddleCopy.canCopyProperty', (context) => {

View File

@ -64,12 +64,12 @@ const [Form, formApi] = useVbenForm({
// ================= =================
/** SKU 扩展类型 */
interface SkuExtension extends MallSpuApi.Sku {
interface SkuExtension extends MallSpuApi.Sku {
productConfig: MallDiscountActivityApi.DiscountProduct;
}
/** SPU 扩展类型 */
interface SpuExtension extends MallSpuApi.Spu {
interface SpuExtension extends MallSpuApi.Spu {
skus?: SkuExtension[];
}

View File

@ -50,7 +50,7 @@ function openRightMessage(item: MallKefuConversationApi.Conversation) {
/** 获得消息类型 */
const getConversationDisplayText = computed(
() => (lastMessageContentType: number, lastMessageContent: string) => {
() => (lastMessageContentType: number, lastMessageContent: string) => {
switch (lastMessageContentType) {
case KeFuMessageContentTypeEnum.IMAGE: {
return '[图片消息]';

View File

@ -21,7 +21,7 @@ import ProductBrowsingHistory from './product-browsing-history.vue';
const activeTab = ref<string>('会员信息');
const tabActivation = computed(() => (tab: string) => activeTab.value === tab);
const tabActivation = computed(() => (tab: string) => activeTab.value === tab);
/** tab 切换 */
const productBrowsingHistoryRef =

View File

@ -57,7 +57,7 @@ const loadHistory = ref(false); // 加载历史消息
/** 获悉消息内容 */
const getMessageContent = computed(
() => (item: any) => jsonParse(item.content),
() => (item: any) => jsonParse(item.content),
);
/** 获得消息列表 */

View File

@ -25,7 +25,7 @@ const emits = defineEmits<{
}>();
/** 选择赠送的优惠类型拓展 */
interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate {
interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate {
giveCount?: number;
}

View File

@ -13,12 +13,14 @@
"axios",
"brotli",
"cascader",
"chatcmpl",
"clsx",
"cropperjs",
"dedup",
"defu",
"demi",
"dotenv",
"echart",
"echarts",
"ependencies",
"esbuild",
@ -63,8 +65,9 @@
"tabler",
"taze",
"tdesign",
"tsdown",
"tsgolint",
"Turborepo",
"turborepo",
"ui-kit",
"uicons",
"unplugin",

View File

@ -42,6 +42,10 @@ export const en = defineConfig({
base: '/en/commercial/',
items: sidebarCommercial(),
},
'/en/components/': {
base: '/en/components/',
items: sidebarComponents(),
},
'/en/guide/': { base: '/en/guide/', items: sidebarGuide() },
},
},
@ -63,6 +67,11 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
},
{ link: 'introduction/quick-start', text: 'Quick Start' },
{ link: 'introduction/thin', text: 'Lite Version' },
{
base: '/',
link: 'components/introduction',
text: 'Components',
},
],
},
{
@ -132,6 +141,68 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
];
}
function sidebarComponents(): DefaultTheme.SidebarItem[] {
return [
{
text: 'Components',
items: [
{
link: 'introduction',
text: 'Introduction',
},
],
},
{
collapsed: false,
text: 'Layout UI',
items: [
{
link: 'layout-ui/page',
text: 'Page',
},
],
},
{
collapsed: false,
text: 'Common UI',
items: [
{
link: 'common-ui/vben-api-component',
text: 'ApiComponent',
},
{
link: 'common-ui/vben-alert',
text: 'Alert',
},
{
link: 'common-ui/vben-modal',
text: 'Modal',
},
{
link: 'common-ui/vben-drawer',
text: 'Drawer',
},
{
link: 'common-ui/vben-form',
text: 'Form',
},
{
link: 'common-ui/vben-vxe-table',
text: 'Vxe Table',
},
{
link: 'common-ui/vben-count-to-animator',
text: 'CountToAnimator',
},
{
link: 'common-ui/vben-ellipsis-text',
text: 'EllipsisText',
},
],
},
];
}
function nav(): DefaultTheme.NavItem[] {
return [
{
@ -143,11 +214,11 @@ function nav(): DefaultTheme.NavItem[] {
link: '/en/guide/introduction/vben',
text: 'Guide',
},
// {
// activeMatch: '^/en/components/',
// link: '/en/components/introduction',
// text: 'Components',
// },
{
activeMatch: '^/en/components/',
link: '/en/components/introduction',
text: 'Components',
},
{
text: 'Historical Versions',
items: [

View File

@ -28,6 +28,7 @@
"devDependencies": {
"@nolebase/vitepress-plugin-git-changelog": "catalog:",
"@tailwindcss/vite": "catalog:",
"@vben/tailwind-config": "workspace:*",
"@vben/vite-config": "workspace:*",
"@vite-pwa/vitepress": "catalog:",
"vitepress": "catalog:",

View File

@ -11,6 +11,7 @@ import { $t } from '@vben/locales';
import { initComponentAdapter } from './component';
initComponentAdapter();
setupVbenForm<ComponentType>({
config: {
baseModelPropName: 'value',

View File

@ -4,132 +4,90 @@ outline: deep
# Vben Alert 轻量提示框
框架提供的一些用于轻量提示的弹窗仅使用js代码即可快速动态创建提示而不需要在template写任何代码
`Alert` 提供了一组纯 JavaScript 调用的轻量提示框能力,适合快速创建 `alert`、`confirm`、`prompt` 这类简单交互
::: info 用场景
::: info 用场景
Alert提供的功能与Modal类似但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求请使用VbenModal
:::
`Alert``Modal` 的能力有部分重叠,但更适合临时确认、简单提示和轻量输入场景。复杂弹窗仍然建议使用 `Vben Modal`。:::
::: tip 注意
Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下不支持HMR热更新代码变更后需要关闭这些弹窗后重新打开。
:::
::: tip README
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
:::
通过 `alert`、`confirm`、`prompt` 动态创建的弹窗,在已经打开的情况下不支持 HMR 热更新。修改相关代码后,需要关闭后重新打开。:::
## 基础用法
使用 `alert` 创建只有一个确认按钮的提示框。
使用 `alert` 创建只有确认按钮的提示框。
<DemoPreview dir="demos/vben-alert/alert" />
使用 `confirm` 创建确认和取消按钮的提示框。
使用 `confirm` 创建带确认和取消按钮的提示框。
<DemoPreview dir="demos/vben-alert/confirm" />
使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。
使用 `prompt` 创建可接收用户输入的提示框。
<DemoPreview dir="demos/vben-alert/prompt" />
## useAlertContext
弹窗的content、footer、icon使用自定义组件时在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗
`content`、`footer` 或 `icon` 使用的是自定义组件时,可以在这些组件内部通过 `useAlertContext()` 获取当前弹窗上下文,并主动触发确认或取消
::: tip 注意
`useAlertContext`只能用在setup或者函数式组件中。
:::
`useAlertContext` 只能在 `setup` 或函数式组件中使用。:::
### Methods
| 方法 | 描述 | 类型 | 版本要求 |
| --------- | ------------------ | -------- | -------- |
| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 |
| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 |
| 方法 | 描述 | 类型 | 版本要求 |
| --------- | ---------------------- | ------------ | -------- |
| doConfirm | 触发当前弹窗的确认操作 | `() => void` | `>5.5.4` |
| doCancel | 触发当前弹窗的取消操作 | `() => void` | `>5.5.4` |
## 类型说明
```ts
/** 预置的图标类型 */
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
export type BeforeCloseScope = {
/** 是否为点击确认按钮触发的关闭 */
isConfirm: boolean;
};
/**
* alert 属性
*/
export type AlertProps = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (
scope: BeforeCloseScope,
) => boolean | Promise<boolean | undefined> | undefined;
/** 边框 */
bordered?: boolean;
/** 按钮对齐方式 */
buttonAlign?: 'center' | 'end' | 'start';
/** 取消按钮的标题 */
cancelText?: string;
/** 是否居中显示 */
centered?: boolean;
/** 确认按钮的标题 */
confirmText?: string;
/** 弹窗容器的额外样式 */
containerClass?: string;
/** 弹窗提示内容 */
content: Component | string;
/** 弹窗内容的额外样式 */
contentClass?: string;
/** 执行beforeClose回调期间在内容区域显示一个loading遮罩*/
contentMasking?: boolean;
/** 弹窗底部内容(与按钮在同一个容器中) */
footer?: Component | string;
/** 弹窗的图标(在标题的前面) */
icon?: Component | IconType;
/**
* 弹窗遮罩模糊效果
*/
overlayBlur?: number;
/** 是否显示取消按钮 */
showCancel?: boolean;
/** 弹窗标题 */
title?: string;
};
/** prompt 属性 */
export type PromptProps<T = any> = {
/** 关闭前的回调如果返回false则终止关闭 */
beforeClose?: (scope: {
isConfirm: boolean;
value: T | undefined;
}) => boolean | Promise<boolean | undefined> | undefined;
/** 用于接受用户输入的组件 */
component?: Component;
/** 输入组件的属性 */
componentProps?: Recordable<any>;
/** 输入组件的插槽 */
componentSlots?: Recordable<Component>;
/** 默认值 */
componentSlots?:
| (() => any)
| Recordable<unknown>
| VNode
| VNodeArrayChildren;
defaultValue?: T;
/** 输入组件的值属性名 */
modelPropName?: string;
} & Omit<AlertProps, 'beforeClose'>;
/**
* 函数签名
* alert和confirm的函数签名相同。
* confirm默认会显示取消按钮而alert默认只有一个按钮
* */
export function alert(options: AlertProps): Promise<void>;
export function alert(
message: string,
@ -141,19 +99,10 @@ export function alert(
options?: Partial<AlertProps>,
): Promise<void>;
/**
* 弹出输入框的函数签名。
* beforeClose的参数会传入用户当前输入的值
* component指定接受用户输入的组件默认为Input
* componentProps 为输入组件设置的属性数据
* defaultValue 默认的值
* modelPropName 输入组件的值属性名称。默认为modelValue
*/
export async function prompt<T = any>(
options: Omit<AlertProps, 'beforeClose'> & {
beforeClose?: (
scope: BeforeCloseScope & {
/** 输入组件的当前值 */
value: T;
},
) => boolean | Promise<boolean | undefined> | undefined;

View File

@ -14,7 +14,7 @@ outline: deep
## 基础用法
通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过`api`获取数据(`beforerFetch`、`afterFetch`将分别在`api`运行前、运行后被调用),使用`resultField`从中提取数组,使用`valueField`、`labelField`等来从数据中提取value和label如果提供了`childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过`optionsPropName`指定的属性传递给目标组件。
通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过 `api` 获取数据(`beforeFetch`、`afterFetch` 将分别在 `api` 运行前、运行后被调用),使用 `resultField` 从中提取数组,使用 `valueField`、`labelField` 等来从数据中提取 value label如果提供了 `childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过 `optionsPropName` 指定的属性传递给目标组件。
::: details 包装级联选择器,点击下拉时开始加载远程数据

View File

@ -4,56 +4,54 @@ outline: deep
# Vben CountToAnimator 数字动画
框架提供的数字动画组件,支持数字动画效果。
`CountToAnimator` 用于展示数字滚动动画效果。
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
> 如果文档内没有覆盖到你需要的细节,可以结合在线示例一起查看。
::: info 写在前面
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
:::
这是一个轻量数字动画组件。如果你需要完全不同的过渡控制方式,也可以直接使用原生动画方案或自行封装。:::
## 基础用法
通过 `start-val``end-val`设置数字动画的开始值和结束值, 持续时间`3000`ms
通过 `start-val``end-val` 设置数字动画的起始值和结束值,配合 `duration` 控制动画时长
<DemoPreview dir="demos/vben-count-to-animator/basic" />
## 自定义前缀分隔符
## 自定义前缀分隔符
通过 `prefix``separator` 设置数字动画的前缀和分隔符
通过 `prefix`、`suffix`、`separator` 和 `decimal` 可以控制展示格式
<DemoPreview dir="demos/vben-count-to-animator/custom" />
### Props
| 属性名 | 描述 | 类型 | 默认值 |
| ---------- | -------------- | --------- | -------- |
| startVal | 起始值 | `number` | `0` |
| endVal | 结束值 | `number` | `2021` |
| duration | 动画持续时间 | `number` | `1500` |
| autoplay | 自动执行 | `boolean` | `true` |
| prefix | 前缀 | `string` | - |
| suffix | 后缀 | `string` | - |
| separator | 分隔符 | `string` | `,` |
| color | 字体颜色 | `string` | - |
| useEasing | 是否开启动画 | `boolean` | `true` |
| transition | 动画效果 | `string` | `linear` |
| decimals | 保留小数点位数 | `number` | `0` |
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| startVal | 起始值 | `number` | `0` |
| endVal | 结束值 | `number` | `2021` |
| duration | 动画持续时间 | `number` | `1500` |
| autoplay | 是否自动播放 | `boolean` | `true` |
| prefix | 前缀 | `string` | `''` |
| suffix | 后缀 | `string` | `''` |
| separator | 千分位分隔符 | `string` | `','` |
| decimal | 小数点分隔符 | `string` | `'.'` |
| color | 文本颜色 | `string` | `''` |
| useEasing | 是否启用过渡预设 | `boolean` | `true` |
| transition | 过渡预设名称 | `keyof typeof TransitionPresets` | `'linear'` |
| decimals | 保留小数位数 | `number` | `0` |
### Events
| 事件名 | 描述 | 类型 |
| -------------- | -------------- | -------------- |
| started | 动画开始 | `()=>void` |
| finished | 动画结束 | `()=>void` |
| ~~onStarted~~ | ~~动画已开始~~ | ~~`()=>void`~~ |
| ~~onFinished~~ | ~~动画已结束~~ | ~~`()=>void`~~ |
| 事件名 | 描述 | 类型 |
| -------------- | ----------------------------- | ---------------- |
| started | 动画开始时触发 | `() => void` |
| finished | 动画结束时触发 | `() => void` |
| ~~onStarted~~ | ~~已废弃,请改用 `started`~~ | ~~`() => void`~~ |
| ~~onFinished~~ | ~~已废弃,请改用 `finished`~~ | ~~`() => void`~~ |
### Methods
| 方法名 | 描述 | 类型 |
| ------ | ------------ | ---------- |
| start | 开始执行动画 | `()=>void` |
| reset | 重置 | `()=>void` |
| 方法名 | 描述 | 类型 |
| ------ | -------------------------------- | ------------ |
| reset | 重置为 `startVal` 并重新执行动画 | `() => void` |

View File

@ -55,7 +55,7 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra
- `VbenDrawer` 组件对于参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
- 如果抽屉的默认行为不符合你的预期,可以在对应应用的 `apps/<app>/src/bootstrap.ts` 中修改 `setDefaultDrawerProps` 的参数来设置默认属性,例如修改默认 `zIndex` 等。
:::
@ -116,7 +116,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
| 事件名 | 描述 | 类型 | 版本限制 |
| --- | --- | --- | --- |
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | --- |
| onBeforeClose | 关闭前触发,返回 `false` 或 Promise reject 则禁止关闭 | `()=>Promise<boolean \| undefined>\|boolean\|undefined` | >5.5.2 支持 Promise |
| onCancel | 点击取消按钮触发 | `()=>void` | --- |
| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 |
| onConfirm | 点击确认按钮触发 | `()=>void` | --- |
@ -140,7 +140,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
| 方法 | 描述 | 类型 | 版本限制 |
| --- | --- | --- | --- |
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>drawerApi` |
| setState | 动态设置抽屉状态属性 | `(((prev: DrawerState) => Partial<DrawerState>)\| Partial<DrawerState>)=>drawerApi` |
| open | 打开弹窗 | `()=>void` | --- |
| close | 关闭弹窗 | `()=>void` | --- |
| setData | 设置共享数据 | `<T>(data:T)=>drawerApi` | --- |

View File

@ -4,31 +4,31 @@ outline: deep
# Vben EllipsisText 省略文本
框架提供的文本展示组件可配置超长省略、tooltip提示、展开收起等功能
`EllipsisText` 用于展示超长文本支持省略、Tooltip 提示以及点击展开收起
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
> 如果文档内没有覆盖到你需要的细节,可以结合在线示例一起查看。
## 基础用法
通过默认插槽设置文本内容,`maxWidth`属性设置最大宽度。
通过默认插槽提供文本内容,`maxWidth` 用于限制文本区域宽度。
<DemoPreview dir="demos/vben-ellipsis-text/line" />
## 可折叠文本
## 可折叠文本
通过`line`设置折叠后的行数,`expand`属性设置是否支持展开收起。
通过 `line` 设置折叠后的最大行数,通过 `expand` 开启点击展开与收起。
<DemoPreview dir="demos/vben-ellipsis-text/expand" />
## 自定义提示浮层
通过名为`tooltip`的插槽定制提示信息
通过 `tooltip` 插槽自定义提示内容
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
## 自动显示 tooltip
## 仅在省略时显示 Tooltip
通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。
通过 `tooltip-when-ellipsis` 控制仅在文本被截断时显示 Tooltip。
<DemoPreview dir="demos/vben-ellipsis-text/auto-display" />
@ -38,27 +38,27 @@ outline: deep
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| expand | 支持点击展开或收起 | `boolean` | `false` |
| line | 文本最大行数 | `number` | `1` |
| expand | 是否支持点击展开或收起 | `boolean` | `false` |
| line | 文本最大显示行数 | `number` | `1` |
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
| placement | 提示浮层位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
| tooltip | 启用文本提示 | `boolean` | `true` |
| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` |
| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` |
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
| tooltipColor | 提示文本的颜色 | `string` | - |
| tooltipFontSize | 提示文本的大小 | `string` | - |
| tooltipMaxWidth | 提示浮层的最大宽度。如不设置则保持与文本宽度一致 | `number` | - |
| tooltipOverlayStyle | 提示内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` |
| placement | 提示浮层位置 | `'bottom' \| 'left' \| 'right' \| 'top'` | `'top'` |
| tooltip | 是否启用文本提示 | `boolean` | `true` |
| tooltipWhenEllipsis | 是否仅在文本被截断时显示提示 | `boolean` | `false` |
| ellipsisThreshold | 文本截断检测阈值,值越大判定越严格 | `number` | `3` |
| tooltipBackgroundColor | 提示背景色 | `string` | `''` |
| tooltipColor | 提示文字颜色 | `string` | `''` |
| tooltipFontSize | 提示文字大小,单位 `px` | `number` | `14` |
| tooltipMaxWidth | 提示内容最大宽度,单位 `px` | `number` | - |
| tooltipOverlayStyle | 提示内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` |
### Events
| 事件名 | 描述 | 类型 |
| ------------ | ------------ | -------------------------- |
| expandChange | 展开状态变 | `(isExpand:boolean)=>void` |
| 事件名 | 描述 | 类型 |
| ------------ | ------------------ | ----------------------------- |
| expandChange | 展开状态变化时触发 | `(isExpand: boolean) => void` |
### Slots
| 插槽名 | 描述 |
| ------- | -------------------------------- |
| tooltip | 启用文本提示时,用来定制提示内容 |
| 插槽名 | 描述 |
| ------- | ---------------------------------- |
| tooltip | 开启文本提示时,用于自定义提示内容 |

View File

@ -35,10 +35,15 @@ import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { initComponentAdapter } from './component';
initComponentAdapter();
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件库空值为 null重置表单时需要和实际组件行为保持一致
emptyStateValue: null,
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
@ -87,55 +92,32 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { globalShareState } from '@vben/common-ui';
import { $t } from '@vben/locales';
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
import {
AutoComplete,
Button,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
const withDefaultPlaceholder = <T extends Component>(
component: T,
@ -171,7 +153,6 @@ export type ComponentType =
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| 'IconPicker';
| BaseFormComponentType;
async function initComponentAdapter() {
@ -189,7 +170,6 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),

View File

@ -69,7 +69,7 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`内部Modal及其子组件会在被关闭后<b>完全销毁</b>
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性如默认隐藏全屏按钮修改默认ZIndex等。
- 如果弹窗的默认行为不符合你的预期,可以在对应应用的 `apps/<app>/src/bootstrap.ts` 中修改 `setDefaultModalProps` 的参数来设置默认属性,例如默认隐藏全屏按钮、修改默认 `zIndex` 等。
:::

View File

@ -4,25 +4,19 @@ outline: deep
# Vben Vxe Table 表格
框架提供的Table 列表组件基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid),结合`Vben Form 表单`进行了二次封装
`Vben Vxe Table` 基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 和 `Vben Form` 做了二次封装,用于构建带搜索表单的列表页面
其中,表头的 **表单搜索** 部分采用了`Vben Form表单`,表格主体部分使用了`vxe-grid`组件,支持表格的分页、排序、筛选等功能。
> 如果文档内没有参数说明,可以尝试在在线示例或者在 [vxe-grid 官方API 文档](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 内寻找
> 如果文档内没有覆盖到你需要的细节,可以结合在线示例和 [vxe-grid 官方 API](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 一起查看。
::: info 写在前面
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
:::
如果现有封装不满足你的场景,可以直接使用原生 `vxe-table` 能力,或者在适配层中继续扩展。:::
## 适配器
表格底层使用 [vxe-table](https://vxetable.cn/#/start/install) 进行实现,所以你可以使用 `vxe-table` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架
底层表格基于 `vxe-table`,每个应用都可以在自己的适配层中配置默认行为、自定义渲染器以及与 UI 组件库的集成
### 适配器说明
每个应用都可以自己配置`vxe-table`的适配器,你可以根据自己的需求。下面是一个简单的配置示例:
### 适配器示例
::: details vxe-table 表格适配器
@ -46,7 +40,6 @@ setupVbenVxeTable({
},
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
proxyConfig: {
@ -65,7 +58,6 @@ setupVbenVxeTable({
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
@ -73,7 +65,6 @@ setupVbenVxeTable({
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
@ -84,9 +75,6 @@ setupVbenVxeTable({
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
@ -100,55 +88,50 @@ export type * from '@vben/plugins/vxe-table';
## 基础表格
使用 `useVbenVxeGrid` 创建最基础的表格。
通过 `useVbenVxeGrid` 创建一个基础表格。
<DemoPreview dir="demos/vben-vxe-table/basic" />
## 远程加载
通过指定 `proxyConfig.ajax``query` 方法,可以实现远程加载数据
通过配置 `proxyConfig.ajax.query` 实现远程数据加载
<DemoPreview dir="demos/vben-vxe-table/remote" />
## 树形表格
树形表格的数据源为扁平结构,可以指定`treeConfig`配置项,实现树形表格
树形表格的数据源通常是扁平结构,可以通过 `treeConfig` 转换为树形展示
```typescript
```ts
treeConfig: {
transform: true, // 指定表格为树形表格
parentField: 'parentId', // 父节点字段名
rowField: 'id', // 行数据字段名
transform: true,
parentField: 'parentId',
rowField: 'id',
},
```
<DemoPreview dir="demos/vben-vxe-table/tree" />
## 固定表头/
## 固定列
列固定可选参数: `'left' | 'right' | '' | null`
固定列可选值为 `'left' | 'right' | '' | null`
<DemoPreview dir="demos/vben-vxe-table/fixed" />
## 自定义单元格
自定义单元格有两种实现方式
可以通过插槽或自定义渲染器实现单元格定制。
- 通过 `slots` 插槽
- 通过 `customCell` 自定义单元格,但是要先添加渲染器
```typescript
// 表格配置项可以用 cellRender: { name: 'CellImage' },
```ts
vxeUI.renderer.add('CellImage', {
renderDefault(_renderOpts, params) {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] } as any); // 注意此处的Image 组件来源于Antd需要自行引入,否则会使用js的Image类
return h(Image, { src: row[column.field] } as any);
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderDefault(renderOpts) {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
@ -163,23 +146,20 @@ vxeUI.renderer.add('CellLink', {
## 搜索表单
**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)
搜索区域底层使用的是 `Vben Form`。启用搜索表单后,可以通过 `gridOptions.toolbarConfig.search = true` 在工具栏中显示搜索面板开关按钮
当启用了表单搜索时可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
所有以 `form-` 开头的具名插槽都会自动转发到搜索表单。
### 定制分隔条
### 自定义分隔条
当你启用表单搜索时在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器如将其放在一个Card内默认的表单和表格之间的分隔条可能就显得格格不入了下面的代码演示了如何定制这个分隔条
启用搜索表单时,表单和表格主体之间默认会显示一个分隔条。可以通过 `separator` 调整或关闭它
```ts
const [Grid] = useVbenVxeGrid({
formOptions: {},
gridOptions: {},
// 完全移除分隔条
separator: false,
// 你也可以使用下面的代码来移除分隔条
// separator: { show: false },
// 或者使用下面的代码来改变分隔条的颜色
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
});
```
@ -188,40 +168,36 @@ const [Grid] = useVbenVxeGrid({
## 单元格编辑
通过指定`editConfig.mode`为`cell`,可以实现单元格编辑。
通过设置 `editConfig.mode = 'cell'` 开启单元格编辑。
<DemoPreview dir="demos/vben-vxe-table/edit-cell" />
## 行编辑
通过指定`editConfig.mode`为`row`,可以实现行编辑。
通过设置 `editConfig.mode = 'row'` 开启整行编辑。
<DemoPreview dir="demos/vben-vxe-table/edit-row" />
## 虚拟滚动
通过 scroll-y.enabled 与 scroll-y.gt 组合开启,其中 enabled 为总开关gt 是指当总行数大于指定行数时自动开启
通过 `scroll-y.enabled``scroll-y.gt` 组合开启纵向虚拟滚动
> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical)
> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical)
<DemoPreview dir="demos/vben-vxe-table/virtual" />
## API
`useVbenVxeGrid` 返回一个数组,第一个元素是表格组件,第二个元素是表格的方法
`useVbenVxeGrid` 返回一个数组,第一个元素是表格组件,第二个元素是表格 API
```vue
<script setup lang="ts">
import { useVbenVxeGrid } from '#/adapter/vxe-table';
// Grid 为表格组件
// gridApi 为表格的方法
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {},
formOptions: {},
gridEvents: {},
// 属性
// 事件
});
</script>
@ -232,45 +208,42 @@ const [Grid, gridApi] = useVbenVxeGrid({
### GridApi
useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。
| 方法名 | 描述 | 类型 | 说明 |
| --- | --- | --- | --- |
| setLoading | 设置loading状态 | `(loading)=>void` | - |
| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partial<VxeGridProps['gridOptions'])=>void` | - |
| reload | 重载表格,会进行初始化 | `(params:any)=>void` | - |
| query | 重载表格,会保留当前分页 | `(params:any)=>void` | - |
| grid | vxe-table grid实例 | `VxeGridInstance` | - |
| formApi | vbenForm api实例 | `FormApi` | - |
| toggleSearchForm | 设置搜索表单显示状态 | `(show?: boolean)=>boolean` | 当省略参数时,则将表单在显示和隐藏两种状态之间切换 |
| setLoading | 设置 loading 状态 | `(loading: boolean) => void` | - |
| setGridOptions | 更新 `gridOptions` | `(options: Partial<VxeGridProps['gridOptions']>) => void` | - |
| reload | 重新加载表格,并重置到初始分页 | `(params?: Record<string, any>) => void` | - |
| query | 重新查询表格,保留当前分页 | `(params?: Record<string, any>) => void` | - |
| grid | `vxe-grid` 实例 | `VxeGridInstance` | - |
| formApi | 搜索表单 API 实例 | `FormApi` | - |
| toggleSearchForm | 切换或指定搜索表单显示状态 | `(show?: boolean) => boolean` | 传入参数时强制设置;不传参数时在显示和隐藏之间切换,并返回当前状态 |
## Props
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中
所有属性都通过 `useVbenVxeGrid` 的第一个参数传入
| 属性名 | 描述 | 类型 | 版本要求 |
| --- | --- | --- | --- |
| tableTitle | 表格标题 | `string` | - |
| tableTitleHelp | 表格标题帮助信息 | `string` | - |
| gridClass | grid组件的class | `string` | - |
| gridOptions | grid组件的参数 | `VxeTableGridProps` | - |
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - |
| formOptions | 表单参数 | `VbenFormProps` | - |
| class | 外层容器的 class | `string` | - |
| gridClass | `vxe-grid` 的 class | `string` | - |
| gridOptions | `vxe-grid` 配置 | `DeepPartial<VxeTableGridOptions>` | - |
| gridEvents | `vxe-grid` 事件 | `DeepPartial<VxeGridListeners>` | - |
| formOptions | 搜索表单配置 | `VbenFormProps` | - |
| showSearchForm | 是否显示搜索表单 | `boolean` | - |
| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 |
| separator | 搜索表单与表格主体之间的分隔条 | `boolean \| SeparatorOptions` | `>5.5.4` |
## Slots
大部分插槽的说明请参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api),但工具栏部分由于做了一些定制封装,需使用以下插槽定制表格的工具栏:
大部分插槽说明可参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api),这里列出封装层新增或约定的部分。
| 插槽名 | 描述 |
| --------------- | -------------------------------------------- |
| toolbar-actions | 工具栏左侧部分(表格标题附近) |
| toolbar-tools | 工具栏右侧部分vxeTable原生工具按钮的左侧 |
| table-title | 表格标题插槽 |
| 插槽名 | 描述 |
| --------------- | ------------------------------------ |
| toolbar-actions | 工具栏左侧区域,位于标题附近 |
| toolbar-tools | 工具栏右侧区域,位于内置工具按钮左侧 |
| table-title | 自定义表格标题 |
::: info 搜索表单插槽
::: info 搜索表单插槽
对于使用了搜索表单的表格来说,所有以`form-`开头的命名插槽都会传递给表单。
:::
当启用了搜索表单时,所有以 `form-` 开头的具名插槽都会被转发给表单。:::

View File

@ -4,41 +4,38 @@ outline: deep
# Page 常规页面组件
提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分
`Page` 是页面内容区最常用的顶层布局容器,内置了标题区、内容区和底部区三部分结构
::: info 写在前面
本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。
:::
这是一个基础页面容器。如果你的业务页面需要更复杂的布局,例如双列、侧边操作区或自定义滚动区域,建议在 `Page` 之上继续封装。:::
## 基础用法
将`Page`作为你的业务页面的根组件即可。
直接 `Page` 作为业务页面的根组件使用即可。
### Props
| 属性名 | 描述 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| title | 页面标题 | `string\|slot` | - | - |
| description | 页面描述(标题下的内容) | `string\|slot` | - | - |
| contentClass | 内容区域的class | `string` | - | - |
| headerClass | 头部区域的class | `string` | - | - |
| footerClass | 底部区域的class | `string` | - | - |
| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | - |
| title | 页面标题 | `string \| slot` | - | - |
| description | 页面描述 | `string \| slot` | - | - |
| contentClass | 内容区域的 class | `string` | - | - |
| headerClass | 头部区域的 class | `string` | - | - |
| footerClass | 底部区域的 class | `string` | - | - |
| autoContentHeight | 根据可视内容高度自动计算内容区高度 | `boolean` | `false` | 开启后内容区会根据布局可视高度自动扣减头部和底部高度 |
| heightOffset | 额外扣减的内容区高度偏移量 | `number` | `0` | 仅在 `autoContentHeight` 开启时生效,单位为 `px` |
::: tip 注意
如果`title`、`description`、`extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。
:::
如果 `title`、`description`、`extra` 三者都没有提供有效内容,无论是通过 `props` 还是 `slots`,头部区域都不会渲染。:::
### Slots
| 插槽名称 | 描述 |
| ----------- | ------------ |
| default | 页面内容 |
| title | 页面标题 |
| description | 页面描述 |
| extra | 页面头部右侧 |
| footer | 页面底部 |
| 插槽名称 | 描述 |
| ----------- | ---------------- |
| default | 页面内容 |
| title | 页面标题 |
| description | 页面描述 |
| extra | 页面头部右侧内容 |
| footer | 页面底部内容 |

View File

@ -32,7 +32,7 @@ function handleUpdate(len: number) {
<div
v-for="item in list"
:key="item"
class="flex-center h-[220px] w-full bg-muted even:bg-heavy"
class="flex-center h-55 w-full bg-muted even:bg-heavy"
>
{{ item }}
</div>

View File

@ -6,6 +6,6 @@ const [Drawer, drawerApi] = useVbenDrawer();
<template>
<div>
<VbenButton @click="() => drawerApi.open()">Open</VbenButton>
<Drawer class="w-[600px]" title="基础示例"> drawer content </Drawer>
<Drawer class="w-150" title="基础示例"> drawer content </Drawer>
</div>
</template>

View File

@ -25,11 +25,11 @@ function openScaleModal() {
<VbenButton @click="openScaleModal"></VbenButton>
</div>
<SlideModal title="滑动动画示例" class="w-[500px]">
<SlideModal title="滑动动画示例" class="w-125">
<p>这是使用滑动动画的弹窗从顶部向下滑动进入</p>
</SlideModal>
<ScaleModal title="缩放动画示例" class="w-[500px]">
<ScaleModal title="缩放动画示例" class="w-125">
<p>这是使用缩放动画的弹窗以缩放淡入淡出的方式显示</p>
</ScaleModal>
</div>

View File

@ -32,7 +32,7 @@ function handleUpdate(len: number) {
<div
v-for="item in list"
:key="item"
class="flex-center h-[220px] w-full bg-muted even:bg-heavy"
class="flex-center h-55 w-full bg-muted even:bg-heavy"
>
{{ item }}
</div>

View File

@ -6,6 +6,6 @@ const [Modal, modalApi] = useVbenModal();
<template>
<div>
<VbenButton @click="() => modalApi.open()">Open</VbenButton>
<Modal class="w-[600px]" title="基础示例"> modal content </Modal>
<Modal class="w-150" title="基础示例"> modal content </Modal>
</div>
</template>

View File

@ -112,7 +112,7 @@ const gridOptions: VxeGridProps<RowType> = {
},
toolbarConfig: {
//
// @ts-ignore
// @ts-ignore -
search: true,
},
};

View File

@ -67,7 +67,7 @@ const collapseAll = () => {
</script>
<template>
<div class="vp-raw h-[300px] w-full">
<div class="vp-raw h-75 w-full">
<Grid>
<template #toolbar-tools>
<Button class="mr-2" type="primary" @click="expandAll">

View File

@ -58,7 +58,7 @@ onMounted(() => {
</script>
<template>
<div class="vp-raw h-[500px] w-full">
<div class="vp-raw h-125 w-full">
<Grid />
</div>
</template>

View File

@ -0,0 +1,76 @@
---
outline: deep
---
# Vben Alert
`Alert` provides lightweight JavaScript-driven dialogs for simple `alert`, `confirm`, and `prompt` style interactions.
## Basic Usage
Use `alert` for a single confirm button dialog:
<DemoPreview dir="demos/vben-alert/alert" />
Use `confirm` for confirm/cancel interactions:
<DemoPreview dir="demos/vben-alert/confirm" />
Use `prompt` when you need simple user input:
<DemoPreview dir="demos/vben-alert/prompt" />
## useAlertContext
If `content`, `footer`, or `icon` is rendered through a custom component, you can call `useAlertContext()` inside that component to access the current dialog actions.
| Method | Description | Type |
| ----------- | -------------------------- | ------------ |
| `doConfirm` | trigger the confirm action | `() => void` |
| `doCancel` | trigger the cancel action | `() => void` |
## Core Types
```ts
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
export type BeforeCloseScope = {
isConfirm: boolean;
};
export type AlertProps = {
beforeClose?: (
scope: BeforeCloseScope,
) => boolean | Promise<boolean | undefined> | undefined;
bordered?: boolean;
buttonAlign?: 'center' | 'end' | 'start';
cancelText?: string;
centered?: boolean;
confirmText?: string;
containerClass?: string;
content: Component | string;
contentClass?: string;
contentMasking?: boolean;
footer?: Component | string;
icon?: Component | IconType;
overlayBlur?: number;
showCancel?: boolean;
title?: string;
};
export type PromptProps<T = any> = {
beforeClose?: (scope: {
isConfirm: boolean;
value: T | undefined;
}) => boolean | Promise<boolean | undefined> | undefined;
component?: Component;
componentProps?: Recordable<any>;
componentSlots?:
| (() => any)
| Recordable<unknown>
| VNode
| VNodeArrayChildren;
defaultValue?: T;
modelPropName?: string;
} & Omit<AlertProps, 'beforeClose'>;
```

View File

@ -0,0 +1,69 @@
---
outline: deep
---
# Vben ApiComponent
`ApiComponent` is a wrapper used to attach remote-option loading behavior to an existing component while preserving the original component usage pattern.
## Common Usage
The current wrapper flow is:
- pass the target component through `component`
- fetch remote data through `api`
- transform data through `beforeFetch` and `afterFetch`
- map remote fields through `resultField`, `valueField`, `labelField`, and `childrenField`
- pass normalized options to the target component through `optionsPropName`
```vue
<script lang="ts" setup>
import { ApiComponent } from '@vben/common-ui';
import { Cascader } from 'ant-design-vue';
function fetchApi() {
return Promise.resolve([
{
label: 'Zhejiang',
value: 'zhejiang',
children: [{ label: 'Hangzhou', value: 'hangzhou' }],
},
]);
}
</script>
<template>
<ApiComponent
:api="fetchApi"
:component="Cascader"
:immediate="false"
children-field="children"
loading-slot="suffixIcon"
visible-event="onDropdownVisibleChange"
/>
</template>
```
## Current Props
| Prop | Description | Type |
| --- | --- | --- |
| `component` | wrapped target component | `Component` |
| `api` | remote request function | `(arg?: any) => Promise<any>` |
| `params` | extra request params | `Record<string, any>` |
| `beforeFetch` | hook before request | `AnyPromiseFunction` |
| `afterFetch` | hook after request | `AnyPromiseFunction` |
| `visibleEvent` | event name used to lazy-load data | `string` |
| `loadingSlot` | slot name used to render the loading icon | `string` |
| `modelPropName` | model prop name of the wrapped component | `string` |
| `autoSelect` | auto-pick the first / last / only option, or use a custom function | `'first' \| 'last' \| 'one' \| ((items) => item) \| false` |
## Exposed Methods
| Method | Description |
| ------------------------ | -------------------------------------- |
| `getComponentRef()` | returns the wrapped component instance |
| `updateParam(newParams)` | merges and updates request params |
| `getOptions()` | returns loaded options |
| `getValue()` | returns the current bound value |

View File

@ -0,0 +1,51 @@
---
outline: deep
---
# Vben CountToAnimator
`CountToAnimator` renders animated number transitions.
## Basic Usage
Use `start-val`, `end-val`, and `duration` to control the animation range and timing.
<DemoPreview dir="demos/vben-count-to-animator/basic" />
## Formatting
Use `prefix`, `suffix`, `separator`, and `decimal` to control how the number is displayed.
<DemoPreview dir="demos/vben-count-to-animator/custom" />
## Props
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| `startVal` | starting value | `number` | `0` |
| `endVal` | ending value | `number` | `2021` |
| `duration` | animation duration in ms | `number` | `1500` |
| `autoplay` | start automatically | `boolean` | `true` |
| `prefix` | value prefix | `string` | `''` |
| `suffix` | value suffix | `string` | `''` |
| `separator` | thousands separator | `string` | `','` |
| `decimal` | decimal separator | `string` | `'.'` |
| `color` | text color | `string` | `''` |
| `useEasing` | enable transition preset easing | `boolean` | `true` |
| `transition` | transition preset name | `keyof typeof TransitionPresets` | `'linear'` |
| `decimals` | decimal places to keep | `number` | `0` |
## Events
| Event | Description | Type |
| ------------ | ------------------------------- | ------------ |
| `started` | fired when the animation starts | `() => void` |
| `finished` | fired when the animation ends | `() => void` |
| `onStarted` | deprecated alias of `started` | `() => void` |
| `onFinished` | deprecated alias of `finished` | `() => void` |
## Exposed Methods
| Method | Description | Type |
| ------- | --------------------------------- | ------------ |
| `reset` | reset to `startVal` and run again | `() => void` |

View File

@ -0,0 +1,56 @@
---
outline: deep
---
# Vben Drawer
`Vben Drawer` is the shared drawer wrapper used by the framework. It supports auto-height layout, loading state, connected components, and an imperative API similar to the modal API.
## Basic Usage
```ts
const [Drawer, drawerApi] = useVbenDrawer({
// props
// events
});
```
<DemoPreview dir="demos/vben-drawer/basic" />
## Current Usage Notes
- If you use `connectedComponent`, the inner and outer components share data through `drawerApi.setData()` and `drawerApi.getData()`.
- Default drawer behavior can be adjusted in `apps/<app>/src/bootstrap.ts` through `setDefaultDrawerProps(...)`.
- `setState(...)` works on `DrawerState`, not `ModalState`.
## Key Props
| Prop | Description | Type |
| --- | --- | --- |
| `appendToMain` | mount inside the main content area instead of `body` | `boolean` |
| `connectedComponent` | connect an inner component to the drawer wrapper | `Component` |
| `closeIconPlacement` | position of the close icon | `'left' \| 'right'` |
| `placement` | drawer side | `'left' \| 'right' \| 'top' \| 'bottom'` |
| `overlayBlur` | blur amount for the overlay | `number` |
| `submitting` | lock drawer interactions while submitting | `boolean` |
## Events
| Event | Description | Type |
| --- | --- | --- |
| `onBeforeClose` | called before close; returning `false` or rejecting prevents close | `() => Promise<boolean \| undefined> \| boolean \| undefined` |
| `onOpenChange` | called when open state changes | `(isOpen: boolean) => void` |
| `onOpened` | called after open animation completes | `() => void` |
| `onClosed` | called after close animation completes | `() => void` |
## drawerApi
| Method | Description |
| ----------------------- | -------------------------------------- |
| `setState(...)` | updates drawer state |
| `open()` | opens the drawer |
| `close()` | closes the drawer |
| `setData(data)` | stores shared data |
| `getData<T>()` | reads shared data |
| `lock(isLocked = true)` | locks the drawer into submitting state |
| `unlock()` | alias for `lock(false)` |

View File

@ -0,0 +1,42 @@
---
outline: deep
---
# Vben EllipsisText
`EllipsisText` displays long text with truncation, tooltip support, and optional expand/collapse behavior.
## Basic Usage
Pass the text through the default slot and limit the visual width with `maxWidth`.
<DemoPreview dir="demos/vben-ellipsis-text/line" />
## Current Props
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| `expand` | allow click-to-expand behavior | `boolean` | `false` |
| `line` | max visible line count | `number` | `1` |
| `maxWidth` | max width of the text area | `number \| string` | `'100%'` |
| `placement` | tooltip placement | `'bottom' \| 'left' \| 'right' \| 'top'` | `'top'` |
| `tooltip` | enable tooltip | `boolean` | `true` |
| `tooltipWhenEllipsis` | only show tooltip when text is actually truncated | `boolean` | `false` |
| `ellipsisThreshold` | pixel threshold used when checking truncation | `number` | `3` |
| `tooltipBackgroundColor` | tooltip background color | `string` | `''` |
| `tooltipColor` | tooltip text color | `string` | `''` |
| `tooltipFontSize` | tooltip font size in px | `number` | `14` |
| `tooltipMaxWidth` | tooltip max width in px | `number` | - |
| `tooltipOverlayStyle` | tooltip content style | `CSSProperties` | `{ textAlign: 'justify' }` |
## Events
| Event | Description | Type |
| --- | --- | --- |
| `expandChange` | fired when expand state changes | `(isExpand: boolean) => void` |
## Slots
| Slot | Description |
| --------- | ---------------------- |
| `tooltip` | custom tooltip content |

View File

@ -0,0 +1,203 @@
---
outline: deep
---
# Vben Form
`Vben Form` is the shared form abstraction used across different UI-library variants such as `Ant Design Vue`, `Element Plus`, `Naive UI`, and other adapters added inside this repository.
> If some details are not obvious from the docs, check the live demos as well.
## Adapter Setup
Each app keeps its own adapter layer under `src/adapter/form.ts` and `src/adapter/component/index.ts`.
The current adapter pattern is:
- initialize the shared component adapter first
- call `setupVbenForm(...)`
- map special `v-model:*` prop names through `modelPropNameMap`
- keep the form empty state aligned with the actual UI library behavior
### Form Adapter Example
```ts
import type {
VbenFormSchema as FormSchema,
VbenFormProps,
} from '@vben/common-ui';
import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { initComponentAdapter } from './component';
initComponentAdapter();
setupVbenForm<ComponentType>({
config: {
baseModelPropName: 'value',
emptyStateValue: null,
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
},
},
defineRules: {
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
const useVbenForm = useForm<ComponentType>;
export { useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };
```
### Component Adapter Example
```ts
import type { Component, SetupContext } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
AutoComplete,
Button,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
export type ComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
AutoComplete,
Checkbox,
CheckboxGroup,
DatePicker,
DefaultButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
PrimaryButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
RadioGroup,
RangePicker,
Rate,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
};
globalShareState.setComponents(components);
globalShareState.defineMessage({
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };
```
## Basic Usage
Create the form through `useVbenForm`:
<DemoPreview dir="demos/vben-form/basic" />
## Key API Notes
- `useVbenForm` returns `[Form, formApi]`
- `formApi.getFieldComponentRef()` and `formApi.getFocusedField()` are available in current versions
- `handleValuesChange(values, fieldsChanged)` includes the second parameter in newer versions
- `fieldMappingTime` and `scrollToFirstError` are part of the current form props
## Reference
For the complete Chinese API tables and more examples, see the Chinese component page if you need the full parameter matrix.

View File

@ -0,0 +1,56 @@
---
outline: deep
---
# Vben Modal
`Vben Modal` is the shared modal wrapper used by the framework. It supports draggable behavior, fullscreen mode, auto-height handling, loading state, connected components, and an imperative API.
## Basic Usage
```ts
const [Modal, modalApi] = useVbenModal({
// props
// events
});
```
<DemoPreview dir="demos/vben-modal/basic" />
## Current Usage Notes
- If you use `connectedComponent`, the inner and outer components share data through `modalApi.setData()` and `modalApi.getData()`.
- When `connectedComponent` is present, avoid pushing extra modal props through the connected side. Prefer `useVbenModal(...)` or `modalApi.setState(...)`.
- Default modal behavior can be adjusted in `apps/<app>/src/bootstrap.ts` through `setDefaultModalProps(...)`.
## Key Props
| Prop | Description | Type |
| --- | --- | --- |
| `appendToMain` | mount inside the main content area instead of `body` | `boolean` |
| `connectedComponent` | connect an inner component to the modal wrapper | `Component` |
| `animationType` | modal enter/leave animation | `'slide' \| 'scale'` |
| `fullscreenButton` | show or hide the fullscreen toggle | `boolean` |
| `overlayBlur` | blur amount for the overlay | `number` |
| `submitting` | lock modal interactions while submitting | `boolean` |
## Events
| Event | Description | Type |
| --- | --- | --- |
| `onBeforeClose` | called before close; returning `false` or rejecting prevents close | `() => Promise<boolean \| undefined> \| boolean \| undefined` |
| `onOpenChange` | called when open state changes | `(isOpen: boolean) => void` |
| `onOpened` | called after open animation completes | `() => void` |
| `onClosed` | called after close animation completes | `() => void` |
## modalApi
| Method | Description |
| ----------------------- | ------------------------------------- |
| `setState(...)` | updates modal state |
| `open()` | opens the modal |
| `close()` | closes the modal |
| `setData(data)` | stores shared data |
| `getData<T>()` | reads shared data |
| `lock(isLocked = true)` | locks the modal into submitting state |
| `unlock()` | alias for `lock(false)` |

View File

@ -0,0 +1,87 @@
---
outline: deep
---
# Vben Vxe Table
`Vben Vxe Table` wraps `vxe-table` together with `Vben Form` so you can build searchable data grids with a shared API.
## Adapter Example
The current renderer adapter uses `renderTableDefault(...)` for table cell rendering:
```ts
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
```
## Basic Usage
```vue
<script setup lang="ts">
import { useVbenVxeGrid } from '#/adapter/vxe-table';
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {},
formOptions: {},
gridEvents: {},
});
</script>
<template>
<Grid />
</template>
```
<DemoPreview dir="demos/vben-vxe-table/basic" />
## GridApi
| Method | Description | Type |
| --- | --- | --- |
| `setLoading` | update loading state | `(loading: boolean) => void` |
| `setGridOptions` | merge new grid options | `(options: Partial<VxeGridProps['gridOptions']>) => void` |
| `reload` | reload data and reset pagination | `(params?: Record<string, any>) => void` |
| `query` | query data while keeping the current page | `(params?: Record<string, any>) => void` |
| `grid` | `vxe-grid` instance | `VxeGridInstance` |
| `formApi` | search form API | `FormApi` |
| `toggleSearchForm` | toggle or force the search form visible state | `(show?: boolean) => boolean` |
## Props
| Prop | Description | Type |
| --- | --- | --- |
| `tableTitle` | table title | `string` |
| `tableTitleHelp` | help text for the table title | `string` |
| `class` | class for the outer container | `string` |
| `gridClass` | class for the `vxe-grid` node | `string` |
| `gridOptions` | `vxe-grid` options | `DeepPartial<VxeTableGridOptions>` |
| `gridEvents` | `vxe-grid` event handlers | `DeepPartial<VxeGridListeners>` |
| `formOptions` | search form options | `VbenFormProps` |
| `showSearchForm` | whether the search form is visible | `boolean` |
| `separator` | separator between the search form and table body | `boolean \| SeparatorOptions` |
## Slots
| Slot | Description |
| ----------------- | ------------------------------------------------------- |
| `toolbar-actions` | left side of the toolbar, near the title |
| `toolbar-tools` | right side of the toolbar, before built-in tool buttons |
| `table-title` | custom table title |
All named slots starting with `form-` are forwarded to the search form.

View File

@ -0,0 +1,15 @@
# Introduction
::: info README
This section documents the framework components, including their usage patterns, configuration points, and major APIs. If the built-in wrappers do not fit your needs, you can always use native components directly or build your own abstractions.
:::
## Layout Components
Layout components are usually used as top-level containers inside the page content area. They provide shared layout styles and some baseline behavior.
## Common Components
Common components include frequently used UI building blocks such as modals, drawers, forms, and API-backed selectors. Most of them are implemented on top of shared Tailwind CSS and shadcn-vue based primitives, while still allowing each app to adapt them to its own UI library.

View File

@ -0,0 +1,29 @@
---
outline: deep
---
# Page
`Page` is the standard top-level layout container for business pages. It provides a header area, a content area, and an optional footer area.
## Props
| Prop | Description | Type | Default |
| --- | --- | --- | --- |
| `title` | page title | `string \| slot` | - |
| `description` | page description | `string \| slot` | - |
| `contentClass` | class for the content area | `string` | - |
| `headerClass` | class for the header area | `string` | - |
| `footerClass` | class for the footer area | `string` | - |
| `autoContentHeight` | auto-calculate the content area height from the visible layout height | `boolean` | `false` |
| `heightOffset` | extra height offset subtracted from the content area when auto height is enabled | `number` | `0` |
## Slots
| Slot | Description |
| ------------- | ------------------------- |
| `default` | page content |
| `title` | custom title |
| `description` | custom description |
| `extra` | right-side header content |
| `footer` | footer content |

View File

@ -32,14 +32,14 @@ If you are using [vscode](https://code.visualstudio.com/) (recommended) as your
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify icon plugin.
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n plugin.
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code linting.
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - Code formatting.
- [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt integration.
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS formatting.
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Spelling checker.
- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - .env file highlighting.
## Npm Scripts
Npm scripts are common configurations used in the project to perform common tasks such as starting the project, building the project, etc. The following scripts can be found in the `package.json` file at the root of the project.
Npm scripts are common configurations used in the project to perform common tasks such as starting the project, building the project, and more. The following scripts can be found in the `package.json` file at the root of the project.
The execution command is: `pnpm run [script]` or `npm run [script]`.
@ -51,15 +51,17 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Build the project with analysis
"build:analyze": "turbo build:analyze",
// Build a local Docker image
"build:docker": "./build-local-docker-image.sh",
"build:docker": "./scripts/deploy/build-local-docker-image.sh",
// Build the web-antd application separately
"build:antd": "pnpm run build --filter=@vben/web-antd",
// Build the web-antdv-next application separately
"build:antdv-next": "pnpm run build --filter=@vben/web-antdv-next",
// Build the documentation separately
"build:docs": "pnpm run build --filter=@vben/docs",
// Build the web-ele application separately
"build:ele": "pnpm run build --filter=@vben/web-ele",
// Build the web-naive application separately
"build:naive": "pnpm run build --filter=@vben/naive",
"build:naive": "pnpm run build --filter=@vben/web-naive",
// Build the web-tdesign application separately
"build:tdesign": "pnpm run build --filter=@vben/web-tdesign",
// Build the playground application separately
@ -71,7 +73,7 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Check for circular dependencies
"check:circular": "vsh check-circular",
// Check spelling
"check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress"
"check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress",
// Check dependencies
"check:dep": "vsh check-dep",
// Check types
@ -84,12 +86,16 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
"dev": "turbo-run dev",
// Start the web-antd application
"dev:antd": "pnpm -F @vben/web-antd run dev",
// Start the web-antdv-next application
"dev:antdv-next": "pnpm -F @vben/web-antdv-next run dev",
// Start the documentation
"dev:docs": "pnpm -F @vben/docs run dev",
// Start the web-ele application
"dev:ele": "pnpm -F @vben/web-ele run dev",
// Start the web-naive application
"dev:naive": "pnpm -F @vben/web-naive run dev",
// Start the web-tdesign application
"dev:tdesign": "pnpm -F @vben/web-tdesign run dev",
// Start the playground application
"dev:play": "pnpm -F @vben/playground run dev",
// Format code
@ -101,17 +107,19 @@ The execution command is: `pnpm run [script]` or `npm run [script]`.
// Only allow using pnpm
"preinstall": "npx only-allow pnpm",
// Install lefthook
"prepare": "is-ci || lefthook install",
"prepare": "pnpm exec lefthook install",
// Preview the application
"preview": "turbo-run preview",
// Package specification check
"publint": "vsh publint",
// Delete all node_modules, yarn.lock, package.lock.json, and reinstall dependencies
// Delete all node_modules, yarn.lock, package-lock.json, and reinstall dependencies
"reinstall": "pnpm clean --del-lock && pnpm install",
// Run e2e tests
"test:e2e": "turbo run test:e2e",
// Run vitest unit tests
"test:unit": "vitest run --dom",
// Update project dependencies
"update:deps": " pnpm update --latest --recursive",
"update:deps": "npx taze -r -w",
// Changeset generation and versioning
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
}
@ -134,6 +142,12 @@ To run the `web-antd` application:
pnpm dev:antd
```
To run the `web-antdv-next` application:
```bash
pnpm dev:antdv-next
```
To run the `web-naive` application:
```bash
@ -146,6 +160,12 @@ To run the `web-ele` application:
pnpm dev:ele
```
To run the `web-tdesign` application:
```bash
pnpm dev:tdesign
```
To run the `docs` application:
```bash
@ -221,9 +241,9 @@ Add the relevant dependent commands in `turbo.json`.
## Public Static Resources
If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.
If you need to use public static resources in the project, such as images and static HTML, and want to import them directly during development through `src="/xxx.png"`.
You need to put the resource in the corresponding project's `public/static` directory. The import path for the resource should be `src="/static/xxx.png"`.
You need to put the resources in the `public/static` directory of the corresponding project. The import path for the resources should be `src="/static/xxx.png"`.
## DevTools
@ -251,7 +271,7 @@ If you encounter dependency-related issues, you can try reinstalling the depende
```bash
# Execute this command at the root of the project.
# This command will delete all node_modules, yarn.lock, and package.lock.json files
# This command will delete all node_modules, yarn.lock, and package-lock.json files
# and then reinstall dependencies (this process will be noticeably slower).
pnpm reinstall
```

View File

@ -20,9 +20,9 @@
- **Dynamic Menu**: Supports dynamic menus that can display based on permissions.
- **Mock Data**: High-performance local Mock data solution based on `Nitro`.
- **Rich Components**: Provides a wide range of components to meet most business needs.
- **Standardization**: Code quality is ensured with tools like `ESLint`, `Prettier`, `Stylelint`, `Publint`, and `CSpell`.
- **Engineering**: Development efficiency is improved with tools like `Pnpm Monorepo`, `TurboRepo`, and `Changeset`.
- **Multi-UI Library Support**: Supports mainstream UI libraries like `Ant Design Vue`, `Element Plus`, and `Vuetify`, without being restricted to a specific framework.
- **Standardization**: Code quality is ensured with tools like `Oxfmt`, `Oxlint`, `ESLint`, `Stylelint`, `Publint`, and `CSpell`.
- **Engineering**: Development efficiency is improved with tools like `Pnpm Monorepo`, `Turborepo`, and `Changeset`.
- **Multi-UI Library Support**: Supports mainstream UI libraries like `Ant Design Vue`, `Ant Design Vue Next`, `Element Plus`, `Naive UI`, and `TDesign`, without being restricted to a specific framework.
## Browser Support

View File

@ -1,68 +1,72 @@
# Directory Explanation
The directory uses Monorepo management, and the project structure is as follows:
The repository uses Monorepo management, and the project structure is as follows:
```bash
.
├── Dockerfile # Docker image build file
├── README.md # Project documentation
├── apps # Project applications directory
│   ├── backend-mock # Backend mock service application
│   ├── web-antd # Frontend application based on Ant Design Vue
│   ├── web-ele # Frontend application based on Element Plus
│   └── web-naive # Frontend application based on Naive UI
├── build-local-docker-image.sh # Script for building Docker images locally
│ ├── backend-mock # Backend mock service application
│ ├── web-antd # Frontend application based on Ant Design Vue
│ ├── web-antdv-next # Frontend application based on Ant Design Vue Next
│ ├── web-ele # Frontend application based on Element Plus
│ ├── web-naive # Frontend application based on Naive UI
│ └── web-tdesign # Frontend application based on TDesign
├── cspell.json # CSpell configuration file
├── docs # Project documentation directory
├── eslint.config.mjs # ESLint configuration file
├── lefthook.yml # Git hook configuration file
├── internal # Internal tools directory
│   ├── lint-configs # Code linting configurations
│   │   ├── commitlint-config # Commitlint configuration
│   │   ├── eslint-config # ESLint configuration
│   │   ├── prettier-config # Prettier configuration
│   │   └── stylelint-config # Stylelint configuration
│   ├── node-utils # Node.js tools
│   ├── tailwind-config # Tailwind configuration
│   ├── tsconfig # Common tsconfig settings
│   └── vite-config # Common Vite configuration
│ ├── lint-configs # Code linting configurations
│ │ ├── commitlint-config # Commitlint configuration
│ │ ├── eslint-config # ESLint configuration
│ │ ├── oxfmt-config # Oxfmt configuration
│ │ ├── oxlint-config # Oxlint configuration
│ │ └── stylelint-config # Stylelint configuration
│ ├── node-utils # Node.js tools
│ ├── tsconfig # Common tsconfig settings
│ └── vite-config # Common Vite configuration
├── oxfmt.config.ts # Oxfmt config entry
├── oxlint.config.ts # Oxlint configuration file
├── package.json # Project dependency configuration
├── packages # Project packages directory
   ├── @core # Core package
   │   ├── base # Base package
   │   │   ├── design # Design related
   │   │   ├── icons # Icons
   │   │   ├── shared # Shared
   │   │   └── typings # Type definitions
   │   ├── composables # Composable APIs
   │   ├── preferences # Preferences
   │   └── ui-kit # UI component collection
   │   ├── layout-ui # Layout UI
   │   ├── menu-ui # Menu UI
   │   ├── shadcn-ui # shadcn UI
   │   └── tabs-ui # Tabs UI
   ├── constants # Constants
   ├── effects # Effects related packages
   │   ├── access # Access control
   │   ├── plugins # Plugins
   │   ├── common-ui # Common UI
   │   ├── hooks # Composable APIs
   │   ├── layouts # Layouts
   │   └── request # Request
   ├── icons # Icons
   ├── locales # Internationalization
   ├── preferences # Preferences
   ├── stores # State management
   ├── styles # Styles
   ├── types # Type definitions
   └── utils # Utilities
├── @core # Core package
├── base # Base package
│ │ ├── design # Design related
│ │ ├── icons # Icons
│ │ ├── shared # Shared
│ │ └── typings # Type definitions
├── composables # Composable APIs
├── preferences # Preferences
└── ui-kit # UI component collection
├── layout-ui # Layout UI
│ ├── menu-ui # Menu UI
├── shadcn-ui # shadcn UI
└── tabs-ui # Tabs UI
├── constants # Constants
├── effects # Effects related packages
├── access # Access control
│ ├── plugins # Large third-party dependency plugins
├── common-ui # Common UI
├── hooks # Composable APIs
├── layouts # Layouts
└── request # Request
├── icons # Icons
├── locales # Internationalization
├── preferences # Preferences
├── stores # State management
├── styles # Styles
├── types # Type definitions
└── utils # Utilities
├── playground # Demo directory
├── pnpm-lock.yaml # pnpm lock file
├── pnpm-workspace.yaml # pnpm workspace configuration file
├── scripts # Scripts directory
│   ├── turbo-run # Turbo run script
│   └── vsh # VSH script
│ ├── deploy # Deployment-related scripts
│ ├── turbo-run # Turbo run script
│ └── vsh # VSH script
├── stylelint.config.mjs # Stylelint configuration file
├── turbo.json # Turbo configuration file
├── vben-admin.code-workspace # VS Code workspace configuration file
└── vitest.config.ts # Vite configuration file
└── vitest.config.ts # Vitest configuration file
```

View File

@ -5,7 +5,7 @@
- If you want to contribute code to the project, please ensure your code complies with the project's coding standards.
- If you are using `vscode`, you need to install the following plugins:
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code checking
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - Code formatting
- [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt integration
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Word syntax checking
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS formatting
@ -27,22 +27,53 @@ The project's configuration files are located in `internal/lint-configs`, where
The project integrates the following code verification tools:
- [ESLint](https://eslint.org/) for JavaScript code checking
- [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) for code formatting
- [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) for JavaScript / TypeScript linting
- [ESLint](https://eslint.org/) for Vue, JSONC, YAML, and related rules
- [Stylelint](https://stylelint.io/) for CSS style checking
- [Prettier](https://prettier.io/) for code formatting
- [Commitlint](https://commitlint.js.org/) for checking the standard of git commit messages
- [Publint](https://publint.dev/) for checking the standard of npm packages
- [Cspell](https://cspell.org/) for checking spelling errors
- [lefthook](https://github.com/evilmartians/lefthook) for managing Git hooks, automatically running code checks and formatting before commits
## ESLint
## Oxfmt
ESLint is a code standard and error checking tool used to identify and report syntax errors in TypeScript code.
Oxfmt is used to keep code formatting consistent across the repository.
### Command
```bash
pnpm eslint .
pnpm oxfmt
pnpm oxfmt --check
```
### Configuration
The root Oxfmt entry file is `oxfmt.config.ts`, and its core configuration is located in `internal/lint-configs/oxfmt-config`.
## Oxlint
Oxlint is the primary script linting tool for the repository.
### Command
```bash
pnpm oxlint
pnpm oxlint --fix
```
### Configuration
The core Oxlint configuration is located in `internal/lint-configs/oxlint-config`, and the root entry file is `oxlint.config.ts`.
## ESLint
ESLint is used to complement Vue, JSONC, YAML, and related rules.
### Command
```bash
pnpm eslint . --cache
```
### Configuration
@ -56,48 +87,34 @@ Stylelint is used to check the style of CSS within the project. Coupled with the
### Command
```bash
pnpm stylelint "**/*.{vue,css,less.scss}"
pnpm stylelint "**/*.{vue,css,less,scss}" --cache
```
### Configuration
The Stylelint configuration file is `stylelint.config.mjs`, with its core configuration located in the `internal/lint-configs/stylelint-config` directory, which can be modified according to project needs.
## Prettier
Prettier Can be used to unify project code style, consistent indentation, single and double quotes, trailing commas, and other styles.
### Command
```bash
pnpm prettier .
```
### Configuration
The Prettier configuration file is `.prettier.mjs`, with its core configuration located in the `internal/lint-configs/prettier-config` directory, which can be modified according to project needs.
## CommitLint
In a team, everyone's git commit messages can vary widely, making it difficult to ensure standardization without a mechanism. How can standardization be achieved? You might think of using git's hook mechanism to write shell scripts to implement this. Of course, this is possible, but actually, JavaScript has a great tool for implementing this template, which is commitlint (used for verifying the standard of git commit messages).
In a team, everyone's git commit messages can vary widely, making it difficult to ensure standardization without a mechanism. You might think of using git's hook mechanism to write shell scripts to implement this. Of course, this is possible, but JavaScript has a good tool for this template: commitlint.
### Configuration
The CommitLint configuration file is `.commitlintrc.mjs`, with its core configuration located in the `internal/lint-configs/commitlint-config` directory, which can be modified according to project needs.
The CommitLint configuration file is `.commitlintrc.js`, with its core configuration located in the `internal/lint-configs/commitlint-config` directory, which can be modified according to project needs.
### Git Commit Standards
Refer to [Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)
- `feat` Add new features
- `fix` Fix problems/BUGs
- `fix` Fix problems / BUGs
- `style` Code style changes that do not affect the outcome
- `perf` Optimization/performance improvement
- `perf` Optimization / performance improvement
- `refactor` Refactoring
- `revert` Revert changes
- `test` Related to tests
- `docs` Documentation/comments
- `chore` Dependency updates/scaffold configuration modifications, etc.
- `docs` Documentation / comments
- `chore` Dependency updates / scaffold configuration changes
- `workflow` Workflow improvements
- `ci` Continuous integration
- `types` Type modifications
@ -112,31 +129,39 @@ If you want to disable Git commit standard checks, there are two ways:
git commit -m 'feat: add home page' --no-verify
```
```bash [Permanent closed]
# Comment out the following code in .husky/commit-msg to disable
pnpm exec commitlint --edit "$1" # [!code --]
```yaml [Long-term disable]
commit-msg:
commands:
# commitlint:
# run: pnpm exec commitlint --edit $1
```
:::
If you changed `lefthook.yml`, reinstall hooks with:
```bash
pnpm exec lefthook install
```
## Publint
Publint is a tool for checking the standard of npm packages, which can check whether the package version conforms to the standard, whether it conforms to the standard ESM package specification, etc.
Publint is a tool for checking npm package standards, including package versioning, exports, and ESM package structure.
### Command
```bash
pnpm vsh publint
pnpm publint
```
## Cspell
Cspell is a tool for checking spelling errors, which can check for spelling errors in the code, avoiding bugs caused by spelling errors.
Cspell is a tool for checking spelling errors in the code, avoiding bugs caused by spelling mistakes.
### Command
```bash
pnpm cspell lint \"**/*.ts\" \"**/README.md\" \".changeset/*.md\" --no-progress
pnpm check:cspell
```
### Configuration
@ -149,9 +174,9 @@ Git hooks are generally combined with various lints to check code style during g
### lefthook
One issue is that the check will verify all code, but we only want to check the code we are committing. This is where lefthook comes in.
One issue is that the check would verify all code, but in practice we usually only want to check the code being committed. This is where lefthook comes in.
The most effective solution is to perform Lint checks locally before committing. A common practice is to use lefthook to perform a Lint check before local submission.
The most effective solution is to perform lint checks locally before committing. A common practice is to use lefthook to perform checks before local submission.
The project defines corresponding hooks inside `lefthook.yml`:
@ -159,52 +184,46 @@ The project defines corresponding hooks inside `lefthook.yml`:
- `code-workspace`: Updates VSCode workspace configuration
- `lint-md`: Formats Markdown files
- `lint-vue`: Formats and checks Vue files
- `lint-js`: Formats and checks JavaScript/TypeScript files
- `lint-js`: Formats and checks JavaScript / TypeScript files
- `lint-style`: Formats and checks style files
- `lint-package`: Formats package.json
- `lint-package`: Formats `package.json`
- `lint-json`: Formats other JSON files
- `post-merge`: Runs after merge, used for automatic dependency installation
- `install`: Runs `pnpm install` to install new dependencies
- `commit-msg`: Runs during commit, used for checking commit message format
- `commitlint`: Uses commitlint to check commit messages
Current hooks can be installed with:
```bash
pnpm exec lefthook install
```
#### How to Disable lefthook
If you want to disable lefthook, there are two ways:
If you want to temporarily disable lefthook, use:
::: code-group
```bash [Temporary disable]
```bash
git commit -m 'feat: add home page' --no-verify
```
```bash [Permanent disable]
# Simply delete the lefthook.yml file
rm lefthook.yml
```
:::
#### How to Modify lefthook Configuration
If you want to modify lefthook's configuration, you can edit the `lefthook.yml` file. For example:
```yaml
pre-commit:
parallel: true # Execute tasks in parallel
jobs:
- name: lint-js
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
parallel: true
commands:
lint-js:
run: pnpm oxfmt {staged_files} && pnpm oxlint --fix {staged_files} && pnpm eslint --cache --fix {staged_files}
glob: '*.{js,jsx,ts,tsx}'
```
Where:
- `parallel`: Whether to execute tasks in parallel
- `jobs`: Defines the list of tasks to execute
- `name`: Task name
- `commands`: Defines the list of tasks to execute
- `run`: Command to execute
- `glob`: File pattern to match
- `{staged_files}`: Represents the list of staged files

View File

@ -1,13 +1,38 @@
# Tailwind CSS
[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for quickly building custom designs.
[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for quickly building custom designs. The current project uses **Tailwind CSS v4**.
## Configuration
The project's configuration file is located in `internal/tailwind-config`, where you can modify the Tailwind CSS configuration.
The project no longer maintains Tailwind through `tailwind.config.*` files. Theme definitions and scan scope are now managed through CSS and the shared Vite configuration.
::: tip Restrictions on using tailwindcss in packages
- Theme entry: `internal/tailwind-config/src/theme.css`
- Vite integration: `internal/vite-config`
Tailwind CSS compilation will only be enabled if there is a `tailwind.config.mjs` file present in the corresponding package. Otherwise, Tailwind CSS will not be enabled. If you have a pure SDK package that does not require Tailwind CSS, you do not need to create a `tailwind.config.mjs` file.
In `global.css`, you will see the Tailwind CSS v4 directives currently used by the project, such as:
- `@source`
- `@custom-variant`
- `@theme`
- `@theme inline`
- `@utility`
## How Packages Use Tailwind CSS
The project does not decide whether Tailwind CSS is enabled based on whether a package contains `tailwind.config.mjs`.
Apps and packages share `@vben/vite-config`, which integrates `@tailwindcss/vite`. The Tailwind scan scope is managed centrally in `@vben/tailwind-config`, backed by `internal/tailwind-config/src/theme.css`.
::: tip Notes on using Tailwind CSS in packages
If you are building a pure SDK package and do not need Tailwind CSS, you do not need to add any legacy `tailwind.config.*` file.
:::
## `@apply` in Vue SFCs
The project applies a shared handling layer for `@apply` inside Vue single-file components. The related logic is located at:
- `internal/vite-config/src/plugins/tailwind-reference.ts`
When component styles use `@apply`, `@reference "@vben/tailwind-config/theme"` is injected automatically in most cases.

View File

@ -24,18 +24,18 @@
### 工具配置
如果您使用的 IDE 是[vscode](https://code.visualstudio.com/)(推荐)的话,可以安装以下工具来提高开发效率及代码格式化:
如果您使用的 IDE 是 [vscode](https://code.visualstudio.com/)(推荐)的话,可以安装以下工具来提高开发效率及代码格式化:
- [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 官方插件(必备)。
- [Tailwind CSS](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - Tailwindcss 提示插件。
- [CSS Variable Autocomplete](https://marketplace.visualstudio.com/items?itemName=bradlc.vunguyentuan.vscode-css-variables) - Css 变量提示插件。
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify 图标插件
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n 插件
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - 代码格式化
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - css 格式化
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - 单词语法检查
- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - .env 文件 高亮
- [CSS Variable Autocomplete](https://marketplace.visualstudio.com/items?itemName=vunguyentuan.vscode-css-variables) - CSS 变量提示插件。
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify 图标插件
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n 插件
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查
- [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt 集成。
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS 格式检查。
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - 单词语法检查
- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - `.env` 文件高亮
## Npm Scripts
@ -51,15 +51,17 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
// 构建项目并分析
"build:analyze": "turbo build:analyze",
// 构建本地 docker 镜像
"build:docker": "./build-local-docker-image.sh",
"build:docker": "./scripts/deploy/build-local-docker-image.sh",
// 单独构建 web-antd 应用
"build:antd": "pnpm run build --filter=@vben/web-antd",
// 单独构建 web-antdv-next 应用
"build:antdv-next": "pnpm run build --filter=@vben/web-antdv-next",
// 单独构建文档
"build:docs": "pnpm run build --filter=@vben/docs",
// 单独构建 web-ele 应用
"build:ele": "pnpm run build --filter=@vben/web-ele",
// 单独构建 web-naive 应用
"build:naive": "pnpm run build --filter=@vben/naive",
"build:naive": "pnpm run build --filter=@vben/web-naive",
// 单独构建 web-tdesign 应用
"build:tdesign": "pnpm run build --filter=@vben/web-tdesign",
// 单独构建 playground 应用
@ -71,48 +73,54 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
// 检查循环引用
"check:circular": "vsh check-circular",
// 检查拼写
"check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress"
"check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress",
// 检查依赖
"check:dep": "vsh check-dep",
// 检查类型
"check:type": "turbo run typecheck",
// 清理项目删除node_modules、dist、.turbo等目录
// 清理项目(删除 node_modules、dist、.turbo等目录
"clean": "node ./scripts/clean.mjs",
// 提交代码
"commit": "czg",
// 启动项目默认会运行整个仓库所有包的dev脚本
// 启动项目(默认会运行整个仓库所有包的 dev 脚本)
"dev": "turbo-run dev",
// 启动web-antd应用
// 启动 web-antd 应用
"dev:antd": "pnpm -F @vben/web-antd run dev",
// 启动 web-antdv-next 应用
"dev:antdv-next": "pnpm -F @vben/web-antdv-next run dev",
// 启动文档
"dev:docs": "pnpm -F @vben/docs run dev",
// 启动web-ele应用
// 启动 web-ele 应用
"dev:ele": "pnpm -F @vben/web-ele run dev",
// 启动web-naive应用
// 启动 web-naive 应用
"dev:naive": "pnpm -F @vben/web-naive run dev",
// 启动 web-tdesign 应用
"dev:tdesign": "pnpm -F @vben/web-tdesign run dev",
// 启动演示应用
"dev:play": "pnpm -F @vben/playground run dev",
// 格式化代码
"format": "vsh lint --format",
// lint 代码
"lint": "vsh lint",
// 依赖安装完成之后执行所有包的stub脚本
// 依赖安装完成之后,执行所有包的 stub 脚本
"postinstall": "pnpm -r run stub --if-present",
// 只允许使用pnpm
// 只允许使用 pnpm
"preinstall": "npx only-allow pnpm",
// lefthook的安装
"prepare": "is-ci || lefthook install",
// 安装 lefthook
"prepare": "pnpm exec lefthook install",
// 预览应用
"preview": "turbo-run preview",
// 包规范检查
"publint": "vsh publint",
// 删除所有的node_modules、yarn.lock、package.lock.json重新安装依赖
// 删除所有的 node_modules、yarn.lock、package-lock.json重新安装依赖
"reinstall": "pnpm clean --del-lock && pnpm install",
// 运行 e2e 测试
"test:e2e": "turbo run test:e2e",
// 运行 vitest 单元测试
"test:unit": "vitest run --dom",
// 更新项目依赖
"update:deps": " pnpm update --latest --recursive",
// changeset生成提交集
"update:deps": "npx taze -r -w",
// changeset 生成提交集
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
}
}
@ -134,6 +142,12 @@ pnpm dev
pnpm dev:antd
```
运行 `web-antdv-next` 应用:
```bash
pnpm dev:antdv-next
```
运行 `web-naive` 应用:
```bash
@ -146,6 +160,12 @@ pnpm dev:naive
pnpm dev:ele
```
运行 `web-tdesign` 应用:
```bash
pnpm dev:tdesign
```
运行 `docs` 应用:
```bash
@ -154,11 +174,11 @@ pnpm dev:docs
## 区分构建环境
在实际的业务开发中,通常会在构建时区分多种环境,如测试环境`test`、生产环境`build`等。
在实际的业务开发中,通常会在构建时区分多种环境,如测试环境 `test`、生产环境 `build` 等。
此时可以修改三个文件,在其中增加对应的脚本配置来达到区分生产环境的效果。
以`@vben/web-antd`添加测试环境`test`为例:
`@vben/web-antd` 添加测试环境 `test` 为例:
- `apps\web-antd\package.json`
@ -173,7 +193,7 @@ pnpm dev:docs
},
```
增加命令`"build:test"`, 并将原`"build"`改为`"build:prod"`以避免同时构建两个环境的包。
增加命令 `"build:test"`,并将原 `"build"` 改为 `"build:prod"` 以避免同时构建两个环境的包。
- `package.json`
@ -188,7 +208,7 @@ pnpm dev:docs
}
```
在根目录`package.json`中加入构建测试环境的命令
在根目录 `package.json` 中加入构建测试环境的命令
- `turbo.json`
@ -217,17 +237,17 @@ pnpm dev:docs
······
```
在`turbo.json`中加入相关依赖的命令
`turbo.json` 中加入相关依赖的命令
## 公共静态资源
项目中需要使用到的公共静态资源图片、静态HTML等需要在开发中通过 `src="/xxx.png"` 直接引入的。
项目中需要使用到的公共静态资源,如:图片、静态 HTML 等,需要在开发中通过 `src="/xxx.png"` 直接引入的。
需要将资源放在对应项目的 `public/static` 目录下。引入的路径为:`src="/static/xxx.png"`。
## DevTools
项目内置了 [Vue DevTools](https://github.com/vuejs/devtools-next) 插件,可以在开发过程中使用。默认关闭,可在`.env.development` 内开启,并重新运行项目即可:
项目内置了 [Vue DevTools](https://github.com/vuejs/devtools-next) 插件,可以在开发过程中使用。默认关闭,可在 `.env.development` 内开启,并重新运行项目即可:
```bash
VITE_DEVTOOLS=true
@ -251,7 +271,7 @@ pnpm dev:docs
```bash
# 请在项目根目录下执行
# 该命令会删除整个仓库所有的 node_modules、yarn.lock、package.lock.json
# 该命令会删除整个仓库所有的 node_modules、yarn.lock、package-lock.json
# 再进行依赖重新安装(安装速度会明显变慢)。
pnpm reinstall
```

View File

@ -20,9 +20,9 @@
- **动态菜单**:支持动态菜单,可以根据权限配置显示菜单。
- **Mock 数据**:基于 `Nitro` 的本地高性能 Mock 数据方案。
- **组件丰富**:提供了丰富的组件,可以满足大部分的业务需求。
- **规范**:代码规范,使用 `ESLint`、`Prettier`、`Stylelint`、`Publint`、`CSpell` 等工具保证代码质量。
- **工程化**:使用 `Pnpm Monorepo`、`TurboRepo`、`Changeset` 等工具,提高开发效率。
- **多UI库支持**:支持 `Ant Design Vue`、`Element Plus`、`Naive` 等主流 UI 库,不再限制于特定框架。
- **规范**:代码规范,使用 `Oxfmt`、`Oxlint`、`ESLint`、`Stylelint`、`Publint`、`CSpell` 等工具保证代码质量。
- **工程化**:使用 `Pnpm Monorepo`、`Turborepo`、`Changeset` 等工具,提高开发效率。
- **多UI库支持**:支持 `Ant Design Vue`、`Ant Design Vue Next`、`Element Plus`、`Naive UI`、`TDesign` 等主流 UI 库,不再限制于特定框架。
## 浏览器支持

View File

@ -20,4 +20,4 @@
## 质量与规范
我们始终高度重视代码的质量与规范。通过使用 ESLint、Prettier、Stylelint、Publint、CSpell 等工具来确保代码质量。我们的代码规范基于 Vue3、Vite、TypeScript 等现代前端技术制定,旨在提供一个简洁、易用的框架,使开发者能够快速上手并专注于业务逻辑的开发。
我们始终高度重视代码的质量与规范。通过使用 Oxfmt、Oxlint、ESLint、Stylelint、Publint、CSpell 等工具来确保代码质量。我们的代码规范基于 Vue3、Vite、TypeScript 等现代前端技术制定,旨在提供一个简洁、易用的框架,使开发者能够快速上手并专注于业务逻辑的开发。

View File

@ -4,65 +4,69 @@
```bash
.
├── Dockerfile # Docker 镜像构建文件
├── README.md # 项目说明文档
├── apps # 项目应用目录
│   ├── backend-mock # 后端模拟服务应用
│   ├── web-antd # 基于 Ant Design Vue 的前端应用
│   ├── web-ele # 基于 Element Plus 的前端应用
│   └── web-naive # 基于 Naive UI 的前端应用
├── build-local-docker-image.sh # 本地构建 Docker 镜像脚本
│ ├── backend-mock # 后端模拟服务应用
│ ├── web-antd # 基于 Ant Design Vue 的前端应用
│ ├── web-antdv-next # 基于 Ant Design Vue Next 的前端应用
│ ├── web-ele # 基于 Element Plus 的前端应用
│ ├── web-naive # 基于 Naive UI 的前端应用
│ └── web-tdesign # 基于 TDesign 的前端应用
├── cspell.json # CSpell 配置文件
├── docs # 项目文档目录
├── eslint.config.mjs # ESLint 配置文件
├── lefthook.yml # Git Hook 配置文件
├── internal # 内部工具目录
│   ├── lint-configs # 代码检查配置
│   │   ├── commitlint-config # Commitlint 配置
│   │   ├── eslint-config # ESLint 配置
│   │   ├── prettier-config # Prettier 配置
│   │   └── stylelint-config # Stylelint 配置
│   ├── node-utils # Node.js 工具
│   ├── tailwind-config # Tailwind 配置
│   ├── tsconfig # 通用 tsconfig 配置
│   └── vite-config # 通用Vite 配置
│ ├── lint-configs # 代码检查配置
│ │ ├── commitlint-config # Commitlint 配置
│ │ ├── eslint-config # ESLint 配置
│ │ ├── oxfmt-config # Oxfmt 配置
│ │ ├── oxlint-config # Oxlint 配置
│ │ └── stylelint-config # Stylelint 配置
│ ├── node-utils # Node.js 工具
│ ├── tsconfig # 通用 tsconfig 配置
│ └── vite-config # 通用 Vite 配置
├── oxfmt.config.ts # Oxfmt 配置入口
├── oxlint.config.ts # Oxlint 配置文件
├── package.json # 项目依赖配置
├── packages # 项目包目录
   ├── @core # 核心包
   │   ├── base # 基础包
   │   │   ├── design # 设计相关
   │   │   ├── icons # 图标
   │   │   ├── shared # 共享
   │   │   └── typings # 类型定义
   │   ├── composables # 组合式 API
   │   ├── preferences # 偏好设置
   │   └── ui-kit # UI 组件集合
   │   ├── layout-ui # 布局 UI
   │   ├── menu-ui # 菜单 UI
   │   ├── shadcn-ui # shadcn UI
   │   └── tabs-ui # 标签页 UI
   ├── constants # 常量
   ├── effects # 副作用相关包
   │   ├── access # 访问控制
   │   ├── plugins # 第三方大型依赖插件
   │   ├── common-ui # 通用 UI
   │   ├── hooks # 组合式 API
   │   ├── layouts # 布局
   │   └── request # 请求
   ├── icons # 图标
   ├── locales # 国际化
   ├── preferences # 偏好设置
   ├── stores # 状态管理
   ├── styles # 样式
   ├── types # 类型定义
   └── utils # 工具
├── @core # 核心包
├── base # 基础包
│ │ ├── design # 设计相关
│ │ ├── icons # 图标
│ │ ├── shared # 共享
│ │ └── typings # 类型定义
├── composables # 组合式 API
├── preferences # 偏好设置
└── ui-kit # UI 组件集合
├── layout-ui # 布局 UI
│ ├── menu-ui # 菜单 UI
├── shadcn-ui # shadcn UI
└── tabs-ui # 标签页 UI
├── constants # 常量
├── effects # 副作用相关包
├── access # 访问控制
├── plugins # 第三方大型依赖插件
├── common-ui # 通用 UI
├── hooks # 组合式 API
├── layouts # 布局
└── request # 请求
├── icons # 图标
├── locales # 国际化
├── preferences # 偏好设置
├── stores # 状态管理
├── styles # 样式
├── types # 类型定义
└── utils # 工具
├── playground # 演示目录
├── pnpm-lock.yaml # pnpm 锁定文件
├── pnpm-workspace.yaml # pnpm 工作区配置文件
├── scripts # 脚本目录
│   ├── turbo-run # Turbo 运行脚本
│   └── vsh # VSH 脚本
│ ├── deploy # 部署相关脚本
│ ├── turbo-run # Turbo 运行脚本
│ └── vsh # VSH 脚本
├── stylelint.config.mjs # Stylelint 配置文件
├── turbo.json # Turbo 配置文件
├── vben-admin.code-workspace # VS Code 工作区配置文件
└── vitest.config.ts # Vite 配置文件
└── vitest.config.ts # Vitest 配置文件
```

View File

@ -5,9 +5,9 @@
- 如果你想向项目贡献代码,请确保你的代码符合项目的代码规范。
- 如果你使用的是 `vscode`,需要安装以下插件:
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - 代码格式化
- [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt 集成
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - 单词语法检查
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - css 格式化
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS 格式检查
:::
@ -23,59 +23,76 @@
## 工具
项目的配置文件位于 `internal/lint-configs`你可以在这里修改各种lint的配置。
项目的配置文件位于 `internal/lint-configs` 下,你可以在这里修改各种 lint 的配置。
项目内集成了以下几种代码校验工具:
- [ESLint](https://eslint.org/) 用于 JavaScript 代码检查
- [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) 用于代码格式化
- [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) 用于 JavaScript / TypeScript 代码检查
- [ESLint](https://eslint.org/) 用于 Vue、JSONC、YAML 等规则检查
- [Stylelint](https://stylelint.io/) 用于 CSS 样式检查
- [Prettier](https://prettier.io/) 用于代码格式化
- [Commitlint](https://commitlint.js.org/) 用于检查 git 提交信息的规范
- [Publint](https://publint.dev/) 用于检查 npm 包的规范
- [Cspell](https://cspell.org/) 用于检查拼写错误
- [lefthook](https://github.com/evilmartians/lefthook) 用于管理 Git hooks在提交前自动运行代码校验和格式化
## ESLint
## Oxfmt
ESLint 是一个代码规范和错误检查工具,用于识别和报告 TypeScript 代码中的语法错误
Oxfmt 用于统一项目代码风格,统一缩进、引号、尾逗号等格式
### 命令
```bash
pnpm eslint .
pnpm oxfmt
pnpm oxfmt --check
```
### 配置
eslint 配置文件为 `eslint.config.mjs`,其核心配置放在`internal/lint-configs/eslint-config`目录下,可以根据项目需求进行修改。
Oxfmt 的根目录入口文件为 `oxfmt.config.ts`,其核心配置位于 `internal/lint-configs/oxfmt-config` 目录下,可以根据项目需求进行修改。
## Oxlint
Oxlint 是当前仓库的主要脚本 lint 工具,用于识别和报告 JavaScript / TypeScript 代码中的问题。
### 命令
```bash
pnpm oxlint
pnpm oxlint --fix
```
### 配置
Oxlint 的核心配置位于 `internal/lint-configs/oxlint-config` 目录下,根目录入口文件为 `oxlint.config.ts`
## ESLint
ESLint 用于补充 Vue、JSONC、YAML 等规则检查。
### 命令
```bash
pnpm eslint . --cache
```
### 配置
ESLint 配置文件为 `eslint.config.mjs`,其核心配置放在 `internal/lint-configs/eslint-config` 目录下,可以根据项目需求进行修改。
## Stylelint
Stylelint 用于校验项目内部 css 的风格,加上编辑器的自动修复,可以很好的统一项目内部 css 风格
Stylelint 用于校验项目内部 CSS 的风格,加上编辑器的自动修复,可以很好的统一项目内部 CSS 风格。
### 命令
```bash
pnpm stylelint "**/*.{vue,css,less.scss}"
pnpm stylelint "**/*.{vue,css,less,scss}" --cache
```
### 配置
Stylelint 配置文件为 `stylelint.config.mjs`,其核心配置放在`internal/lint-configs/stylelint-config`目录下,可以根据项目需求进行修改。
## Prettier
Prettier 可以用于统一项目代码风格,统一的缩进,单双引号,尾逗号等等风格
### 命令
```bash
pnpm prettier .
```
### 配置
Prettier 配置文件为 `.prettier.mjs`,其核心配置放在`internal/lint-configs/prettier-config`目录下,可以根据项目需求进行修改。
Stylelint 配置文件为 `stylelint.config.mjs`,其核心配置放在 `internal/lint-configs/stylelint-config` 目录下,可以根据项目需求进行修改。
## CommitLint
@ -83,26 +100,26 @@ Prettier 配置文件为 `.prettier.mjs`,其核心配置放在`internal/lint-c
### 配置
CommitLint 配置文件为 `.commitlintrc.mjs`,其核心配置放在`internal/lint-configs/commitlint-config`目录下,可以根据项目需求进行修改。
CommitLint 配置文件为 `.commitlintrc.js`,其核心配置放在 `internal/lint-configs/commitlint-config` 目录下,可以根据项目需求进行修改。
### Git 提交规范
参考 [Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `fix` 修复问题 / BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `perf` 优化 / 性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `docs` 文档 / 注释
- `chore` 依赖更新 / 脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型修改
### 关闭Git提交规范检查
### 关闭 Git 提交规范检查
如果你想关闭 Git 提交规范检查,有两种方式:
@ -112,21 +129,29 @@ CommitLint 配置文件为 `.commitlintrc.mjs`,其核心配置放在`internal/
git commit -m 'feat: add home page' --no-verify
```
```bash [永久关闭]
# 在 .husky/commit-msg 内注释以下代码即可
pnpm exec commitlint --edit "$1" # [!code --]
```yaml [长期关闭]
commit-msg:
commands:
# commitlint:
# run: pnpm exec commitlint --edit $1
```
:::
如果修改了 `lefthook.yml`,请重新执行:
```bash
pnpm exec lefthook install
```
## Publint
Publint 是一个用于检查 npm 包的规范的工具,可以检查包的版本号是否符合规范,是否符合标准的 ESM 规范包等等。
Publint 是一个用于检查 npm 包规范的工具,可以检查包的版本号、导出方式以及 ESM 包结构等问题
### 命令
```bash
pnpm vsh publint
pnpm publint
```
## Cspell
@ -136,7 +161,7 @@ Cspell 是一个用于检查拼写错误的工具,可以检查代码中的拼
### 命令
```bash
pnpm cspell lint \"**/*.ts\" \"**/README.md\" \".changeset/*.md\" --no-progress
pnpm check:cspell
```
### 配置
@ -145,13 +170,13 @@ cspell 配置文件为 `cspell.json`,可以根据项目需求进行修改。
## Git Hook
git hook 一般结合各种 lint在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交
git hook 一般结合各种 lint在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交
### lefthook
有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 lefthook。
最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 lefthook 在本地提交之前先做一次 Lint 校验。
最有效的解决方案就是将 lint 校验放到本地,常见做法是使用 lefthook 在本地提交之前先做一次 lint 校验。
项目在 `lefthook.yml` 内部定义了相应的 hooks
@ -159,52 +184,46 @@ git hook 一般结合各种 lint在 git 提交代码的时候进行代码风
- `code-workspace`: 更新 VSCode 工作区配置
- `lint-md`: 格式化 Markdown 文件
- `lint-vue`: 格式化并检查 Vue 文件
- `lint-js`: 格式化并检查 JavaScript/TypeScript 文件
- `lint-js`: 格式化并检查 JavaScript / TypeScript 文件
- `lint-style`: 格式化并检查样式文件
- `lint-package`: 格式化 package.json
- `lint-package`: 格式化 `package.json`
- `lint-json`: 格式化其他 JSON 文件
- `post-merge`: 在合并后运行,用于自动安装依赖
- `install`: 运行 `pnpm install` 安装新依赖
- `commit-msg`: 在提交时运行,用于检查提交信息格式
- `commitlint`: 使用 commitlint 检查提交信息
当前 hooks 可通过以下命令安装:
```bash
pnpm exec lefthook install
```
#### 如何关闭 lefthook
如果你想关闭 lefthook有两种方式
如果你想临时关闭 lefthook可以使用
::: code-group
```bash [临时关闭]
```bash
git commit -m 'feat: add home page' --no-verify
```
```bash [永久关闭]
# 删除 lefthook.yml 文件即可
rm lefthook.yml
```
:::
#### 如何修改 lefthook 配置
如果你想修改 lefthook 的配置,可以编辑 `lefthook.yml` 文件。例如:
```yaml
pre-commit:
parallel: true # 并行执行任务
jobs:
- name: lint-js
run: pnpm prettier --cache --ignore-unknown --write {staged_files}
parallel: true
commands:
lint-js:
run: pnpm oxfmt {staged_files} && pnpm oxlint --fix {staged_files} && pnpm eslint --cache --fix {staged_files}
glob: '*.{js,jsx,ts,tsx}'
```
其中:
- `parallel`: 是否并行执行任务
- `jobs`: 定义要执行的任务列表
- `name`: 任务名称
- `commands`: 定义要执行的任务列表
- `run`: 要执行的命令
- `glob`: 匹配的文件模式
- `{staged_files}`: 表示暂存的文件列表

View File

@ -1,17 +1,38 @@
# Tailwind CSS
[Tailwind CSS](https://tailwindcss.com/) 是一个实用性优先的CSS框架用于快速构建自定义设计。
[Tailwind CSS](https://tailwindcss.com/) 是一个实用性优先的 CSS 框架,用于快速构建自定义设计。当前项目使用的是 **Tailwind CSS v4**
## 配置
项目的配置文件位于 `internal/tailwind-config` 下,你可以在这里修改 Tailwind CSS 的配置
项目当前不再通过 `tailwind.config.*` 文件维护 Tailwind 配置,主题与扫描范围都统一放在 CSS 与共享 Vite 配置中
::: tip 包使用 tailwindcss 的限制
- 主题入口:`internal/tailwind-config/src/theme.css`
- Vite 集成:`internal/vite-config`
当前只有对应的包下面存在 `tailwind.config.mjs` 文件才会启用 tailwindcss 的编译,否则不会启用 tailwindcss。如果你是纯粹的 SDK 包,不需要使用 tailwindcss可以不用创建 `tailwind.config.mjs` 文件。
`global.css` 中你会看到当前项目使用的 Tailwind CSS v4 指令,例如:
- `@source`
- `@custom-variant`
- `@theme`
- `@theme inline`
- `@utility`
## 包使用 Tailwind CSS 的方式
当前项目不会根据某个包下是否存在 `tailwind.config.mjs` 来决定是否启用 Tailwind CSS。
应用和包统一复用 `@vben/vite-config`,并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `@vben/tailwind-config` 对应的 `internal/tailwind-config/src/theme.css` 中维护。
::: tip 包使用 Tailwind CSS 的说明
如果你是纯粹的 SDK 包,不需要使用 Tailwind CSS则无需额外增加旧版 `tailwind.config.*` 文件。
:::
## 提示
## Vue SFC 中的 `@apply`
现`tailwindcss`已至v4.x版本使用方法与`tailwindcss: ^3.4.17`有差异v4.0无法与v3.x版本兼容在开发前请确认`package.json`中的`tailwindcss`版本。
项目对 Vue 单文件组件中的 `@apply` 做了统一处理,相关逻辑位于:
- `internal/vite-config/src/plugins/tailwind-reference.ts`
当组件样式中使用 `@apply` 时,会自动注入 `@reference "@vben/tailwind-config/theme"`,一般不需要手动补充。

View File

@ -1,5 +1,3 @@
// @ts-check
import { defineConfig } from '@vben/eslint-config';
export default defineConfig();

View File

@ -1,7 +0,0 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index'],
});

View File

@ -12,7 +12,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
"stub": "pnpm exec tsdown"
},
"files": [
"dist"
@ -27,19 +27,13 @@
}
},
"dependencies": {
"@vben/oxlint-config": "workspace:*",
"eslint-config-turbo": "catalog:",
"eslint-plugin-command": "catalog:"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
"@eslint/js": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@vben/oxlint-config": "workspace:*",
"eslint": "catalog:",
"eslint-plugin-jsonc": "catalog:",
"eslint-plugin-n": "catalog:",
"eslint-plugin-oxlint": "catalog:",
"eslint-plugin-perfectionist": "catalog:",
"eslint-plugin-pnpm": "catalog:",
"eslint-plugin-unicorn": "catalog:",

View File

@ -1,9 +0,0 @@
import createCommand from 'eslint-plugin-command/config';
export async function command() {
return [
{
...createCommand(),
},
];
}

View File

@ -1,23 +0,0 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function comments(): Promise<Linter.Config[]> {
const pluginComments = await interopDefault(
import('@eslint-community/eslint-plugin-eslint-comments'),
);
return [
{
plugins: {
'eslint-comments': pluginComments,
},
rules: {
'eslint-comments/no-aggregating-enable': 'error',
'eslint-comments/no-duplicate-disable': 'error',
'eslint-comments/no-unlimited-disable': 'error',
'eslint-comments/no-unused-enable': 'error',
},
},
];
}

View File

@ -1,13 +1,9 @@
export * from './command';
export * from './comments';
export * from './ignores';
export * from './javascript';
export * from './jsonc';
export * from './node';
export * from './oxlint';
export * from './perfectionist';
export * from './pnpm';
export * from './turbo';
export * from './typescript';
export * from './unicorn';
export * from './vue';

View File

@ -17,7 +17,8 @@ export async function node(): Promise<Linter.Config[]> {
'error',
{
allowModules: [
'unbuild',
'tsdown',
'unplugin-vue',
'@vben/vite-config',
'vitest',
'vite',

View File

@ -1,13 +0,0 @@
import type { Linter } from 'eslint';
import { mergeOxlintConfigs, oxlintConfig } from '@vben/oxlint-config';
import oxlint from 'eslint-plugin-oxlint';
export async function oxcCompat(): Promise<Linter.Config[]> {
const { extends: _extends, ...config } = mergeOxlintConfigs(oxlintConfig);
return oxlint.buildFromOxlintConfig(
config as Parameters<typeof oxlint.buildFromOxlintConfig>[0],
);
}

View File

@ -1,15 +0,0 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function turbo(): Promise<Linter.Config[]> {
const pluginTurbo = await interopDefault(import('eslint-config-turbo'));
return [
{
plugins: {
turbo: pluginTurbo,
},
},
];
}

View File

@ -143,6 +143,12 @@ const customConfig: Linter.Config[] = [
'no-console': 'off',
},
},
{
files: ['packages/@core/base/shared/src/utils/inference.ts'],
rules: {
'vue/prefer-import-from-vue': 'off',
},
},
];
export { customConfig };

View File

@ -1,16 +1,12 @@
import type { Linter } from 'eslint';
import {
command,
comments,
ignores,
javascript,
jsonc,
node,
oxcCompat,
perfectionist,
pnpm,
turbo,
typescript,
unicorn,
vue,
@ -35,14 +31,10 @@ async function defineConfig(config: FlatConfig[] = []) {
jsonc(),
node(),
perfectionist(),
comments(),
unicorn(),
command(),
turbo(),
yaml(),
pnpm(),
...customConfig,
oxcCompat(),
...config,
];

View File

@ -0,0 +1,16 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
clean: true,
deps: {
skipNodeModulesBundle: true,
},
dts: {
resolver: 'tsc',
},
entry: ['src/index.ts'],
format: ['esm'],
outExtensions: () => ({
dts: '.d.ts',
}),
});

View File

@ -1,7 +0,0 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index'],
});

View File

@ -12,7 +12,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
"stub": "pnpm exec tsdown"
},
"files": [
"dist"

View File

@ -2,16 +2,33 @@ import { defineConfig as defineOxfmtConfig } from 'oxfmt';
type OxfmtConfig = Parameters<typeof defineOxfmtConfig>[0];
const oxfmtConfig = defineOxfmtConfig({
const oxfmtConfig: OxfmtConfig = defineOxfmtConfig({
printWidth: 80,
proseWrap: 'never',
semi: true,
singleQuote: true,
sortPackageJson: false,
trailingComma: 'all',
overrides: [
{
files: [
'*.json',
'*.json5',
'*.jsonc',
'*.code-workspace',
'**/*.json',
'**/*.json5',
'**/*.jsonc',
'**/*.code-workspace',
],
options: {
trailingComma: 'none',
},
},
],
});
function defineConfig(config: OxfmtConfig = {}) {
function defineConfig(config: OxfmtConfig = {}): OxfmtConfig {
return defineOxfmtConfig({
...oxfmtConfig,
...config,

View File

@ -0,0 +1,11 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
clean: true,
dts: true,
entry: ['src/index.ts'],
format: ['esm'],
outExtensions: () => ({
dts: '.d.ts',
}),
});

View File

@ -1,7 +0,0 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index'],
});

View File

@ -12,7 +12,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
"stub": "pnpm exec tsdown"
},
"files": [
"dist"
@ -27,7 +27,9 @@
}
},
"dependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-command": "catalog:",
"oxlint": "catalog:"
}
}

View File

@ -0,0 +1,15 @@
import type { OxlintConfig } from 'oxlint';
const command: OxlintConfig = {
jsPlugins: [
{
name: 'command',
specifier: 'eslint-plugin-command',
},
],
rules: {
'command/command': 'error',
},
};
export { command };

View File

@ -0,0 +1,18 @@
import type { OxlintConfig } from 'oxlint';
const comments: OxlintConfig = {
jsPlugins: [
{
name: 'eslint-comments',
specifier: '@eslint-community/eslint-plugin-eslint-comments',
},
],
rules: {
'eslint-comments/no-aggregating-enable': 'error',
'eslint-comments/no-duplicate-disable': 'error',
'eslint-comments/no-unlimited-disable': 'error',
'eslint-comments/no-unused-enable': 'error',
},
};
export { comments };

View File

@ -2,6 +2,8 @@ import type { OxlintConfig } from 'oxlint';
import { defineConfig as defineOxlintConfig } from 'oxlint';
import { command } from './command';
import { comments } from './comments';
import { ignores } from './ignores';
import { importPluginConfig } from './import';
import { javascript } from './javascript';
@ -60,6 +62,8 @@ function mergeOxlintConfigs(...configs: OxlintConfig[]): OxlintConfig {
const oxlintConfig = defineOxlintConfig(
mergeOxlintConfigs(
javascript,
command,
comments,
ignores,
plugins,
importPluginConfig,
@ -74,6 +78,8 @@ const oxlintConfig = defineOxlintConfig(
);
export {
command,
comments,
ignores,
importPluginConfig,
javascript,

View File

@ -14,7 +14,7 @@ const selectors = [
];
const settings = {
entryPoint: 'packages/@core/base/design/src/css/global.css',
entryPoint: 'internal/tailwind-config/src/theme.css',
selectors,
};

View File

@ -0,0 +1,11 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
clean: true,
dts: true,
entry: ['src/index.ts'],
format: ['esm'],
outExtensions: () => ({
dts: '.d.ts',
}),
});

View File

@ -1,7 +0,0 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index'],
});

View File

@ -12,7 +12,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
"stub": "node ./scripts/build.mjs"
},
"files": [
"dist"
@ -22,7 +22,7 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"default": "./dist/index.mjs"
}

View File

@ -0,0 +1,40 @@
import { spawnSync } from 'node:child_process';
const pnpmCommand =
process.env.npm_execpath && process.env.npm_execpath.endsWith('.cjs')
? [process.execPath, process.env.npm_execpath]
: ['pnpm'];
const steps = [
['exec', 'tsdown', '--no-dts'],
[
'exec',
'tsc',
'-p',
'tsconfig.build.json',
'--emitDeclarationOnly',
'--declaration',
'--outDir',
'dist',
],
];
for (const args of steps) {
const [command, ...commandArgs] = pnpmCommand;
let cmd = command;
if (cmd.includes(' ')) {
cmd = `"${command}"`;
}
const result = spawnSync(cmd, [...commandArgs, ...args], {
shell: true,
stdio: 'inherit',
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}

View File

@ -1,6 +1,6 @@
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone.js';
import utc from 'dayjs/plugin/utc.js';
dayjs.extend(utc);
dayjs.extend(timezone);

View File

@ -1,21 +1,31 @@
import { dirname } from 'node:path';
import type { Package } from '@manypkg/get-packages';
import {
getPackages as getPackagesFunc,
getPackagesSync as getPackagesSyncFunc,
} from '@manypkg/get-packages';
import { findUpSync } from 'find-up';
import { existsSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import * as manypkg from '@manypkg/get-packages';
const { getPackages: getPackagesFunc, getPackagesSync: getPackagesSyncFunc } =
manypkg;
/**
*
* @param cwd
*/
function findMonorepoRoot(cwd: string = process.cwd()) {
const lockFile = findUpSync('pnpm-lock.yaml', {
cwd,
type: 'file',
});
return dirname(lockFile || '');
let currentDir = resolve(cwd);
while (true) {
if (existsSync(join(currentDir, 'pnpm-lock.yaml'))) {
return currentDir;
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
return '';
}
currentDir = parentDir;
}
}
/**
@ -40,7 +50,7 @@ async function getPackages() {
*/
async function getPackage(pkgName: string) {
const { packages } = await getPackages();
return packages.find((pkg) => pkg.packageJson.name === pkgName);
return packages.find((pkg: Package) => pkg.packageJson.name === pkgName);
}
export { findMonorepoRoot, getPackage, getPackages, getPackagesSync };

View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false
},
"exclude": ["node_modules", "src/__tests__"]
}

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
clean: false,
deps: {
skipNodeModulesBundle: true,
},
entry: ['src/index.ts'],
format: ['esm'],
});

View File

@ -0,0 +1,38 @@
{
"name": "@vben/tailwind-config",
"version": "5.7.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/tailwind-config"
},
"license": "MIT",
"type": "module",
"files": [
"src"
],
"sideEffects": [
"**/*.css"
],
"main": "./src/index.ts",
"module": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./theme": {
"default": "./src/theme.css"
}
},
"dependencies": {
"@iconify/tailwind4": "catalog:",
"@tailwindcss/typography": "catalog:",
"tailwindcss": "catalog:",
"tw-animate-css": "catalog:"
}
}

View File

@ -0,0 +1 @@
import './theme.css';

View File

@ -0,0 +1,569 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@plugin '@tailwindcss/typography';
@plugin '@iconify/tailwind4';
/* Monorepo source detection: scan all packages and apps for utility classes */
@source '../../../packages/';
@source '../../../apps/';
@source '../../../docs/';
@source '../../../playground/';
/* Dark mode uses .dark class selector, not prefers-color-scheme */
@custom-variant dark (&:is(.dark *));
/* Explicitly pin Tailwind v4 dynamic spacing for classes like w-150/h-55. */
@theme {
--spacing: 0.25rem;
}
@theme inline {
/* Font */
--font-sans: var(--font-family);
/* Border Radius */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* Box Shadow */
--shadow-float:
0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%),
0 9px 28px 8px rgb(0 0 0 / 5%);
/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-collapsible-down: collapsible-down 0.2s ease-in-out;
--animate-collapsible-up: collapsible-up 0.2s ease-in-out;
--animate-float: float 5s linear 0ms infinite;
/* ===== Semantic Colors (shadcn-ui) ===== */
--color-background: hsl(var(--background));
--color-background-deep: hsl(var(--background-deep));
--color-foreground: hsl(var(--foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-accent-hover: hsl(var(--accent-hover));
--color-accent-lighter: hsl(var(--accent-lighter));
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-input-background: hsl(var(--input-background));
--color-ring: hsl(var(--ring));
--color-secondary: hsl(var(--secondary));
--color-secondary-desc: hsl(var(--secondary-desc));
--color-secondary-foreground: hsl(var(--secondary-foreground));
/* ===== Custom Semantic Colors ===== */
--color-header: hsl(var(--header));
--color-heavy: hsl(var(--heavy));
--color-heavy-foreground: hsl(var(--heavy-foreground));
--color-main: hsl(var(--main));
--color-overlay: hsl(var(--overlay));
--color-overlay-content: hsl(var(--overlay-content));
--color-sidebar: hsl(var(--sidebar));
--color-sidebar-deep: hsl(var(--sidebar-deep));
/* ===== Primary Palette ===== */
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-primary-50: hsl(var(--primary-50));
--color-primary-100: hsl(var(--primary-100));
--color-primary-200: hsl(var(--primary-200));
--color-primary-300: hsl(var(--primary-300));
--color-primary-400: hsl(var(--primary-400));
--color-primary-500: hsl(var(--primary-500));
--color-primary-600: hsl(var(--primary-600));
--color-primary-700: hsl(var(--primary-700));
--color-primary-active: hsl(var(--primary-700));
--color-primary-background-light: hsl(var(--primary-200));
--color-primary-background-lighter: hsl(var(--primary-100));
--color-primary-background-lightest: hsl(var(--primary-50));
--color-primary-border: hsl(var(--primary-400));
--color-primary-border-light: hsl(var(--primary-300));
--color-primary-hover: hsl(var(--primary-600));
--color-primary-text: hsl(var(--primary-500));
--color-primary-text-active: hsl(var(--primary-700));
--color-primary-text-hover: hsl(var(--primary-600));
/* ===== Destructive Palette ===== */
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-destructive-50: hsl(var(--destructive-50));
--color-destructive-100: hsl(var(--destructive-100));
--color-destructive-200: hsl(var(--destructive-200));
--color-destructive-300: hsl(var(--destructive-300));
--color-destructive-400: hsl(var(--destructive-400));
--color-destructive-500: hsl(var(--destructive-500));
--color-destructive-600: hsl(var(--destructive-600));
--color-destructive-700: hsl(var(--destructive-700));
--color-destructive-active: hsl(var(--destructive-700));
--color-destructive-background-light: hsl(var(--destructive-200));
--color-destructive-background-lighter: hsl(var(--destructive-100));
--color-destructive-background-lightest: hsl(var(--destructive-50));
--color-destructive-border: hsl(var(--destructive-400));
--color-destructive-border-light: hsl(var(--destructive-300));
--color-destructive-hover: hsl(var(--destructive-600));
--color-destructive-text: hsl(var(--destructive-500));
--color-destructive-text-active: hsl(var(--destructive-700));
--color-destructive-text-hover: hsl(var(--destructive-600));
/* ===== Success Palette ===== */
--color-success: hsl(var(--success));
--color-success-foreground: hsl(var(--success-foreground));
--color-success-50: hsl(var(--success-50));
--color-success-100: hsl(var(--success-100));
--color-success-200: hsl(var(--success-200));
--color-success-300: hsl(var(--success-300));
--color-success-400: hsl(var(--success-400));
--color-success-500: hsl(var(--success-500));
--color-success-600: hsl(var(--success-600));
--color-success-700: hsl(var(--success-700));
--color-success-active: hsl(var(--success-700));
--color-success-background-light: hsl(var(--success-200));
--color-success-background-lighter: hsl(var(--success-100));
--color-success-background-lightest: hsl(var(--success-50));
--color-success-border: hsl(var(--success-400));
--color-success-border-light: hsl(var(--success-300));
--color-success-hover: hsl(var(--success-600));
--color-success-text: hsl(var(--success-500));
--color-success-text-active: hsl(var(--success-700));
--color-success-text-hover: hsl(var(--success-600));
/* ===== Warning Palette ===== */
--color-warning: hsl(var(--warning));
--color-warning-foreground: hsl(var(--warning-foreground));
--color-warning-50: hsl(var(--warning-50));
--color-warning-100: hsl(var(--warning-100));
--color-warning-200: hsl(var(--warning-200));
--color-warning-300: hsl(var(--warning-300));
--color-warning-400: hsl(var(--warning-400));
--color-warning-500: hsl(var(--warning-500));
--color-warning-600: hsl(var(--warning-600));
--color-warning-700: hsl(var(--warning-700));
--color-warning-active: hsl(var(--warning-700));
--color-warning-background-light: hsl(var(--warning-200));
--color-warning-background-lighter: hsl(var(--warning-100));
--color-warning-background-lightest: hsl(var(--warning-50));
--color-warning-border: hsl(var(--warning-400));
--color-warning-border-light: hsl(var(--warning-300));
--color-warning-hover: hsl(var(--warning-600));
--color-warning-text: hsl(var(--warning-500));
--color-warning-text-active: hsl(var(--warning-700));
--color-warning-text-hover: hsl(var(--warning-600));
/* ===== Green Palette (alias for success shades) ===== */
--color-green-50: hsl(var(--green-50));
--color-green-100: hsl(var(--green-100));
--color-green-200: hsl(var(--green-200));
--color-green-300: hsl(var(--green-300));
--color-green-400: hsl(var(--green-400));
--color-green-500: hsl(var(--green-500));
--color-green-600: hsl(var(--green-600));
--color-green-700: hsl(var(--green-700));
--color-green-active: hsl(var(--green-700));
--color-green-background-light: hsl(var(--green-200));
--color-green-background-lighter: hsl(var(--green-100));
--color-green-background-lightest: hsl(var(--green-50));
--color-green-border: hsl(var(--green-400));
--color-green-border-light: hsl(var(--green-300));
--color-green-foreground: hsl(var(--success-foreground));
--color-green-hover: hsl(var(--green-600));
--color-green-text: hsl(var(--green-500));
--color-green-text-active: hsl(var(--green-700));
--color-green-text-hover: hsl(var(--green-600));
/* ===== Red Palette (alias for destructive shades) ===== */
--color-red-50: hsl(var(--red-50));
--color-red-100: hsl(var(--red-100));
--color-red-200: hsl(var(--red-200));
--color-red-300: hsl(var(--red-300));
--color-red-400: hsl(var(--red-400));
--color-red-500: hsl(var(--red-500));
--color-red-600: hsl(var(--red-600));
--color-red-700: hsl(var(--red-700));
--color-red-active: hsl(var(--red-700));
--color-red-background-light: hsl(var(--red-200));
--color-red-background-lighter: hsl(var(--red-100));
--color-red-background-lightest: hsl(var(--red-50));
--color-red-border: hsl(var(--red-400));
--color-red-border-light: hsl(var(--red-300));
--color-red-foreground: hsl(var(--destructive-foreground));
--color-red-hover: hsl(var(--red-600));
--color-red-text: hsl(var(--red-500));
--color-red-text-active: hsl(var(--red-700));
--color-red-text-hover: hsl(var(--red-600));
/* ===== Yellow Palette (alias for warning shades) ===== */
--color-yellow-50: hsl(var(--yellow-50));
--color-yellow-100: hsl(var(--yellow-100));
--color-yellow-200: hsl(var(--yellow-200));
--color-yellow-300: hsl(var(--yellow-300));
--color-yellow-400: hsl(var(--yellow-400));
--color-yellow-500: hsl(var(--yellow-500));
--color-yellow-600: hsl(var(--yellow-600));
--color-yellow-700: hsl(var(--yellow-700));
--color-yellow-active: hsl(var(--yellow-700));
--color-yellow-background-light: hsl(var(--yellow-200));
--color-yellow-background-lighter: hsl(var(--yellow-100));
--color-yellow-background-lightest: hsl(var(--yellow-50));
--color-yellow-border: hsl(var(--yellow-400));
--color-yellow-border-light: hsl(var(--yellow-300));
--color-yellow-foreground: hsl(var(--warning-foreground));
--color-yellow-hover: hsl(var(--yellow-600));
--color-yellow-text: hsl(var(--yellow-500));
--color-yellow-text-active: hsl(var(--yellow-700));
--color-yellow-text-hover: hsl(var(--yellow-600));
}
/* Keyframes */
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--reka-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--reka-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes collapsible-down {
from {
height: 0;
}
to {
height: var(--reka-collapsible-content-height);
}
}
@keyframes collapsible-up {
from {
height: var(--reka-collapsible-content-height);
}
to {
height: 0;
}
}
@keyframes float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
100% {
transform: translateY(0);
}
}
/* Base styles */
@layer base {
*,
::after,
::before {
@apply border-border outline-ring/50;
box-sizing: border-box;
border-style: solid;
border-width: 0;
}
html {
@apply text-foreground bg-background font-sans;
scroll-behavior: smooth;
font-size: var(--font-size-base, 16px);
font-variation-settings: normal;
font-synthesis-weight: none;
line-height: 1.15;
text-rendering: optimizelegibility;
text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}
#app,
body,
html {
@apply size-full;
}
body {
min-height: 100vh;
}
a,
a:active,
a:hover,
a:link,
a:visited {
@apply no-underline;
}
::view-transition-new(root),
::view-transition-old(root) {
@apply animate-none mix-blend-normal;
}
::view-transition-old(root) {
@apply z-1;
}
::view-transition-new(root) {
@apply z-2147483646;
}
html.dark::view-transition-old(root) {
@apply z-2147483646;
}
html.dark::view-transition-new(root) {
@apply z-1;
}
input::placeholder,
textarea::placeholder {
@apply opacity-100;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
@apply m-0 appearance-none;
}
/* Only adjust scrollbar for non-macOS */
html:not([data-platform='macOs']) {
::-webkit-scrollbar {
@apply h-2.5 w-2.5;
}
::-webkit-scrollbar-thumb {
@apply bg-border rounded-sm border-none;
}
::-webkit-scrollbar-track {
@apply rounded-sm border-none bg-transparent shadow-none;
}
::-webkit-scrollbar-button {
@apply hidden;
}
}
}
/* Custom utilities (v4 @utility syntax) */
@utility flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@utility flex-col-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* Component styles (complex selectors, not convertible to @utility) */
.outline-box {
@apply outline-border relative cursor-pointer rounded-md p-1 outline-1;
}
.outline-box::after {
@apply absolute top-1/2 left-1/2 z-20 h-0 w-px rounded-sm opacity-0 outline-2 outline-transparent transition-all duration-300 content-[""];
}
.outline-box.outline-box-active {
@apply outline-primary outline-2;
}
.outline-box.outline-box-active::after {
display: none;
}
.outline-box:not(.outline-box-active):hover::after {
@apply outline-primary top-0 left-0 h-full w-full p-1 opacity-100;
}
.vben-link {
@apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer;
}
.card-box {
@apply bg-card text-card-foreground border-border rounded-xl border;
}
/* Enter animations (converted from enterAnimationPlugin) */
@keyframes enter-x-animation {
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enter-y-animation {
to {
opacity: 1;
transform: translateY(0);
}
}
.enter-x:nth-child(1) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
}
.enter-x:nth-child(2) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
}
.enter-x:nth-child(3) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
}
.enter-x:nth-child(4) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
}
.enter-x:nth-child(5) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
}
.enter-y:nth-child(1) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
}
.enter-y:nth-child(2) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
}
.enter-y:nth-child(3) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
}
.enter-y:nth-child(4) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
}
.enter-y:nth-child(5) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
}
.-enter-x:nth-child(1) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
}
.-enter-x:nth-child(2) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
}
.-enter-x:nth-child(3) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
}
.-enter-x:nth-child(4) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
}
.-enter-x:nth-child(5) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
}
.-enter-y:nth-child(1) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
}
.-enter-y:nth-child(2) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
}
.-enter-y:nth-child(3) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
}
.-enter-y:nth-child(4) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
}
.-enter-y:nth-child(5) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
}
html.invert-mode {
@apply invert;
}
html.grayscale-mode {
@apply grayscale;
}

View File

@ -1,7 +0,0 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index'],
});

View File

@ -12,7 +12,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
"stub": "pnpm exec tsdown"
},
"files": [
"dist"
@ -30,6 +30,7 @@
"@intlify/unplugin-vue-i18n": "catalog:",
"@jspm/generator": "catalog:",
"@tailwindcss/vite": "catalog:",
"@vben/node-utils": "workspace:*",
"archiver": "catalog:",
"cheerio": "catalog:",
"get-port": "catalog:",
@ -43,18 +44,16 @@
"@pnpm/workspace.read-manifest": "catalog:",
"@types/archiver": "catalog:",
"@types/html-minifier-terser": "catalog:",
"@vben/node-utils": "workspace:*",
"@vitejs/plugin-vue": "catalog:",
"@vitejs/plugin-vue-jsx": "catalog:",
"dayjs": "catalog:",
"dotenv": "catalog:",
"rollup": "catalog:",
"rollup-plugin-visualizer": "catalog:",
"sass": "catalog:",
"sass-embedded": "catalog:",
"unplugin-dts": "catalog:",
"vite": "catalog:",
"vite-plugin-compression": "catalog:",
"vite-plugin-dts": "catalog:",
"vite-plugin-html": "catalog:",
"vite-plugin-lazy-import": "catalog:"
}

View File

@ -49,10 +49,8 @@ async function viteExtraAppConfigPlugin({
console.log(colors.cyan(`✨configuration file is build successfully!`));
} catch (error) {
// oxlint-disable-next-line no-console
console.log(
colors.red(
// oxlint-disable-next-line typescript/restrict-template-expressions
`configuration file configuration file failed to package:\n${error}`,
),
);

View File

@ -68,7 +68,6 @@ async function viteImportMapPlugin(
});
if (options?.debug) {
// oxlint-disable-next-line typescript/no-floating-promises
(async () => {
for await (const { message, type } of generator.logStream()) {
console.log(`${type}: ${message}`);
@ -139,7 +138,6 @@ async function viteImportMapPlugin(
buildEnd() {
// 未生成importmap时抛出错误防止被turbo缓存
if (!installed && !isSSR) {
// oxlint-disable-next-line no-unused-expressions
installError && console.error(installError);
throw new Error('Importmap installation failed.');
}

View File

@ -12,8 +12,8 @@ import tailwindcss from '@tailwindcss/vite';
import viteVue from '@vitejs/plugin-vue';
import viteVueJsx from '@vitejs/plugin-vue-jsx';
import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';
import viteDtsPlugin from 'unplugin-dts/vite';
import viteCompressPlugin from 'vite-plugin-compression';
import viteDtsPlugin from 'vite-plugin-dts';
import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html';
import { VitePWA } from 'vite-plugin-pwa';
import viteVueDevTools from 'vite-plugin-vue-devtools';
@ -231,12 +231,13 @@ async function loadLibraryPlugins(
// 单独取否则commonOptions拿不到
const isBuild = options.isBuild;
const { dts, ...commonOptions } = options;
const dtsOptions = typeof dts === 'object' ? dts : undefined;
const commonPlugins = await loadCommonPlugins(commonOptions);
return await loadConditionPlugins([
...commonPlugins,
{
condition: isBuild && !!dts,
plugins: () => [viteDtsPlugin({ logLevel: 'error' })],
plugins: () => [viteDtsPlugin(dtsOptions)],
},
]);
}

View File

@ -1,6 +1,6 @@
import type { Plugin } from 'vite';
const REFERENCE_LINE = '@reference "@vben-core/design/theme";\n';
const REFERENCE_LINE = '@reference "@vben/tailwind-config/theme";\n';
/**
* Auto-inject @reference into Vue SFC <style> blocks that use @apply.

View File

@ -1,11 +1,11 @@
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
import type { PluginOptions } from 'unplugin-dts';
import type {
ConfigEnv,
PluginOption,
UserConfig,
UserConfigFnPromise,
} from 'vite';
import type { PluginOptions } from 'vite-plugin-dts';
import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
/**

View File

@ -0,0 +1,41 @@
import { cp, mkdir } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'tsdown';
const rootDir = dirname(fileURLToPath(import.meta.url));
const loadingAssets = ['default-loading-antd.html', 'default-loading.html'];
export default defineConfig({
clean: true,
deps: {
neverBundle: ['@vben/node-utils'],
skipNodeModulesBundle: true,
},
dts: {
resolver: 'tsc',
},
entry: ['src/index.ts'],
format: ['esm'],
hooks: {
'build:done': async (context) => {
const outDir = context.options.outDir;
if (!outDir) {
return;
}
await mkdir(outDir, { recursive: true });
for (const file of loadingAssets) {
await cp(
join(rootDir, 'src/plugins/inject-app-loading', file),
join(outDir, file),
);
}
},
},
outExtensions: () => ({
dts: '.d.ts',
}),
});

Some files were not shown because too many files have changed in this diff Show More