feat: improve page component (#5013)

* feat: `page` component support fixed header

* docs: `page`  component documentation

* docs: Improve `props` types of `page`

* docs: improve `fixedHeader` description of `page`

* fix: `page` header border color with fixedHeader

* feat: add `headerClass` for `Page`
This commit is contained in:
Netfan 2024-12-04 21:40:41 +08:00 committed by GitHub
parent 24b9aa44d2
commit 17c7ce8f21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 126 additions and 7 deletions

View File

@ -148,6 +148,16 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
},
],
},
{
collapsed: false,
text: '布局组件',
items: [
{
link: 'layout-ui/page',
text: 'Page 页面',
},
],
},
{
collapsed: false,
text: '通用组件',

View File

@ -6,6 +6,10 @@
:::
## 布局组件
布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。
## 通用组件
通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。

View File

@ -0,0 +1,45 @@
---
outline: deep
---
# Page 常规页面组件
提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分。
::: info 写在前面
本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。
:::
## 基础用法
将`Page`作为你的业务页面的根组件即可。
### Props
| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 页面标题 | `string\|slot` | - |
| description | 页面描述(标题下的内容) | `string\|slot` | - |
| contentClass | 内容区域的class | `string` | - |
| headerClass | 头部区域的class | `string` | - |
| footerClass | 底部区域的class | `string` | - |
| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` |
| fixedHeader | 固定头部在页面内容区域顶部,在滚动时保持可见 | `boolean` | `false` |
::: tip 注意
如果`title`、`description`、`extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。
:::
### Slots
| 插槽名称 | 描述 |
| ----------- | ------------ |
| default | 页面内容 |
| title | 页面标题 |
| description | 页面描述 |
| extra | 页面头部右侧 |
| footer | 页面底部 |

View File

@ -503,7 +503,7 @@ function handleHeaderToggle() {
<div
ref="contentRef"
class="flex flex-1 flex-col overflow-hidden transition-all duration-300 ease-in"
class="flex flex-1 flex-col transition-all duration-300 ease-in"
>
<div
:class="[

View File

@ -22,6 +22,7 @@
"dependencies": {
"@vben-core/form-ui": "workspace:*",
"@vben-core/popup-ui": "workspace:*",
"@vben-core/preferences": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben/constants": "workspace:*",

View File

@ -1,5 +1,15 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
import {
computed,
nextTick,
onMounted,
ref,
type StyleValue,
useTemplateRef,
} from 'vue';
import { preferences } from '@vben-core/preferences';
import { cn } from '@vben-core/shared/utils';
interface Props {
title?: string;
@ -9,6 +19,10 @@ interface Props {
* 根据content可见高度自适应
*/
autoContentHeight?: boolean;
/** 头部固定 */
fixedHeader?: boolean;
headerClass?: string;
footerClass?: string;
}
defineOptions({
@ -20,6 +34,7 @@ const {
description = '',
autoContentHeight = false,
title = '',
fixedHeader = false,
} = defineProps<Props>();
const headerHeight = ref(0);
@ -29,6 +44,17 @@ const shouldAutoHeight = ref(false);
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
const headerStyle = computed<StyleValue>(() => {
return fixedHeader
? {
position: 'sticky',
zIndex: 200,
top:
preferences.header.mode === 'fixed' ? 'var(--vben-header-height)' : 0,
}
: undefined;
});
const contentStyle = computed(() => {
if (autoContentHeight) {
return {
@ -69,7 +95,14 @@ onMounted(() => {
$slots.extra
"
ref="headerRef"
class="bg-card relative px-6 py-4"
:class="
cn(
'bg-card relative px-6 py-4',
headerClass,
fixedHeader ? 'border-border border-b' : '',
)
"
:style="headerStyle"
>
<slot name="title">
<div v-if="title" class="mb-2 flex text-lg font-semibold">
@ -95,7 +128,12 @@ onMounted(() => {
<div
v-if="$slots.footer"
ref="footerRef"
class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
:class="
cn(
footerClass,
'bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4',
)
"
>
<slot name="footer"></slot>
</div>

View File

@ -1,13 +1,17 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Button, Card, message } from 'ant-design-vue';
import { Button, Card, message, TabPane, Tabs } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenForm } from '#/adapter/form';
import DocButton from '../doc-button.vue';
const activeTab = ref('basic');
const [BaseForm, baseFormApi] = useVbenForm({
//
commonConfig: {
@ -331,18 +335,31 @@ function handleSetFormValue() {
<Page
content-class="flex flex-col gap-4"
description="表单组件基础示例,请注意,该页面用到的参数代码会添加一些简单注释,方便理解,请仔细查看。"
fixed-header
header-class="pb-0"
title="表单组件"
>
<template #description>
<div class="text-muted-foreground">
<p>
表单组件基础示例请注意该页面用到的参数代码会添加一些简单注释方便理解请仔细查看
</p>
</div>
<Tabs v-model:active-key="activeTab" :tab-bar-style="{ marginBottom: 0 }">
<TabPane key="basic" tab="基础示例" />
<TabPane key="layout" tab="自定义布局" />
</Tabs>
</template>
<template #extra>
<DocButton path="/components/common-ui/vben-form" />
</template>
<Card title="基础示例">
<Card v-show="activeTab === 'basic'" title="基础示例">
<template #extra>
<Button type="primary" @click="handleSetFormValue">设置表单值</Button>
</template>
<BaseForm />
</Card>
<Card title="使用tailwind自定义布局">
<Card v-show="activeTab === 'layout'" title="使用tailwind自定义布局">
<CustomLayoutForm />
</Card>
</Page>

View File

@ -77,6 +77,7 @@ function openFormModal() {
<template>
<Page
description="弹窗组件常用于在不离开当前页面的情况下显示额外的信息、表单或操作提示更多api请查看组件文档。"
fixed-header
title="弹窗组件示例"
>
<template #extra>

3
pnpm-lock.yaml generated
View File

@ -1460,6 +1460,9 @@ importers:
'@vben-core/popup-ui':
specifier: workspace:*
version: link:../../@core/ui-kit/popup-ui
'@vben-core/preferences':
specifier: workspace:*
version: link:../../@core/preferences
'@vben-core/shadcn-ui':
specifier: workspace:*
version: link:../../@core/ui-kit/shadcn-ui