update 调整代码格式
This commit is contained in:
		| @ -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  */ | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
| @ -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) { | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 疯狂的狮子Li
					疯狂的狮子Li