update 调整代码格式

This commit is contained in:
疯狂的狮子Li
2023-04-03 00:05:09 +08:00
parent 8b01bfd2a0
commit 1595cb282a
194 changed files with 12184 additions and 12267 deletions

View File

@ -1,6 +1,19 @@
<template>
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition :enter-active-class="animante" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews">
<component v-if="!route.meta.link" :is="Component" :key="route.path" />
</keep-alive>
</transition>
</router-view>
<iframe-toggle />
</section>
</template>
<script lang="ts">
export default {
name: 'AppMin'
name: 'AppMin'
}
</script>
@ -16,28 +29,15 @@ const tagsViewStore = useTagsViewStore();
const animante = ref<string>('');
const animationEnable = ref(useSettingsStore().animationEnable);
watch(()=> useSettingsStore().animationEnable, (val) => {
animationEnable.value = val;
if (val) {
animante.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
} else {
animante.value = proxy?.animate.defaultAnimate as string;
}
animationEnable.value = val;
if (val) {
animante.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
} else {
animante.value = proxy?.animate.defaultAnimate as string;
}
}, { immediate: true });
</script>
<template>
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition :enter-active-class="animante" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews">
<component v-if="!route.meta.link" :is="Component" :key="route.path" />
</keep-alive>
</transition>
</router-view>
<iframe-toggle />
</section>
</template>
<style lang="scss" scoped>
.app-main {
/* 50= navbar 50 */

View File

@ -7,13 +7,13 @@ const tagsViewStore = useTagsViewStore()
</script>
<template>
<transition-group name="fade-transform" mode="out-in">
<inner-link
v-for="(item, index) in tagsViewStore.iframeViews"
:key="item.path"
:iframeId="'iframe' + index"
v-show="route.path === item.path"
:src="item.meta ? item.meta.link : ''"
></inner-link>
</transition-group>
<transition-group name="fade-transform" mode="out-in">
<inner-link
v-for="(item, index) in tagsViewStore.iframeViews"
:key="item.path"
:iframeId="'iframe' + index"
v-show="route.path === item.path"
:src="item.meta ? item.meta.link : ''"
></inner-link>
</transition-group>
</template>

View File

@ -1,18 +1,18 @@
<template>
<div :style="'height:' + height">
<iframe :id="iframeId" style="width: 100%; height: 100%" :src="src" frameborder="no"></iframe>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
src: {
type: String,
default: "/"
},
iframeId: {
type: String
}
src: {
type: String,
default: "/"
},
iframeId: {
type: String
}
});
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
</script>
<template>
<div :style="'height:' + height">
<iframe :id="iframeId" style="width: 100%; height: 100%" :src="src" frameborder="no"></iframe>
</div>
</template>
</script>

View File

