🔖 项目重命名.

This commit is contained in:
lijiahangmax
2024-05-16 00:03:30 +08:00
parent f7189e34cb
commit d3a045ec20
1511 changed files with 4199 additions and 4128 deletions

View File

@@ -0,0 +1,42 @@
<template>
<a-layout-footer class="footer">
<a-space direction="vertical" size="small">
<a-space size="large">
<!-- <a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor">官网</a-link> -->
<!-- <a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor">教程</a-link> -->
<a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor">github</a-link>
<a-link target="_blank" href="https://gitee.com/lijiahangmax/orion-visor">gitee</a-link>
<a-link target="_blank" href="https://lijiahangmax.github.io/orion-visor">文档</a-link>
<a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor/blob/main/LICENSE">License</a-link>
<a-link target="_blank" :href="`https://github.com/lijiahangmax/orion-visor/releases/tag/v${version}`">v{{ version }} Community</a-link>
</a-space>
<span class="copyright">
Copyright<icon-copyright /> {{ new Date().getFullYear() }} Li Jiahang All rights reserved.
</span>
</a-space>
</a-layout-footer>
</template>
<script lang="ts" setup>
const version = import.meta.env.VITE_APP_VERSION;
</script>
<style lang="less" scoped>
.footer {
display: flex;
align-items: flex-start;
justify-content: center;
text-align: center;
height: 64px;
a, span {
text-decoration: none;
color: rgb(var(--primary-6));
}
.copyright {
color: var(--color-text-3);
}
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<a-breadcrumb class="container-breadcrumb">
<a-breadcrumb-item>
<icon-apps />
</a-breadcrumb-item>
<a-breadcrumb-item v-for="item in items" :key="item">
{{ item }}
</a-breadcrumb-item>
</a-breadcrumb>
</template>
<script lang="ts" setup>
import { useRoute } from 'vue-router';
const props = withDefaults(defineProps<{
items?: Array<string>;
}>(), {
items: () => {
return useRoute().matched
.map(s => s.meta?.locale as string)
.filter(Boolean) || [];
},
});
</script>
<style lang="less" scoped>
.container-breadcrumb {
:deep(.arco-breadcrumb-item) {
color: rgb(var(--gray-6));
&:last-child {
color: rgb(var(--gray-8));
}
}
}
</style>

View File

@@ -0,0 +1 @@
export const preferenceTipsKey = 'home:preference';

View File

@@ -0,0 +1,417 @@
<template>
<div class="navbar">
<!-- 左侧按钮 -->
<div class="left-side">
<a-space>
<!-- LOGO -->
<img class="left-side-logo"
alt="logo"
draggable="false"
src="@/assets/images/logo.svg?url" />
<!-- 标头 -->
<a-typography-title :heading="5"
:style="{ margin: 0, fontSize: '18px', height: '1.4em', overflow: 'hidden' }">
Orion Visor
</a-typography-title>
<!-- 收缩菜单 -->
<icon-menu-fold v-if="!topMenu && appStore.device === 'mobile'"
style="font-size: 22px; cursor: pointer"
@click="toggleDrawerMenu" />
</a-space>
</div>
<!-- 顶部菜单 -->
<div class="center-side">
<system-menu-tree v-if="topMenu" />
</div>
<!-- 右侧操作 -->
<ul class="right-side">
<!-- 主机终端 -->
<li v-permission="['asset:host-terminal:access']">
<a-tooltip content="主机终端">
<a-button class="nav-btn"
type="outline"
shape="circle"
@click="openNewRoute({ name: 'terminal' })">
<template #icon>
<icon-code-square />
</template>
</a-button>
</a-tooltip>
</li>
<!-- 切换语言 -->
<li v-if="false">
<a-tooltip content="语言">
<a-button class="nav-btn"
type="outline"
shape="circle"
@click="setLocalesVisible">
<template #icon>
<icon-language />
</template>
</a-button>
</a-tooltip>
<a-dropdown trigger="click" @select="s => changeLocale(s as string)">
<div ref="localeRef" class="trigger-btn" />
<template #content>
<a-doption v-for="item in locales"
:key="item.value"
:value="item.value">
<template #icon>
<icon-check v-show="item.value === currentLocale" />
</template>
{{ item.label }}
</a-doption>
</template>
</a-dropdown>
</li>
<!-- 暗色模式 -->
<li>
<a-tooltip :content="theme === 'light'
? '点击切换为暗黑模式'
: '点击切换为亮色模式'">
<a-button class="nav-btn"
type="outline"
shape="circle"
@click="handleToggleTheme">
<template #icon>
<icon-moon-fill v-if="theme === 'dark'" />
<icon-sun-fill v-else />
</template>
</a-button>
</a-tooltip>
</li>
<!-- 系统消息 -->
<li>
<a-tooltip content="系统消息" :show-arrow="false">
<div class="message-box-trigger">
<a-badge :count="messageCount" dot>
<a-button class="nav-btn"
type="outline"
shape="circle"
@click="setMessageBoxVisible">
<icon-notification />
</a-button>
</a-badge>
</div>
</a-tooltip>
<a-popover trigger="click"
content-class="message-popover"
position="br"
:show-arrow="false"
:popup-style="{ marginLeft: '198px' }"
:content-style="{ padding: 0, width: '428px' }"
@hide="pullHasUnreadMessage">
<div ref="messageRef" class="ref-btn" />
<template #content>
<message-box />
</template>
</a-popover>
</li>
<!-- 全屏模式 -->
<li>
<a-tooltip :content="isFullscreen
? '点击退出全屏模式'
: '点击切换全屏模式'">
<a-button class="nav-btn"
type="outline"
shape="circle"
@click="toggleFullScreen">
<template #icon>
<icon-fullscreen-exit v-if="isFullscreen" />
<icon-fullscreen v-else />
</template>
</a-button>
</a-tooltip>
</li>
<!-- 刷新页面 -->
<li>
<a-tooltip content="刷新页面">
<a-button class="nav-btn"
type="outline"
shape="circle"
@click="reloadCurrent">
<template #icon>
<icon-refresh />
</template>
</a-button>
</a-tooltip>
</li>
<!-- 偏好设置 -->
<li>
<a-popover :popup-visible="tippedPreference" position="br">
<template #title>
💡 点击这里可以修改系统偏好~
</template>
<template #content>
<span style="line-height: 1.8">
可以修改页面布局<br>
可以切换显示视图
</span>
<div class="tips-buttons">
<a-button size="mini" class="mr8" @click="closePreferenceTip(false)">关闭</a-button>
<a-button size="mini" type="primary" @click="closePreferenceTip(true)">我知道了</a-button>
</div>
</template>
<a-tooltip content="偏好设置">
<a-button class="nav-btn"
type="outline"
shape="circle"
@click="openAppSetting">
<template #icon>
<icon-settings />
</template>
</a-button>
</a-tooltip>
</a-popover>
</li>
<!-- 用户信息 -->
<li>
<a-dropdown trigger="click" position="br">
<!-- 头像 -->
<a-avatar draggable="false"
:size="32"
:style="{ cursor: 'pointer', backgroundColor: 'rgb(var(--primary-6))', userSelect: 'none' }">
{{ nickname }}
</a-avatar>
<template #content>
<!-- 个人中心 -->
<a-doption>
<a-space @click="$router.push({ name: 'userInfo' })">
<icon-user />
<span>个人中心</span>
</a-space>
</a-doption>
<!-- 修改密码 -->
<a-doption>
<a-space @click="() => updatePasswordRef.open()">
<icon-lock />
<span>修改密码</span>
</a-space>
</a-doption>
<!-- 退出登录 -->
<a-doption>
<a-space @click="handleLogout">
<icon-export />
<span>退出登录</span>
</a-space>
</a-doption>
</template>
</a-dropdown>
</li>
</ul>
<!-- 修改密码模态框-->
<update-password-modal ref="updatePasswordRef" @updated="handleLogout" />
</div>
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
import useLocale from '@/hooks/locale';
import useUser from '@/hooks/user';
import { useRoute, useRouter } from 'vue-router';
import { useDark, useFullscreen, useToggle } from '@vueuse/core';
import { useAppStore, useTabBarStore, useTipsStore, useUserStore } from '@/store';
import { LOCALE_OPTIONS } from '@/locale';
import { triggerMouseEvent } from '@/utils/event';
import { openAppSettingKey, toggleDrawerMenuKey } from '@/types/symbol';
import { preferenceTipsKey } from './const';
import { REDIRECT_ROUTE_NAME, routerToTag } from '@/router/constants';
import { openNewRoute } from '@/router';
import { checkHasUnreadMessage } from '@/api/system/message';
import SystemMenuTree from '@/components/system/menu/tree/index.vue';
import MessageBox from '@/components/system/message-box/index.vue';
import UpdatePasswordModal from '@/components/user/user/update-password-modal/index.vue';
const tipsStore = useTipsStore();
const appStore = useAppStore();
const userStore = useUserStore();
const tabBarStore = useTabBarStore();
const route = useRoute();
const router = useRouter();
const { logout } = useUser();
const { changeLocale, currentLocale } = useLocale();
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
// 主题
const darkTheme = useDark({
selector: 'body',
attribute: 'arco-theme',
valueDark: 'dark',
valueLight: 'light',
storageKey: 'arco-theme',
onChanged(dark: boolean) {
appStore.updateSettings({
theme: dark ? 'dark' : 'light'
});
},
});
// 用户名
const nickname = computed(() => userStore.nickname?.substring(0, 1));
// 是否展示顶部菜单
const topMenu = computed(() => appStore.topMenu && appStore.menu);
// 当前主题
const theme = computed(() => appStore.theme);
const locales = [...LOCALE_OPTIONS];
// 偏好提示
const tippedPreference = ref(tipsStore.isNotTipped(preferenceTipsKey));
// 修改密码
const updatePasswordRef = ref();
// 消息
const messageRef = ref();
// 语言
const localeRef = ref();
// 消息数量
const messageCount = ref(0);
const messageIntervalId = ref();
// 打开应用设置
const openAppSetting = inject(openAppSettingKey) as () => void;
// 注入收缩菜单
const toggleDrawerMenu = inject(toggleDrawerMenuKey) as () => void;
// 切换主题
const handleToggleTheme = () => {
useToggle(darkTheme)();
};
// 打开消息
const setMessageBoxVisible = () => {
triggerMouseEvent(messageRef);
};
// 打开语言切换
const setLocalesVisible = () => {
triggerMouseEvent(localeRef);
};
// 刷新页面
const reloadCurrent = async () => {
if (appStore.tabBar) {
// 重新加载 tab
const itemData = routerToTag(route);
tabBarStore.deleteCache(itemData);
await router.push({
name: REDIRECT_ROUTE_NAME,
params: { path: route.fullPath },
});
tabBarStore.addCache(itemData.name);
} else {
// 刷新页面
router.go(0);
}
};
// 退出登录
const handleLogout = async () => {
await logout();
};
// 获取是否有未读的消息
const pullHasUnreadMessage = () => {
// 查询
checkHasUnreadMessage().then(({ data }) => {
messageCount.value = data ? 1 : 0;
});
};
// 关闭偏好提示
const closePreferenceTip = (ack: boolean) => {
tippedPreference.value = false;
if (ack) {
tipsStore.setTipped(preferenceTipsKey);
}
};
onMounted(() => {
// 查询未读消息
pullHasUnreadMessage();
// 注册未读消息轮询
messageIntervalId.value = setInterval(pullHasUnreadMessage, 30000);
});
onUnmounted(() => {
// 清理消息轮询
clearInterval(messageIntervalId.value);
});
</script>
<style lang="less" scoped>
.navbar {
display: flex;
justify-content: space-between;
height: 100%;
background-color: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
}
.left-side {
user-select: none;
display: flex;
align-items: center;
padding-left: 20px;
color: var(--color-text-1);
&-logo {
width: 32px;
height: 32px;
}
}
.center-side {
flex: 1;
}
.right-side {
display: flex;
list-style: none;
:deep(.locale-select) {
border-radius: 20px;
}
li {
display: flex;
align-items: center;
padding: 0 10px;
}
a {
color: var(--color-text-1);
text-decoration: none;
}
.nav-btn {
border-color: rgb(var(--gray-2));
color: rgb(var(--gray-8));
font-size: 16px;
}
.trigger-btn,
.ref-btn {
position: absolute;
bottom: 14px;
}
.trigger-btn {
margin-left: 14px;
}
}
</style>
<style lang="less">
.message-popover {
.arco-popover-content {
margin-top: 0;
}
}
.tips-buttons {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div class="block">
<h5 class="title">{{ title }}</h5>
<template v-for="option in options" :key="option.name">
<div class="option-wrapper"
v-permission="option.permission || []"
:style="{ 'margin': option.margin || '' }">
<!-- 偏好项 -->
<span>{{ option.name }}</span>
<!-- 偏好值 -->
<form-wrapper :name="option.key"
:type="option.type as string"
:default-value="option.defaultVal"
:options="option.options"
@input-change="handleChange" />
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import type { RadioOption } from '@arco-design/web-vue/es/radio/interface';
import type { SelectOption } from '@arco-design/web-vue/es/select/interface';
import { useAppStore } from '@/store';
import { updatePreference } from '@/api/user/preference';
import FormWrapper from './form-wrapper.vue';
interface OptionsProps {
name: string;
key: string;
type?: string;
permission?: string[];
defaultVal?: boolean | string | number;
options?: Array<RadioOption | SelectOption>;
margin?: string;
}
defineProps<Partial<{
title: string;
options: Array<OptionsProps>;
}>>();
const appStore = useAppStore();
/**
* 修改配置
*/
const handleChange = async ({ key, value, }: {
key: string;
value: unknown;
}) => {
// 顶部菜单
if (key === 'topMenu') {
appStore.updateSettings({
menuCollapse: false,
});
}
// 修改配置
appStore.updateSettings({ [key]: value });
// 同步偏好
try {
await updatePreference({
type: 'SYSTEM',
item: key,
value
});
} catch (e) {
}
};
</script>
<style lang="less" scoped>
.block {
margin-bottom: 24px;
user-select: none;
}
.title {
margin: 10px 0;
padding: 0;
font-size: 14px;
}
.option-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<!-- 数字框 -->
<a-input-number v-if="type === 'number'"
:style="{ width: '80px' }"
size="small"
:precision="0"
:default-value="defaultValue as number"
@change="handleChange"
hide-button />
<!-- 开关 -->
<a-switch v-else-if="type === 'switch'"
type="round"
:default-checked="defaultValue as boolean"
size="small"
@change="handleChange" />
<!-- 单选按钮 -->
<a-radio-group v-else-if="type === 'radio-group'"
type="button"
size="small"
:default-value="defaultValue"
:options="options as Array<RadioOption>"
@change="handleChange" />
<!-- 选择框 -->
<a-select v-else-if="type === 'select'"
size="small"
style="width: 128px;"
:default-value="defaultValue"
:options="options as Array<SelectOption>"
@change="handleChange" />
</template>
<script lang="ts" setup>
import type { RadioOption } from '@arco-design/web-vue/es/radio/interface';
import type { SelectOption } from '@arco-design/web-vue/es/select/interface';
const props = withDefaults(defineProps<Partial<{
type: string;
name: string;
defaultValue: string | boolean | number;
options: Array<RadioOption | SelectOption>;
}>>(), {
type: 'switch',
name: '',
defaultValue: '',
options: () => []
});
const emit = defineEmits(['inputChange']);
const handleChange = (value: unknown) => {
emit('inputChange', {
value,
key: props.name,
});
};
</script>

View File

@@ -0,0 +1,163 @@
<template>
<!-- 左侧固定配置按钮 -->
<div v-if="!appStore.navbar" class="fixed-settings" @click="open">
<a-button type="primary">
<template #icon>
<icon-settings />
</template>
</a-button>
</div>
<!-- 偏好配置抽屉 -->
<a-drawer v-model:visible="visible"
title="偏好设置"
:width="300"
:footer="false"
:unmount-on-close="true"
@cancel="() => setVisible(false)">
<div class="preference-containers">
<!-- 布局设置 -->
<block :options="layoutOpts" title="布局设置" />
<!-- 数据设置 -->
<block :options="dataOpts" title="数据设置" />
<!-- 页面视图 -->
<block :options="viewsOpts" title="页面视图" />
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useAppStore } from '@/store';
import useVisible from '@/hooks/visible';
import { CardPageSizeOptions, TablePageSizeOptions } from '@/types/const';
import Block from './block.vue';
const appStore = useAppStore();
const { visible, setVisible } = useVisible();
// 打开
const open = () => {
setVisible(true);
};
defineExpose({ open });
// 布局设置
const layoutOpts = computed(() => [
{
name: '导航栏',
key: 'navbar',
defaultVal: appStore.navbar
},
{
name: '菜单栏',
key: 'menu',
defaultVal: appStore.menu,
},
{
name: '顶部菜单栏',
key: 'topMenu',
defaultVal: appStore.topMenu,
},
{
name: '底部页脚',
key: 'footer',
defaultVal: appStore.footer
},
{
name: '多页签',
key: 'tabBar',
defaultVal: appStore.tabBar
},
{
name: '色弱模式',
key: 'colorWeak',
defaultVal: appStore.colorWeak,
},
{
name: '菜单宽度 (px)',
key: 'menuWidth',
type: 'number',
defaultVal: appStore.menuWidth,
},
]);
// 布局设置
const dataOpts = computed(() => [
{
name: '表格默认页数',
key: 'defaultTablePageSize',
type: 'select',
margin: '0 0 4px 0',
defaultVal: appStore.defaultTablePageSize,
options: TablePageSizeOptions.map(s => {
return {
value: s,
label: `${s} 条/页`
};
})
},
{
name: '卡片默认页数',
key: 'defaultCardPageSize',
type: 'select',
defaultVal: appStore.defaultCardPageSize,
options: CardPageSizeOptions.map(s => {
return {
value: s,
label: `${s} 条/页`
};
})
},
]);
// 页面视图配置
const viewsOpts = computed(() => [
{
name: '主机列表',
key: 'hostView',
type: 'radio-group',
margin: '0 0 4px 0',
permission: ['asset:host:query'],
defaultVal: appStore.hostView,
options: [{ value: 'table', label: '表格' }, { value: 'card', label: '卡片' }]
},
{
name: '主机秘钥',
key: 'hostKeyView',
type: 'radio-group',
margin: '0 0 4px 0',
permission: ['asset:host-key:query'],
defaultVal: appStore.hostKeyView,
options: [{ value: 'table', label: '表格' }, { value: 'card', label: '卡片' }]
},
{
name: '主机身份',
key: 'hostIdentityView',
type: 'radio-group',
margin: '0 0 4px 0',
permission: ['asset:host-identity:query'],
defaultVal: appStore.hostIdentityView,
options: [{ value: 'table', label: '表格' }, { value: 'card', label: '卡片' }]
},
]);
</script>
<style lang="less" scoped>
.fixed-settings {
position: fixed;
top: 280px;
right: 0;
svg {
font-size: 18px;
vertical-align: -4px;
}
}
.preference-containers{
padding: 0 16px;
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div class="tab-bar-container">
<a-affix ref="affixRef" :offset-top="offsetTop">
<div class="tab-bar-box">
<div class="tab-bar-scroll">
<div class="tags-wrap">
<tab-item v-for="(tag, index) in tagList"
:key="tag.fullPath"
:index="index"
:item-data="tag" />
</div>
</div>
<div class="tag-bar-operation"></div>
</div>
</a-affix>
</div>
</template>
<script lang="ts" setup>
import type { RouteLocationNormalized } from 'vue-router';
import { computed, onUnmounted, ref, watch } from 'vue';
import { routerToTag } from '@/router/constants';
import { listenerRouteChange, removeRouteListener, } from '@/utils/route-listener';
import { useAppStore, useTabBarStore } from '@/store';
import TabItem from './tab-item.vue';
const appStore = useAppStore();
const tabBarStore = useTabBarStore();
const affixRef = ref();
const tagList = computed(() => {
return tabBarStore.getTabList;
});
const offsetTop = computed(() => {
return appStore.navbar ? 60 : 0;
});
watch(
() => appStore.navbar,
() => {
affixRef.value.updatePosition();
}
);
// 监听路由变化
listenerRouteChange((route: RouteLocationNormalized) => {
if (
!route.meta.noAffix &&
!tagList.value.some((tag) => tag.path === route.path)
) {
// 固定并且没有此 tab 则添加
tabBarStore.addTab(routerToTag(route), route.meta?.ignoreCache as unknown as boolean);
}
}, true);
onUnmounted(() => {
removeRouteListener();
});
</script>
<style lang="less" scoped>
.tab-bar-container {
position: relative;
background-color: var(--color-bg-2);
.tab-bar-box {
display: flex;
padding: 0 0 0 6px;
background-color: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
.tab-bar-scroll {
height: 32px;
flex: 1;
overflow: hidden;
.tags-wrap {
padding: 4px 0;
height: 48px;
white-space: nowrap;
overflow-x: auto;
:deep(.arco-tag) {
display: inline-flex;
align-items: center;
margin-right: 6px;
cursor: pointer;
&:first-child {
.arco-tag-close-btn {
display: none;
}
}
.tag-link {
user-select: none;
}
}
}
}
}
.tag-bar-operation {
width: 100px;
height: 32px;
}
}
</style>

View File

@@ -0,0 +1,199 @@
<template>
<a-dropdown trigger="contextMenu"
:popup-max-height="false"
@select="actionSelect">
<span class="arco-tag arco-tag-size-medium arco-tag-checked"
:class="{ 'link-activated': itemData?.path === $route.path }"
@click="goto(itemData as TagProps)">
<span class="tag-link">
{{ itemData.title }}
</span>
<span class="arco-icon-hover arco-tag-icon-hover arco-icon-hover-size-medium arco-tag-close-btn"
@click.stop="tagClose(itemData as TagProps, index)">
<icon-close />
</span>
</span>
<template #content>
<a-doption :disabled="disabledReload" :value="Option.reload">
<icon-refresh />
<span>重新加载</span>
</a-doption>
<a-doption
class="group-line"
:disabled="disabledCurrent"
:value="Option.current">
<icon-close />
<span>关闭当前标签页</span>
</a-doption>
<a-doption :disabled="disabledLeft" :value="Option.left">
<icon-to-left />
<span>关闭左侧标签页</span>
</a-doption>
<a-doption
class="group-line"
:disabled="disabledRight"
:value="Option.right">
<icon-to-right />
<span>关闭右侧标签页</span>
</a-doption>
<a-doption :value="Option.others">
<icon-swap />
<span>关闭其它标签页</span>
</a-doption>
<a-doption :value="Option.all">
<icon-folder-delete />
<span>关闭全部标签页</span>
</a-doption>
</template>
</a-dropdown>
</template>
<script lang="ts" setup>
import type { TagProps } from '@/store/modules/tab-bar/types';
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useTabBarStore } from '@/store';
import { DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
enum Option {
reload = 'reload',
current = 'current',
left = 'left',
right = 'right',
others = 'others',
all = 'all',
}
const props = defineProps<{
index: number;
itemData: TagProps;
}>();
const router = useRouter();
const route = useRoute();
const tabBarStore = useTabBarStore();
const goto = (tag: TagProps) => {
router.push({ ...tag });
};
const tagList = computed(() => {
return tabBarStore.getTabList;
});
const disabledReload = computed(() => {
return props.itemData.path !== route.path;
});
const disabledCurrent = computed(() => {
return props.index === 0;
});
const disabledLeft = computed(() => {
return [0, 1].includes(props.index);
});
const disabledRight = computed(() => {
return props.index === tagList.value.length - 1;
});
// 关闭 tag
const tagClose = (tag: TagProps, idx: number) => {
tabBarStore.deleteTab(idx, tag);
if (props.itemData.path === route.path) {
// 获取队列的前一个 tab
const latest = tagList.value[idx - 1];
router.push({ name: latest.name });
}
};
// 获取当前路由索引
const findCurrentRouteIndex = () => {
return tagList.value.findIndex((el) => el.path === route.path);
};
// 选择操作
const actionSelect = async (value: any) => {
const { itemData, index } = props;
const copyTagList = [...tagList.value];
if (value === Option.current) {
// 关闭当前
tagClose(itemData, index);
} else if (value === Option.left) {
// 关闭左侧
const currentRouteIdx = findCurrentRouteIndex();
copyTagList.splice(1, props.index - 1);
tabBarStore.freshTabList(copyTagList);
if (currentRouteIdx < index) {
await router.push({ name: itemData.name });
}
} else if (value === Option.right) {
// 关闭右侧
const currentRouteIdx = findCurrentRouteIndex();
copyTagList.splice(props.index + 1);
tabBarStore.freshTabList(copyTagList);
if (currentRouteIdx > index) {
await router.push({ name: itemData.name });
}
} else if (value === Option.others) {
// 关闭其他
const filterList = tagList.value.filter((el, idx) => {
return idx === 0 || idx === props.index;
});
tabBarStore.freshTabList(filterList);
await router.push({ name: itemData.name });
} else if (value === Option.reload) {
// 重新加载
tabBarStore.deleteCache(itemData);
await router.push({
name: REDIRECT_ROUTE_NAME,
params: { path: route.fullPath },
});
tabBarStore.addCache(itemData.name);
} else {
// 关闭全部
tabBarStore.resetTabList();
await router.push({ name: DEFAULT_ROUTE_NAME });
}
};
</script>
<style lang="less" scoped>
.tag-link {
color: var(--color-text-2);
text-decoration: none;
}
.link-activated {
color: rgb(var(--link-6));
.tag-link {
color: rgb(var(--link-6));
}
& + .arco-tag-close-btn {
color: rgb(var(--link-6));
}
}
:deep(.arco-dropdown-option-content) {
span {
margin-left: 10px;
}
}
.arco-dropdown-open {
.tag-link {
color: rgb(var(--danger-6));
}
.arco-tag-close-btn {
color: rgb(var(--danger-6));
}
}
.group-line {
border-bottom: 1px solid var(--color-neutral-3);
}
</style>