chore: init project

This commit is contained in:
vben
2024-05-19 21:20:42 +08:00
commit 399334ac57
630 changed files with 45623 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
export { default as Tabs } from './tabs.vue';
export { default as TabsMore } from './tabs-more.vue';
export { default as TabsScreen } from './tabs-screen.vue';

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { useNamespace } from '@vben-core/toolkit';
defineOptions({
name: 'TabBackground',
});
const { b, e } = useNamespace('tab-background');
</script>
<template>
<div :class="b()">
<div :class="e('divider')"></div>
<div :class="e('content')"></div>
<svg width="10" height="10" :class="e('before')">
<path d="M 0 10 A 10 10 0 0 0 10 0 L 10 10 Z" />
</svg>
<svg width="10" height="10" :class="e('after')">
<path d="M 0 0 A 10 10 0 0 0 10 10 L 0 10 Z" />
</svg>
</div>
</template>

View File

@@ -0,0 +1,68 @@
<script setup lang="ts">
import type { IContextMenuItem } from '@vben-core/shadcn-ui';
import type { TabItem } from '@vben-core/typings';
import { IcRoundClose, MdiPin } from '@vben-core/iconify';
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
import { useNamespace } from '@vben-core/toolkit';
import TabBackground from './tab-background.vue';
interface Props {
affixTab?: boolean;
icon?: string;
menus: (data: any) => IContextMenuItem[];
onlyOne?: boolean;
showIcon?: boolean;
tab: TabItem;
title: string;
}
defineOptions({
name: 'Tab',
});
withDefaults(defineProps<Props>(), {
icon: '',
});
const emit = defineEmits<{ close: []; unPushPin: [] }>();
const { b, e, is } = useNamespace('tab');
function handleClose() {
emit('close');
}
function handleUnPushPin() {
emit('unPushPin');
}
</script>
<template>
<div :class="[b()]">
<VbenContextMenu :menus="menus" :handler-data="tab" item-class="pr-4">
<div class="h-full">
<TabBackground />
<div :class="e('content')" :title="title">
<VbenIcon v-if="showIcon" :class="e('icon')" :icon="icon" fallback />
<span :class="[e('label'), is('hidden-icon', !icon)]">
{{ title }}
</span>
</div>
<div
v-show="!affixTab && !onlyOne"
:class="e('extra')"
@click.stop="handleClose"
>
<IcRoundClose :class="e('extra-icon')" />
</div>
<div
v-show="affixTab && !onlyOne"
:class="[e('extra'), is('pin', true)]"
@click.stop="handleUnPushPin"
>
<MdiPin :class="e('extra-icon')" />
</div>
</div>
</VbenContextMenu>
</div>
</template>

View File

@@ -0,0 +1,18 @@
<script lang="ts" setup>
import type { DropdownMenuProps } from '@vben-core/shadcn-ui';
import { IcRoundMoreVert } from '@vben-core/iconify';
import { VbenDropdownMenu } from '@vben-core/shadcn-ui';
defineProps<DropdownMenuProps>();
</script>
<template>
<VbenDropdownMenu :menus="menus">
<div
class="flex-center hover:bg-accent hover:text-foreground text-muted-foreground h-full cursor-pointer border-l px-2 text-lg font-semibold"
>
<IcRoundMoreVert />
</div>
</VbenDropdownMenu>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
import { IcRoundFitScreen, IcTwotoneFitScreen } from '@vben-core/iconify';
const screen = defineModel<boolean>('screen');
function toggleScreen() {
screen.value = !screen.value;
}
</script>
<template>
<div
class="flex-center hover:bg-accent hover:text-foreground text-muted-foreground h-full cursor-pointer border-l px-2 text-lg font-semibold"
@click="toggleScreen"
>
<IcTwotoneFitScreen v-if="screen" />
<IcRoundFitScreen v-else />
</div>
</template>

View File

@@ -0,0 +1,119 @@
<script setup lang="ts">
import type { IContextMenuItem } from '@vben-core/shadcn-ui';
import { useNamespace } from '@vben-core/toolkit';
import { TabItem } from '@vben-core/typings';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import Tab from './tab.vue';
interface Props {
maxWidth?: number;
menus?: (data: any) => IContextMenuItem[];
minWidth?: number;
showIcon?: boolean;
tabs?: TabItem[];
}
defineOptions({
name: 'Tabs',
});
const props = withDefaults(defineProps<Props>(), {
maxWidth: 150,
menus: () => [],
minWidth: 40,
tabs: () => [],
});
const emit = defineEmits<{ close: [string]; unPushPin: [TabItem] }>();
const gap = 7;
const active = defineModel<string>('active');
const { b, e, is } = useNamespace('tabs-ui');
const contentRef = ref();
const tabWidth = ref<number>(0);
const layout = () => {
const { maxWidth, minWidth, tabs } = props;
if (!contentRef.value) {
return Math.max(maxWidth, minWidth);
}
const contentWidth = contentRef.value.clientWidth - gap * 3;
let width = contentWidth / tabs.length;
width += gap * 2;
if (width > maxWidth) {
width = maxWidth;
}
if (width < minWidth) {
width = minWidth;
}
tabWidth.value = width;
};
const tabsView = computed(() => {
return props.tabs.map((tab) => {
return {
...tab,
affixTab: !!tab.meta?.affixTab,
icon: tab.meta.icon as string,
key: tab.fullPath || tab.path,
title: (tab.meta?.title || tab.name) as string,
};
});
});
watch(
() => props.tabs,
() => {
nextTick(() => {
layout();
});
},
);
onMounted(() => {
layout();
});
function handleClose(key: string) {
emit('close', key);
}
function handleUnPushPin(tab: TabItem) {
emit('unPushPin', tab);
}
</script>
<template>
<div :class="b()">
<div ref="contentRef" :class="e('content')">
<TransitionGroup name="slide-down">
<Tab
v-for="(tab, i) in tabsView"
:key="tab.key"
:menus="menus"
:tab="tab"
:icon="tab.icon"
:title="tab.title"
:show-icon="showIcon"
:affix-tab="tab.affixTab"
:only-one="tabsView.length <= 1"
:class="[e('tab'), is('active', tab.key === active)]"
:style="{
width: `${tabWidth}px`,
left: `${(tabWidth - gap * 2) * i}px`,
}"
@click="active = tab.key"
@close="() => handleClose(tab.key)"
@un-push-pin="() => handleUnPushPin(tab)"
/>
</TransitionGroup>
</div>
</div>
</template>
<style lang="scss">
@import '../styles/tabs.scss';
</style>