物业代码生成

This commit is contained in:
2025-06-18 11:03:42 +08:00
commit 1262d4c745
1881 changed files with 249599 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { Page } from '..';
describe('page.vue', () => {
it('renders title when passed', () => {
const wrapper = mount(Page, {
props: {
title: 'Test Title',
},
});
expect(wrapper.text()).toContain('Test Title');
});
it('renders description when passed', () => {
const wrapper = mount(Page, {
props: {
description: 'Test Description',
},
});
expect(wrapper.text()).toContain('Test Description');
});
it('renders default slot content', () => {
const wrapper = mount(Page, {
slots: {
default: '<p>Default Slot Content</p>',
},
});
expect(wrapper.html()).toContain('<p>Default Slot Content</p>');
});
it('renders footer slot when showFooter is true', () => {
const wrapper = mount(Page, {
props: {
showFooter: true,
},
slots: {
footer: '<p>Footer Slot Content</p>',
},
});
expect(wrapper.html()).toContain('<p>Footer Slot Content</p>');
});
it('applies the custom contentClass', () => {
const wrapper = mount(Page, {
props: {
contentClass: 'custom-class',
},
});
const contentDiv = wrapper.find('.p-4');
expect(contentDiv.classes()).toContain('custom-class');
});
it('does not render title slot if title prop is provided', () => {
const wrapper = mount(Page, {
props: {
title: 'Test Title',
},
slots: {
title: '<p>Title Slot Content</p>',
},
});
expect(wrapper.text()).toContain('Title Slot Content');
expect(wrapper.html()).not.toContain('Test Title');
});
it('does not render description slot if description prop is provided', () => {
const wrapper = mount(Page, {
props: {
description: 'Test Description',
},
slots: {
description: '<p>Description Slot Content</p>',
},
});
expect(wrapper.text()).toContain('Description Slot Content');
expect(wrapper.html()).not.toContain('Test Description');
});
});

View File

@@ -0,0 +1,2 @@
export { default as Page } from './page.vue';
export * from './types';

View File

@@ -0,0 +1,106 @@
<script setup lang="ts">
import type { StyleValue } from 'vue';
import type { PageProps } from './types';
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/shared/constants';
import { cn } from '@vben-core/shared/utils';
defineOptions({
name: 'Page',
});
const { autoContentHeight = false, heightOffset = 0 } =
defineProps<PageProps>();
const headerHeight = ref(0);
const footerHeight = ref(0);
const shouldAutoHeight = ref(false);
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
const contentStyle = computed<StyleValue>(() => {
if (autoContentHeight) {
return {
height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px - ${typeof heightOffset === 'number' ? `${heightOffset}px` : heightOffset})`,
overflowY: shouldAutoHeight.value ? 'auto' : 'unset',
};
}
return {};
});
async function calcContentHeight() {
if (!autoContentHeight) {
return;
}
await nextTick();
headerHeight.value = headerRef.value?.offsetHeight || 0;
footerHeight.value = footerRef.value?.offsetHeight || 0;
setTimeout(() => {
shouldAutoHeight.value = true;
}, 30);
}
onMounted(() => {
calcContentHeight();
});
</script>
<template>
<div class="relative">
<div
v-if="
description ||
$slots.description ||
title ||
$slots.title ||
$slots.extra
"
ref="headerRef"
:class="
cn(
'bg-card border-border relative flex items-end border-b px-6 py-4',
headerClass,
)
"
>
<div class="flex-auto">
<slot name="title">
<div v-if="title" class="mb-2 flex text-lg font-semibold">
{{ title }}
</div>
</slot>
<slot name="description">
<p v-if="description" class="text-muted-foreground">
{{ description }}
</p>
</slot>
</div>
<div v-if="$slots.extra">
<slot name="extra"></slot>
</div>
</div>
<div :class="cn('h-full p-4', contentClass)" :style="contentStyle">
<slot></slot>
</div>
<div
v-if="$slots.footer"
ref="footerRef"
:class="
cn(
'bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4',
footerClass,
)
"
>
<slot name="footer"></slot>
</div>
</div>
</template>

View File

@@ -0,0 +1,17 @@
export interface PageProps {
title?: string;
description?: string;
contentClass?: string;
/**
* 根据content可见高度自适应
*/
autoContentHeight?: boolean;
headerClass?: string;
footerClass?: string;
/**
* Custom height offset value (in pixels) to adjust content area sizing
* when used with autoContentHeight
* @default 0
*/
heightOffset?: number;
}