🔱: [client] sync upgrade with 7 commits [trident-sync]

chore:
Merge branch 'vben'

# Conflicts:
#	package.json
perf: antdv示例改成使用vben框架
chore: vben
chore: vben
chore: vben
This commit is contained in:
GitHub Actions Bot
2025-03-03 19:24:51 +00:00
parent de26ee9383
commit 335d175d57
649 changed files with 36984 additions and 826 deletions
@@ -0,0 +1,2 @@
export { default as TabsChrome } from './tabs-chrome/tabs.vue';
export { default as Tabs } from './tabs/tabs.vue';
@@ -0,0 +1,181 @@
<script setup lang="ts">
import type { TabDefinition } from "../../../typings";
import type { TabConfig, TabsProps } from "../../types";
import { computed, ref } from "vue";
import { Pin, X } from "../../../icons";
import { VbenContextMenu, VbenIcon } from "../../../shadcn-ui";
interface Props extends TabsProps {}
defineOptions({
name: "VbenTabsChrome",
// eslint-disable-next-line perfectionist/sort-objects
inheritAttrs: false
});
const props = withDefaults(defineProps<Props>(), {
contentClass: "vben-tabs-content",
contextMenus: () => [],
gap: 7,
tabs: () => []
});
const emit = defineEmits<{
close: [string];
unpin: [TabDefinition];
}>();
const active = defineModel<string>("active");
const contentRef = ref();
const tabRef = ref();
const style = computed(() => {
const { gap } = props;
return {
"--gap": `${gap}px`
};
});
const tabsView = computed(() => {
return props.tabs.map((tab) => {
const { fullPath, meta, name, path } = tab || {};
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
return {
affixTab: !!affixTab,
closable: Reflect.has(meta, "tabClosable") ? !!tabClosable : true,
fullPath,
icon: icon as string,
key: fullPath || path,
meta,
name,
path,
title: (newTabTitle || title || name) as string
} as TabConfig;
});
});
function onMouseDown(e: MouseEvent, tab: TabConfig) {
if (e.button === 1 && tab.closable && !tab.affixTab && tabsView.value.length > 1 && props.middleClickToClose) {
e.preventDefault();
e.stopPropagation();
emit("close", tab.key);
}
}
</script>
<template>
<div ref="contentRef" :class="contentClass" :style="style" class="tabs-chrome !flex h-full w-max overflow-y-hidden pr-6">
<TransitionGroup name="slide-left">
<div
v-for="(tab, i) in tabsView"
:key="tab.key"
ref="tabRef"
:class="[
{
'is-active': tab.key === active,
draggable: !tab.affixTab,
'affix-tab': tab.affixTab
}
]"
:data-active-tab="active"
:data-index="i"
class="tabs-chrome__item draggable translate-all group relative -mr-3 flex h-full select-none items-center"
data-tab-item="true"
@click="active = tab.key"
@mousedown="onMouseDown($event, tab)"
>
<VbenContextMenu :handler-data="tab" :menus="contextMenus" :modal="false" item-class="pr-6">
<div class="relative size-full px-1">
<!-- divider -->
<div v-if="i !== 0 && tab.key !== active" class="tabs-chrome__divider bg-border absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"></div>
<!-- background -->
<div class="tabs-chrome__background absolute z-[-1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150">
<div class="tabs-chrome__background-content group-[.is-active]:bg-primary/15 dark:group-[.is-active]:bg-accent h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"></div>
<svg
class="tabs-chrome__background-before group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 left-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
</svg>
<svg
class="tabs-chrome__background-after group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 right-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
</svg>
</div>
<!-- extra -->
<div class="tabs-chrome__extra absolute right-[var(--gap)] top-1/2 z-[3] size-4 translate-y-[-50%]">
<!-- close-icon -->
<X
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[2px] size-3 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('close', tab.key)"
/>
<Pin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:text-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[1px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('unpin', tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="tabs-chrome__item-main group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground text-accent-foreground z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pl-2 pr-4 duration-150"
>
<VbenIcon v-if="showIcon" :icon="tab.icon" class="mr-1 flex size-4 items-center overflow-hidden" />
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
{{ tab.title }}
</span>
</div>
</div>
</VbenContextMenu>
</div>
</TransitionGroup>
</div>
</template>
<style scoped>
.tabs-chrome {
&__item:not(.dragging) {
@apply cursor-pointer;
&:hover:not(.is-active) {
& + .tabs-chrome__item {
.tabs-chrome__divider {
@apply opacity-0;
}
}
.tabs-chrome__divider {
@apply opacity-0;
}
.tabs-chrome__background {
@apply pb-[2px];
&-content {
@apply bg-accent mx-[2px] rounded-md;
}
}
}
&.is-active {
@apply z-[2];
& + .tabs-chrome__item {
.tabs-chrome__divider {
@apply opacity-0 !important;
}
}
}
}
}
</style>
@@ -0,0 +1,125 @@
<script lang="ts" setup>
import type { TabDefinition } from "../../../typings";
import type { TabConfig, TabsProps } from "../../types";
import { computed } from "vue";
import { Pin, X } from "../../../icons";
import { VbenContextMenu, VbenIcon } from "../../../shadcn-ui";
interface Props extends TabsProps {}
defineOptions({
name: "VbenTabs",
// @ts-ignore
inheritAttrs: false
});
const props = withDefaults(defineProps<Props>(), {
contentClass: "vben-tabs-content",
contextMenus: () => [],
tabs: () => []
});
const emit = defineEmits<{
close: [string];
unpin: [TabDefinition];
}>();
const active = defineModel<string>("active");
const typeWithClass = computed(() => {
const typeClasses: Record<string, { content: string }> = {
brisk: {
content: `h-full after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-full after:h-[1.5px] after:bg-primary after:scale-x-0 after:transition-[transform] after:ease-out after:duration-300 hover:after:scale-x-100 after:origin-left [&.is-active]:after:scale-x-100 [&:not(:first-child)]:border-l last:border-r last:border-r border-border`
},
card: {
content: "h-[calc(100%-6px)] rounded-md ml-2 border border-border transition-all"
},
plain: {
content: "h-full [&:not(:first-child)]:border-l last:border-r border-border"
}
};
return typeClasses[props.styleType || "plain"] || { content: "" };
});
const tabsView = computed(() => {
return props.tabs.map((tab) => {
const { fullPath, meta, name, path } = tab || {};
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
return {
affixTab: !!affixTab,
closable: Reflect.has(meta, "tabClosable") ? !!tabClosable : true,
fullPath,
icon: icon as string,
key: fullPath || path,
meta,
name,
path,
title: (newTabTitle || title || name) as string
} as TabConfig;
});
});
function onMouseDown(e: MouseEvent, tab: TabConfig) {
if (e.button === 1 && tab.closable && !tab.affixTab && tabsView.value.length > 1 && props.middleClickToClose) {
e.preventDefault();
e.stopPropagation();
emit("close", tab.key);
}
}
</script>
<template>
<div :class="contentClass" class="relative !flex h-full w-max items-center overflow-hidden pr-6">
<TransitionGroup name="slide-left">
<div
v-for="(tab, i) in tabsView"
:key="tab.key"
:class="[
{
'is-active dark:bg-accent bg-primary/15': tab.key === active,
draggable: !tab.affixTab,
'affix-tab': tab.affixTab
},
typeWithClass.content
]"
:data-index="i"
class="tab-item [&:not(.is-active)]:hover:bg-accent translate-all group relative flex cursor-pointer select-none"
data-tab-item="true"
@click="active = tab.key"
@mousedown="onMouseDown($event, tab)"
>
<VbenContextMenu :handler-data="tab" :menus="contextMenus" :modal="false" item-class="pr-6">
<div class="relative flex size-full items-center">
<!-- extra -->
<div class="absolute right-1.5 top-1/2 z-[3] translate-y-[-50%] overflow-hidden">
<!-- close-icon -->
<X
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('close', tab.key)"
/>
<Pin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[1px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="() => emit('unpin', tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="text-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
>
<VbenIcon v-if="showIcon" :icon="tab.icon" class="mr-2 flex size-4 items-center overflow-hidden" fallback />
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
{{ tab.title }}
</span>
</div>
</div>
</VbenContextMenu>
</div>
</TransitionGroup>
</div>
</template>
@@ -0,0 +1,2 @@
export { default as TabsToolMore } from "./tool-more.vue";
export { default as TabsToolScreen } from "./tool-screen.vue";
@@ -0,0 +1,16 @@
<script lang="ts" setup>
import type { DropdownMenuProps } from "../../../shadcn-ui";
import { ChevronDown } from "../../../icons";
import { VbenDropdownMenu } from "../../../shadcn-ui";
defineProps<DropdownMenuProps>();
</script>
<template>
<VbenDropdownMenu :menus="menus" :modal="false">
<div class="flex-center hover:bg-muted hover:text-foreground text-muted-foreground border-border h-full cursor-pointer border-l px-2 text-lg font-semibold">
<ChevronDown class="size-4" />
</div>
</VbenDropdownMenu>
</template>
@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { Fullscreen, Minimize2 } from "../../../icons";
const screen = defineModel<boolean>("screen");
function toggleScreen() {
screen.value = !screen.value;
}
</script>
<template>
<div class="flex-center hover:bg-muted hover:text-foreground text-muted-foreground border-border h-full cursor-pointer border-l px-2 text-lg font-semibold" @click="toggleScreen">
<Minimize2 v-if="screen" class="size-4" />
<Fullscreen v-else class="size-4" />
</div>
</template>