This commit is contained in:
dap
2024-08-12 07:38:55 +08:00
226 changed files with 3687 additions and 1111 deletions

View File

@@ -1,6 +1,6 @@
## Effects 目录
`effects` 目录专门用于存放与副作用相关的代码和逻辑。如果你的包具有以下特点,建议将其放置在 `effects` 目录下:
`effects` 目录专门用于存放与轻微耦合相关的代码和逻辑。如果你的包具有以下特点,建议将其放置在 `effects` 目录下:
- **状态管理**:使用状态管理框架 `pinia`并包含处理副作用如异步操作、API 调用)的部分。
- **用户偏好设置**:使用 `@vben-core/preferences` 处理用户偏好设置,涉及本地存储或浏览器缓存逻辑(如使用 `localStorage`)。

View File

@@ -0,0 +1,39 @@
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { EllipsisText } from '..';
describe('ellipsis-text.vue', () => {
it('renders the correct content and truncates text', async () => {
const wrapper = mount(EllipsisText, {
props: {
line: 1,
title: 'Test Title',
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
expect(wrapper.text()).toContain('This is a very long text');
// 检查 ellipsis 是否应用了正确的 class
const ellipsis = wrapper.find('.truncate');
expect(ellipsis.exists()).toBe(true);
});
it('expands text on click if expand is true', async () => {
const wrapper = mount(EllipsisText, {
props: {
expand: true,
line: 1,
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
const ellipsis = wrapper.find('.truncate');
await ellipsis.trigger('click');
expect(wrapper.emitted('expandChange')).toBeTruthy();
});
});

View File

@@ -23,7 +23,7 @@ interface Props {
* 提示框位置
* @default 'top'
*/
placement: 'bottom' | 'left' | 'right' | 'top';
placement?: 'bottom' | 'left' | 'right' | 'top';
/**
* 是否启用文本提示框
* @default true
@@ -49,8 +49,9 @@ interface Props {
* 提示框内容区域样式
* @default { textAlign: 'justify' }
*/
tooltipOverlayStyle?: CSSProperties; //
tooltipOverlayStyle?: CSSProperties;
}
const props = withDefaults(defineProps<Props>(), {
expand: false,
line: 1,
@@ -99,6 +100,14 @@ function onExpand() {
emit('expandChange', !isExpanded);
}
function handleExpand() {
if (props.expand) {
onExpand();
} else {
return false;
}
}
</script>
<template>
<VbenTooltip
@@ -110,7 +119,6 @@ function onExpand() {
backgroundColor: tooltipBackgroundColor,
}"
:disabled="!showTooltip"
:overlay-style="tooltipOverlayStyle"
:side="placement"
>
<slot name="tooltip">
@@ -127,7 +135,7 @@ function onExpand() {
}"
:style="`-webkit-line-clamp: ${line}; max-width: ${textMaxWidth};`"
class="cursor-text overflow-hidden"
@click="expand ? onExpand() : () => false"
@click="handleExpand"
v-bind="$attrs"
>
<slot></slot>

View File

@@ -0,0 +1,2 @@
export * from './ellipsis-text';
export * from './page';

View File

@@ -0,0 +1,74 @@
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('.m-4');
expect(contentDiv.classes()).toContain('custom-class');
});
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('Test Description');
expect(wrapper.html()).not.toContain('Description Slot Content');
});
});

View File

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

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
interface Props {
title?: string;
description?: string;
contentClass?: string;
showFooter?: boolean;
}
defineOptions({
name: 'Page',
});
const props = withDefaults(defineProps<Props>(), {
contentClass: '',
description: '',
showFooter: false,
title: '',
});
</script>
<template>
<div class="relative h-full">
<div
v-if="description || $slots.description || title"
class="bg-card px-6 py-4"
>
<div class="mb-2 flex justify-between text-xl font-bold leading-10">
{{ title }}
</div>
<template v-if="description">{{ description }}</template>
<slot v-else name="description"></slot>
</div>
<div :class="contentClass" class="m-4">
<slot></slot>
</div>
<div
v-if="props.showFooter"
class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
>
<slot name="footer"></slot>
</div>
</div>
</template>

View File

@@ -1,6 +1,3 @@
export * from './about';
export * from './authentication';
export * from './dashboard';
export * from './ellipsis-text';
export * from './fallback';
export * from './components';
export * from './ui';
export { useToast } from '@vben-core/shadcn-ui';

View File

@@ -10,6 +10,8 @@ import {
} from '@vben/constants';
import { VbenLink, VbenRenderContent } from '@vben-core/shadcn-ui';
import { Page } from '../../components';
interface Props extends AboutProps {}
defineOptions({
@@ -119,18 +121,18 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
</script>
<template>
<div class="m-5">
<Page :title="title">
<template #description>
<p class="text-foreground mt-3 text-sm leading-6">
<VbenLink :href="VBEN_GITHUB_URL">
{{ name }}
</VbenLink>
{{ description }}
</p>
</template>
<div class="card-box p-5">
<div>
<h3 class="text-foreground text-2xl font-semibold leading-7">
{{ title }}
</h3>
<p class="text-foreground/80 mt-3 text-sm leading-6">
<VbenLink :href="VBEN_GITHUB_URL">
{{ name }}
</VbenLink>
{{ description }}
</p>
<h5 class="text-foreground text-lg">基本信息</h5>
</div>
<div class="mt-4">
<dl class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
@@ -139,7 +141,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
<dt class="text-foreground text-sm font-medium leading-6">
{{ item.title }}
</dt>
<dd class="text-foreground/80 mt-1 text-sm leading-6 sm:mt-2">
<dd class="text-foreground mt-1 text-sm leading-6 sm:mt-2">
<VbenRenderContent :content="item.content" />
</dd>
</div>
@@ -159,7 +161,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
<dt class="text-foreground text-sm">
{{ item.title }}
</dt>
<dd class="text-foreground/60 mt-1 text-sm sm:mt-2">
<dd class="text-foreground/80 mt-1 text-sm sm:mt-2">
<VbenRenderContent :content="item.content" />
</dd>
</div>
@@ -178,7 +180,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
<dt class="text-foreground text-sm">
{{ item.title }}
</dt>
<dd class="text-foreground/60 mt-1 text-sm sm:mt-2">
<dd class="text-foreground/80 mt-1 text-sm sm:mt-2">
<VbenRenderContent :content="item.content" />
</dd>
</div>
@@ -186,5 +188,5 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
</dl>
</div>
</div>
</div>
</Page>
</template>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { LoginCodeEmits } from './typings';
import type { LoginCodeEmits } from './types';
import { computed, onBeforeUnmount, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';

View File

@@ -8,4 +8,4 @@ export type {
AuthenticationProps,
LoginAndRegisterParams,
LoginCodeParams,
} from './typings';
} from './types';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { AuthenticationProps, LoginAndRegisterParams } from './typings';
import type { AuthenticationProps, LoginAndRegisterParams } from './types';
import { useForwardPropsEmits } from '@vben/hooks';
import {

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { AuthenticationProps, LoginEmits } from './typings';
import type { AuthenticationProps, LoginEmits } from './types';
import { computed, reactive, watch } from 'vue';
import { useRouter } from 'vue-router';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { RegisterEmits } from './typings';
import type { RegisterEmits } from './types';
import { computed, reactive } from 'vue';
import { useRouter } from 'vue-router';

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,4 @@
export * from './about';
export * from './authentication';
export * from './dashboard';
export * from './fallback';

View File

@@ -6,7 +6,6 @@ import { preferences, usePreferences } from '@vben/preferences';
import AuthenticationFormView from './form.vue';
import SloganIcon from './icons/slogan.vue';
import Toolbar from './toolbar.vue';
defineOptions({ name: 'Authentication' });
@@ -56,11 +55,7 @@ const logoSource = computed(() => preferences.logo.source);
<div v-if="authPanelCenter" class="flex-center bg-authentication w-full">
<AuthenticationFormView
class="md:bg-background w-full rounded-3xl pb-20 shadow-2xl md:w-2/3 lg:w-1/2 xl:w-2/5"
>
<template #toolbar>
<Toolbar />
</template>
</AuthenticationFormView>
/>
</div>
<!-- 右侧认证面板 -->

View File

@@ -30,7 +30,7 @@ async function handleSelect(key: string) {
<Menu
:accordion="accordion"
:collapse="collapse"
:default-active="route.path"
:default-active="route.meta?.activePath || route.path"
:menus="menus"
:rounded="rounded"
:theme="theme"

View File

@@ -81,7 +81,7 @@ function useExtraMenu() {
watch(
() => route.path,
(path) => {
const currentPath = path;
const currentPath = route.meta?.activePath || path;
// if (preferences.sidebar.expandOnHover) {
// return;
// }

View File

@@ -113,7 +113,7 @@ function useMixedMenu() {
// 初始化计算侧边菜单
onBeforeMount(() => {
calcSideMenus();
calcSideMenus(route.meta?.activePath || route.path);
});
return {

View File

@@ -100,15 +100,11 @@ export function useTabbar() {
watch(
() => route.path,
() => {
// 这里不能用route用route时vue-router会自动将父级meta进行合并
const routes = router.getRoutes();
const currentRoute = routes.find((item) => item.path === route.path);
if (currentRoute) {
tabbarStore.addTab({
...route,
meta: currentRoute.meta,
} as unknown as RouteLocationNormalizedGeneric);
}
const meta = route.matched?.[route.matched.length - 1]?.meta;
tabbarStore.addTab({
...route,
meta: meta || route.meta,
});
},
{ immediate: true },
);

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { RegisterEmits } from './typings';
import { computed, reactive } from 'vue';
import {
@@ -19,6 +17,14 @@ interface Props {
text?: string;
}
interface LockAndRegisterParams {
lockScreenPassword: string;
}
interface RegisterEmits {
submit: [LockAndRegisterParams];
}
defineOptions({
name: 'LockScreenModal',
});

View File

@@ -1,9 +0,0 @@
interface LockAndRegisterParams {
lockScreenPassword: string;
}
interface RegisterEmits {
submit: [LockAndRegisterParams];
}
export type { LockAndRegisterParams, RegisterEmits };