diff --git a/apps/backend-mock/api/upload.ts b/apps/backend-mock/api/upload.ts
new file mode 100644
index 00000000..1bb9e602
--- /dev/null
+++ b/apps/backend-mock/api/upload.ts
@@ -0,0 +1,13 @@
+import { verifyAccessToken } from '~/utils/jwt-utils';
+import { unAuthorizedResponse } from '~/utils/response';
+
+export default eventHandler((event) => {
+ const userinfo = verifyAccessToken(event);
+ if (!userinfo) {
+ return unAuthorizedResponse(event);
+ }
+ return useResponseSuccess({
+ url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
+ });
+ // return useResponseError("test")
+});
diff --git a/apps/backend-mock/routes/[...].ts b/apps/backend-mock/routes/[...].ts
index 70c5f7c7..99f544b6 100644
--- a/apps/backend-mock/routes/[...].ts
+++ b/apps/backend-mock/routes/[...].ts
@@ -7,6 +7,7 @@ export default defineEventHandler(() => {
/api/menu/all
/api/auth/codes
/api/auth/login
+/api/upload
`;
});
diff --git a/docs/src/components/common-ui/vben-modal.md b/docs/src/components/common-ui/vben-modal.md
index 56ff6d3c..3c8200f9 100644
--- a/docs/src/components/common-ui/vben-modal.md
+++ b/docs/src/components/common-ui/vben-modal.md
@@ -59,7 +59,7 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
::: info 注意
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
-- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
+- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`,内部Modal及其子组件会在被关闭后完全销毁。
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
:::
diff --git a/docs/src/guide/project/tailwindcss.md b/docs/src/guide/project/tailwindcss.md
index cfab5964..837076cb 100644
--- a/docs/src/guide/project/tailwindcss.md
+++ b/docs/src/guide/project/tailwindcss.md
@@ -11,3 +11,7 @@
当前只有对应的包下面存在 `tailwind.config.mjs` 文件才会启用 tailwindcss 的编译,否则不会启用 tailwindcss。如果你是纯粹的 SDK 包,不需要使用 tailwindcss,可以不用创建 `tailwind.config.mjs` 文件。
:::
+
+## 提示
+
+现`tailwindcss`已至v4.x版本,使用方法与`tailwindcss: ^3.4.17`有差异,v4.0无法与v3.x版本兼容,在开发前请确认`package.json`中的`tailwindcss`版本。
diff --git a/packages/@core/ui-kit/popup-ui/src/alert/alert.vue b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
index a5b4d0da..9b133ee1 100644
--- a/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
+++ b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
@@ -91,14 +91,13 @@ const getIconRender = computed(() => {
});
function doCancel() {
- isConfirm.value = false;
+ handleCancel();
handleOpenChange(false);
}
function doConfirm() {
- isConfirm.value = true;
+ handleConfirm();
handleOpenChange(false);
- emits('confirm');
}
provideAlertContext({
@@ -117,11 +116,13 @@ function handleCancel() {
const loading = ref(false);
async function handleOpenChange(val: boolean) {
+ const confirmState = isConfirm.value;
+ isConfirm.value = false;
await nextTick();
if (!val && props.beforeClose) {
loading.value = true;
try {
- const res = await props.beforeClose({ isConfirm: isConfirm.value });
+ const res = await props.beforeClose({ isConfirm: confirmState });
if (res !== false) {
open.value = false;
}
diff --git a/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts
index 2f40ddea..61965a02 100644
--- a/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts
+++ b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts
@@ -1,6 +1,14 @@
import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
-import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue';
+import {
+ defineComponent,
+ h,
+ inject,
+ nextTick,
+ provide,
+ reactive,
+ ref,
+} from 'vue';
import { useStore } from '@vben-core/shared/store';
@@ -24,6 +32,7 @@ export function useVbenModal(
const { connectedComponent } = options;
if (connectedComponent) {
const extendedApi = reactive({});
+ const isModalReady = ref(true);
const Modal = defineComponent(
(props: TParentModalProps, { attrs, slots }) => {
provide(USER_MODAL_INJECT_KEY, {
@@ -33,6 +42,11 @@ export function useVbenModal(
Object.setPrototypeOf(extendedApi, api);
},
options,
+ async reCreateModal() {
+ isModalReady.value = false;
+ await nextTick();
+ isModalReady.value = true;
+ },
});
checkProps(extendedApi as ExtendedModalApi, {
...props,
@@ -41,7 +55,7 @@ export function useVbenModal(
});
return () =>
h(
- connectedComponent,
+ isModalReady.value ? connectedComponent : 'div',
{
...props,
...attrs,
@@ -70,6 +84,13 @@ export function useVbenModal(
injectData.options?.onOpenChange?.(isOpen);
};
+ mergedOptions.onClosed = () => {
+ options.onClosed?.();
+ if (options.destroyOnClose) {
+ injectData.reCreateModal?.();
+ }
+ };
+
const api = new ModalApi(mergedOptions);
const extendedApi: ExtendedModalApi = api as never;
diff --git a/packages/effects/layouts/src/basic/menu/use-mixed-menu.ts b/packages/effects/layouts/src/basic/menu/use-mixed-menu.ts
index 6129e9d8..dc727447 100644
--- a/packages/effects/layouts/src/basic/menu/use-mixed-menu.ts
+++ b/packages/effects/layouts/src/basic/menu/use-mixed-menu.ts
@@ -74,7 +74,7 @@ function useMixedMenu() {
*/
const headerActive = computed(() => {
if (!needSplit.value) {
- return route.path;
+ return route.meta?.activePath ?? route.path;
}
return rootMenuPath.value;
});
diff --git a/playground/src/api/examples/upload.ts b/playground/src/api/examples/upload.ts
new file mode 100644
index 00000000..246d4f26
--- /dev/null
+++ b/playground/src/api/examples/upload.ts
@@ -0,0 +1,25 @@
+import { requestClient } from '#/api/request';
+
+interface UploadFileParams {
+ file: File;
+ onError?: (error: Error) => void;
+ onProgress?: (progress: { percent: number }) => void;
+ onSuccess?: (data: any, file: File) => void;
+}
+export async function upload_file({
+ file,
+ onError,
+ onProgress,
+ onSuccess,
+}: UploadFileParams) {
+ try {
+ onProgress?.({ percent: 0 });
+
+ const data = await requestClient.upload('/upload', { file });
+
+ onProgress?.({ percent: 100 });
+ onSuccess?.(data, file);
+ } catch (error) {
+ onError?.(error instanceof Error ? error : new Error(String(error)));
+ }
+}
diff --git a/playground/src/locales/langs/en-US/examples.json b/playground/src/locales/langs/en-US/examples.json
index 1a25a983..9335b28b 100644
--- a/playground/src/locales/langs/en-US/examples.json
+++ b/playground/src/locales/langs/en-US/examples.json
@@ -18,7 +18,11 @@
"dynamic": "Dynamic Form",
"custom": "Custom Component",
"api": "Api",
- "merge": "Merge Form"
+ "merge": "Merge Form",
+ "upload-error": "Partial file upload failed",
+ "upload-urls": "Urls after file upload",
+ "file": "file",
+ "upload-image": "Click to upload image"
},
"vxeTable": {
"title": "Vxe Table",
diff --git a/playground/src/locales/langs/zh-CN/examples.json b/playground/src/locales/langs/zh-CN/examples.json
index 8f15d020..ff11d7fd 100644
--- a/playground/src/locales/langs/zh-CN/examples.json
+++ b/playground/src/locales/langs/zh-CN/examples.json
@@ -21,7 +21,11 @@
"dynamic": "动态表单",
"custom": "自定义组件",
"api": "Api",
- "merge": "合并表单"
+ "merge": "合并表单",
+ "upload-error": "部分文件上传失败",
+ "upload-urls": "文件上传后的网址",
+ "file": "文件",
+ "upload-image": "点击上传图片"
},
"vxeTable": {
"title": "Vxe 表格",
diff --git a/playground/src/views/examples/form/basic.vue b/playground/src/views/examples/form/basic.vue
index b98ace26..d0e91d33 100644
--- a/playground/src/views/examples/form/basic.vue
+++ b/playground/src/views/examples/form/basic.vue
@@ -1,5 +1,7 @@