@ -1,3 +1,68 @@
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
<div class="right-menu flex align-center">
<template v-if="appStore.device !== 'mobile'">
<el-select
v-model="companyName"
clearable
filterable
reserve-keyword
placeholder="请选择租户"
v-if="userId === 1 && tenantEnabled"
@change="dynamicTenantEvent"
@clear="dynamicClearEvent"
>
<el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
<template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
</el-select>
<header-search id="header-search" class="right-menu-item" />
<el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="文档地址" effect="dark" placement="bottom">
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="全屏" effect="dark" placement="bottom">
<screenfull id="screenfull" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/user/profile" v-if="!dynamic">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item command="setLayout">
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
@ -23,130 +88,65 @@ const tenantEnabled = ref(true);
// 动态切换
const dynamicTenantEvent = async (tenantId: string) => {
if (companyName.value != null && companyName.value !== '') {
await dynamicTenant(tenantId);
dynamic.value = true;
proxy?.$tab.closeAllPage();
proxy?.$router.push('/');
}
if (companyName.value != null && companyName.value !== '') {
await dynamicTenant(tenantId);
dynamic.value = true;
proxy?.$tab.closeAllPage();
proxy?.$router.push('/');
}
}
const dynamicClearEvent = async () => {
await dynamicClear();
dynamic.value = false;
proxy?.$tab.closeAllPage();
proxy?.$router.push('/')
await dynamicClear();
dynamic.value = false;
proxy?.$tab.closeAllPage();
proxy?.$router.push('/')
}
/** 租户列表 */
const initTenantList = async () => {
const { data } = await getTenantList();
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
if (tenantEnabled.value) {
tenantList.value = data.voList;
}
const { data } = await getTenantList();
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
if (tenantEnabled.value) {
tenantList.value = data.voList;
}
}
defineExpose({
initTenantList,
initTenantList,
})
const toggleSideBar = () => {
appStore.toggleSideBar()
appStore.toggleSideBar()
}
const logout = async () => {
await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await userStore.logout()
location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
await userStore.logout()
location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
}
const emits = defineEmits(['setLayout'])
const setLayout = () => {
emits('setLayout');
emits('setLayout');
}
// 定义Command方法对象 通过key直接调用方法
const commandMap: {[key: string]: any} = {
setLayout,
logout
setLayout,
logout
};
const handleCommand = (command: string) => {
// 判断是否存在该方法
if (commandMap[command]) {
commandMap[command]();
}
// 判断是否存在该方法
if (commandMap[command]) {
commandMap[command]();
}
}
</script>
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
<div class="right-menu flex align-center">
<template v-if="appStore.device !== 'mobile'">
<el-select
v-model="companyName"
clearable
filterable
reserve-keyword
placeholder="请选择租户"
v-if="userId === 1 && tenantEnabled"
@change="dynamicTenantEvent"
@clear="dynamicClearEvent"
>
<el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
<template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
</el-select>
<header-search id="header-search" class="right-menu-item" />
<el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="文档地址" effect="dark" placement="bottom">
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="全屏" effect="dark" placement="bottom">
<screenfull id="screenfull" class="right-menu-item hover-effect" />
</el-tooltip>
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/user/profile" v-if="!dynamic">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item command="setLayout">
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-select .el-input__wrapper) {

View File

@ -101,86 +101,86 @@ defineExpose({
</script>
<template>
<el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px" close-on-click-modal>
<div class="setting-drawer-title">
<h3 class="drawer-title">主题风格设置</h3>
</div>
<div class="setting-drawer-block-checbox">
<div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
<img src="@/assets/images/dark.svg" alt="dark" />
<div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
<i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
<path
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
/>
</svg>
</i>
</div>
</div>
<div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
<img src="@/assets/images/light.svg" alt="light" />
<div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
<i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
<path
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
/>
</svg>
</i>
</div>
</div>
</div>
<div class="drawer-item">
<span>主题颜色</span>
<span class="comp-style">
<el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange" />
</span>
</div>
<el-divider />
<el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px" close-on-click-modal>
<div class="setting-drawer-title">
<h3 class="drawer-title">主题风格设置</h3>
</div>
<div class="setting-drawer-block-checbox">
<div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
<img src="@/assets/images/dark.svg" alt="dark" />
<div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
<i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
<path
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
/>
</svg>
</i>
</div>
</div>
<div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
<img src="@/assets/images/light.svg" alt="light" />
<div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
<i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
<path
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
/>
</svg>
</i>
</div>
</div>
</div>
<div class="drawer-item">
<span>主题颜色</span>
<span class="comp-style">
<el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange" />
</span>
</div>
<el-divider />
<h3 class="drawer-title">系统布局配置</h3>
<h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>开启 TopNav</span>
<span class="comp-style">
<el-switch v-model="topNav" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>开启 TopNav</span>
<span class="comp-style">
<el-switch v-model="topNav" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>开启 Tags-Views</span>
<span class="comp-style">
<el-switch v-model="tagsView" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>开启 Tags-Views</span>
<span class="comp-style">
<el-switch v-model="tagsView" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>固定 Header</span>
<span class="comp-style">
<el-switch v-model="fixedHeader" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>固定 Header</span>
<span class="comp-style">
<el-switch v-model="fixedHeader" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>显示 Logo</span>
<span class="comp-style">
<el-switch v-model="sidebarLogo" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>显示 Logo</span>
<span class="comp-style">
<el-switch v-model="sidebarLogo" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>动态标题</span>
<span class="comp-style">
<el-switch v-model="dynamicTitle" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>动态标题</span>
<span class="comp-style">
<el-switch v-model="dynamicTitle" class="drawer-switch" />
</span>
</div>
<el-divider />
<el-divider />
<el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button>
<el-button plain icon="Refresh" @click="resetSetting">重置配置</el-button>
</el-drawer>
<el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button>
<el-button plain icon="Refresh" @click="resetSetting">重置配置</el-button>
</el-drawer>
</template>
<style lang="scss" scoped>

View File

@ -34,7 +34,7 @@ function linkProps() {
</script>
<template>
<component :is="type" v-bind="linkProps()">
<slot />
</component>
<component :is="type" v-bind="linkProps()">
<slot />
</component>
</template>

View File

@ -18,26 +18,26 @@ const sideTheme = computed(() => settingsStore.sideTheme);
</script>
<template>
<div
class="sidebar-logo-container"
:class="{ 'collapse': collapse }"
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
>
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }}
</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }}
</h1>
</router-link>
</transition>
</div>
<div
class="sidebar-logo-container"
:class="{ 'collapse': collapse }"
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
>
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }}
</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }}
</h1>
</router-link>
</transition>
</div>
</template>
<style lang="scss" scoped>

