386 changed files with 12432 additions and 2070 deletions
@ -1 +1 @@ |
|||||
20.14.0 |
22.1.0 |
||||
|
|||||
@ -1,2 +1 @@ |
|||||
export * from './menu'; |
|
||||
export { useAbpConfigApi } from './useAbpConfigApi'; |
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 }); |
|
||||
@ -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') { |
if (event.method === 'OPTIONS') { |
||||
event.node.res.statusCode = 204; |
event.node.res.statusCode = 204; |
||||
event.node.res.statusMessage = 'No Content.'; |
event.node.res.statusMessage = 'No Content.'; |
||||
return 'OK'; |
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