;
+```
diff --git a/docs/src/demos/vben-alert/alert/index.vue b/docs/src/demos/vben-alert/alert/index.vue
new file mode 100644
index 00000000..103ce64f
--- /dev/null
+++ b/docs/src/demos/vben-alert/alert/index.vue
@@ -0,0 +1,31 @@
+
+
+
+ Alert
+ Alert With Icon
+ Alert With Custom Content
+
+
diff --git a/docs/src/demos/vben-alert/confirm/index.vue b/docs/src/demos/vben-alert/confirm/index.vue
new file mode 100644
index 00000000..07f3570b
--- /dev/null
+++ b/docs/src/demos/vben-alert/confirm/index.vue
@@ -0,0 +1,39 @@
+
+
+
+ Confirm
+ Confirm With Icon
+ Async Confirm
+
+
diff --git a/docs/src/demos/vben-alert/prompt/index.vue b/docs/src/demos/vben-alert/prompt/index.vue
new file mode 100644
index 00000000..423124e7
--- /dev/null
+++ b/docs/src/demos/vben-alert/prompt/index.vue
@@ -0,0 +1,41 @@
+
+
+
+ Prompt
+ Confirm With Select
+
+
diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts
index 44d07f94..70e6a426 100644
--- a/packages/@core/base/icons/src/lucide.ts
+++ b/packages/@core/base/icons/src/lucide.ts
@@ -15,8 +15,10 @@ export {
ChevronsLeft,
ChevronsRight,
Circle,
+ CircleAlert,
CircleCheckBig,
CircleHelp,
+ CircleX,
Copy,
CornerDownLeft,
Ellipsis,
diff --git a/packages/@core/composables/src/use-simple-locale/messages.ts b/packages/@core/composables/src/use-simple-locale/messages.ts
index 4b1eca29..efc5c3cc 100644
--- a/packages/@core/composables/src/use-simple-locale/messages.ts
+++ b/packages/@core/composables/src/use-simple-locale/messages.ts
@@ -6,6 +6,7 @@ export const messages: Record> = {
collapse: 'Collapse',
confirm: 'Confirm',
expand: 'Expand',
+ prompt: 'Prompt',
reset: 'Reset',
submit: 'Submit',
},
@@ -14,6 +15,7 @@ export const messages: Record> = {
collapse: '收起',
confirm: '确认',
expand: '展开',
+ prompt: '提示',
reset: '重置',
submit: '提交',
},
diff --git a/packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts b/packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts
new file mode 100644
index 00000000..71e7d9db
--- /dev/null
+++ b/packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts
@@ -0,0 +1,203 @@
+import type { Component } from 'vue';
+
+import type { Recordable } from '@vben-core/typings';
+
+import type { AlertProps } from './alert';
+
+import { h, ref, render } from 'vue';
+
+import { useSimpleLocale } from '@vben-core/composables';
+import { Input } from '@vben-core/shadcn-ui';
+import { isFunction, isString } from '@vben-core/shared/utils';
+
+import Alert from './alert.vue';
+
+const alerts = ref>([]);
+
+const { $t } = useSimpleLocale();
+
+export function vbenAlert(options: AlertProps): Promise;
+export function vbenAlert(
+ message: string,
+ options?: Partial,
+): Promise;
+export function vbenAlert(
+ message: string,
+ title?: string,
+ options?: Partial,
+): Promise;
+
+export function vbenAlert(
+ arg0: AlertProps | string,
+ arg1?: Partial | string,
+ arg2?: Partial,
+): Promise {
+ return new Promise((resolve, reject) => {
+ const options: AlertProps = isString(arg0)
+ ? {
+ content: arg0,
+ }
+ : { ...arg0 };
+ if (arg1) {
+ if (isString(arg1)) {
+ options.title = arg1;
+ } else if (!isString(arg1)) {
+ // 如果第二个参数是对象,则合并到选项中
+ Object.assign(options, arg1);
+ }
+ }
+
+ if (arg2 && !isString(arg2)) {
+ Object.assign(options, arg2);
+ }
+ // 创建容器元素
+ const container = document.createElement('div');
+ document.body.append(container);
+
+ // 创建一个引用,用于在回调中访问实例
+ const alertRef = { container, instance: null as any };
+
+ const props: AlertProps & Recordable = {
+ onClosed: (isConfirm: boolean) => {
+ // 移除组件实例以及创建的所有dom(恢复页面到打开前的状态)
+ // 从alerts数组中移除该实例
+ alerts.value = alerts.value.filter((item) => item !== alertRef);
+
+ // 从DOM中移除容器
+ render(null, container);
+ if (container.parentNode) {
+ container.remove();
+ }
+
+ // 解析 Promise,传递用户操作结果
+ if (isConfirm) {
+ resolve();
+ } else {
+ reject(new Error('dialog cancelled'));
+ }
+ },
+ ...options,
+ open: true,
+ title: options.title ?? $t.value('prompt'),
+ };
+
+ // 创建Alert组件的VNode
+ const vnode = h(Alert, props);
+
+ // 渲染组件到容器
+ render(vnode, container);
+
+ // 保存组件实例引用
+ alertRef.instance = vnode.component?.proxy as Component;
+
+ // 将实例和容器添加到alerts数组中
+ alerts.value.push(alertRef);
+ });
+}
+
+export function vbenConfirm(options: AlertProps): Promise;
+export function vbenConfirm(
+ message: string,
+ options?: Partial,
+): Promise;
+export function vbenConfirm(
+ message: string,
+ title?: string,
+ options?: Partial,
+): Promise;
+
+export function vbenConfirm(
+ arg0: AlertProps | string,
+ arg1?: Partial | string,
+ arg2?: Partial,
+): Promise {
+ const defaultProps: Partial = {
+ showCancel: true,
+ };
+ if (!arg1) {
+ return isString(arg0)
+ ? vbenAlert(arg0, defaultProps)
+ : vbenAlert({ ...defaultProps, ...arg0 });
+ } else if (!arg2) {
+ return isString(arg1)
+ ? vbenAlert(arg0 as string, arg1, defaultProps)
+ : vbenAlert(arg0 as string, { ...defaultProps, ...arg1 });
+ }
+ return vbenAlert(arg0 as string, arg1 as string, {
+ ...defaultProps,
+ ...arg2,
+ });
+}
+
+export async function vbenPrompt(
+ options: Omit & {
+ beforeClose?: (
+ val: T,
+ ) => boolean | Promise | undefined;
+ component?: Component;
+ componentProps?: Recordable;
+ defaultValue?: T;
+ modelPropName?: string;
+ },
+): Promise {
+ const {
+ component: _component,
+ componentProps: _componentProps,
+ content,
+ defaultValue,
+ modelPropName: _modelPropName,
+ ...delegated
+ } = options;
+ const contents: Component[] = [];
+ const modelValue = ref(defaultValue);
+ if (isString(content)) {
+ contents.push(h('span', content));
+ } else {
+ contents.push(content);
+ }
+ const componentProps = _componentProps || {};
+ const modelPropName = _modelPropName || 'modelValue';
+ componentProps[modelPropName] = modelValue.value;
+ componentProps[`onUpdate:${modelPropName}`] = (val: any) => {
+ modelValue.value = val;
+ };
+ const componentRef = h(_component || Input, componentProps);
+ contents.push(componentRef);
+ const props: AlertProps & Recordable = {
+ ...delegated,
+ async beforeClose() {
+ if (delegated.beforeClose) {
+ return await delegated.beforeClose(modelValue.value);
+ }
+ },
+ content: h(
+ 'div',
+ { class: 'flex flex-col gap-2' },
+ { default: () => contents },
+ ),
+ onOpened() {
+ // 组件挂载完成后,自动聚焦到输入组件
+ if (
+ componentRef.component?.exposed &&
+ isFunction(componentRef.component.exposed.focus)
+ ) {
+ componentRef.component.exposed.focus();
+ } else if (componentRef.el && isFunction(componentRef.el.focus)) {
+ componentRef.el.focus();
+ }
+ },
+ };
+ await vbenConfirm(props);
+ return modelValue.value;
+}
+
+export function clearAllAlerts() {
+ alerts.value.forEach((alert) => {
+ // 从DOM中移除容器
+ render(null, alert.container);
+ if (alert.container.parentNode) {
+ alert.container.remove();
+ }
+ });
+ alerts.value = [];
+}
diff --git a/packages/@core/ui-kit/popup-ui/src/alert/alert.ts b/packages/@core/ui-kit/popup-ui/src/alert/alert.ts
new file mode 100644
index 00000000..97002f70
--- /dev/null
+++ b/packages/@core/ui-kit/popup-ui/src/alert/alert.ts
@@ -0,0 +1,28 @@
+import type { Component } from 'vue';
+
+export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
+
+export type AlertProps = {
+ /** 关闭前的回调,如果返回false,则终止关闭 */
+ beforeClose?: () => boolean | Promise | undefined;
+ /** 边框 */
+ bordered?: boolean;
+ /** 取消按钮的标题 */
+ cancelText?: string;
+ /** 是否居中显示 */
+ centered?: boolean;
+ /** 确认按钮的标题 */
+ confirmText?: string;
+ /** 弹窗容器的额外样式 */
+ containerClass?: string;
+ /** 弹窗提示内容 */
+ content: Component | string;
+ /** 弹窗内容的额外样式 */
+ contentClass?: string;
+ /** 弹窗的图标(在标题的前面) */
+ icon?: Component | IconType;
+ /** 是否显示取消按钮 */
+ showCancel?: boolean;
+ /** 弹窗标题 */
+ title?: string;
+};
diff --git a/packages/@core/ui-kit/popup-ui/src/alert/alert.vue b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
new file mode 100644
index 00000000..7cc54ff5
--- /dev/null
+++ b/packages/@core/ui-kit/popup-ui/src/alert/alert.vue
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+
+
{{ $t(title) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ cancelText || $t('cancel') }}
+
+
+
+
+ {{ confirmText || $t('confirm') }}
+
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/popup-ui/src/alert/index.ts b/packages/@core/ui-kit/popup-ui/src/alert/index.ts
new file mode 100644
index 00000000..af8f424f
--- /dev/null
+++ b/packages/@core/ui-kit/popup-ui/src/alert/index.ts
@@ -0,0 +1,9 @@
+export * from './alert';
+
+export { default as Alert } from './alert.vue';
+export {
+ vbenAlert as alert,
+ clearAllAlerts,
+ vbenConfirm as confirm,
+ vbenPrompt as prompt,
+} from './AlertBuilder';
diff --git a/packages/@core/ui-kit/popup-ui/src/index.ts b/packages/@core/ui-kit/popup-ui/src/index.ts
index 56e7ade5..7b9c47f4 100644
--- a/packages/@core/ui-kit/popup-ui/src/index.ts
+++ b/packages/@core/ui-kit/popup-ui/src/index.ts
@@ -1,2 +1,3 @@
+export * from './alert';
export * from './drawer';
export * from './modal';
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 87f9769b..4c008ea8 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
@@ -3,7 +3,7 @@ import type { Component, PropType } from 'vue';
import { defineComponent, h } from 'vue';
-import { isFunction, isObject } from '@vben-core/shared/utils';
+import { isFunction, isObject, isString } from '@vben-core/shared/utils';
export default defineComponent({
name: 'RenderContent',
@@ -14,6 +14,10 @@ export default defineComponent({
| undefined,
type: [Object, String, Function],
},
+ renderBr: {
+ default: false,
+ type: Boolean,
+ },
},
setup(props, { attrs, slots }) {
return () => {
@@ -24,7 +28,20 @@ export default defineComponent({
(isObject(props.content) || isFunction(props.content)) &&
props.content !== null;
if (!isComponent) {
- return props.content;
+ 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'));
+ }
+ }
+ return result;
+ } else {
+ return props.content;
+ }
}
return h(props.content as never, {
...attrs,
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialog.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialog.vue
new file mode 100644
index 00000000..f2052887
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialog.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogAction.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogAction.vue
new file mode 100644
index 00000000..6b2d75c8
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogAction.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogCancel.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogCancel.vue
new file mode 100644
index 00000000..9f1d2903
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogCancel.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogContent.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogContent.vue
new file mode 100644
index 00000000..b14dc0ec
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogContent.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+ emits('close')"
+ />
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogDescription.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogDescription.vue
new file mode 100644
index 00000000..da231989
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogDescription.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogOverlay.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogOverlay.vue
new file mode 100644
index 00000000..5673319d
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogOverlay.vue
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogTitle.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogTitle.vue
new file mode 100644
index 00000000..01d4759f
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogTitle.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/index.ts b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/index.ts
new file mode 100644
index 00000000..ef14f2d5
--- /dev/null
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/index.ts
@@ -0,0 +1,6 @@
+export { default as AlertDialog } from './AlertDialog.vue';
+export { default as AlertDialogAction } from './AlertDialogAction.vue';
+export { default as AlertDialogCancel } from './AlertDialogCancel.vue';
+export { default as AlertDialogContent } from './AlertDialogContent.vue';
+export { default as AlertDialogDescription } from './AlertDialogDescription.vue';
+export { default as AlertDialogTitle } from './AlertDialogTitle.vue';
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/index.ts b/packages/@core/ui-kit/shadcn-ui/src/ui/index.ts
index 5c9e8046..1be71f86 100644
--- a/packages/@core/ui-kit/shadcn-ui/src/ui/index.ts
+++ b/packages/@core/ui-kit/shadcn-ui/src/ui/index.ts
@@ -1,4 +1,5 @@
export * from './accordion';
+export * from './alert-dialog';
export * from './avatar';
export * from './badge';
export * from './breadcrumb';
diff --git a/playground/src/views/examples/modal/index.vue b/playground/src/views/examples/modal/index.vue
index 690f62e7..23cfab12 100644
--- a/playground/src/views/examples/modal/index.vue
+++ b/playground/src/views/examples/modal/index.vue
@@ -1,7 +1,16 @@
@@ -195,6 +259,14 @@ function openFormModal() {
+
+ 通过快捷方法创建动态提示弹窗,适合一些轻量的提示和确认、输入等
+
+
+
+
+
+