View File

@ -1,3 +1,34 @@
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
<template #title>
<span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
</template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta ? item.meta.icon : '' " />
<span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child as RouteOption"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
</template>
<script setup lang="ts">
import { isExternal } from '@/utils/validate'
import AppLink from './Link.vue'
@ -6,100 +37,69 @@ import { RouteOption } from "vue-router";
import { PropType } from "vue";
const props = defineProps({
// route object
item: {
type: Object as PropType<RouteOption>,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
// route object
item: {
type: Object as PropType<RouteOption>,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
})
const onlyOneChild = ref<any>({});
const hasOneShowingChild = (children:RouteOption[] = [], parent: RouteOption) => {
if (!children) {
children = [];
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item
return true
if (!children) {
children = [];
}
})
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
return false
};
const resolvePath = (routePath:string, routeQuery?:string): any => {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery);
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
}
return getNormalPath(props.basePath + '/' + routePath)
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery);
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
}
return getNormalPath(props.basePath + '/' + routePath)
}
const hasTitle = (title: string | undefined): string => {
if(!title || title.length <= 5) {
return "";
}
return title;
if(!title || title.length <= 5) {
return "";
}
return title;
}
</script>
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
<template #title>
<span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
</template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta ? item.meta.icon : '' " />
<span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child as RouteOption"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
</template>

View File

