+
diff --git a/packages/@core/ui-kit/popup-ui/src/alert/index.ts b/packages/@core/ui-kit/popup-ui/src/alert/index.ts
index af8f424f1..8419b5b85 100644
--- a/packages/@core/ui-kit/popup-ui/src/alert/index.ts
+++ b/packages/@core/ui-kit/popup-ui/src/alert/index.ts
@@ -1,5 +1,10 @@
-export * from './alert';
-
+export type {
+ AlertProps,
+ BeforeCloseScope,
+ IconType,
+ PromptProps,
+} from './alert';
+export { useAlertContext } from './alert';
export { default as Alert } from './alert.vue';
export {
vbenAlert as alert,
diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts
index 46dcafc30..365a2e4a0 100644
--- a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts
+++ b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts
@@ -54,7 +54,6 @@ describe('drawerApi', () => {
});
it('should close the drawer if onBeforeClose allows it', () => {
- drawerApi.open();
drawerApi.close();
expect(drawerApi.store.state.isOpen).toBe(false);
});
diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts
index 785a9029e..a4a3ac4aa 100644
--- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts
+++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts
@@ -86,12 +86,13 @@ export class DrawerApi {
}
/**
- * 关闭弹窗
+ * 关闭抽屉
+ * @description 关闭抽屉时会调用 onBeforeClose 钩子函数,如果 onBeforeClose 返回 false,则不关闭弹窗
*/
- close() {
+ async close() {
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
// 如果 onBeforeClose 返回 false,则不关闭弹窗
- const allowClose = this.api.onBeforeClose?.() ?? true;
+ const allowClose = (await this.api.onBeforeClose?.()) ?? true;
if (allowClose) {
this.store.setState((prev) => ({
...prev,
diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts
index b3ae0fb8b..30009a649 100644
--- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts
+++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts
@@ -1,6 +1,6 @@
import type { Component, Ref } from 'vue';
-import type { ClassType } from '@vben-core/typings';
+import type { ClassType, MaybePromise } from '@vben-core/typings';
import type { DrawerApi } from './drawer-api';
@@ -151,7 +151,7 @@ export interface DrawerApiOptions extends DrawerState {
* 关闭前的回调,返回 false 可以阻止关闭
* @returns
*/
- onBeforeClose?: () => void;
+ onBeforeClose?: () => MaybePromise
;
/**
* 点击取消按钮的回调
*/
diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
index 410c3ffe0..b5535ba47 100644
--- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
+++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
@@ -274,7 +274,7 @@ const getAppendTo = computed(() => {
{{ cancelText || $t('cancel') }}
-
+
state?.value?.isOpen,
@@ -186,7 +186,7 @@ const getAppendTo = computed(() => {
});
const getForceMount = computed(() => {
- return !unref(destroyOnClose);
+ return !unref(destroyOnClose) && unref(firstOpened);
});
function handleClosed() {
@@ -321,7 +321,7 @@ function handleClosed() {
{{ cancelText || $t('cancel') }}
-
+
(
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/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue b/packages/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue
index 4c008ea86..1816051c6 100644
--- a/packages/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue
+++ b/packages/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue
@@ -31,12 +31,11 @@ export default defineComponent({
if (props.renderBr && isString(props.content)) {
const lines = props.content.split('\n');
const result = [];
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- result.push(h('span', { key: i }, line));
- if (i < lines.length - 1) {
- result.push(h('br'));
- }
+ for (const [i, line] of lines.entries()) {
+ result.push(h('p', { key: i }, line));
+ // if (i < lines.length - 1) {
+ // result.push(h('br'));
+ // }
}
return result;
} else {
diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue b/packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue
index f066b91ac..e101ba59a 100644
--- a/packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue
+++ b/packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue
@@ -39,6 +39,14 @@ const isAtRight = ref(false);
const isAtBottom = ref(false);
const isAtLeft = ref(true);
+/**
+ * We have to check if the scroll amount is close enough to some threshold in order to
+ * more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
+ * numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
+ * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
+ */
+const ARRIVED_STATE_THRESHOLD_PIXELS = 1;
+
const showShadowTop = computed(() => props.shadow && props.shadowTop);
const showShadowBottom = computed(() => props.shadow && props.shadowBottom);
const showShadowLeft = computed(() => props.shadow && props.shadowLeft);
@@ -60,14 +68,18 @@ function handleScroll(event: Event) {
const target = event.target as HTMLElement;
const scrollTop = target?.scrollTop ?? 0;
const scrollLeft = target?.scrollLeft ?? 0;
- const offsetHeight = target?.offsetHeight ?? 0;
- const offsetWidth = target?.offsetWidth ?? 0;
+ const clientHeight = target?.clientHeight ?? 0;
+ const clientWidth = target?.clientWidth ?? 0;
const scrollHeight = target?.scrollHeight ?? 0;
const scrollWidth = target?.scrollWidth ?? 0;
isAtTop.value = scrollTop <= 0;
isAtLeft.value = scrollLeft <= 0;
- isAtBottom.value = scrollTop + offsetHeight >= scrollHeight;
- isAtRight.value = scrollLeft + offsetWidth >= scrollWidth;
+ isAtBottom.value =
+ Math.abs(scrollTop) + clientHeight >=
+ scrollHeight - ARRIVED_STATE_THRESHOLD_PIXELS;
+ isAtRight.value =
+ Math.abs(scrollLeft) + clientWidth >=
+ scrollWidth - ARRIVED_STATE_THRESHOLD_PIXELS;
emit('scrollAt', {
bottom: isAtBottom.value,
diff --git a/packages/effects/common-ui/src/components/api-component/api-component.vue b/packages/effects/common-ui/src/components/api-component/api-component.vue
index dcbacd4e2..a3e72b440 100644
--- a/packages/effects/common-ui/src/components/api-component/api-component.vue
+++ b/packages/effects/common-ui/src/components/api-component/api-component.vue
@@ -242,6 +242,10 @@ function emitChange() {
}
const componentRef = ref();
defineExpose({
+ /** 获取options数据 */
+ getOptions: () => unref(getOptions),
+ /** 获取当前值 */
+ getValue: () => unref(modelValue),
/** 获取被包装的组件实例 */
getComponentRef: () => componentRef.value as T,
/** 更新Api参数 */
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 6129e9d80..dc727447c 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/packages/effects/plugins/src/vxe-table/style.css b/packages/effects/plugins/src/vxe-table/style.css
index cd1d67c44..5b47fa2cf 100644
--- a/packages/effects/plugins/src/vxe-table/style.css
+++ b/packages/effects/plugins/src/vxe-table/style.css
@@ -45,6 +45,9 @@
);
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
+ --vxe-ui-font-primary-tinge-color: hsl(var(--primary));
+ --vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%);
+ --vxe-ui-font-primary-darken-color: hsl(var(--primary));
height: auto !important;
diff --git a/playground/src/adapter/vxe-table.ts b/playground/src/adapter/vxe-table.ts
index cb24b561b..9c0cd73ba 100644
--- a/playground/src/adapter/vxe-table.ts
+++ b/playground/src/adapter/vxe-table.ts
@@ -212,7 +212,12 @@ setupVbenVxeTable({
Popconfirm,
{
getPopupContainer(el) {
- return el.closest('tbody') || document.body;
+ return (
+ el
+ .closest('.vxe-table--viewport-wrapper')
+ ?.querySelector('.vxe-table--main-wrapper')
+ ?.querySelector('tbody') || document.body
+ );
},
placement: 'topLeft',
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
diff --git a/playground/src/api/examples/upload.ts b/playground/src/api/examples/upload.ts
new file mode 100644
index 000000000..246d4f267
--- /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 1a25a983b..9335b28b7 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 8f15d0202..ff11d7fd2 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/drawer/base-demo.vue b/playground/src/views/examples/drawer/base-demo.vue
index 87ded0a2d..08bc68fc5 100644
--- a/playground/src/views/examples/drawer/base-demo.vue
+++ b/playground/src/views/examples/drawer/base-demo.vue
@@ -30,5 +30,6 @@ function lockDrawer() {
+
diff --git a/playground/src/views/examples/form/basic.vue b/playground/src/views/examples/form/basic.vue
index 75e868d8f..d0e91d33a 100644
--- a/playground/src/views/examples/form/basic.vue
+++ b/playground/src/views/examples/form/basic.vue
@@ -1,5 +1,7 @@