Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
44ad2c5f8d
@ -168,6 +168,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||||||
link: 'common-ui/vben-api-component',
|
link: 'common-ui/vben-api-component',
|
||||||
text: 'ApiComponent Api组件包装器',
|
text: 'ApiComponent Api组件包装器',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: 'common-ui/vben-alert',
|
||||||
|
text: 'Alert 轻量提示框',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
link: 'common-ui/vben-modal',
|
link: 'common-ui/vben-modal',
|
||||||
text: 'Modal 模态框',
|
text: 'Modal 模态框',
|
||||||
|
101
docs/src/components/common-ui/vben-alert.md
Normal file
101
docs/src/components/common-ui/vben-alert.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vben Alert 轻量提示框
|
||||||
|
|
||||||
|
框架提供的一些用于轻量提示的弹窗,仅使用js代码即可快速动态创建提示而不需要在template写任何代码。
|
||||||
|
|
||||||
|
::: info 应用场景
|
||||||
|
|
||||||
|
Alert提供的功能与Modal类似,但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求,请使用VbenModal
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: tip README
|
||||||
|
|
||||||
|
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
使用 `alert` 创建只有一个确认按钮的提示框。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-alert/alert" />
|
||||||
|
|
||||||
|
使用 `confirm` 创建有确认和取消按钮的提示框。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-alert/confirm" />
|
||||||
|
|
||||||
|
使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-alert/prompt" />
|
||||||
|
|
||||||
|
## 类型说明
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/** 预置的图标类型 */
|
||||||
|
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
|
export type AlertProps = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: () => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 边框 */
|
||||||
|
bordered?: boolean;
|
||||||
|
/** 取消按钮的标题 */
|
||||||
|
cancelText?: string;
|
||||||
|
/** 是否居中显示 */
|
||||||
|
centered?: boolean;
|
||||||
|
/** 确认按钮的标题 */
|
||||||
|
confirmText?: string;
|
||||||
|
/** 弹窗容器的额外样式 */
|
||||||
|
containerClass?: string;
|
||||||
|
/** 弹窗提示内容 */
|
||||||
|
content: Component | string;
|
||||||
|
/** 弹窗内容的额外样式 */
|
||||||
|
contentClass?: string;
|
||||||
|
/** 弹窗的图标(在标题的前面) */
|
||||||
|
icon?: Component | IconType;
|
||||||
|
/** 是否显示取消按钮 */
|
||||||
|
showCancel?: boolean;
|
||||||
|
/** 弹窗标题 */
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 函数签名
|
||||||
|
* alert和confirm的函数签名相同。
|
||||||
|
* confirm默认会显示取消按钮,而alert默认只有一个按钮
|
||||||
|
* */
|
||||||
|
export function alert(options: AlertProps): Promise<void>;
|
||||||
|
export function alert(
|
||||||
|
message: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
export function alert(
|
||||||
|
message: string,
|
||||||
|
title?: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出输入框的函数签名。
|
||||||
|
* 参数beforeClose会传入用户当前输入的值
|
||||||
|
* component指定接受用户输入的组件,默认为Input
|
||||||
|
* componentProps 为输入组件设置的属性数据
|
||||||
|
* defaultValue 默认的值
|
||||||
|
* modelPropName 输入组件的值属性名称。默认为modelValue
|
||||||
|
*/
|
||||||
|
export async function prompt<T = any>(
|
||||||
|
options: Omit<AlertProps, 'beforeClose'> & {
|
||||||
|
beforeClose?: (
|
||||||
|
val: T,
|
||||||
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
component?: Component;
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
defaultValue?: T;
|
||||||
|
modelPropName?: string;
|
||||||
|
},
|
||||||
|
): Promise<T | undefined>;
|
||||||
|
```
|
31
docs/src/demos/vben-alert/alert/index.vue
Normal file
31
docs/src/demos/vben-alert/alert/index.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { alert, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Empty } from 'ant-design-vue';
|
||||||
|
|
||||||
|
function showAlert() {
|
||||||
|
alert('This is an alert message');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showIconAlert() {
|
||||||
|
alert({
|
||||||
|
content: 'This is an alert message with icon',
|
||||||
|
icon: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCustomAlert() {
|
||||||
|
alert({
|
||||||
|
content: h(Empty, { description: '什么都没有' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<VbenButton @click="showAlert">Alert</VbenButton>
|
||||||
|
<VbenButton @click="showIconAlert">Alert With Icon</VbenButton>
|
||||||
|
<VbenButton @click="showCustomAlert">Alert With Custom Content</VbenButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
39
docs/src/demos/vben-alert/confirm/index.vue
Normal file
39
docs/src/demos/vben-alert/confirm/index.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
function showConfirm() {
|
||||||
|
confirm('This is an alert message')
|
||||||
|
.then(() => {
|
||||||
|
alert('Confirmed');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Canceled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showIconConfirm() {
|
||||||
|
confirm({
|
||||||
|
content: 'This is an alert message with icon',
|
||||||
|
icon: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAsyncConfirm() {
|
||||||
|
confirm({
|
||||||
|
beforeClose() {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
},
|
||||||
|
content: 'This is an alert message with async confirm',
|
||||||
|
icon: 'success',
|
||||||
|
}).then(() => {
|
||||||
|
alert('Confirmed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
||||||
|
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
||||||
|
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
41
docs/src/demos/vben-alert/prompt/index.vue
Normal file
41
docs/src/demos/vben-alert/prompt/index.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { VbenSelect } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
function showPrompt() {
|
||||||
|
prompt({
|
||||||
|
content: '请输入一些东西',
|
||||||
|
})
|
||||||
|
.then((val) => {
|
||||||
|
alert(`已收到你的输入:${val}`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Canceled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSelectPrompt() {
|
||||||
|
prompt({
|
||||||
|
component: VbenSelect,
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: 'Option 1', value: 'option1' },
|
||||||
|
{ label: 'Option 2', value: 'option2' },
|
||||||
|
{ label: 'Option 3', value: 'option3' },
|
||||||
|
],
|
||||||
|
placeholder: '请选择',
|
||||||
|
},
|
||||||
|
content: 'This is an alert message with icon',
|
||||||
|
icon: 'question',
|
||||||
|
}).then((val) => {
|
||||||
|
alert(`你选择的是${val}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||||
|
<VbenButton @click="showSelectPrompt">Confirm With Select</VbenButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -150,6 +150,73 @@ To run the `docs` application:
|
|||||||
pnpm dev:docs
|
pnpm dev:docs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Distinguishing Build Environments
|
||||||
|
|
||||||
|
In actual business development, multiple environments are usually distinguished during the build process, such as the test environment `test` and the production environment `build`.
|
||||||
|
|
||||||
|
At this point, you can modify three files and add corresponding script configurations to distinguish between production environments.
|
||||||
|
|
||||||
|
Take the addition of the test environment `test` to `@vben/web-antd` as an example:
|
||||||
|
|
||||||
|
- `apps\web-antd\package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build:prod": "pnpm vite build --mode production",
|
||||||
|
"build:test": "pnpm vite build --mode test",
|
||||||
|
"build:analyze": "pnpm vite build --mode analyze",
|
||||||
|
"dev": "pnpm vite --mode development",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the command `"build:test"` and change the original `"build"` to `"build:prod"` to avoid building packages for two environments simultaneously.
|
||||||
|
|
||||||
|
- `package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
|
||||||
|
"build:analyze": "turbo build:analyze",
|
||||||
|
"build:antd": "pnpm run build --filter=@vben/web-antd",
|
||||||
|
"build-test:antd": "pnpm run build --filter=@vben/web-antd build:test",
|
||||||
|
|
||||||
|
······
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the command to build the test environment in the root directory `package.json`.
|
||||||
|
|
||||||
|
- `turbo.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": [
|
||||||
|
"dist/**",
|
||||||
|
"dist.zip",
|
||||||
|
".vitepress/dist.zip",
|
||||||
|
".vitepress/dist/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"build-test:antd": {
|
||||||
|
"dependsOn": ["@vben/web-antd#build:test"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"@vben/web-antd#build:test": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
······
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the relevant dependent commands in `turbo.json`.
|
||||||
|
|
||||||
## Public Static Resources
|
## Public Static Resources
|
||||||
|
|
||||||
If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.
|
If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.
|
||||||
|
@ -150,6 +150,73 @@ pnpm dev:ele
|
|||||||
pnpm dev:docs
|
pnpm dev:docs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 区分构建环境
|
||||||
|
|
||||||
|
在实际的业务开发中,通常会在构建时区分多种环境,如测试环境`test`、生产环境`build`等。
|
||||||
|
|
||||||
|
此时可以修改三个文件,在其中增加对应的脚本配置来达到区分生产环境的效果。
|
||||||
|
|
||||||
|
以`@vben/web-antd`添加测试环境`test`为例:
|
||||||
|
|
||||||
|
- `apps\web-antd\package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build:prod": "pnpm vite build --mode production",
|
||||||
|
"build:test": "pnpm vite build --mode test",
|
||||||
|
"build:analyze": "pnpm vite build --mode analyze",
|
||||||
|
"dev": "pnpm vite --mode development",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
增加命令`"build:test"`, 并将原`"build"`改为`"build:prod"`以避免同时构建两个环境的包。
|
||||||
|
|
||||||
|
- `package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
|
||||||
|
"build:analyze": "turbo build:analyze",
|
||||||
|
"build:antd": "pnpm run build --filter=@vben/web-antd",
|
||||||
|
"build-test:antd": "pnpm run build --filter=@vben/web-antd build:test",
|
||||||
|
|
||||||
|
······
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在根目录`package.json`中加入构建测试环境的命令
|
||||||
|
|
||||||
|
- `turbo.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": [
|
||||||
|
"dist/**",
|
||||||
|
"dist.zip",
|
||||||
|
".vitepress/dist.zip",
|
||||||
|
".vitepress/dist/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"build-test:antd": {
|
||||||
|
"dependsOn": ["@vben/web-antd#build:test"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"@vben/web-antd#build:test": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
······
|
||||||
|
```
|
||||||
|
|
||||||
|
在`turbo.json`中加入相关依赖的命令
|
||||||
|
|
||||||
## 公共静态资源
|
## 公共静态资源
|
||||||
|
|
||||||
项目中需要使用到的公共静态资源,如:图片、静态HTML等,需要在开发中通过 `src="/xxx.png"` 直接引入的。
|
项目中需要使用到的公共静态资源,如:图片、静态HTML等,需要在开发中通过 `src="/xxx.png"` 直接引入的。
|
||||||
|
@ -46,3 +46,47 @@ async function getVersionTag() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 替换为第三方库检查更新方式
|
||||||
|
|
||||||
|
如果需要通过其他方式检查更新,例如使用其他版本控制方式(chunkHash、version.json)、使用`Web Worker`在后台轮询更新、自定义检查更新时机(不使用轮询),你可以通过JS库`version-polling`来实现。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add version-polling
|
||||||
|
```
|
||||||
|
|
||||||
|
以`apps/web-antd`项目为例,在项目入口文件`main.ts`或者`app.vue`添加以下代码
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { Button, notification } from 'ant-design-vue';
|
||||||
|
import { createVersionPolling } from 'version-polling';
|
||||||
|
|
||||||
|
createVersionPolling({
|
||||||
|
silent: import.meta.env.MODE === 'development', // 开发环境下不检测
|
||||||
|
onUpdate: (self) => {
|
||||||
|
const key = `open${Date.now()}`;
|
||||||
|
notification.info({
|
||||||
|
message: '提示',
|
||||||
|
description: '检测到网页有更新, 是否刷新页面加载最新版本?',
|
||||||
|
btn: () =>
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => {
|
||||||
|
notification.close(key);
|
||||||
|
self.onRefresh();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ default: () => '刷新' },
|
||||||
|
),
|
||||||
|
key,
|
||||||
|
duration: null,
|
||||||
|
placement: 'bottomRight',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
@ -15,8 +15,10 @@ export {
|
|||||||
ChevronsLeft,
|
ChevronsLeft,
|
||||||
ChevronsRight,
|
ChevronsRight,
|
||||||
Circle,
|
Circle,
|
||||||
|
CircleAlert,
|
||||||
CircleCheckBig,
|
CircleCheckBig,
|
||||||
CircleHelp,
|
CircleHelp,
|
||||||
|
CircleX,
|
||||||
Copy,
|
Copy,
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
|
@ -6,6 +6,7 @@ export const messages: Record<Locale, Record<string, string>> = {
|
|||||||
collapse: 'Collapse',
|
collapse: 'Collapse',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
expand: 'Expand',
|
expand: 'Expand',
|
||||||
|
prompt: 'Prompt',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
submit: 'Submit',
|
submit: 'Submit',
|
||||||
},
|
},
|
||||||
@ -14,6 +15,7 @@ export const messages: Record<Locale, Record<string, string>> = {
|
|||||||
collapse: '收起',
|
collapse: '收起',
|
||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
expand: '展开',
|
expand: '展开',
|
||||||
|
prompt: '提示',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
submit: '提交',
|
submit: '提交',
|
||||||
},
|
},
|
||||||
|
203
packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts
Normal file
203
packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts
Normal file
@ -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<Array<{ container: HTMLElement; instance: Component }>>([]);
|
||||||
|
|
||||||
|
const { $t } = useSimpleLocale();
|
||||||
|
|
||||||
|
export function vbenAlert(options: AlertProps): Promise<void>;
|
||||||
|
export function vbenAlert(
|
||||||
|
message: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
export function vbenAlert(
|
||||||
|
message: string,
|
||||||
|
title?: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
export function vbenAlert(
|
||||||
|
arg0: AlertProps | string,
|
||||||
|
arg1?: Partial<AlertProps> | string,
|
||||||
|
arg2?: Partial<AlertProps>,
|
||||||
|
): Promise<void> {
|
||||||
|
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<any> = {
|
||||||
|
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<void>;
|
||||||
|
export function vbenConfirm(
|
||||||
|
message: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
export function vbenConfirm(
|
||||||
|
message: string,
|
||||||
|
title?: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
export function vbenConfirm(
|
||||||
|
arg0: AlertProps | string,
|
||||||
|
arg1?: Partial<AlertProps> | string,
|
||||||
|
arg2?: Partial<AlertProps>,
|
||||||
|
): Promise<void> {
|
||||||
|
const defaultProps: Partial<AlertProps> = {
|
||||||
|
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<T = any>(
|
||||||
|
options: Omit<AlertProps, 'beforeClose'> & {
|
||||||
|
beforeClose?: (
|
||||||
|
val: T,
|
||||||
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
component?: Component;
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
defaultValue?: T;
|
||||||
|
modelPropName?: string;
|
||||||
|
},
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
const {
|
||||||
|
component: _component,
|
||||||
|
componentProps: _componentProps,
|
||||||
|
content,
|
||||||
|
defaultValue,
|
||||||
|
modelPropName: _modelPropName,
|
||||||
|
...delegated
|
||||||
|
} = options;
|
||||||
|
const contents: Component[] = [];
|
||||||
|
const modelValue = ref<T | undefined>(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<any> = {
|
||||||
|
...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 = [];
|
||||||
|
}
|
28
packages/@core/ui-kit/popup-ui/src/alert/alert.ts
Normal file
28
packages/@core/ui-kit/popup-ui/src/alert/alert.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
|
export type AlertProps = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: () => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 边框 */
|
||||||
|
bordered?: boolean;
|
||||||
|
/** 取消按钮的标题 */
|
||||||
|
cancelText?: string;
|
||||||
|
/** 是否居中显示 */
|
||||||
|
centered?: boolean;
|
||||||
|
/** 确认按钮的标题 */
|
||||||
|
confirmText?: string;
|
||||||
|
/** 弹窗容器的额外样式 */
|
||||||
|
containerClass?: string;
|
||||||
|
/** 弹窗提示内容 */
|
||||||
|
content: Component | string;
|
||||||
|
/** 弹窗内容的额外样式 */
|
||||||
|
contentClass?: string;
|
||||||
|
/** 弹窗的图标(在标题的前面) */
|
||||||
|
icon?: Component | IconType;
|
||||||
|
/** 是否显示取消按钮 */
|
||||||
|
showCancel?: boolean;
|
||||||
|
/** 弹窗标题 */
|
||||||
|
title?: string;
|
||||||
|
};
|
181
packages/@core/ui-kit/popup-ui/src/alert/alert.vue
Normal file
181
packages/@core/ui-kit/popup-ui/src/alert/alert.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import type { AlertProps } from './alert';
|
||||||
|
|
||||||
|
import { computed, h, nextTick, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
|
import {
|
||||||
|
CircleAlert,
|
||||||
|
CircleCheckBig,
|
||||||
|
CircleHelp,
|
||||||
|
CircleX,
|
||||||
|
Info,
|
||||||
|
X,
|
||||||
|
} from '@vben-core/icons';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
VbenButton,
|
||||||
|
VbenLoading,
|
||||||
|
VbenRenderContent,
|
||||||
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<AlertProps>(), {
|
||||||
|
bordered: true,
|
||||||
|
centered: true,
|
||||||
|
containerClass: 'w-[520px]',
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['closed', 'confirm', 'opened']);
|
||||||
|
const open = defineModel<boolean>('open', { default: false });
|
||||||
|
const { $t } = useSimpleLocale();
|
||||||
|
const components = globalShareState.getComponents();
|
||||||
|
const isConfirm = ref(false);
|
||||||
|
watch(open, async (val) => {
|
||||||
|
await nextTick();
|
||||||
|
if (val) {
|
||||||
|
isConfirm.value = false;
|
||||||
|
} else {
|
||||||
|
emits('closed', isConfirm.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const getIconRender = computed(() => {
|
||||||
|
let iconRender: Component | null = null;
|
||||||
|
if (props.icon) {
|
||||||
|
if (typeof props.icon === 'string') {
|
||||||
|
switch (props.icon) {
|
||||||
|
case 'error': {
|
||||||
|
iconRender = h(CircleX, {
|
||||||
|
style: { color: 'hsl(var(--destructive))' },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'info': {
|
||||||
|
iconRender = h(Info, { style: { color: 'hsl(var(--info))' } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'question': {
|
||||||
|
iconRender = CircleHelp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'success': {
|
||||||
|
iconRender = h(CircleCheckBig, {
|
||||||
|
style: { color: 'hsl(var(--success))' },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'warning': {
|
||||||
|
iconRender = h(CircleAlert, {
|
||||||
|
style: { color: 'hsl(var(--warning))' },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
iconRender = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iconRender = props.icon ?? null;
|
||||||
|
}
|
||||||
|
return iconRender;
|
||||||
|
});
|
||||||
|
function handleConfirm() {
|
||||||
|
isConfirm.value = true;
|
||||||
|
emits('confirm');
|
||||||
|
}
|
||||||
|
function handleCancel() {
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
const loading = ref(false);
|
||||||
|
async function handleOpenChange(val: boolean) {
|
||||||
|
if (!val && props.beforeClose) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await props.beforeClose();
|
||||||
|
if (res !== false) {
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
open.value = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<AlertDialog :open="open" @update:open="handleOpenChange">
|
||||||
|
<AlertDialogContent
|
||||||
|
:open="open"
|
||||||
|
:centered="centered"
|
||||||
|
@opened="emits('opened')"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
containerClass,
|
||||||
|
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
|
||||||
|
{
|
||||||
|
'border-border border': bordered,
|
||||||
|
'shadow-3xl': !bordered,
|
||||||
|
'top-1/2 !-translate-y-1/2': centered,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div :class="cn('relative flex-1 overflow-y-auto p-3', contentClass)">
|
||||||
|
<AlertDialogTitle v-if="title">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component :is="getIconRender" class="mr-2" />
|
||||||
|
<span class="flex-auto">{{ $t(title) }}</span>
|
||||||
|
<AlertDialogCancel v-if="showCancel">
|
||||||
|
<VbenButton
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="rounded-full"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
<X class="text-muted-foreground size-4" />
|
||||||
|
</VbenButton>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
</div>
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
<div class="m-4 mb-6 min-h-[30px]">
|
||||||
|
<VbenRenderContent :content="content" render-br />
|
||||||
|
</div>
|
||||||
|
<VbenLoading v-if="loading" :spinning="loading" />
|
||||||
|
</AlertDialogDescription>
|
||||||
|
<div class="flex justify-end gap-x-2">
|
||||||
|
<AlertDialogCancel
|
||||||
|
v-if="showCancel"
|
||||||
|
@click="handleCancel"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="components.DefaultButton || VbenButton"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
{{ cancelText || $t('cancel') }}
|
||||||
|
</component>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction @click="handleConfirm">
|
||||||
|
<component
|
||||||
|
:is="components.PrimaryButton || VbenButton"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
{{ confirmText || $t('confirm') }}
|
||||||
|
</component>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</template>
|
9
packages/@core/ui-kit/popup-ui/src/alert/index.ts
Normal file
9
packages/@core/ui-kit/popup-ui/src/alert/index.ts
Normal file
@ -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';
|
@ -1,2 +1,3 @@
|
|||||||
|
export * from './alert';
|
||||||
export * from './drawer';
|
export * from './drawer';
|
||||||
export * from './modal';
|
export * from './modal';
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Component, PropType } from 'vue';
|
import type { Component, PropType } from 'vue';
|
||||||
|
|
||||||
import { isFunction, isObject } from '@vben-core/shared/utils';
|
|
||||||
import { defineComponent, h } from 'vue';
|
import { defineComponent, h } from 'vue';
|
||||||
|
|
||||||
|
import { isFunction, isObject, isString } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RenderContent',
|
name: 'RenderContent',
|
||||||
props: {
|
props: {
|
||||||
@ -13,6 +14,10 @@ export default defineComponent({
|
|||||||
| undefined,
|
| undefined,
|
||||||
type: [Object, String, Function],
|
type: [Object, String, Function],
|
||||||
},
|
},
|
||||||
|
renderBr: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props, { attrs, slots }) {
|
setup(props, { attrs, slots }) {
|
||||||
return () => {
|
return () => {
|
||||||
@ -23,7 +28,20 @@ export default defineComponent({
|
|||||||
(isObject(props.content) || isFunction(props.content)) &&
|
(isObject(props.content) || isFunction(props.content)) &&
|
||||||
props.content !== null;
|
props.content !== null;
|
||||||
if (!isComponent) {
|
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, {
|
return h(props.content as never, {
|
||||||
...attrs,
|
...attrs,
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogEmits, AlertDialogProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { AlertDialogRoot, useForwardPropsEmits } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogProps>();
|
||||||
|
const emits = defineEmits<AlertDialogEmits>();
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogRoot v-bind="forwarded">
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogRoot>
|
||||||
|
</template>
|
@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogActionProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { AlertDialogAction } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogActionProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogAction v-bind="props">
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</template>
|
@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogCancelProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { AlertDialogCancel } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogCancelProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogCancel v-bind="props">
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
</template>
|
@ -0,0 +1,91 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type {
|
||||||
|
AlertDialogContentEmits,
|
||||||
|
AlertDialogContentProps,
|
||||||
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
import type { ClassType } from '@vben-core/typings';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogPortal,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
import AlertDialogOverlay from './AlertDialogOverlay.vue';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<
|
||||||
|
AlertDialogContentProps & {
|
||||||
|
centered?: boolean;
|
||||||
|
class?: ClassType;
|
||||||
|
modal?: boolean;
|
||||||
|
open?: boolean;
|
||||||
|
overlayBlur?: number;
|
||||||
|
zIndex?: number;
|
||||||
|
}
|
||||||
|
>(),
|
||||||
|
{ modal: true },
|
||||||
|
);
|
||||||
|
const emits = defineEmits<
|
||||||
|
AlertDialogContentEmits & { close: []; closed: []; opened: [] }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, modal: _modal, open: _open, ...delegated } = props;
|
||||||
|
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
|
const contentRef = ref<InstanceType<typeof AlertDialogContent> | null>(null);
|
||||||
|
function onAnimationEnd(event: AnimationEvent) {
|
||||||
|
// 只有在 contentRef 的动画结束时才触发 opened/closed 事件
|
||||||
|
if (event.target === contentRef.value?.$el) {
|
||||||
|
if (props.open) {
|
||||||
|
emits('opened');
|
||||||
|
} else {
|
||||||
|
emits('closed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
getContentRef: () => contentRef.value,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<Transition name="fade">
|
||||||
|
<AlertDialogOverlay
|
||||||
|
v-if="open && modal"
|
||||||
|
:style="{
|
||||||
|
...(zIndex ? { zIndex } : {}),
|
||||||
|
position: 'fixed',
|
||||||
|
backdropFilter:
|
||||||
|
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||||
|
}"
|
||||||
|
@click="() => emits('close')"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<AlertDialogContent
|
||||||
|
ref="contentRef"
|
||||||
|
:style="{ ...(zIndex ? { zIndex } : {}), position: 'fixed' }"
|
||||||
|
@animationend="onAnimationEnd"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
</template>
|
@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AlertDialogDescriptionProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import { AlertDialogDescription, useForwardProps } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogDescriptionProps & { class?: any }>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props;
|
||||||
|
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogDescription
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</template>
|
@ -0,0 +1,8 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useScrollLock } from '@vben-core/composables';
|
||||||
|
|
||||||
|
useScrollLock();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="bg-overlay z-popup inset-0"></div>
|
||||||
|
</template>
|
@ -0,0 +1,30 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogTitleProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import { AlertDialogTitle, useForwardProps } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogTitleProps & { class?: any }>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props;
|
||||||
|
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogTitle
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="
|
||||||
|
cn('text-lg font-semibold leading-none tracking-tight', props.class)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogTitle>
|
||||||
|
</template>
|
@ -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';
|
@ -1,4 +1,5 @@
|
|||||||
export * from './accordion';
|
export * from './accordion';
|
||||||
|
export * from './alert-dialog';
|
||||||
export * from './avatar';
|
export * from './avatar';
|
||||||
export * from './badge';
|
export * from './badge';
|
||||||
export * from './breadcrumb';
|
export * from './breadcrumb';
|
||||||
|
@ -33,7 +33,7 @@ async function generateRoutesByBackend(
|
|||||||
return routes;
|
return routes;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return [];
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
import { Button, Card, Flex } from 'ant-design-vue';
|
import {
|
||||||
|
alert,
|
||||||
|
clearAllAlerts,
|
||||||
|
confirm,
|
||||||
|
Page,
|
||||||
|
prompt,
|
||||||
|
useVbenModal,
|
||||||
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Card, Flex, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import DocButton from '../doc-button.vue';
|
import DocButton from '../doc-button.vue';
|
||||||
import AutoHeightDemo from './auto-height-demo.vue';
|
import AutoHeightDemo from './auto-height-demo.vue';
|
||||||
@ -103,6 +112,61 @@ function openFormModal() {
|
|||||||
})
|
})
|
||||||
.open();
|
.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAlert() {
|
||||||
|
alert({
|
||||||
|
content: '这是一个弹窗',
|
||||||
|
icon: 'success',
|
||||||
|
}).then(() => {
|
||||||
|
message.info('用户关闭了弹窗');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// 清除所有弹窗
|
||||||
|
clearAllAlerts();
|
||||||
|
});
|
||||||
|
|
||||||
|
function openConfirm() {
|
||||||
|
confirm({
|
||||||
|
beforeClose() {
|
||||||
|
// 这里可以做一些异步操作
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(true);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
content: '这是一个确认弹窗',
|
||||||
|
icon: 'question',
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
message.success('用户确认了操作');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.error('用户取消了操作');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openPrompt() {
|
||||||
|
prompt<string>({
|
||||||
|
async beforeClose(val) {
|
||||||
|
if (val === '芝士') {
|
||||||
|
message.error('不能吃芝士');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentProps: { placeholder: '不能吃芝士...' },
|
||||||
|
content: '中午吃了什么?',
|
||||||
|
icon: 'question',
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
message.success(`用户输入了:${res}`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.error('用户取消了输入');
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -195,6 +259,14 @@ function openFormModal() {
|
|||||||
<Button type="primary" @click="openBlurModal">打开弹窗</Button>
|
<Button type="primary" @click="openBlurModal">打开弹窗</Button>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card class="w-[300px]" title="轻量提示弹窗">
|
||||||
|
<p>通过快捷方法创建动态提示弹窗,适合一些轻量的提示和确认、输入等</p>
|
||||||
|
<template #actions>
|
||||||
|
<Button type="primary" @click="openAlert">Alert</Button>
|
||||||
|
<Button type="primary" @click="openConfirm">Confirm</Button>
|
||||||
|
<Button type="primary" @click="openPrompt">Prompt</Button>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user