Merge remote-tracking branch 'origin/main'
commit
da3580cbd7
|
|
@ -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,
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
"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"
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -126,260 +131,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'] || [],
|
||||
);
|
||||
|
|
@ -393,12 +399,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 &&
|
||||
|
|
@ -406,14 +414,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;
|
||||
|
|
@ -421,12 +426,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(
|
||||
|
|
@ -443,21 +445,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) => {
|
||||
|
|
@ -465,18 +534,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,
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
"tabler",
|
||||
"taze",
|
||||
"tdesign",
|
||||
"tsdown",
|
||||
"tsgolint",
|
||||
"turborepo",
|
||||
"ui-kit",
|
||||
|
|
|
|||
|
|
@ -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:",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
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.
|
||||
|
||||
- Theme entry: `packages/@core/base/design/src/css/global.css`
|
||||
- Theme entry: `internal/tailwind-config/src/theme.css`
|
||||
- Vite integration: `internal/vite-config`
|
||||
|
||||
In `global.css`, you will see the Tailwind CSS v4 directives currently used by the project, such as:
|
||||
|
|
@ -21,7 +21,7 @@ In `global.css`, you will see the Tailwind CSS v4 directives currently used by t
|
|||
|
||||
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 `packages/@core/base/design/src/css/global.css`.
|
||||
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
|
||||
|
||||
|
|
@ -35,4 +35,4 @@ The project applies a shared handling layer for `@apply` inside Vue single-file
|
|||
|
||||
- `internal/vite-config/src/plugins/tailwind-reference.ts`
|
||||
|
||||
When component styles use `@apply`, the required `@reference` is injected automatically in most cases.
|
||||
When component styles use `@apply`, `@reference "@vben/tailwind-config/theme"` is injected automatically in most cases.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
项目当前不再通过 `tailwind.config.*` 文件维护 Tailwind 配置,主题与扫描范围都统一放在 CSS 与共享 Vite 配置中。
|
||||
|
||||
- 主题入口:`packages/@core/base/design/src/css/global.css`
|
||||
- 主题入口:`internal/tailwind-config/src/theme.css`
|
||||
- Vite 集成:`internal/vite-config`
|
||||
|
||||
在 `global.css` 中你会看到当前项目使用的 Tailwind CSS v4 指令,例如:
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
当前项目不会根据某个包下是否存在 `tailwind.config.mjs` 来决定是否启用 Tailwind CSS。
|
||||
|
||||
应用和包统一复用 `@vben/vite-config`,并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `packages/@core/base/design/src/css/global.css` 中维护。
|
||||
应用和包统一复用 `@vben/vite-config`,并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `@vben/tailwind-config` 对应的 `internal/tailwind-config/src/theme.css` 中维护。
|
||||
|
||||
::: tip 包使用 Tailwind CSS 的说明
|
||||
|
||||
|
|
@ -35,4 +35,4 @@
|
|||
|
||||
- `internal/vite-config/src/plugins/tailwind-reference.ts`
|
||||
|
||||
当组件样式中使用 `@apply` 时,会自动注入对应的 `@reference`,一般不需要手动补充。
|
||||
当组件样式中使用 `@apply` 时,会自动注入 `@reference "@vben/tailwind-config/theme"`,一般不需要手动补充。
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
@ -27,12 +27,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben/oxlint-config": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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:",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ export async function node(): Promise<Linter.Config[]> {
|
|||
'error',
|
||||
{
|
||||
allowModules: [
|
||||
'unbuild',
|
||||
'tsdown',
|
||||
'unplugin-vue',
|
||||
'@vben/vite-config',
|
||||
'vitest',
|
||||
'vite',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ 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,
|
||||
|
|
@ -28,7 +28,7 @@ const oxfmtConfig = defineOxfmtConfig({
|
|||
],
|
||||
});
|
||||
|
||||
function defineConfig(config: OxfmtConfig = {}) {
|
||||
function defineConfig(config: OxfmtConfig = {}): OxfmtConfig {
|
||||
return defineOxfmtConfig({
|
||||
...oxfmtConfig,
|
||||
...config,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false
|
||||
},
|
||||
"exclude": ["node_modules", "src/__tests__"]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: false,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
});
|
||||
|
|
@ -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:"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
import './theme.css';
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -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:"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -68,12 +68,14 @@
|
|||
"@changesets/changelog-github": "catalog:",
|
||||
"@changesets/cli": "catalog:",
|
||||
"@playwright/test": "catalog:",
|
||||
"@tsdown/css": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@vben/commitlint-config": "workspace:*",
|
||||
"@vben/eslint-config": "workspace:*",
|
||||
"@vben/oxfmt-config": "workspace:*",
|
||||
"@vben/oxlint-config": "workspace:*",
|
||||
"@vben/stylelint-config": "workspace:*",
|
||||
"@vben/tailwind-config": "workspace:*",
|
||||
"@vben/tsconfig": "workspace:*",
|
||||
"@vben/turbo-run": "workspace:*",
|
||||
"@vben/vite-config": "workspace:*",
|
||||
|
|
@ -92,16 +94,17 @@
|
|||
"playwright": "catalog:",
|
||||
"rimraf": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"turbo": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"unplugin-vue": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-tsc": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || ^24.0.0",
|
||||
"node": "^20.19.0 || ^22.18.0 || ^24.0.0",
|
||||
"pnpm": ">=10.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.32.1"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"exports": {
|
||||
"./bem": {
|
||||
"development": "./src/scss-bem/bem.scss",
|
||||
"production": "./src/scss-bem/bem.scss",
|
||||
"default": "./dist/bem.scss"
|
||||
},
|
||||
"./theme": {
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/design.css"
|
||||
}
|
||||
},
|
||||
|
|
@ -42,9 +44,6 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json": "catalog:",
|
||||
"@iconify/tailwind4": "catalog:",
|
||||
"@tailwindcss/typography": "catalog:",
|
||||
"tw-animate-css": "catalog:"
|
||||
"@iconify/json": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,569 +1 @@
|
|||
@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;
|
||||
}
|
||||
@import '@vben/tailwind-config/theme';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@reference "./global.css";
|
||||
@reference "@vben/tailwind-config/theme";
|
||||
|
||||
/* Make clicks pass-through */
|
||||
#nprogress {
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild"
|
||||
"build": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
'src/store',
|
||||
'src/constants/index',
|
||||
'src/utils/index',
|
||||
'src/color/index',
|
||||
'src/cache/index',
|
||||
'src/global-state',
|
||||
],
|
||||
});
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"stub": "pnpm unbuild --stub"
|
||||
"build": "pnpm exec tsdown",
|
||||
"stub": "pnpm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
@ -22,31 +22,37 @@
|
|||
"./constants": {
|
||||
"types": "./src/constants/index.ts",
|
||||
"development": "./src/constants/index.ts",
|
||||
"production": "./src/constants/index.ts",
|
||||
"default": "./dist/constants/index.mjs"
|
||||
},
|
||||
"./utils": {
|
||||
"types": "./src/utils/index.ts",
|
||||
"development": "./src/utils/index.ts",
|
||||
"production": "./src/utils/index.ts",
|
||||
"default": "./dist/utils/index.mjs"
|
||||
},
|
||||
"./color": {
|
||||
"types": "./src/color/index.ts",
|
||||
"development": "./src/color/index.ts",
|
||||
"production": "./src/color/index.ts",
|
||||
"default": "./dist/color/index.mjs"
|
||||
},
|
||||
"./cache": {
|
||||
"types": "./src/cache/index.ts",
|
||||
"development": "./src/cache/index.ts",
|
||||
"production": "./src/cache/index.ts",
|
||||
"default": "./dist/cache/index.mjs"
|
||||
},
|
||||
"./store": {
|
||||
"types": "./src/store.ts",
|
||||
"development": "./src/store.ts",
|
||||
"production": "./src/store.ts",
|
||||
"default": "./dist/store.mjs"
|
||||
},
|
||||
"./global-state": {
|
||||
"types": "./src/global-state.ts",
|
||||
"development": "./src/global-state.ts",
|
||||
"production": "./src/global-state.ts",
|
||||
"default": "./dist/global-state.mjs"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
dts: true,
|
||||
entry: {
|
||||
'cache/index': 'src/cache/index.ts',
|
||||
'color/index': 'src/color/index.ts',
|
||||
'constants/index': 'src/constants/index.ts',
|
||||
'global-state': 'src/global-state.ts',
|
||||
store: 'src/store.ts',
|
||||
'utils/index': 'src/utils/index.ts',
|
||||
},
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -11,10 +11,11 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild"
|
||||
"build": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"vue-router.d.ts"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
|
|
@ -23,6 +24,7 @@
|
|||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./vue-router": {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable no-restricted-imports */
|
||||
import type { RouteMeta as IRouteMeta } from '@vben-core/typings';
|
||||
import type { RouteMeta as IRouteMeta } from './dist/index.d.mts';
|
||||
|
||||
import 'vue-router';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild"
|
||||
"build": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"#build": "pnpm unbuild"
|
||||
"#build": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
|
@ -20,12 +20,23 @@
|
|||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./src/index.ts",
|
||||
"#default": "./dist/index.mjs"
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"build": "pnpm exec tsdown",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
|
|
@ -22,16 +22,19 @@
|
|||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { defineConfig } from 'tsdown';
|
||||
import Vue from 'unplugin-vue/rolldown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
dts: {
|
||||
vue: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
js: '.mjs',
|
||||
}),
|
||||
platform: 'neutral',
|
||||
plugins: [Vue({ isProduction: true })],
|
||||
unbundle: true,
|
||||
});
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"build": "pnpm exec tsdown",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
|
|
@ -22,16 +22,19 @@
|
|||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { defineConfig } from 'tsdown';
|
||||
import Vue from 'unplugin-vue/rolldown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
dts: {
|
||||
vue: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
js: '.mjs',
|
||||
}),
|
||||
platform: 'neutral',
|
||||
plugins: [Vue({ isProduction: true })],
|
||||
unbundle: true,
|
||||
});
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
pattern: ['**/*'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"build": "pnpm exec tsdown",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
|
|
@ -22,16 +22,19 @@
|
|||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function menuIcon(menu: MenuRecordRaw) {
|
|||
</ul>
|
||||
</template>
|
||||
<style scoped>
|
||||
@reference "@vben-core/design/theme";
|
||||
@reference "@vben/tailwind-config/theme";
|
||||
|
||||
.vben-normal-menu {
|
||||
--menu-item-margin-y: 4px;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { defineConfig } from 'tsdown';
|
||||
import Vue from 'unplugin-vue/rolldown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
dts: {
|
||||
vue: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
js: '.mjs',
|
||||
}),
|
||||
platform: 'neutral',
|
||||
plugins: [Vue({ isProduction: true })],
|
||||
unbundle: true,
|
||||
});
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"build": "pnpm exec tsdown",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
|
|
@ -22,16 +22,19 @@
|
|||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { defineConfig } from 'tsdown';
|
||||
import Vue from 'unplugin-vue/rolldown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
dts: {
|
||||
vue: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
js: '.mjs',
|
||||
}),
|
||||
platform: 'neutral',
|
||||
plugins: [Vue({ isProduction: true })],
|
||||
unbundle: true,
|
||||
});
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
|
||||
pattern: ['**/*'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
{
|
||||
"name": "@vben-core/shadcn-ui",
|
||||
"version": "5.7.0",
|
||||
"#main": "./dist/index.mjs",
|
||||
"#module": "./dist/index.mjs",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
|
@ -12,29 +10,28 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"#build": "pnpm unbuild",
|
||||
"#prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"components.json",
|
||||
"src"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./src/index.ts",
|
||||
"//default": "./dist/index.mjs"
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
@reference "@vben/tailwind-config/theme";
|
||||
|
|
@ -50,7 +50,7 @@ function handleClick(index: number, path?: string) {
|
|||
</ul>
|
||||
</template>
|
||||
<style scoped>
|
||||
@reference "@vben-core/design/theme";
|
||||
@reference "@vben/tailwind-config/theme";
|
||||
|
||||
li {
|
||||
@apply h-7;
|
||||
|
|
|
|||
|
|
@ -448,8 +448,9 @@ defineExpose({
|
|||
}
|
||||
|
||||
.item {
|
||||
@apply h-7.5 w-full box-border;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: #f3f3f3;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"build": "pnpm exec tsdown",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
|
|
@ -22,16 +22,19 @@
|
|||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"production": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "@vben-core/design/theme";
|
||||
@reference "@vben/tailwind-config/theme";
|
||||
|
||||
.tabs-chrome__item:not(.dragging) {
|
||||
@apply cursor-pointer;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { defineConfig } from 'tsdown';
|
||||
import Vue from 'unplugin-vue/rolldown';
|
||||
|
||||
export default defineConfig({
|
||||
clean: true,
|
||||
deps: {
|
||||
skipNodeModulesBundle: true,
|
||||
},
|
||||
dts: {
|
||||
vue: true,
|
||||
},
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
outExtensions: () => ({
|
||||
dts: '.d.ts',
|
||||
js: '.mjs',
|
||||
}),
|
||||
platform: 'neutral',
|
||||
plugins: [Vue({ isProduction: true })],
|
||||
unbundle: true,
|
||||
});
|
||||
|
|
@ -528,7 +528,7 @@ const handleImageLoad = () => {
|
|||
* @param {number} targetHeight - 目标高度(可选,不传则为原始裁剪高度)
|
||||
*/
|
||||
const getCropImage = async (
|
||||
format: 'image/jpeg' | 'image/png' = 'image/jpeg',
|
||||
format: 'image/jpeg' | 'image/png' = 'image/png',
|
||||
quality: number = 0.92,
|
||||
outputType: 'base64' | 'blob' = 'blob',
|
||||
targetWidth?: number,
|
||||
|
|
@ -851,7 +851,7 @@ defineExpose({ getCropImage });
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "@vben-core/design/theme";
|
||||
@reference "@vben/tailwind-config/theme";
|
||||
|
||||
.cropper-action-wrapper {
|
||||
@apply box-border flex items-center justify-center;
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ function toggleTheme(event: MouseEvent) {
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "@vben-core/design/theme";
|
||||
@reference "@vben/tailwind-config/theme";
|
||||
|
||||
.theme-toggle__moon > circle {
|
||||
transition: transform 0.5s cubic-bezier(0, 0, 0.3, 1);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,16 @@ let isInit = false;
|
|||
|
||||
let tableFormFactory: typeof useVbenForm | undefined;
|
||||
|
||||
function normalizeVxeLocale<T extends Record<string, any>>(localeModule: T) {
|
||||
return (
|
||||
localeModule &&
|
||||
typeof localeModule === 'object' &&
|
||||
'default' in localeModule
|
||||
? localeModule.default
|
||||
: localeModule
|
||||
) as T;
|
||||
}
|
||||
|
||||
export const useTableForm: typeof useVbenForm = ((...args) => {
|
||||
if (!tableFormFactory) {
|
||||
throw new Error('useTableForm is not initialized');
|
||||
|
|
@ -116,8 +126,8 @@ export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
|
|||
const { isDark, locale } = usePreferences();
|
||||
|
||||
const localMap = {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS,
|
||||
'zh-CN': normalizeVxeLocale(zhCN),
|
||||
'en-US': normalizeVxeLocale(enUS),
|
||||
};
|
||||
|
||||
watch(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@reference "@vben-core/design/theme";
|
||||
@reference "@vben/tailwind-config/theme";
|
||||
|
||||
:root .vxe-grid {
|
||||
--vxe-ui-font-color: hsl(var(--foreground));
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -126,260 +131,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'] || [],
|
||||
);
|
||||
|
|
@ -393,12 +399,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 &&
|
||||
|
|
@ -406,14 +414,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;
|
||||
|
|
@ -421,12 +426,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(
|
||||
|
|
@ -443,21 +445,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) => {
|
||||
|
|
@ -465,18 +534,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,
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ function openInContentDrawer(placement: DrawerPlacement = 'right') {
|
|||
}
|
||||
|
||||
function openMaxContentDrawer() {
|
||||
// 这里只是用来演示方便。实际上自己使用的时候可以直接将这些配置卸载Drawer的属性里
|
||||
// 这里只是用来演示方便。实际上自己使用的时候可以直接将这些配置写在Drawer的属性里
|
||||
inContentDrawerApi.setState({ class: 'w-full', placement: 'right' }).open();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -348,13 +348,14 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||
// 自动携带认证信息
|
||||
customRequest: upload_file,
|
||||
disabled: false,
|
||||
maxCount: 1,
|
||||
maxCount: 3,
|
||||
// 单位:MB
|
||||
maxSize: 2,
|
||||
multiple: false,
|
||||
showUploadList: true,
|
||||
// 上传列表的内建样式,支持四种基本样式 text, picture, picture-card 和 picture-circle
|
||||
listType: 'picture-card',
|
||||
draggable: true, // 启用拖拽排序
|
||||
// onChange事件已被重写,如需自定义请在此基础上扩展
|
||||
handleChange: ({ file }: { file: UploadFile }) => {
|
||||
const { name, status } = file;
|
||||
|
|
@ -364,6 +365,9 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||
message.error(`${name} ${$t('examples.form.upload-fail')}`);
|
||||
}
|
||||
},
|
||||
onDragSort: (oldIndex: number, newIndex: number) => {
|
||||
console.warn(`图片从 ${oldIndex} 移动到 ${newIndex}`);
|
||||
},
|
||||
},
|
||||
fieldName: 'files',
|
||||
label: $t('examples.form.file'),
|
||||
|
|
|
|||
3186
pnpm-lock.yaml
3186
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -20,32 +20,33 @@ overrides:
|
|||
pinia: 'catalog:'
|
||||
vue: 'catalog:'
|
||||
catalog:
|
||||
'@ast-grep/napi': ^0.41.1
|
||||
'@ast-grep/napi': ^0.42.0
|
||||
'@changesets/changelog-github': ^0.6.0
|
||||
'@changesets/cli': ^2.30.0
|
||||
'@changesets/git': ^3.0.4
|
||||
'@clack/prompts': ^1.1.0
|
||||
'@commitlint/cli': ^20.4.4
|
||||
'@commitlint/config-conventional': ^20.4.4
|
||||
'@commitlint/cli': ^20.5.0
|
||||
'@commitlint/config-conventional': ^20.5.0
|
||||
'@ctrl/tinycolor': ^4.2.0
|
||||
'@eslint-community/eslint-plugin-eslint-comments': ^4.7.1
|
||||
'@eslint/js': ^10.0.1
|
||||
'@faker-js/faker': ^10.3.0
|
||||
'@iconify/json': ^2.2.449
|
||||
'@iconify/json': ^2.2.451
|
||||
'@iconify/tailwind4': ^1.2.3
|
||||
'@iconify/vue': ^5.0.0
|
||||
'@intlify/core-base': ^11.3.0
|
||||
'@intlify/unplugin-vue-i18n': ^11.0.7
|
||||
'@jspm/generator': ^2.11.0
|
||||
'@jspm/generator': ^2.12.0
|
||||
'@manypkg/get-packages': ^3.1.0
|
||||
'@nolebase/vitepress-plugin-git-changelog': ^2.18.2
|
||||
'@playwright/test': ^1.58.2
|
||||
'@pnpm/workspace.read-manifest': ^1000.3.0
|
||||
'@stylistic/stylelint-plugin': ^5.0.1
|
||||
'@tailwindcss/typography': ^0.5.19
|
||||
'@tailwindcss/vite': ^4.2.1
|
||||
'@tanstack/vue-query': ^5.92.9
|
||||
'@tailwindcss/vite': ^4.2.2
|
||||
'@tanstack/vue-query': ^5.92.10
|
||||
'@tanstack/vue-store': ^0.9.2
|
||||
'@tsdown/css': ^0.21.4
|
||||
'@types/archiver': ^7.0.0
|
||||
'@types/html-minifier-terser': ^7.0.2
|
||||
'@types/json-bigint': ^1.0.4
|
||||
|
|
@ -56,8 +57,8 @@ catalog:
|
|||
'@types/qrcode': ^1.5.6
|
||||
'@types/qs': ^6.15.0
|
||||
'@types/sortablejs': ^1.15.9
|
||||
'@typescript-eslint/eslint-plugin': ^8.57.0
|
||||
'@typescript-eslint/parser': ^8.57.0
|
||||
'@typescript-eslint/eslint-plugin': ^8.57.1
|
||||
'@typescript-eslint/parser': ^8.57.1
|
||||
'@vee-validate/zod': ^4.15.1
|
||||
'@vite-pwa/vitepress': ^1.1.0
|
||||
'@vitejs/plugin-vue': ^6.0.5
|
||||
|
|
@ -68,7 +69,7 @@ catalog:
|
|||
'@vueuse/integrations': ^14.2.1
|
||||
'@vueuse/motion': ^3.0.3
|
||||
ant-design-vue: ^4.2.6
|
||||
antdv-next: ^1.1.4
|
||||
antdv-next: ^1.1.5
|
||||
archiver: ^7.0.1
|
||||
axios: ^1.13.6
|
||||
axios-mock-adapter: ^2.1.0
|
||||
|
|
@ -96,7 +97,7 @@ catalog:
|
|||
eslint-plugin-command: ^3.5.2
|
||||
eslint-plugin-jsonc: ^3.1.2
|
||||
eslint-plugin-n: ^17.24.0
|
||||
eslint-plugin-perfectionist: ^5.6.0
|
||||
eslint-plugin-perfectionist: ^5.7.0
|
||||
eslint-plugin-pnpm: ^1.6.0
|
||||
eslint-plugin-unicorn: ^63.0.0
|
||||
eslint-plugin-unused-imports: ^4.4.1
|
||||
|
|
@ -106,7 +107,7 @@ catalog:
|
|||
find-up: ^8.0.0
|
||||
get-port: ^7.1.0
|
||||
globals: ^17.4.0
|
||||
h3: ^1.15.6
|
||||
h3: ^1.15.8
|
||||
happy-dom: ^20.8.4
|
||||
html-minifier-terser: ^7.2.0
|
||||
is-ci: ^4.1.0
|
||||
|
|
@ -120,9 +121,9 @@ catalog:
|
|||
nitropack: ^2.13.1
|
||||
nprogress: ^0.2.0
|
||||
ora: ^9.3.0
|
||||
oxfmt: ^0.40.0
|
||||
oxlint: ^1.55.0
|
||||
oxlint-tsgolint: ^0.16.0
|
||||
oxfmt: ^0.41.0
|
||||
oxlint: ^1.56.0
|
||||
oxlint-tsgolint: ^0.17.0
|
||||
pinia: ^3.0.4
|
||||
pinia-plugin-persistedstate: ^4.7.1
|
||||
pkg-types: ^2.3.0
|
||||
|
|
@ -136,14 +137,13 @@ catalog:
|
|||
reka-ui: ^2.9.2
|
||||
resolve.exports: ^2.0.3
|
||||
rimraf: ^6.1.3
|
||||
rollup: ^4.59.0
|
||||
rollup-plugin-visualizer: ^7.0.1
|
||||
sass: ^1.98.0
|
||||
sass-embedded: ^1.98.0
|
||||
secure-ls: ^2.0.0
|
||||
sortablejs: ^1.15.7
|
||||
stylelint: ^17.4.0
|
||||
stylelint-config-recess-order: ^7.6.1
|
||||
stylelint-config-recess-order: ^7.7.0
|
||||
stylelint-config-recommended: ^18.0.0
|
||||
stylelint-config-recommended-scss: ^17.0.0
|
||||
stylelint-config-recommended-vue: ^1.6.1
|
||||
|
|
@ -151,19 +151,20 @@ catalog:
|
|||
stylelint-order: ^8.1.1
|
||||
stylelint-scss: ^7.0.0
|
||||
tailwind-merge: ^3.5.0
|
||||
tailwindcss: ^4.2.1
|
||||
tailwindcss: ^4.2.2
|
||||
tdesign-vue-next: ^1.18.5
|
||||
theme-colors: ^0.1.0
|
||||
tippy.js: ^6.3.7
|
||||
turbo: ^2.8.17
|
||||
tsdown: ^0.21.4
|
||||
turbo: ^2.8.19
|
||||
tw-animate-css: ^1.4.0
|
||||
typescript: ^5.9.3
|
||||
unbuild: ^3.6.1
|
||||
unplugin-dts: ^1.0.0-beta.6
|
||||
unplugin-element-plus: ^0.11.2
|
||||
unplugin-vue: ^7.1.1
|
||||
vee-validate: ^4.15.1
|
||||
vite: ^8.0.0
|
||||
vite-plugin-compression: ^0.5.1
|
||||
vite-plugin-dts: ^4.5.4
|
||||
vite-plugin-html: ^3.2.2
|
||||
vite-plugin-lazy-import: ^1.0.7
|
||||
vite-plugin-pwa: ^1.2.0
|
||||
|
|
@ -177,9 +178,9 @@ catalog:
|
|||
vue-json-viewer: ^3.0.4
|
||||
vue-router: ^5.0.3
|
||||
vue-tippy: ^6.7.1
|
||||
vue-tsc: ^3.2.5
|
||||
vxe-pc-ui: ^4.13.6
|
||||
vxe-table: ^4.18.2
|
||||
vue-tsc: ^3.2.6
|
||||
vxe-pc-ui: ^4.13.10
|
||||
vxe-table: ^4.18.5
|
||||
watermark-js-plus: ^1.6.3
|
||||
yaml-eslint-parser: ^2.0.0
|
||||
zod: ^3.25.76
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"stub": "pnpm unbuild --stub"
|
||||
"stub": "pnpm exec tsdown"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import type { CAC } from 'cac';
|
||||
|
||||
import { extname } from 'node:path';
|
||||
import { access, mkdtemp, readFile, rm } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { extname, join } from 'node:path';
|
||||
|
||||
import { getStagedFiles } from '@vben/node-utils';
|
||||
import { execa, getStagedFiles } from '@vben/node-utils';
|
||||
|
||||
import { circularDepsDetect } from 'circular-dependency-scanner';
|
||||
const require = createRequire(import.meta.url);
|
||||
const circularScannerCli =
|
||||
require.resolve('circular-dependency-scanner/dist/cli.js');
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG = {
|
||||
|
|
@ -41,6 +46,44 @@ interface CommandOptions {
|
|||
// 缓存机制
|
||||
const cache = new Map<string, CircularDependencyResult[]>();
|
||||
|
||||
async function detectCircularDependencies({
|
||||
cwd,
|
||||
ignorePattern,
|
||||
staged,
|
||||
}: {
|
||||
cwd: string;
|
||||
ignorePattern: string;
|
||||
staged: boolean;
|
||||
}): Promise<CircularDependencyResult[]> {
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'vsh-check-circular-'));
|
||||
const outputFile = join(tempDir, 'circles.json');
|
||||
|
||||
try {
|
||||
const args = [circularScannerCli, cwd, '--output', outputFile];
|
||||
|
||||
if (staged) {
|
||||
args.push('--absolute');
|
||||
}
|
||||
|
||||
args.push('--ignore', ignorePattern);
|
||||
|
||||
await execa(process.execPath, args, {
|
||||
cwd,
|
||||
});
|
||||
|
||||
await access(outputFile);
|
||||
const output = await readFile(outputFile, 'utf8');
|
||||
return JSON.parse(output) as CircularDependencyResult[];
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await rm(tempDir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化循环依赖的输出
|
||||
* @param circles - 循环依赖结果
|
||||
|
|
@ -85,17 +128,17 @@ async function checkCircular({
|
|||
const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`;
|
||||
if (cache.has(cacheKey)) {
|
||||
const cachedResults = cache.get(cacheKey);
|
||||
if (cachedResults) {
|
||||
verbose && formatCircles(cachedResults);
|
||||
if (cachedResults && verbose) {
|
||||
formatCircles(cachedResults);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测循环依赖
|
||||
const results = await circularDepsDetect({
|
||||
absolute: staged,
|
||||
const results = await detectCircularDependencies({
|
||||
cwd: process.cwd(),
|
||||
ignore: [ignorePattern],
|
||||
ignorePattern,
|
||||
staged,
|
||||
});
|
||||
|
||||
if (staged) {
|
||||
|
|
@ -118,11 +161,15 @@ async function checkCircular({
|
|||
|
||||
// 更新缓存
|
||||
cache.set(cacheKey, circularFiles);
|
||||
verbose && formatCircles(circularFiles);
|
||||
if (verbose) {
|
||||
formatCircles(circularFiles);
|
||||
}
|
||||
} else {
|
||||
// 更新缓存
|
||||
cache.set(cacheKey, results);
|
||||
verbose && formatCircles(results);
|
||||
if (verbose) {
|
||||
formatCircles(results);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果发现循环依赖,只输出警告信息
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ const DEFAULT_CONFIG = {
|
|||
ignoreMatches: [
|
||||
'vite',
|
||||
'vitest',
|
||||
'unbuild',
|
||||
'tsdown',
|
||||
'@vben/tailwind-config',
|
||||
'@vben/tsconfig',
|
||||
'@vben/vite-config',
|
||||
'@types/*',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
@ -52,6 +52,10 @@
|
|||
"name": "@vben/node-utils",
|
||||
"path": "internal/node-utils"
|
||||
},
|
||||
{
|
||||
"name": "@vben/tailwind-config",
|
||||
"path": "internal/tailwind-config"
|
||||
},
|
||||
{
|
||||
"name": "@vben/tsconfig",
|
||||
"path": "internal/tsconfig"
|
||||
|
|
|
|||
Loading…
Reference in New Issue