mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
refactor(projects): 重构browser tab初步
This commit is contained in:
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative flex-y-center h-34px -mr-20px cursor-pointer"
|
||||
:class="{ 'z-10': isHover, 'z-11': isActive }"
|
||||
@mouseenter="handleMouseOnTab('enter')"
|
||||
@mouseleave="handleMouseOnTab('leave')"
|
||||
>
|
||||
<div class="relative w-10px h-full">
|
||||
<div class="absolute-lt w-full h-full rounded-br-8px overflow-hidden bg-white z-3"></div>
|
||||
<div
|
||||
class="absolute-rb w-full h-10px z-2 bg-opacity-10"
|
||||
:class="{ 'bg-primary': isActive, 'bg-black': !isActive && isHover }"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="flex-y-center h-full rounded-t-8px bg-opacity-10"
|
||||
:class="{ 'bg-primary': isActive, 'bg-black': !isActive && isHover }"
|
||||
>
|
||||
<div class="w-16px">
|
||||
<n-divider v-if="!isHover && !isActive" :vertical="true" class="!bg-gray-300" />
|
||||
</div>
|
||||
<span :class="[closable ? 'pr-24px' : 'pr-16px']">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<icon-close v-if="closable" :is-primary="isActive" @click="handleClose" />
|
||||
<div v-if="closable" class="w-8px"></div>
|
||||
</div>
|
||||
<div class="relative w-10px h-full">
|
||||
<div class="absolute-lt w-full h-full rounded-bl-8px overflow-hidden bg-white z-3"></div>
|
||||
<div
|
||||
class="absolute-lb w-full h-10px bg-opacity-10 z-2"
|
||||
:class="{ 'bg-primary': isActive, 'bg-black': !isActive && isHover }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { NDivider } from 'naive-ui';
|
||||
import { useBoolean } from '@/hooks';
|
||||
import { IconClose } from '../../common';
|
||||
|
||||
const props = defineProps({
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
activeIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
hoverIndex: {
|
||||
type: Number,
|
||||
default: NaN
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'update:hoverIndex']);
|
||||
|
||||
const { bool: isHover, setTrue, setFalse } = useBoolean();
|
||||
|
||||
const isActive = computed(() => props.currentIndex === props.activeIndex);
|
||||
|
||||
const hoveredIndex = ref(props.hoverIndex);
|
||||
function setHoverIndex(index: number) {
|
||||
hoveredIndex.value = index;
|
||||
}
|
||||
function resetHoverIndex() {
|
||||
hoveredIndex.value = NaN;
|
||||
}
|
||||
|
||||
function handleMouseOnTab(mode: 'enter' | 'leave') {
|
||||
if (mode === 'enter') {
|
||||
setTrue();
|
||||
setHoverIndex(props.currentIndex);
|
||||
} else {
|
||||
setFalse();
|
||||
resetHoverIndex();
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
emit('close');
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.hoverIndex,
|
||||
newValue => {
|
||||
setHoverIndex(newValue);
|
||||
}
|
||||
);
|
||||
watch(hoveredIndex, newValue => {
|
||||
emit('update:hoverIndex', newValue);
|
||||
});
|
||||
watch(
|
||||
() => props.activeIndex,
|
||||
() => {
|
||||
resetHoverIndex();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style scoped></style>
|
@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<style scoped></style>
|
@ -1,4 +1,5 @@
|
||||
import BrowserTabItem from './BrowserTabItem.vue';
|
||||
import LeftTabRadius from './LeftTabRadius.vue';
|
||||
import RightTabRadius from './RightTabRadius.vue';
|
||||
|
||||
export { LeftTabRadius, RightTabRadius };
|
||||
export { BrowserTabItem, LeftTabRadius, RightTabRadius };
|
||||
|
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
relative
|
||||
inline-flex-center
|
||||
h-30px
|
||||
px-32px
|
||||
transition-background
|
||||
duration-400
|
||||
ease-in-out
|
||||
bg-opacity-10
|
||||
cursor-pointer
|
||||
"
|
||||
:class="{ 'text-primary bg-primary z-3': active, 'bg-black z-2': isHover && !active }"
|
||||
@mouseenter="handleMouseOnTab('enter')"
|
||||
@mouseleave="handleMouseOnTab('leave')"
|
||||
>
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
<div
|
||||
v-if="closable"
|
||||
class="transition-width duration-400 ease-in-out overflow-hidden"
|
||||
:class="[isHover ? 'w-18px' : 'w-0']"
|
||||
>
|
||||
<icon-close :is-primary="active" @click="handleClose" />
|
||||
</div>
|
||||
<left-tab-radius
|
||||
class="transition-opacity duration-400 ease-in-out"
|
||||
:class="[showRadius ? 'opacity-100' : 'opacity-0']"
|
||||
:is-primary="active"
|
||||
:is-hover="isHover"
|
||||
:is-left-hover="isLeftHover"
|
||||
/>
|
||||
<right-tab-radius
|
||||
class="transition-opacity duration-400 ease-out"
|
||||
:class="[showRadius ? 'opacity-100' : 'opacity-0']"
|
||||
:is-primary="active"
|
||||
:is-hover="isHover"
|
||||
:is-right-hover="isRightHover"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useBoolean } from '@/hooks';
|
||||
import { IconClose } from '../common';
|
||||
import { LeftTabRadius, RightTabRadius } from './components';
|
||||
|
||||
const props = defineProps({
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
activeIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
hoverIndex: {
|
||||
type: Number,
|
||||
default: NaN
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['close', 'update:hoverIndex']);
|
||||
|
||||
const { bool: isHover, setTrue, setFalse } = useBoolean();
|
||||
|
||||
const hoveredIndex = ref(props.hoverIndex);
|
||||
function setHoverIndex(index: number) {
|
||||
hoveredIndex.value = index;
|
||||
}
|
||||
function resetHoverIndex() {
|
||||
hoveredIndex.value = NaN;
|
||||
}
|
||||
|
||||
const active = computed(() => props.currentIndex === props.activeIndex);
|
||||
const showRadius = computed(() => isHover.value || active.value);
|
||||
const isLeftHover = computed(() => active.value && props.activeIndex === hoveredIndex.value + 1);
|
||||
const isRightHover = computed(() => active.value && props.activeIndex === hoveredIndex.value - 1);
|
||||
|
||||
function handleMouseOnTab(mode: 'enter' | 'leave') {
|
||||
if (mode === 'enter') {
|
||||
setTrue();
|
||||
setHoverIndex(props.currentIndex);
|
||||
} else {
|
||||
setFalse();
|
||||
resetHoverIndex();
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
emit('close');
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.hoverIndex,
|
||||
newValue => {
|
||||
setHoverIndex(newValue);
|
||||
}
|
||||
);
|
||||
watch(hoveredIndex, newValue => {
|
||||
emit('update:hoverIndex', newValue);
|
||||
});
|
||||
watch(
|
||||
() => props.activeIndex,
|
||||
() => {
|
||||
resetHoverIndex();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style scoped></style>
|
@ -1,117 +1,29 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
relative
|
||||
inline-flex-center
|
||||
h-30px
|
||||
px-32px
|
||||
transition-background
|
||||
duration-400
|
||||
ease-in-out
|
||||
bg-opacity-10
|
||||
cursor-pointer
|
||||
"
|
||||
:class="{ 'text-primary bg-primary z-3': active, 'bg-black z-2': isHover && !active }"
|
||||
@mouseenter="handleMouseOnTab('enter')"
|
||||
@mouseleave="handleMouseOnTab('leave')"
|
||||
>
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
<div
|
||||
v-if="closable"
|
||||
class="transition-width duration-400 ease-in-out overflow-hidden"
|
||||
:class="[isHover ? 'w-18px' : 'w-0']"
|
||||
<div class="flex items-end h-full">
|
||||
<browser-tab-item
|
||||
v-for="(item, index) in app.multiTab.routes"
|
||||
:key="item.path"
|
||||
v-model:hover-index="hoverIndex"
|
||||
:current-index="index"
|
||||
:active-index="app.activeMultiTabIndex"
|
||||
:closable="item.name !== ROUTE_HOME.name"
|
||||
@click="handleClickTab(item.fullPath)"
|
||||
@close="removeMultiTab(item.fullPath)"
|
||||
>
|
||||
<icon-close :is-primary="active" @click="handleClose" />
|
||||
</div>
|
||||
<left-tab-radius
|
||||
class="transition-opacity duration-400 ease-in-out"
|
||||
:class="[showRadius ? 'opacity-100' : 'opacity-0']"
|
||||
:is-primary="active"
|
||||
:is-hover="isHover"
|
||||
:is-left-hover="isLeftHover"
|
||||
/>
|
||||
<right-tab-radius
|
||||
class="transition-opacity duration-400 ease-out"
|
||||
:class="[showRadius ? 'opacity-100' : 'opacity-0']"
|
||||
:is-primary="active"
|
||||
:is-hover="isHover"
|
||||
:is-right-hover="isRightHover"
|
||||
/>
|
||||
{{ item.meta?.title }}
|
||||
</browser-tab-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useBoolean } from '@/hooks';
|
||||
import { IconClose } from '../common';
|
||||
import { LeftTabRadius, RightTabRadius } from './components';
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useAppStore } from '@/store';
|
||||
import { ROUTE_HOME } from '@/router';
|
||||
import { BrowserTabItem } from './components';
|
||||
|
||||
const props = defineProps({
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
activeIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
hoverIndex: {
|
||||
type: Number,
|
||||
default: NaN
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['close', 'update:hoverIndex']);
|
||||
const app = useAppStore();
|
||||
const { removeMultiTab, handleClickTab } = useAppStore();
|
||||
|
||||
const { bool: isHover, setTrue, setFalse } = useBoolean();
|
||||
|
||||
const hoveredIndex = ref(props.hoverIndex);
|
||||
function setHoverIndex(index: number) {
|
||||
hoveredIndex.value = index;
|
||||
}
|
||||
function resetHoverIndex() {
|
||||
hoveredIndex.value = NaN;
|
||||
}
|
||||
|
||||
const active = computed(() => props.currentIndex === props.activeIndex);
|
||||
const showRadius = computed(() => isHover.value || active.value);
|
||||
const isLeftHover = computed(() => active.value && props.activeIndex === hoveredIndex.value + 1);
|
||||
const isRightHover = computed(() => active.value && props.activeIndex === hoveredIndex.value - 1);
|
||||
|
||||
function handleMouseOnTab(mode: 'enter' | 'leave') {
|
||||
if (mode === 'enter') {
|
||||
setTrue();
|
||||
setHoverIndex(props.currentIndex);
|
||||
} else {
|
||||
setFalse();
|
||||
resetHoverIndex();
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
emit('close');
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.hoverIndex,
|
||||
newValue => {
|
||||
setHoverIndex(newValue);
|
||||
}
|
||||
);
|
||||
watch(hoveredIndex, newValue => {
|
||||
emit('update:hoverIndex', newValue);
|
||||
});
|
||||
watch(
|
||||
() => props.activeIndex,
|
||||
() => {
|
||||
resetHoverIndex();
|
||||
}
|
||||
);
|
||||
const hoverIndex = ref(NaN);
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative flex-center w-18px h-18px text-14px"
|
||||
:class="{ 'text-primary': isPrimary }"
|
||||
:class="[isPrimary ? 'text-primary' : 'text-gray-400']"
|
||||
@mouseenter="setTrue"
|
||||
@mouseleave="setFalse"
|
||||
>
|
||||
|
@ -21,21 +21,7 @@
|
||||
{{ item.meta?.title }}
|
||||
</button-tab>
|
||||
</n-space>
|
||||
<n-space v-if="theme.multiTabStyle.mode === 'browser'" :align="'flex-end'" :size="0" class="h-full px-16px">
|
||||
<browser-tab
|
||||
v-for="(item, index) in app.multiTab.routes"
|
||||
:key="item.path"
|
||||
v-model:hover-index="hoverIndex"
|
||||
:current-index="index"
|
||||
:active-index="app.activeMultiTabIndex"
|
||||
:closable="item.name !== ROUTE_HOME.name"
|
||||
@click="handleClickTab(item.fullPath)"
|
||||
@close="removeMultiTab(item.fullPath)"
|
||||
@contextmenu="handleContextMenu($event, item.fullPath)"
|
||||
>
|
||||
{{ item.meta?.title }}
|
||||
</browser-tab>
|
||||
</n-space>
|
||||
<browser-tab />
|
||||
<reload-button />
|
||||
<context-menu
|
||||
:visible="dropdownVisible"
|
||||
|
@ -14,6 +14,8 @@ const app = useAppStore();
|
||||
const theme = useThemeStore();
|
||||
const title = useAppTitle();
|
||||
|
||||
const showTitle = computed(() => !app.menu.collapsed || !theme.isVerticalNav);
|
||||
const showTitle = computed(
|
||||
() => !theme.isVerticalNav || (!app.menu.collapsed && theme.navStyle.mode !== 'vertical-mix')
|
||||
);
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
Reference in New Issue
Block a user