committed by
GitHub
969 changed files with 46146 additions and 3746 deletions
@ -1 +1 @@ |
|||
20.14.0 |
|||
22.1.0 |
|||
|
|||
@ -1,2 +1 @@ |
|||
export * from './menu'; |
|||
export { useAbpConfigApi } from './useAbpConfigApi'; |
|||
|
|||
@ -1,10 +0,0 @@ |
|||
import type { RouteRecordStringComponent } from '@vben/types'; |
|||
|
|||
import { requestClient } from '#/api/request'; |
|||
|
|||
/** |
|||
* 获取用户所有菜单 |
|||
*/ |
|||
export async function getAllMenusApi() { |
|||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all'); |
|||
} |
|||
@ -1,114 +0,0 @@ |
|||
/** |
|||
* 该文件可自行根据业务逻辑进行调整 |
|||
*/ |
|||
import type { HttpResponse } from '@vben/request'; |
|||
|
|||
import { useAppConfig } from '@vben/hooks'; |
|||
import { preferences } from '@vben/preferences'; |
|||
import { |
|||
authenticateResponseInterceptor, |
|||
errorMessageResponseInterceptor, |
|||
RequestClient, |
|||
} from '@vben/request'; |
|||
import { useAccessStore } from '@vben/stores'; |
|||
|
|||
import { message } from 'ant-design-vue'; |
|||
|
|||
import { useAuthStore } from '#/store'; |
|||
|
|||
// import { refreshTokenApi } from './core';
|
|||
|
|||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
|
|||
function createRequestClient(baseURL: string) { |
|||
const client = new RequestClient({ |
|||
baseURL, |
|||
}); |
|||
|
|||
/** |
|||
* 重新认证逻辑 |
|||
*/ |
|||
async function doReAuthenticate() { |
|||
console.warn('Access token or refresh token is invalid or expired. '); |
|||
const accessStore = useAccessStore(); |
|||
const authStore = useAuthStore(); |
|||
accessStore.setAccessToken(null); |
|||
if ( |
|||
preferences.app.loginExpiredMode === 'modal' && |
|||
accessStore.isAccessChecked |
|||
) { |
|||
accessStore.setLoginExpired(true); |
|||
} else { |
|||
await authStore.logout(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 刷新token逻辑 |
|||
*/ |
|||
async function doRefreshToken() { |
|||
const accessStore = useAccessStore(); |
|||
// const resp = await refreshTokenApi();
|
|||
// const newToken = resp.data;
|
|||
// accessStore.setAccessToken(newToken);
|
|||
return String(accessStore.accessToken); |
|||
} |
|||
|
|||
function formatToken(token: null | string) { |
|||
return token ? `Bearer ${token}` : null; |
|||
} |
|||
|
|||
// 请求头处理
|
|||
client.addRequestInterceptor({ |
|||
fulfilled: async (config) => { |
|||
const accessStore = useAccessStore(); |
|||
|
|||
config.headers.Authorization = formatToken(accessStore.accessToken); |
|||
config.headers['Accept-Language'] = preferences.app.locale; |
|||
return config; |
|||
}, |
|||
}); |
|||
|
|||
// response数据解构
|
|||
client.addResponseInterceptor<HttpResponse>({ |
|||
fulfilled: (response) => { |
|||
const { data: responseData, status } = response; |
|||
|
|||
const { code, data } = responseData; |
|||
if (status >= 200 && status < 400 && code === 0) { |
|||
return data; |
|||
} |
|||
|
|||
throw Object.assign({}, response, { response }); |
|||
}, |
|||
}); |
|||
|
|||
// token过期的处理
|
|||
client.addResponseInterceptor( |
|||
authenticateResponseInterceptor({ |
|||
client, |
|||
doReAuthenticate, |
|||
doRefreshToken, |
|||
enableRefreshToken: preferences.app.enableRefreshToken, |
|||
formatToken, |
|||
}), |
|||
); |
|||
|
|||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
|||
client.addResponseInterceptor( |
|||
errorMessageResponseInterceptor((msg: string, error) => { |
|||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
|||
// 当前mock接口返回的错误字段是 error 或者 message
|
|||
const responseData = error?.response?.data ?? {}; |
|||
const errorMessage = responseData?.error ?? responseData?.message ?? ''; |
|||
// 如果没有错误信息,则会根据状态码进行提示
|
|||
message.error(errorMessage || msg); |
|||
}), |
|||
); |
|||
|
|||
return client; |
|||
} |
|||
|
|||
export const requestClient = createRequestClient(apiURL); |
|||
|
|||
export const baseRequestClient = new RequestClient({ baseURL: apiURL }); |
|||
@ -1,53 +0,0 @@ |
|||
import { useAppConfig } from '@vben/hooks'; |
|||
|
|||
import { UserManager, WebStorageStateStore } from 'oidc-client-ts'; |
|||
|
|||
const { authority, audience, clientId, clientSecret, disablePKCE } = |
|||
useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
|
|||
const userManager = new UserManager({ |
|||
authority, |
|||
client_id: clientId, |
|||
client_secret: clientSecret, |
|||
redirect_uri: `${window.location.origin}/signin-callback`, |
|||
response_type: 'code', |
|||
scope: audience, |
|||
post_logout_redirect_uri: `${window.location.origin}/`, |
|||
silent_redirect_uri: `${window.location.origin}/silent-renew.html`, |
|||
automaticSilentRenew: true, |
|||
loadUserInfo: true, |
|||
userStore: new WebStorageStateStore({ store: window.localStorage }), |
|||
disablePKCE, |
|||
}); |
|||
|
|||
export default { |
|||
async login() { |
|||
return userManager.signinRedirect(); |
|||
}, |
|||
|
|||
async logout() { |
|||
return userManager.signoutRedirect(); |
|||
}, |
|||
|
|||
async refreshToken() { |
|||
return userManager.signinSilent(); |
|||
}, |
|||
|
|||
async getAccessToken() { |
|||
const user = await userManager.getUser(); |
|||
return user?.access_token; |
|||
}, |
|||
|
|||
async isAuthenticated() { |
|||
const user = await userManager.getUser(); |
|||
return !!user && !user.expired; |
|||
}, |
|||
|
|||
async handleCallback() { |
|||
return userManager.signinRedirectCallback(); |
|||
}, |
|||
|
|||
async getUser() { |
|||
return userManager.getUser(); |
|||
}, |
|||
}; |
|||
@ -0,0 +1,28 @@ |
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
const data = ` |
|||
{ |
|||
"code": 0, |
|||
"message": "success", |
|||
"data": [ |
|||
{ |
|||
"id": 123456789012345678901234567890123456789012345678901234567890, |
|||
"name": "John Doe", |
|||
"age": 30, |
|||
"email": "john-doe@demo.com" |
|||
}, |
|||
{ |
|||
"id": 987654321098765432109876543210987654321098765432109876543210, |
|||
"name": "Jane Smith", |
|||
"age": 25, |
|||
"email": "jane@demo.com" |
|||
} |
|||
] |
|||
} |
|||
`;
|
|||
setHeader(event, 'Content-Type', 'application/json'); |
|||
return data; |
|||
}); |
|||
@ -0,0 +1,15 @@ |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { |
|||
sleep, |
|||
unAuthorizedResponse, |
|||
useResponseSuccess, |
|||
} from '~/utils/response'; |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
await sleep(600); |
|||
return useResponseSuccess(null); |
|||
}); |
|||
@ -0,0 +1,15 @@ |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { |
|||
sleep, |
|||
unAuthorizedResponse, |
|||
useResponseSuccess, |
|||
} from '~/utils/response'; |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
await sleep(1000); |
|||
return useResponseSuccess(null); |
|||
}); |
|||
@ -0,0 +1,15 @@ |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { |
|||
sleep, |
|||
unAuthorizedResponse, |
|||
useResponseSuccess, |
|||
} from '~/utils/response'; |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
await sleep(2000); |
|||
return useResponseSuccess(null); |
|||
}); |
|||
@ -0,0 +1,61 @@ |
|||
import { faker } from '@faker-js/faker'; |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; |
|||
|
|||
const formatterCN = new Intl.DateTimeFormat('zh-CN', { |
|||
timeZone: 'Asia/Shanghai', |
|||
year: 'numeric', |
|||
month: '2-digit', |
|||
day: '2-digit', |
|||
hour: '2-digit', |
|||
minute: '2-digit', |
|||
second: '2-digit', |
|||
}); |
|||
|
|||
function generateMockDataList(count: number) { |
|||
const dataList = []; |
|||
|
|||
for (let i = 0; i < count; i++) { |
|||
const dataItem: Record<string, any> = { |
|||
id: faker.string.uuid(), |
|||
pid: 0, |
|||
name: faker.commerce.department(), |
|||
status: faker.helpers.arrayElement([0, 1]), |
|||
createTime: formatterCN.format( |
|||
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }), |
|||
), |
|||
remark: faker.lorem.sentence(), |
|||
}; |
|||
if (faker.datatype.boolean()) { |
|||
dataItem.children = Array.from( |
|||
{ length: faker.number.int({ min: 1, max: 5 }) }, |
|||
() => ({ |
|||
id: faker.string.uuid(), |
|||
pid: dataItem.id, |
|||
name: faker.commerce.department(), |
|||
status: faker.helpers.arrayElement([0, 1]), |
|||
createTime: formatterCN.format( |
|||
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }), |
|||
), |
|||
remark: faker.lorem.sentence(), |
|||
}), |
|||
); |
|||
} |
|||
dataList.push(dataItem); |
|||
} |
|||
|
|||
return dataList; |
|||
} |
|||
|
|||
const mockData = generateMockDataList(10); |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
|
|||
const listData = structuredClone(mockData); |
|||
|
|||
return useResponseSuccess(listData); |
|||
}); |
|||
@ -0,0 +1,12 @@ |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { MOCK_MENU_LIST } from '~/utils/mock-data'; |
|||
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
|
|||
return useResponseSuccess(MOCK_MENU_LIST); |
|||
}); |
|||
@ -0,0 +1,28 @@ |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { MOCK_MENU_LIST } from '~/utils/mock-data'; |
|||
import { unAuthorizedResponse } from '~/utils/response'; |
|||
|
|||
const namesMap: Record<string, any> = {}; |
|||
|
|||
function getNames(menus: any[]) { |
|||
menus.forEach((menu) => { |
|||
namesMap[menu.name] = String(menu.id); |
|||
if (menu.children) { |
|||
getNames(menu.children); |
|||
} |
|||
}); |
|||
} |
|||
getNames(MOCK_MENU_LIST); |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
const { id, name } = getQuery(event); |
|||
|
|||
return (name as string) in namesMap && |
|||
(!id || namesMap[name as string] !== String(id)) |
|||
? useResponseSuccess(true) |
|||
: useResponseSuccess(false); |
|||
}); |
|||
@ -0,0 +1,28 @@ |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { MOCK_MENU_LIST } from '~/utils/mock-data'; |
|||
import { unAuthorizedResponse } from '~/utils/response'; |
|||
|
|||
const pathMap: Record<string, any> = { '/': 0 }; |
|||
|
|||
function getPaths(menus: any[]) { |
|||
menus.forEach((menu) => { |
|||
pathMap[menu.path] = String(menu.id); |
|||
if (menu.children) { |
|||
getPaths(menu.children); |
|||
} |
|||
}); |
|||
} |
|||
getPaths(MOCK_MENU_LIST); |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
const { id, path } = getQuery(event); |
|||
|
|||
return (path as string) in pathMap && |
|||
(!id || pathMap[path as string] !== String(id)) |
|||
? useResponseSuccess(true) |
|||
: useResponseSuccess(false); |
|||
}); |
|||
@ -0,0 +1,83 @@ |
|||
import { faker } from '@faker-js/faker'; |
|||
import { verifyAccessToken } from '~/utils/jwt-utils'; |
|||
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data'; |
|||
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; |
|||
|
|||
const formatterCN = new Intl.DateTimeFormat('zh-CN', { |
|||
timeZone: 'Asia/Shanghai', |
|||
year: 'numeric', |
|||
month: '2-digit', |
|||
day: '2-digit', |
|||
hour: '2-digit', |
|||
minute: '2-digit', |
|||
second: '2-digit', |
|||
}); |
|||
|
|||
const menuIds = getMenuIds(MOCK_MENU_LIST); |
|||
|
|||
function generateMockDataList(count: number) { |
|||
const dataList = []; |
|||
|
|||
for (let i = 0; i < count; i++) { |
|||
const dataItem: Record<string, any> = { |
|||
id: faker.string.uuid(), |
|||
name: faker.commerce.product(), |
|||
status: faker.helpers.arrayElement([0, 1]), |
|||
createTime: formatterCN.format( |
|||
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }), |
|||
), |
|||
permissions: faker.helpers.arrayElements(menuIds), |
|||
remark: faker.lorem.sentence(), |
|||
}; |
|||
|
|||
dataList.push(dataItem); |
|||
} |
|||
|
|||
return dataList; |
|||
} |
|||
|
|||
const mockData = generateMockDataList(100); |
|||
|
|||
export default eventHandler(async (event) => { |
|||
const userinfo = verifyAccessToken(event); |
|||
if (!userinfo) { |
|||
return unAuthorizedResponse(event); |
|||
} |
|||
|
|||
const { |
|||
page = 1, |
|||
pageSize = 20, |
|||
name, |
|||
id, |
|||
remark, |
|||
startTime, |
|||
endTime, |
|||
status, |
|||
} = getQuery(event); |
|||
let listData = structuredClone(mockData); |
|||
if (name) { |
|||
listData = listData.filter((item) => |
|||
item.name.toLowerCase().includes(String(name).toLowerCase()), |
|||
); |
|||
} |
|||
if (id) { |
|||
listData = listData.filter((item) => |
|||
item.id.toLowerCase().includes(String(id).toLowerCase()), |
|||
); |
|||
} |
|||
if (remark) { |
|||
listData = listData.filter((item) => |
|||
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()), |
|||
); |
|||
} |
|||
if (startTime) { |
|||
listData = listData.filter((item) => item.createTime >= startTime); |
|||
} |
|||
if (endTime) { |
|||
listData = listData.filter((item) => item.createTime <= endTime); |
|||
} |
|||
if (['0', '1'].includes(status as string)) { |
|||
listData = listData.filter((item) => item.status === Number(status)); |
|||
} |
|||
return usePageResponseSuccess(page as string, pageSize as string, listData); |
|||
}); |
|||
@ -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")
|
|||
}); |
|||
@ -1,7 +1,19 @@ |
|||
export default defineEventHandler((event) => { |
|||
import { forbiddenResponse, sleep } from '~/utils/response'; |
|||
|
|||
export default defineEventHandler(async (event) => { |
|||
event.node.res.setHeader( |
|||
'Access-Control-Allow-Origin', |
|||
event.headers.get('Origin') ?? '*', |
|||
); |
|||
if (event.method === 'OPTIONS') { |
|||
event.node.res.statusCode = 204; |
|||
event.node.res.statusMessage = 'No Content.'; |
|||
return 'OK'; |
|||
} else if ( |
|||
['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) && |
|||
event.path.startsWith('/api/system/') |
|||
) { |
|||
await sleep(Math.floor(Math.random() * 2000)); |
|||
return forbiddenResponse(event, '演示环境,禁止修改'); |
|||
} |
|||
}); |
|||
|
|||
@ -0,0 +1,166 @@ |
|||
--- |
|||
outline: deep |
|||
--- |
|||
|
|||
# Vben Alert 轻量提示框 |
|||
|
|||
框架提供的一些用于轻量提示的弹窗,仅使用js代码即可快速动态创建提示而不需要在template写任何代码。 |
|||
|
|||
::: info 应用场景 |
|||
|
|||
Alert提供的功能与Modal类似,但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求,请使用VbenModal |
|||
|
|||
::: |
|||
|
|||
::: tip 注意 |
|||
|
|||
Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下,不支持HMR(热更新),代码变更后需要关闭这些弹窗后重新打开。 |
|||
|
|||
::: |
|||
|
|||
::: tip README |
|||
|
|||
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。 |
|||
|
|||
::: |
|||
|
|||
## 基础用法 |
|||
|
|||
使用 `alert` 创建只有一个确认按钮的提示框。 |
|||
|
|||
<DemoPreview dir="demos/vben-alert/alert" /> |
|||
|
|||
使用 `confirm` 创建有确认和取消按钮的提示框。 |
|||
|
|||
<DemoPreview dir="demos/vben-alert/confirm" /> |
|||
|
|||
使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。 |
|||
|
|||
<DemoPreview dir="demos/vben-alert/prompt" /> |
|||
|
|||
## useAlertContext |
|||
|
|||
当弹窗的content、footer、icon使用自定义组件时,在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗。 |
|||
|
|||
::: tip 注意 |
|||
|
|||
`useAlertContext`只能用在setup或者函数式组件中。 |
|||
|
|||
::: |
|||
|
|||
### Methods |
|||
|
|||
| 方法 | 描述 | 类型 | 版本要求 | |
|||
| --------- | ------------------ | -------- | -------- | |
|||
| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 | |
|||
| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 | |
|||
|
|||
## 类型说明 |
|||
|
|||
```ts |
|||
/** 预置的图标类型 */ |
|||
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning'; |
|||
|
|||
export type BeforeCloseScope = { |
|||
/** 是否为点击确认按钮触发的关闭 */ |
|||
isConfirm: boolean; |
|||
}; |
|||
|
|||
/** |
|||
* alert 属性 |
|||
*/ |
|||
export type AlertProps = { |
|||
/** 关闭前的回调,如果返回false,则终止关闭 */ |
|||
beforeClose?: ( |
|||
scope: BeforeCloseScope, |
|||
) => boolean | Promise<boolean | undefined> | undefined; |
|||
/** 边框 */ |
|||
bordered?: boolean; |
|||
/** 按钮对齐方式 */ |
|||
buttonAlign?: 'center' | 'end' | 'start'; |
|||
/** 取消按钮的标题 */ |
|||
cancelText?: string; |
|||
/** 是否居中显示 */ |
|||
centered?: boolean; |
|||
/** 确认按钮的标题 */ |
|||
confirmText?: string; |
|||
/** 弹窗容器的额外样式 */ |
|||
containerClass?: string; |
|||
/** 弹窗提示内容 */ |
|||
content: Component | string; |
|||
/** 弹窗内容的额外样式 */ |
|||
contentClass?: string; |
|||
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/ |
|||
contentMasking?: boolean; |
|||
/** 弹窗底部内容(与按钮在同一个容器中) */ |
|||
footer?: Component | string; |
|||
/** 弹窗的图标(在标题的前面) */ |
|||
icon?: Component | IconType; |
|||
/** |
|||
* 弹窗遮罩模糊效果 |
|||
*/ |
|||
overlayBlur?: number; |
|||
/** 是否显示取消按钮 */ |
|||
showCancel?: boolean; |
|||
/** 弹窗标题 */ |
|||
title?: string; |
|||
}; |
|||
|
|||
/** prompt 属性 */ |
|||
export type PromptProps<T = any> = { |
|||
/** 关闭前的回调,如果返回false,则终止关闭 */ |
|||
beforeClose?: (scope: { |
|||
isConfirm: boolean; |
|||
value: T | undefined; |
|||
}) => boolean | Promise<boolean | undefined> | undefined; |
|||
/** 用于接受用户输入的组件 */ |
|||
component?: Component; |
|||
/** 输入组件的属性 */ |
|||
componentProps?: Recordable<any>; |
|||
/** 输入组件的插槽 */ |
|||
componentSlots?: Recordable<Component>; |
|||
/** 默认值 */ |
|||
defaultValue?: T; |
|||
/** 输入组件的值属性名 */ |
|||
modelPropName?: string; |
|||
} & Omit<AlertProps, 'beforeClose'>; |
|||
|
|||
/** |
|||
* 函数签名 |
|||
* 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?: ( |
|||
scope: BeforeCloseScope & { |
|||
/** 输入组件的当前值 */ |
|||
value: T; |
|||
}, |
|||
) => boolean | Promise<boolean | undefined> | undefined; |
|||
component?: Component; |
|||
componentProps?: Recordable<any>; |
|||
defaultValue?: T; |
|||
modelPropName?: string; |
|||
}, |
|||
): Promise<T | undefined>; |
|||
``` |
|||
@ -0,0 +1,36 @@ |
|||
<script lang="ts" setup> |
|||
import { h } from 'vue'; |
|||
|
|||
import { alert, VbenButton } from '@vben/common-ui'; |
|||
|
|||
import { Result } 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({ |
|||
buttonAlign: 'center', |
|||
content: h(Result, { |
|||
status: 'success', |
|||
subTitle: '已成功创建订单。订单ID:2017182818828182881', |
|||
title: '操作成功', |
|||
}), |
|||
}); |
|||
} |
|||
</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> |
|||
@ -0,0 +1,75 @@ |
|||
<script lang="ts" setup> |
|||
import { h, ref } from 'vue'; |
|||
|
|||
import { alert, confirm, VbenButton } from '@vben/common-ui'; |
|||
|
|||
import { Checkbox, message } from 'ant-design-vue'; |
|||
|
|||
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 showfooterConfirm() { |
|||
const checked = ref(false); |
|||
confirm({ |
|||
cancelText: '不要虾扯蛋', |
|||
confirmText: '是的,我们都是NPC', |
|||
content: |
|||
'刚才发生的事情,为什么我似乎早就经历过一般?\n我甚至能在事情发生过程中潜意识里预知到接下来会发生什么。\n\n听起来挺玄乎的,你有过这种感觉吗?', |
|||
footer: () => |
|||
h( |
|||
Checkbox, |
|||
{ |
|||
checked: checked.value, |
|||
class: 'flex-1', |
|||
'onUpdate:checked': (v) => (checked.value = v), |
|||
}, |
|||
'不再提示', |
|||
), |
|||
icon: 'question', |
|||
title: '未解之谜', |
|||
}).then(() => { |
|||
if (checked.value) { |
|||
message.success('我不会再拿这个问题烦你了'); |
|||
} else { |
|||
message.info('下次还要继续问你哟'); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function showAsyncConfirm() { |
|||
confirm({ |
|||
beforeClose({ isConfirm }) { |
|||
if (isConfirm) { |
|||
// 这里可以执行一些异步操作。如果最终返回了false,将阻止关闭弹窗 |
|||
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="showfooterConfirm">Confirm With Footer</VbenButton> |
|||
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,118 @@ |
|||
<script lang="ts" setup> |
|||
import { h } from 'vue'; |
|||
|
|||
import { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui'; |
|||
|
|||
import { Input, RadioGroup, Select } from 'ant-design-vue'; |
|||
import { BadgeJapaneseYen } from 'lucide-vue-next'; |
|||
|
|||
function showPrompt() { |
|||
prompt({ |
|||
content: '请输入一些东西', |
|||
}) |
|||
.then((val) => { |
|||
alert(`已收到你的输入:${val}`); |
|||
}) |
|||
.catch(() => { |
|||
alert('Canceled'); |
|||
}); |
|||
} |
|||
|
|||
function showSlotsPrompt() { |
|||
prompt({ |
|||
component: () => { |
|||
// 获取弹窗上下文。注意:只能在setup或者函数式组件中调用 |
|||
const { doConfirm } = useAlertContext(); |
|||
return h( |
|||
Input, |
|||
{ |
|||
onKeydown(e: KeyboardEvent) { |
|||
if (e.key === 'Enter') { |
|||
e.preventDefault(); |
|||
// 调用弹窗提供的确认方法 |
|||
doConfirm(); |
|||
} |
|||
}, |
|||
placeholder: '请输入', |
|||
prefix: '充值金额:', |
|||
type: 'number', |
|||
}, |
|||
{ |
|||
addonAfter: () => h(BadgeJapaneseYen), |
|||
}, |
|||
); |
|||
}, |
|||
content: |
|||
'此弹窗演示了如何使用自定义插槽,并且可以使用useAlertContext获取到弹窗的上下文。\n在输入框中按下回车键会触发确认操作。', |
|||
icon: 'question', |
|||
modelPropName: 'value', |
|||
}).then((val) => { |
|||
if (val) alert(`你输入的是${val}`); |
|||
}); |
|||
} |
|||
|
|||
function showSelectPrompt() { |
|||
prompt({ |
|||
component: Select, |
|||
componentProps: { |
|||
options: [ |
|||
{ label: 'Option A', value: 'Option A' }, |
|||
{ label: 'Option B', value: 'Option B' }, |
|||
{ label: 'Option C', value: 'Option C' }, |
|||
], |
|||
placeholder: '请选择', |
|||
// 弹窗会设置body的pointer-events为none,这回影响下拉框的点击事件 |
|||
popupClassName: 'pointer-events-auto', |
|||
}, |
|||
content: '此弹窗演示了如何使用component传递自定义组件', |
|||
icon: 'question', |
|||
modelPropName: 'value', |
|||
}).then((val) => { |
|||
if (val) { |
|||
alert(`你选择了${val}`); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function sleep(ms: number) { |
|||
return new Promise((resolve) => setTimeout(resolve, ms)); |
|||
} |
|||
|
|||
function showAsyncPrompt() { |
|||
prompt({ |
|||
async beforeClose(scope) { |
|||
if (scope.isConfirm) { |
|||
if (scope.value) { |
|||
// 模拟异步操作,如果不成功,可以返回false |
|||
await sleep(2000); |
|||
} else { |
|||
alert('请选择一个选项'); |
|||
return false; |
|||
} |
|||
} |
|||
}, |
|||
component: RadioGroup, |
|||
componentProps: { |
|||
class: 'flex flex-col', |
|||
options: [ |
|||
{ label: 'Option 1', value: 'option1' }, |
|||
{ label: 'Option 2', value: 'option2' }, |
|||
{ label: 'Option 3', value: 'option3' }, |
|||
], |
|||
}, |
|||
content: '选择一个选项后再点击[确认]', |
|||
icon: 'question', |
|||
modelPropName: 'value', |
|||
}).then((val) => { |
|||
alert(`${val} 已设置。`); |
|||
}); |
|||
} |
|||
</script> |
|||
<template> |
|||
<div class="flex gap-4"> |
|||
<VbenButton @click="showPrompt">Prompt</VbenButton> |
|||
<VbenButton @click="showSlotsPrompt"> Prompt With slots </VbenButton> |
|||
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton> |
|||
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,16 @@ |
|||
<script lang="ts" setup> |
|||
import { EllipsisText } from '@vben/common-ui'; |
|||
|
|||
const text = ` |
|||
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。 |
|||
`; |
|||
</script> |
|||
<template> |
|||
<EllipsisText :line="2" :tooltip-when-ellipsis="true"> |
|||
{{ text }} |
|||
</EllipsisText> |
|||
|
|||
<EllipsisText :line="3" :tooltip-when-ellipsis="true"> |
|||
{{ text }} |
|||
</EllipsisText> |
|||
</template> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue