Vben
2 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with
94 additions and
29 deletions
-
apps/backend-mock/api/auth/login.post.ts
-
apps/backend-mock/utils/response.ts
-
apps/web-antd/src/api/request.ts
-
apps/web-ele/src/api/request.ts
-
apps/web-naive/src/api/request.ts
-
docs/src/components/common-ui/vben-modal.md
-
docs/src/en/guide/essentials/server.md
-
docs/src/guide/essentials/route.md
-
docs/src/guide/essentials/server.md
-
packages/@core/base/typings/src/vue-router.d.ts
-
packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts
-
packages/@core/ui-kit/popup-ui/src/modal/modal.ts
-
packages/@core/ui-kit/popup-ui/src/modal/modal.vue
-
packages/effects/layouts/src/basic/menu/use-navigation.ts
-
packages/effects/layouts/src/widgets/global-search/global-search.vue
-
packages/stores/src/modules/tabbar.ts
-
playground/src/api/request.ts
-
playground/src/locales/langs/en-US/demos.json
-
playground/src/locales/langs/zh-CN/demos.json
-
playground/src/router/routes/modules/demos.ts
-
playground/src/views/demos/features/menu-query/index.vue
|
|
|
@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => { |
|
|
|
|
|
|
|
if (!findUser) { |
|
|
|
clearRefreshTokenCookie(event); |
|
|
|
return forbiddenResponse(event); |
|
|
|
return forbiddenResponse(event, 'Username or password is incorrect.'); |
|
|
|
} |
|
|
|
|
|
|
|
const accessToken = generateAccessToken(findUser); |
|
|
|
|
|
|
|
@ -39,9 +39,12 @@ export function useResponseError(message: string, error: any = null) { |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
export function forbiddenResponse(event: H3Event<EventHandlerRequest>) { |
|
|
|
export function forbiddenResponse( |
|
|
|
event: H3Event<EventHandlerRequest>, |
|
|
|
message = 'Forbidden Exception', |
|
|
|
) { |
|
|
|
setResponseStatus(event, 403); |
|
|
|
return useResponseError('Forbidden Exception', 'Forbidden Exception'); |
|
|
|
return useResponseError(message, message); |
|
|
|
} |
|
|
|
|
|
|
|
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) { |
|
|
|
|
|
|
|
@ -79,8 +79,7 @@ function createRequestClient(baseURL: string) { |
|
|
|
return data; |
|
|
|
} |
|
|
|
|
|
|
|
const error = { response }; |
|
|
|
throw error; |
|
|
|
throw Object.assign({}, response, { response }); |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@ -78,8 +78,7 @@ function createRequestClient(baseURL: string) { |
|
|
|
if (status >= 200 && status < 400 && code === 0) { |
|
|
|
return data; |
|
|
|
} |
|
|
|
const error = { response }; |
|
|
|
throw error; |
|
|
|
throw Object.assign({}, response, { response }); |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@ -77,8 +77,7 @@ function createRequestClient(baseURL: string) { |
|
|
|
if (status >= 200 && status < 400 && code === 0) { |
|
|
|
return data; |
|
|
|
} |
|
|
|
const error = { response }; |
|
|
|
throw error; |
|
|
|
throw Object.assign({}, response, { response }); |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@ -104,6 +104,7 @@ const [Modal, modalApi] = useVbenModal({ |
|
|
|
| contentClass | modal内容区域的class | `string` | - | |
|
|
|
| footerClass | modal底部区域的class | `string` | - | |
|
|
|
| headerClass | modal顶部区域的class | `string` | - | |
|
|
|
| bordered | 是否显示border | `boolean` | `false` | |
|
|
|
|
|
|
|
### Event |
|
|
|
|
|
|
|
|
|
|
|
@ -238,8 +238,7 @@ function createRequestClient(baseURL: string) { |
|
|
|
if (status >= 200 && status < 400 && code === 0) { |
|
|
|
return data; |
|
|
|
} |
|
|
|
const error = { response }; |
|
|
|
throw error; |
|
|
|
throw Object.assign({}, response, { response }); |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@ -386,6 +386,10 @@ interface RouteMeta { |
|
|
|
* 用于路由->菜单排序 |
|
|
|
*/ |
|
|
|
order?: number; |
|
|
|
/** |
|
|
|
* 菜单所携带的参数 |
|
|
|
*/ |
|
|
|
query?: Recordable; |
|
|
|
/** |
|
|
|
* 标题名称 |
|
|
|
*/ |
|
|
|
@ -542,6 +546,15 @@ interface RouteMeta { |
|
|
|
|
|
|
|
用于配置页面的排序,用于路由到菜单排序。 |
|
|
|
|
|
|
|
_注意:_ 排序仅针对一级菜单有效,二级菜单的排序需要在对应的一级菜单中按代码顺序设置。 |
|
|
|
|
|
|
|
### query |
|
|
|
|
|
|
|
- 类型:`Recordable` |
|
|
|
- 默认值:`{}` |
|
|
|
|
|
|
|
用于配置页面的菜单参数,会在菜单中传递给页面。 |
|
|
|
|
|
|
|
## 路由刷新 |
|
|
|
|
|
|
|
路由刷新方式如下: |
|
|
|
|
|
|
|
@ -241,8 +241,7 @@ function createRequestClient(baseURL: string) { |
|
|
|
if (status >= 200 && status < 400 && code === 0) { |
|
|
|
return data; |
|
|
|
} |
|
|
|
const error = { response }; |
|
|
|
throw error; |
|
|
|
throw Object.assign({}, response, { response }); |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@ -102,6 +102,10 @@ interface RouteMeta { |
|
|
|
* 用于路由->菜单排序 |
|
|
|
*/ |
|
|
|
order?: number; |
|
|
|
/** |
|
|
|
* 菜单所携带的参数 |
|
|
|
*/ |
|
|
|
query?: Recordable; |
|
|
|
/** |
|
|
|
* 标题名称 |
|
|
|
*/ |
|
|
|
|
|
|
|
@ -29,6 +29,7 @@ export class ModalApi { |
|
|
|
} = options; |
|
|
|
|
|
|
|
const defaultState: ModalState = { |
|
|
|
bordered: false, |
|
|
|
centered: false, |
|
|
|
class: '', |
|
|
|
closeOnClickModal: true, |
|
|
|
|
|
|
|
@ -3,15 +3,22 @@ import type { ModalApi } from './modal-api'; |
|
|
|
import type { Component, Ref } from 'vue'; |
|
|
|
|
|
|
|
export interface ModalProps { |
|
|
|
/** |
|
|
|
* 是否显示边框 |
|
|
|
* @default false |
|
|
|
*/ |
|
|
|
bordered?: boolean; |
|
|
|
/** |
|
|
|
* 取消按钮文字 |
|
|
|
*/ |
|
|
|
cancelText?: string; |
|
|
|
|
|
|
|
/** |
|
|
|
* 是否居中 |
|
|
|
* @default false |
|
|
|
*/ |
|
|
|
centered?: boolean; |
|
|
|
|
|
|
|
class?: string; |
|
|
|
/** |
|
|
|
* 是否显示右上角的关闭按钮 |
|
|
|
|
|
|
|
@ -52,6 +52,7 @@ const { isMobile } = useIsMobile(); |
|
|
|
const state = props.modalApi?.useStore?.(); |
|
|
|
|
|
|
|
const { |
|
|
|
bordered, |
|
|
|
cancelText, |
|
|
|
centered, |
|
|
|
class: modalClass, |
|
|
|
@ -170,9 +171,11 @@ function handleFocusOutside(e: Event) { |
|
|
|
ref="contentRef" |
|
|
|
:class=" |
|
|
|
cn( |
|
|
|
'border-border left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col border p-0', |
|
|
|
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0 sm:rounded-2xl', |
|
|
|
modalClass, |
|
|
|
{ |
|
|
|
'border-border border': bordered, |
|
|
|
'shadow-3xl': !bordered, |
|
|
|
'left-0 top-0 size-full max-h-full !translate-x-0 !translate-y-0': |
|
|
|
shouldFullscreen, |
|
|
|
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen, |
|
|
|
@ -195,8 +198,9 @@ function handleFocusOutside(e: Event) { |
|
|
|
ref="headerRef" |
|
|
|
:class=" |
|
|
|
cn( |
|
|
|
'border-b px-5 py-4', |
|
|
|
'px-5 py-4', |
|
|
|
{ |
|
|
|
'border-b': bordered, |
|
|
|
hidden: !header, |
|
|
|
'cursor-move select-none': shouldDraggable, |
|
|
|
}, |
|
|
|
|
|
|
|
@ -4,12 +4,24 @@ import { isHttpUrl, openWindow } from '@vben/utils'; |
|
|
|
|
|
|
|
function useNavigation() { |
|
|
|
const router = useRouter(); |
|
|
|
const routes = router.getRoutes(); |
|
|
|
|
|
|
|
const routeMetaMap = new Map<string, any>(); |
|
|
|
|
|
|
|
routes.forEach((route) => { |
|
|
|
routeMetaMap.set(route.path, route.meta); |
|
|
|
}); |
|
|
|
|
|
|
|
const navigation = async (path: string) => { |
|
|
|
if (isHttpUrl(path)) { |
|
|
|
openWindow(path, { target: '_blank' }); |
|
|
|
} else { |
|
|
|
await router.push(path); |
|
|
|
const meta = routeMetaMap.get(path); |
|
|
|
const query = meta?.query ?? {}; |
|
|
|
await router.push({ |
|
|
|
path, |
|
|
|
query, |
|
|
|
}); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
@ -95,7 +95,11 @@ onMounted(() => { |
|
|
|
|
|
|
|
<template> |
|
|
|
<div> |
|
|
|
<Modal :fullscreen-button="false" class="w-[600px]" header-class="py-2"> |
|
|
|
<Modal |
|
|
|
:fullscreen-button="false" |
|
|
|
class="w-[600px]" |
|
|
|
header-class="py-2 border-b" |
|
|
|
> |
|
|
|
<template #title> |
|
|
|
<div class="flex items-center"> |
|
|
|
<Search class="text-muted-foreground mr-2 size-4" /> |
|
|
|
|
|
|
|
@ -336,7 +336,7 @@ export const useTabbarStore = defineStore('core-tabbar', { |
|
|
|
* @zh_CN 重置标签页标题 |
|
|
|
*/ |
|
|
|
async resetTabTitle(tab: TabDefinition) { |
|
|
|
if (!tab?.meta?.newTabTitle) { |
|
|
|
if (tab?.meta?.newTabTitle) { |
|
|
|
return; |
|
|
|
} |
|
|
|
const findTab = this.tabs.find( |
|
|
|
|
|
|
|
@ -79,8 +79,7 @@ function createRequestClient(baseURL: string) { |
|
|
|
if (status >= 200 && status < 400 && code === 0) { |
|
|
|
return data; |
|
|
|
} |
|
|
|
const error = { response }; |
|
|
|
throw error; |
|
|
|
throw Object.assign({}, response, { response }); |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@ -46,10 +46,9 @@ |
|
|
|
"watermark": "Watermark", |
|
|
|
"tabs": "Tabs", |
|
|
|
"tabDetail": "Tab Detail Page", |
|
|
|
"fullScreen": { |
|
|
|
"title": "FullScreen" |
|
|
|
}, |
|
|
|
"clipboard": "Clipboard" |
|
|
|
"fullScreen": "FullScreen", |
|
|
|
"clipboard": "Clipboard", |
|
|
|
"menuWithQuery": "Menu With Query" |
|
|
|
}, |
|
|
|
"breadcrumb": { |
|
|
|
"navigation": "Breadcrumb Navigation", |
|
|
|
|
|
|
|
@ -46,10 +46,9 @@ |
|
|
|
"watermark": "水印", |
|
|
|
"tabs": "标签页", |
|
|
|
"tabDetail": "标签详情页", |
|
|
|
"fullScreen": { |
|
|
|
"title": "全屏" |
|
|
|
}, |
|
|
|
"clipboard": "剪贴板" |
|
|
|
"fullScreen": "全屏", |
|
|
|
"clipboard": "剪贴板", |
|
|
|
"menuWithQuery": "带参菜单" |
|
|
|
}, |
|
|
|
"breadcrumb": { |
|
|
|
"navigation": "面包屑导航", |
|
|
|
|
|
|
|
@ -174,7 +174,7 @@ const routes: RouteRecordRaw[] = [ |
|
|
|
import('#/views/demos/features/full-screen/index.vue'), |
|
|
|
meta: { |
|
|
|
icon: 'lucide:fullscreen', |
|
|
|
title: $t('demos.features.fullScreen.title'), |
|
|
|
title: $t('demos.features.title'), |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
@ -187,6 +187,19 @@ const routes: RouteRecordRaw[] = [ |
|
|
|
title: $t('demos.features.clipboard'), |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: 'MenuQueryDemo', |
|
|
|
path: '/demos/menu-query', |
|
|
|
component: () => |
|
|
|
import('#/views/demos/features/menu-query/index.vue'), |
|
|
|
meta: { |
|
|
|
icon: 'lucide:curly-braces', |
|
|
|
query: { |
|
|
|
id: 1, |
|
|
|
}, |
|
|
|
title: $t('demos.features.menuWithQuery'), |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: 'VueQueryDemo', |
|
|
|
path: '/demos/features/vue-query', |
|
|
|
|
|
|
|
@ -0,0 +1,11 @@ |
|
|
|
<script lang="ts" setup> |
|
|
|
import { Fallback } from '@vben/common-ui'; |
|
|
|
</script> |
|
|
|
|
|
|
|
<template> |
|
|
|
<Fallback |
|
|
|
description="点击菜单,将会带上参数" |
|
|
|
status="coming-soon" |
|
|
|
title="菜单带参示例" |
|
|
|
/> |
|
|
|
</template> |