@ -24,7 +24,7 @@ const activeMenu = computed(() => {
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu;
}
}
return path;
})
@ -33,23 +33,23 @@ const textColor = computed(() => sideTheme.value === 'theme-dark' ? variables.me
</script>
<template>
<div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
<transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
<el-menu
:default-active="activeMenu as string"
:collapse="isCollapse"
:background-color="bgColor"
:text-color="textColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
</el-menu>
</transition>
</el-scrollbar>
</div>
<div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
<transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
<el-menu
:default-active="activeMenu as string"
:collapse="isCollapse"
:background-color="bgColor"
:text-color="textColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
</el-menu>
</transition>
</el-scrollbar>
</div>
</template>

View File

@ -81,9 +81,9 @@ defineExpose({
</script>
<template>
<el-scrollbar ref="scrollContainerRef" :vertical="false" class="scroll-container" @wheel.prevent="handleScroll">
<slot />
</el-scrollbar>
<el-scrollbar ref="scrollContainerRef" :vertical="false" class="scroll-container" @wheel.prevent="handleScroll">
<slot />
</el-scrollbar>
</template>
<style lang="scss" scoped>

View File

@ -1,3 +1,34 @@
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
<router-link
v-for="tag in visitedViews"
:key="tag.path"
:data-path="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path ? tag.path : '', query: tag.query, fullPath: tag.fullPath ? tag.fullPath : '' }"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ tag.title }}
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
</span>
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)"><refresh-right style="width: 1em; height: 1em;" /> 刷新页面</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><close style="width: 1em; height: 1em;" /> 关闭当前</li>
<li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em;" /> 关闭其他</li>
<li v-if="!isFirstView()" @click="closeLeftTags"><back style="width: 1em; height: 1em;" /> 关闭左侧</li>
<li v-if="!isLastView()" @click="closeRightTags"><right style="width: 1em; height: 1em;" /> 关闭右侧</li>
<li @click="closeAllTags(selectedTag)"><circle-close style="width: 1em; height: 1em;" /> 全部关闭</li>
</ul>
</div>
</template>
<script setup lang="ts">
import ScrollPane from './ScrollPane.vue'
import { getNormalPath } from '@/utils/ruoyi'
@ -23,216 +54,185 @@ const routes = computed(() => usePermissionStore().routes);
const theme = computed(() => useSettingsStore().theme);
watch(route, () => {
addTags();
moveToCurrentTag();
addTags();
moveToCurrentTag();
})
watch(visible, (value) => {
if (value) {
document.body.addEventListener('click', closeMenu);
} else {
document.body.removeEventListener('click', closeMenu);
}
if (value) {
document.body.addEventListener('click', closeMenu);
} else {
document.body.removeEventListener('click', closeMenu);
}
})
const isActive = (r: TagView): boolean => {
return r.path === route.path;
return r.path === route.path;
}
const activeStyle = (tag: TagView) => {
if (!isActive(tag)) return {};
return {
"background-color": theme.value,
"border-color": theme.value
};
if (!isActive(tag)) return {};
return {
"background-color": theme.value,
"border-color": theme.value
};
}
const isAffix = (tag: TagView) => {
return tag.meta && tag.meta.affix;
return tag.meta && tag.meta.affix;
}
const isFirstView = () => {
try {
return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath;
} catch (err) {
return false;
}
try {
return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath;
} catch (err) {
return false;
}
}
const isLastView = () => {
try {
return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath;
} catch (err) {
return false;
}
try {
return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath;
} catch (err) {
return false;
}
}
const filterAffixTags = (routes:RouteOption [], basePath = '') => {
let tags:TagView[] = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = getNormalPath(basePath + '/' + route.path);
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags = filterAffixTags(route.children, route.path);
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags];
}
}
})
return tags
let tags:TagView[] = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = getNormalPath(basePath + '/' + route.path);
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags = filterAffixTags(route.children, route.path);
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags];
}
}
})
return tags
}
const initTags = () => {
const res = filterAffixTags(routes.value);
affixTags.value = res;
for (const tag of res) {
// Must have tag name
if (tag.name) {
useTagsViewStore().addVisitedView(tag);
const res = filterAffixTags(routes.value);
affixTags.value = res;
for (const tag of res) {
// Must have tag name
if (tag.name) {
useTagsViewStore().addVisitedView(tag);
}
}
}
}
const addTags = () => {
const { name } = route;
if (name) {
useTagsViewStore().addView(route);
if (route.meta.link) {
useTagsViewStore().addIframeView(route);
const { name } = route;
if (name) {
useTagsViewStore().addView(route);
if (route.meta.link) {
useTagsViewStore().addIframeView(route);
}
}
}
return false
return false
}
const moveToCurrentTag = () => {
nextTick(() => {
for (const r of visitedViews.value) {
if (r.path === route.path) {
scrollPaneRef.value.moveToTarget(r);
// when query is different then update
if (r.fullPath !== route.fullPath) {
useTagsViewStore().updateVisitedView(route);
nextTick(() => {
for (const r of visitedViews.value) {
if (r.path === route.path) {
scrollPaneRef.value.moveToTarget(r);
// when query is different then update
if (r.fullPath !== route.fullPath) {
useTagsViewStore().updateVisitedView(route);
}
}
}
}
}
})
})
}
const refreshSelectedTag = (view: TagView) => {
proxy?.$tab.refreshPage(view);
if (route.meta.link) {
useTagsViewStore().delIframeView(route);
}
proxy?.$tab.refreshPage(view);
if (route.meta.link) {
useTagsViewStore().delIframeView(route);
}
}
const closeSelectedTag = (view: TagView) => {
proxy?.$tab.closePage(view).then(({ visitedViews }: any) => {
if (isActive(view)) {
toLastView(visitedViews, view);
}
})
proxy?.$tab.closePage(view).then(({ visitedViews }: any) => {
if (isActive(view)) {
toLastView(visitedViews, view);
}
})
}
const closeRightTags = () => {
proxy?.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
toLastView(visitedViews);
}
})
proxy?.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
toLastView(visitedViews);
}
})
}
const closeLeftTags = () => {
proxy?.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
toLastView(visitedViews);
}
})
proxy?.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
toLastView(visitedViews);
}
})
}
const closeOthersTags = () => {
router.push(selectedTag.value as RouteLocationRaw).catch(() => { });
proxy?.$tab.closeOtherPage(selectedTag.value).then(() => {
moveToCurrentTag();
})
router.push(selectedTag.value as RouteLocationRaw).catch(() => { });
proxy?.$tab.closeOtherPage(selectedTag.value).then(() => {
moveToCurrentTag();
})
}
const closeAllTags = (view: TagView) => {
proxy?.$tab.closeAllPage().then(({ visitedViews }) => {
if (affixTags.value.some(tag => tag.path === route.path)) {
return;
}
toLastView(visitedViews, view);
})
proxy?.$tab.closeAllPage().then(({ visitedViews }) => {
if (affixTags.value.some(tag => tag.path === route.path)) {
return;
}
toLastView(visitedViews, view);
})
}
const toLastView = (visitedViews:TagView[], view?: TagView) => {
const latestView = visitedViews.slice(-1)[0];
if (latestView) {
router.push(latestView.fullPath as string);
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view?.name === 'Dashboard') {
// to reload home page
router.replace({ path: '/redirect' + view?.fullPath });
const latestView = visitedViews.slice(-1)[0];
if (latestView) {
router.push(latestView.fullPath as string);
} else {
router.push('/');
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view?.name === 'Dashboard') {
// to reload home page
router.replace({ path: '/redirect' + view?.fullPath });
} else {
router.push('/');
}
}
}
}
const openMenu = (tag: TagView, e: MouseEvent) => {
const menuMinWidth = 105;
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = proxy?.$el.offsetWidth; // container width
const maxLeft = offsetWidth - menuMinWidth; // left boundary
const l = e.clientX - offsetLeft + 15; // 15: margin right
const menuMinWidth = 105;
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = proxy?.$el.offsetWidth; // container width
const maxLeft = offsetWidth - menuMinWidth; // left boundary
const l = e.clientX - offsetLeft + 15; // 15: margin right
if (l > maxLeft) {
left.value = maxLeft;
} else {
left.value = l;
}
if (l > maxLeft) {
left.value = maxLeft;
} else {
left.value = l;
}
top.value = e.clientY
visible.value = true;
selectedTag.value = tag;
top.value = e.clientY
visible.value = true;
selectedTag.value = tag;
}
const closeMenu = () => {
visible.value = false;
visible.value = false;
}
const handleScroll = () => {
closeMenu();
closeMenu();
}
onMounted(() => {
initTags();
addTags();
initTags();
addTags();
})
</script>
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
<router-link
v-for="tag in visitedViews"
:key="tag.path"
:data-path="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path ? tag.path : '', query: tag.query, fullPath: tag.fullPath ? tag.fullPath : '' }"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ tag.title }}
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
</span>
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)"><refresh-right style="width: 1em; height: 1em;" /> 刷新页面</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><close style="width: 1em; height: 1em;" /> 关闭当前</li>
<li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em;" /> 关闭其他</li>
<li v-if="!isFirstView()" @click="closeLeftTags"><back style="width: 1em; height: 1em;" /> 关闭左侧</li>
<li v-if="!isLastView()" @click="closeRightTags"><right style="width: 1em; height: 1em;" /> 关闭右侧</li>
<li @click="closeAllTags(selectedTag)"><circle-close style="width: 1em; height: 1em;" /> 全部关闭</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.tags-view-container {
height: 34px;