feat: 🎸 初始化项目
This commit is contained in:
32
src/pages/index/layout/components/AppMain.vue
Normal file
32
src/pages/index/layout/components/AppMain.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="fade" mode="out-in" appear>
|
||||
<keep-alive :include="getCaches">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { useCache } from '_c/ParentView/useCache'
|
||||
export default defineComponent({
|
||||
name: 'AppMain',
|
||||
setup() {
|
||||
const { getCaches } = useCache(true)
|
||||
return {
|
||||
getCaches
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-main {
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
77
src/pages/index/layout/components/Logo.vue
Normal file
77
src/pages/index/layout/components/Logo.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<router-link :class="['app-logo', 'app-logo-' + theme]" to="/">
|
||||
<img :src="require('@/assets/img/logo.png')">
|
||||
<div v-if="show" class="sidebar-title">{{ title }}</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, PropType } from 'vue'
|
||||
import config from '_p/index/config'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Logo',
|
||||
props: {
|
||||
collapsed: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
required: true
|
||||
},
|
||||
theme: {
|
||||
type: String as PropType<'light' | 'dark'>,
|
||||
default: 'dark'
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const show = ref<boolean>(true)
|
||||
watch(
|
||||
() => props.collapsed,
|
||||
(collapsed: boolean) => {
|
||||
if (!collapsed) {
|
||||
setTimeout(() => {
|
||||
show.value = !collapsed
|
||||
}, 400)
|
||||
} else {
|
||||
show.value = !collapsed
|
||||
}
|
||||
}
|
||||
)
|
||||
return {
|
||||
show,
|
||||
title: config.title
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 18px;
|
||||
cursor: pointer;
|
||||
height: @topSilderHeight;
|
||||
max-width: 200px;
|
||||
img {
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
}
|
||||
.sidebar-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
transition: .5s;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
.app-logo-dark {
|
||||
background-color: @menuBg;
|
||||
.sidebar-title {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.app-logo-light {
|
||||
background-color: #fff;
|
||||
.sidebar-title {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/pages/index/layout/components/Navbar.vue
Normal file
75
src/pages/index/layout/components/Navbar.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<hamburger :collapsed="collapsed" class="hamburger-container" @toggleClick="setCollapsed" />
|
||||
<breadcrumb class="breadcrumb-container" />
|
||||
|
||||
<div class="right-menu">
|
||||
<screenfull class="right-menu-item hover-effect" />
|
||||
|
||||
<user-info class="right-menu-item hover-effect" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import Hamburger from '_c/Hamburger/index.vue'
|
||||
import Breadcrumb from '_c/Breadcrumb/index.vue'
|
||||
import Screenfull from '_c/Screenfull/index.vue'
|
||||
import UserInfo from './UserInfo.vue'
|
||||
import { appStore } from '_p/index/store/modules/app'
|
||||
export default defineComponent({
|
||||
name: 'Navbar',
|
||||
components: {
|
||||
Hamburger,
|
||||
Breadcrumb,
|
||||
Screenfull,
|
||||
UserInfo
|
||||
},
|
||||
setup() {
|
||||
const collapsed = computed(() => appStore.collapsed)
|
||||
|
||||
function setCollapsed(collapsed: boolean): void {
|
||||
appStore.SetCollapsed(collapsed)
|
||||
}
|
||||
|
||||
return {
|
||||
collapsed,
|
||||
setCollapsed
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.navbar {
|
||||
.hamburger-container {
|
||||
line-height: @navbarHeight;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
margin-left: 15px;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .025);
|
||||
}
|
||||
}
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
}
|
||||
.right-menu {
|
||||
float: right;
|
||||
height: 100%;
|
||||
line-height: @navbarHeight;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
.right-menu-item {
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #5a5e66;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
34
src/pages/index/layout/components/Silder/Item.vue
Normal file
34
src/pages/index/layout/components/Silder/Item.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <i v-if="icon.includes('el-icon')" :class="[icon, 'sub-el-icon', 'anticon']" /> -->
|
||||
<svg-icon :icon-class="icon" class="anticon" />
|
||||
<slot name="title">
|
||||
<span class="anticon-item">{{ title }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Item',
|
||||
props: {
|
||||
icon: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.anticon-item {
|
||||
opacity: 1;
|
||||
transition: opacity .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ref } from 'vue'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import path from 'path'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
// import { setSidebarItem } from './types'
|
||||
|
||||
export function setSidebarItem() {
|
||||
const onlyOneChild = ref<any>(null)
|
||||
|
||||
function hasOneShowingChild(children: RouteRecordRaw[] = [], parent: RouteRecordRaw): boolean {
|
||||
const showingChildren: RouteRecordRaw[] = children.filter((item: RouteRecordRaw) => {
|
||||
if (item.meta && item.meta.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
|
||||
}
|
||||
|
||||
// Show parent if there are no child router to display
|
||||
if (showingChildren.length === 0) {
|
||||
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function resolvePath(basePath: string, routePath: string): string {
|
||||
if (isExternal(routePath)) {
|
||||
return routePath
|
||||
}
|
||||
return path.resolve(basePath, routePath)
|
||||
}
|
||||
|
||||
function treeFindRouter(tree: any[], func: Function, result: RouteRecordRaw[] = []): RouteRecordRaw[] {
|
||||
if (!tree) return []
|
||||
for (const data of tree) {
|
||||
result.push(data)
|
||||
if (func(data)) return result
|
||||
if (data.children) {
|
||||
const findChildren = treeFindRouter(data.children, func, result)
|
||||
if (findChildren.length) return findChildren
|
||||
}
|
||||
result.pop()
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function getFullPath(arr: string[]): string[] {
|
||||
const result: string[] = []
|
||||
let basePath = '/'
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (i === arr.length) {
|
||||
continue
|
||||
}
|
||||
result.push(resolvePath(basePath, arr[i]))
|
||||
basePath = resolvePath(basePath, arr[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function findCurrentRoute(routers: RouteRecordRaw[], path: string, basePath = '/', result: Array<any> = []): any {
|
||||
for (const item of routers) {
|
||||
if (!item.meta?.hidden) {
|
||||
const _basePath = resolvePath(basePath, item.path)
|
||||
if (_basePath === path && !item.children) {
|
||||
result.push(item)
|
||||
} else {
|
||||
if (item.children) {
|
||||
findCurrentRoute(item.children, path, _basePath, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result ? result[0] : null
|
||||
}
|
||||
|
||||
return {
|
||||
onlyOneChild,
|
||||
hasOneShowingChild,
|
||||
resolvePath,
|
||||
treeFindRouter,
|
||||
getFullPath,
|
||||
findCurrentRoute
|
||||
}
|
||||
}
|
||||
10
src/pages/index/layout/components/Silder/hooks/types.d.ts
vendored
Normal file
10
src/pages/index/layout/components/Silder/hooks/types.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Ref } from 'vue'
|
||||
import ref from 'vue'
|
||||
|
||||
export interface setSidebarItem = {
|
||||
onlyOneChild: Ref<any | null>
|
||||
hasOneShowingChild: Function<boolean>
|
||||
resolvePath: Function<string>
|
||||
treeFindPath: Function<string[]>
|
||||
getFullPath: Function<string[]>
|
||||
}
|
||||
10
src/pages/index/layout/components/Silder/index.less
Normal file
10
src/pages/index/layout/components/Silder/index.less
Normal file
@@ -0,0 +1,10 @@
|
||||
.sidebar-container {
|
||||
height: 100%;
|
||||
}
|
||||
.has-logo {
|
||||
height: calc(~"100% - @{topSilderHeight}");
|
||||
}
|
||||
.menu-wrap {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
3
src/pages/index/layout/components/Silder/index.ts
Normal file
3
src/pages/index/layout/components/Silder/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Silder from './index'
|
||||
|
||||
export default Silder
|
||||
176
src/pages/index/layout/components/Silder/index.tsx
Normal file
176
src/pages/index/layout/components/Silder/index.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import { ref, defineComponent, PropType, computed, watch } from 'vue'
|
||||
// import { Menu } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import Scrollbar from '_c/Scrollbar/index.vue'
|
||||
import Item from './Item.vue'
|
||||
import { permissionStore } from '_p/index/store/modules/permission'
|
||||
import { setSidebarItem } from './hooks/setSidebarItem'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import { findIndex } from '@/utils'
|
||||
import config from '_p/index/config'
|
||||
const { show_logo } = config
|
||||
|
||||
import './index.less'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Silder',
|
||||
components: {
|
||||
Scrollbar,
|
||||
Item
|
||||
},
|
||||
props: {
|
||||
collapsed: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
inlineIndent: {
|
||||
type: Number as PropType<number>,
|
||||
default: 24
|
||||
},
|
||||
forceSubMenuRender: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String as PropType<'vertical' | 'horizontal' | 'vertical-right' | 'inline'>,
|
||||
default: 'inline'
|
||||
},
|
||||
theme: {
|
||||
type: String as PropType<'light' | 'dark'>,
|
||||
default: 'dark'
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const { currentRoute, push } = useRouter()
|
||||
const { resolvePath, treeFindRouter, getFullPath, findCurrentRoute } = setSidebarItem()
|
||||
const routers = computed((): RouteRecordRaw[] => {
|
||||
return permissionStore.routers
|
||||
})
|
||||
const selectedKeys = ref<string[]>([])
|
||||
const openKeys = ref<string[]>([])
|
||||
const activeMenuName = ref<string>('')
|
||||
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
setSelectedKeys(route)
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.collapsed,
|
||||
(collapsed: boolean) => {
|
||||
setOpenKeys(currentRoute.value, collapsed)
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function handleOpenChange(keyArr: string[]) {
|
||||
// 需要自己管理openKeys
|
||||
openKeys.value = keyArr
|
||||
}
|
||||
|
||||
function setOpenKeys(route: RouteLocationNormalizedLoaded, collapsed: boolean) {
|
||||
let currentRoute: any = null
|
||||
if (route.meta.activeMenu) {
|
||||
currentRoute = findCurrentRoute(routers.value, route.meta.activeMenu)
|
||||
} else {
|
||||
currentRoute = route
|
||||
}
|
||||
const parentRoute: RouteRecordRaw[] = treeFindRouter(permissionStore.routers, (data: any) => data.name === currentRoute.name)
|
||||
openKeys.value = collapsed ? [] : getFullPath(parentRoute.map((v: RouteRecordRaw) => v.path))
|
||||
}
|
||||
|
||||
function setSelectedKeys(route: RouteLocationNormalizedLoaded) {
|
||||
const { meta, path, name } = route
|
||||
activeMenuName.value = name as string
|
||||
if (meta.activeMenu) {
|
||||
selectedKeys.value = [meta.activeMenu]
|
||||
return
|
||||
}
|
||||
selectedKeys.value = [path]
|
||||
}
|
||||
|
||||
function highlightMenu(className: string, basePath: string) {
|
||||
const parentRoute: RouteRecordRaw[] = treeFindRouter(permissionStore.routers, (data: any) => data.name === currentRoute.value.name)
|
||||
const parentFullPath: string[] = getFullPath(parentRoute.map((v: RouteRecordRaw) => v.path))
|
||||
return findIndex(parentFullPath, (item: string) => item === basePath) !== -1 ? className : ''
|
||||
}
|
||||
|
||||
function handleMenuClick({ key }: any) {
|
||||
console.log(key)
|
||||
if (isExternal(key)) {
|
||||
window.open(key)
|
||||
} else {
|
||||
push({ path: key })
|
||||
}
|
||||
}
|
||||
|
||||
// function renderMenu(routers: RouteRecordRaw[], basePath = '/') {
|
||||
// if (routers && routers.length) {
|
||||
// return routers.map((item: RouteRecordRaw) => {
|
||||
// if (!item.meta?.hidden) {
|
||||
// const _basePath = resolvePath(basePath, item.path)
|
||||
// const { onlyOneChild, hasOneShowingChild } = setSidebarItem()
|
||||
// if (hasOneShowingChild(item.children, item) && (!onlyOneChild.value.children || onlyOneChild.value.noShowingChildren) && !item.meta?.alwaysShow) {
|
||||
// return (
|
||||
// <Menu.Item key={resolvePath(_basePath, onlyOneChild.value.path)} v-slots={{
|
||||
// default: () => [
|
||||
// <Item
|
||||
// icon={onlyOneChild.value.meta && onlyOneChild.value.meta.icon}
|
||||
// title={onlyOneChild.value.meta?.title}
|
||||
// />
|
||||
// ]
|
||||
// }}>
|
||||
// </Menu.Item>
|
||||
// )
|
||||
// } else {
|
||||
// return (
|
||||
// <Menu.SubMenu
|
||||
// key={_basePath}
|
||||
// class={highlightMenu(props.theme + '-active-item', _basePath)}
|
||||
// popupClassName={highlightMenu(props.theme + '-popup-active-item', _basePath)}
|
||||
// v-slots={{
|
||||
// title: () => [
|
||||
// <Item
|
||||
// icon={item.meta && item.meta.icon}
|
||||
// title={item.meta?.title}
|
||||
// />
|
||||
// ]
|
||||
// }}
|
||||
// >
|
||||
// {renderMenu(item.children as any, _basePath)}
|
||||
// </Menu.SubMenu>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// return () => {
|
||||
// return (
|
||||
// // <div class={{ 'has-logo': show_logo, 'sidebar-container': true }}>
|
||||
// // <scrollbar class='menu-wrap'>
|
||||
// // <Menu
|
||||
// // selectedKeys={selectedKeys.value}
|
||||
// // openKeys={openKeys.value}
|
||||
// // theme={props.theme}
|
||||
// // mode={props.mode}
|
||||
// // onClick={handleMenuClick}
|
||||
// // onOpenChange={handleOpenChange}
|
||||
// // >
|
||||
// // {renderMenu(routers.value)}
|
||||
// // </Menu>
|
||||
// // </scrollbar>
|
||||
// // </div>
|
||||
// )
|
||||
// }
|
||||
}
|
||||
})
|
||||
377
src/pages/index/layout/components/TagsView.vue
Normal file
377
src/pages/index/layout/components/TagsView.vue
Normal file
@@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<div ref="wrapper" class="tags-view-container">
|
||||
<span class="move-btn prev-btn">
|
||||
<a-button @click="move(-200)">
|
||||
<template #icon><LeftOutlined /></template>
|
||||
</a-button>
|
||||
</span>
|
||||
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
|
||||
<div>
|
||||
<router-link
|
||||
v-for="tag in visitedViews"
|
||||
:ref="setTagRef"
|
||||
:key="tag.path"
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
tag="span"
|
||||
class="tags-view-item"
|
||||
@click.middle="closeSelectedTag(tag)"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
{{ tag.title }}
|
||||
<CloseOutlined v-if="!tag.meta.affix" class="icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
|
||||
</router-link>
|
||||
</div>
|
||||
</scroll-pane>
|
||||
<span class="move-btn next-btn">
|
||||
<a-button @click="move(200)">
|
||||
<template #icon><RightOutlined /></template>
|
||||
</a-button>
|
||||
</span>
|
||||
<ul v-show="visible" :style="{left: left + 'px', top: top + 'px'}" class="contextmenu">
|
||||
<li @click="refreshSelectedTag(selectedTag)">
|
||||
刷新
|
||||
</li>
|
||||
<li v-if="!(selectedTag.meta&&selectedTag.meta.affix)" @click="closeSelectedTag(selectedTag)">
|
||||
关闭
|
||||
</li>
|
||||
<li @click="closeOthersTags">
|
||||
关闭其他
|
||||
</li>
|
||||
<li @click="closeAllTags(selectedTag)">
|
||||
关闭全部
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import ScrollPane from '_c/ScrollPane/index.vue'
|
||||
import path from 'path'
|
||||
import { defineComponent, ref, unref, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import { tagsViewStore } from '_p/index/store/modules/tagsView'
|
||||
import { permissionStore } from '_p/index/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||
// import { LeftOutlined, RightOutlined, CloseOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TagsView',
|
||||
components: {
|
||||
ScrollPane,
|
||||
// CloseOutlined,
|
||||
// LeftOutlined,
|
||||
// RightOutlined
|
||||
},
|
||||
setup() {
|
||||
const { currentRoute, push, replace } = useRouter()
|
||||
const wrapper = ref<HTMLElement | null>(null)
|
||||
const scrollPane = ref<HTMLElement | null>(null)
|
||||
const visible = ref<boolean>(false)
|
||||
const top = ref<number>(0)
|
||||
const left = ref<number>(0)
|
||||
const selectedTag = ref<any>({})
|
||||
const affixTags = ref<any[]>([])
|
||||
const visitedViews = computed(() => tagsViewStore.visitedViews)
|
||||
const routers = computed(() => permissionStore.routers)
|
||||
|
||||
const tagRefs = ref<any[]>([])
|
||||
|
||||
function setTagRef(el: any): void {
|
||||
tagRefs.value.push(el)
|
||||
}
|
||||
|
||||
function isActive(route: RouteLocationNormalizedLoaded): boolean {
|
||||
return route.path === currentRoute.value.path
|
||||
}
|
||||
|
||||
function filterAffixTags(routes: RouteRecordRaw[], basePath = '/'): any[] {
|
||||
let tags: any[] = []
|
||||
routes.forEach((route: RouteRecordRaw) => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
const tagPath = path.resolve(basePath, route.path)
|
||||
tags.push({
|
||||
fullPath: tagPath,
|
||||
path: tagPath,
|
||||
name: route.name,
|
||||
meta: { ...route.meta }
|
||||
})
|
||||
}
|
||||
if (route.children) {
|
||||
const tempTags: any[] = filterAffixTags(route.children, route.path)
|
||||
if (tempTags.length >= 1) {
|
||||
tags = [...tags, ...tempTags]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
function initTags(): void {
|
||||
affixTags.value = filterAffixTags(routers.value)
|
||||
const affixTagArr: any[] = affixTags.value
|
||||
for (const tag of affixTagArr) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
tagsViewStore.addVisitedView(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addTags(): void | boolean {
|
||||
const { name } = currentRoute.value
|
||||
if (name) {
|
||||
tagsViewStore.addView(currentRoute.value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function moveToCurrentTag() {
|
||||
// TODO 要手动清除tagRefs,不然会一直重复,后续看看有没有什么解决办法
|
||||
tagRefs.value = []
|
||||
const tags = unref(tagRefs)
|
||||
nextTick(() => {
|
||||
for (const tag of tags) {
|
||||
if (tag && tag.to.path === currentRoute.value.path) {
|
||||
(unref(scrollPane) as any).moveToTarget(tag)
|
||||
// when query is different then update
|
||||
if (tag.to.fullPath !== currentRoute.value.fullPath) {
|
||||
tagsViewStore.updateVisitedView(currentRoute.value)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function refreshSelectedTag(view: RouteLocationNormalizedLoaded) {
|
||||
await tagsViewStore.delCachedView()
|
||||
const { fullPath } = view
|
||||
nextTick(() => {
|
||||
replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function closeSelectedTag(view: RouteLocationNormalizedLoaded) {
|
||||
const views: any = await tagsViewStore.delView(view)
|
||||
if (isActive(view)) {
|
||||
toLastView(views.visitedViews)
|
||||
}
|
||||
}
|
||||
|
||||
function closeOthersTags() {
|
||||
push(selectedTag.value)
|
||||
tagsViewStore.delOthersViews(selectedTag.value).then(() => {
|
||||
moveToCurrentTag()
|
||||
})
|
||||
}
|
||||
|
||||
async function closeAllTags(view: RouteLocationNormalizedLoaded) {
|
||||
const views: any = await tagsViewStore.delAllViews()
|
||||
if (affixTags.value.some(tag => tag.path === view.path)) {
|
||||
return
|
||||
}
|
||||
toLastView(views.visitedViews)
|
||||
}
|
||||
|
||||
function toLastView(visitedViews: RouteLocationNormalizedLoaded[]) {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
setTimeout(() => {
|
||||
if (latestView) {
|
||||
push(latestView)
|
||||
} else {
|
||||
// You can set another route
|
||||
push('/')
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function openMenu(tag: RouteLocationNormalizedLoaded, e: any) {
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft: number = (unref(wrapper) as any).getBoundingClientRect().left // container margin left
|
||||
const offsetWidth: number = (unref(wrapper) as any).offsetWidth // container width
|
||||
const maxLeft: number = offsetWidth - menuMinWidth// left boundary
|
||||
const itemLeft: number = e.clientX - offsetLeft + 4
|
||||
|
||||
if (itemLeft > maxLeft) {
|
||||
left.value = maxLeft
|
||||
} else {
|
||||
left.value = itemLeft
|
||||
}
|
||||
top.value = e.offsetY
|
||||
|
||||
visible.value = true
|
||||
selectedTag.value = tag
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
function move(to: number) {
|
||||
(unref(scrollPane) as any).moveTo(to)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initTags()
|
||||
addTags()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
addTags()
|
||||
moveToCurrentTag()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
(visible: boolean) => {
|
||||
if (visible) {
|
||||
document.body.addEventListener('click', closeMenu)
|
||||
} else {
|
||||
document.body.removeEventListener('click', closeMenu)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
wrapper, scrollPane,
|
||||
visible, top, left,
|
||||
selectedTag, affixTags,
|
||||
visitedViews, routers,
|
||||
tagRefs, setTagRef,
|
||||
isActive,
|
||||
filterAffixTags,
|
||||
initTags,
|
||||
addTags,
|
||||
moveToCurrentTag,
|
||||
refreshSelectedTag,
|
||||
closeSelectedTag,
|
||||
closeOthersTags,
|
||||
closeAllTags,
|
||||
toLastView,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
move
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tags-view-container {
|
||||
height: @tagsViewHeight;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
||||
position: relative;
|
||||
display: flex;
|
||||
z-index: 100;
|
||||
&::after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-top: #d8dce5;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.move-btn {
|
||||
display: inline-block;
|
||||
width: @tagsViewHeight;
|
||||
height: @tagsViewHeight;
|
||||
line-height: @tagsViewHeight;
|
||||
text-align: center;
|
||||
.ant-btn {
|
||||
width: @tagsViewHeight;
|
||||
height: @tagsViewHeight;
|
||||
line-height: @tagsViewHeight;
|
||||
}
|
||||
}
|
||||
.tags-view-wrapper {
|
||||
width: calc(~"100% - 78px");
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border: 1px solid #d8dce5;
|
||||
color: #495060;
|
||||
background: #fff;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
&:last-of-type {
|
||||
margin-right: 4px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #304156;
|
||||
color: #fff;
|
||||
border-color: #304156;
|
||||
&::before {
|
||||
content: '';
|
||||
background: #fff;
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
||||
transform-origin: 100% 50%;
|
||||
margin-left: 5px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
&:before {
|
||||
transform: scale(.6);
|
||||
display: inline-block;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #b4bccc;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
z-index: 200;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@{deep}(.scrollbar__view) {
|
||||
height: @tagsViewHeight;
|
||||
line-height: @tagsViewHeight;
|
||||
}
|
||||
</style>
|
||||
68
src/pages/index/layout/components/UserInfo.vue
Normal file
68
src/pages/index/layout/components/UserInfo.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<a-dropdown class="avatar-container" :trigger="['hover']">
|
||||
<div>
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="require('@/assets/img/avatar.gif')" class="user-avatar">
|
||||
<span class="name-item">管理员</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1">
|
||||
<router-link to="/">
|
||||
首页
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="2">
|
||||
<span style="display: block;" @click="loginOut">退出登录</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { resetRouter } from '_p/index/router'
|
||||
import wsCache from '@/cache'
|
||||
import { useRouter } from 'vue-router'
|
||||
export default defineComponent({
|
||||
name: 'UserInfo',
|
||||
setup() {
|
||||
const { replace } = useRouter()
|
||||
async function loginOut(): Promise<void> {
|
||||
wsCache.clear()
|
||||
await resetRouter() // 重置静态路由表
|
||||
// this.$store.dispatch('delAllViews') // 删除所有的tags标签页
|
||||
replace('/login')
|
||||
}
|
||||
return {
|
||||
loginOut
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.avatar-container {
|
||||
margin-right: 30px;
|
||||
padding: 0 10px;
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
.user-avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.name-item {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
38
src/pages/index/layout/index.vue
Normal file
38
src/pages/index/layout/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="app-wrapper">
|
||||
<component :is="component" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Classic from './modules/Classic.vue'
|
||||
import Top from './modules/Top.vue'
|
||||
import LeftTop from './modules/LeftTop.vue'
|
||||
import Test from './modules/Test.vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import config from '_p/index/config'
|
||||
export default defineComponent({
|
||||
name: 'Layout',
|
||||
components: {
|
||||
Classic,
|
||||
Top,
|
||||
LeftTop,
|
||||
Test
|
||||
},
|
||||
setup() {
|
||||
const { layout } = config
|
||||
const component = ref<string>(layout)
|
||||
return {
|
||||
component
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-wrapper {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
112
src/pages/index/layout/modules/Classic.vue
Normal file
112
src/pages/index/layout/modules/Classic.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<a-layout class="app-wrapper">
|
||||
<a-layout-sider
|
||||
v-model:collapsed="collapsed"
|
||||
:trigger="null"
|
||||
collapsible
|
||||
:class="'ant-layout-sider--' + theme"
|
||||
class="sidebar-container-wrap"
|
||||
>
|
||||
<logo
|
||||
v-if="show_logo"
|
||||
:collapsed="collapsed"
|
||||
:theme="theme"
|
||||
/>
|
||||
<silder
|
||||
:collapsed="collapsed"
|
||||
:theme="theme"
|
||||
/>
|
||||
</a-layout-sider>
|
||||
<a-layout>
|
||||
<a-layout-header :class="{'ant-layout-header-collapsed': collapsed}">
|
||||
<navbar />
|
||||
</a-layout-header>
|
||||
<a-layout-content :class="{'layout-content-has-tags':has_tags}">
|
||||
<scrollbar class="main-wrap">
|
||||
<tags-view :class="{'has-tags':has_tags, 'has-tags-collapsed': collapsed && has_tags}" />
|
||||
<app-main class="classic-module--wrap" />
|
||||
</scrollbar>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, PropType } from 'vue'
|
||||
import { appStore } from '_p/index/store/modules/app'
|
||||
import Silder from '../components/Silder'
|
||||
import Navbar from '../components/Navbar.vue'
|
||||
import AppMain from '../components/AppMain.vue'
|
||||
import TagsView from '../components/TagsView.vue'
|
||||
import Logo from '../components/Logo.vue'
|
||||
import Scrollbar from '_c/Scrollbar/index.vue'
|
||||
import config from '_p/index/config'
|
||||
const { show_logo, has_tags, theme } = config
|
||||
export default defineComponent({
|
||||
name: 'Classic',
|
||||
components: {
|
||||
Silder,
|
||||
Navbar,
|
||||
AppMain,
|
||||
TagsView,
|
||||
Logo,
|
||||
Scrollbar
|
||||
},
|
||||
props: {
|
||||
theme: {
|
||||
type: String as PropType<'light' | 'dark'>,
|
||||
default: theme
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const collapsed = computed(() => appStore.collapsed)
|
||||
|
||||
return {
|
||||
collapsed,
|
||||
show_logo,
|
||||
has_tags
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@{deep}(.ant-layout-header) {
|
||||
line-height: @navbarHeight;
|
||||
height: @navbarHeight;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: @menuWidth;
|
||||
width: calc(~"100% - @{menuWidth}");
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.ant-layout-sider--light {
|
||||
background: @menuLightBg;
|
||||
}
|
||||
.ant-layout-sider--dark {
|
||||
background: @menuBg;
|
||||
}
|
||||
.ant-layout-content {
|
||||
margin-top: @navbarHeight;
|
||||
.mian-wrap {
|
||||
background-color: @contentBg;
|
||||
}
|
||||
}
|
||||
.layout-content-has-tags {
|
||||
margin-top: @navbarHeight + @tagsViewHeight;
|
||||
}
|
||||
.has-tags {
|
||||
position: fixed;
|
||||
top: @navbarHeight;
|
||||
left: @menuWidth;
|
||||
width: calc(~"100% - @{menuWidth}");
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.ant-layout-header-collapsed,
|
||||
.has-tags-collapsed {
|
||||
left: 80px;
|
||||
width: calc(~"100% - 80px");
|
||||
}
|
||||
</style>
|
||||
185
src/pages/index/layout/modules/LeftTop.vue
Normal file
185
src/pages/index/layout/modules/LeftTop.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<a-layout>
|
||||
<a-layout-header :class="'ant-layout-header--' + silderTheme">
|
||||
<logo
|
||||
v-if="show_logo"
|
||||
:theme="silderTheme"
|
||||
/>
|
||||
<silder
|
||||
:theme="silderTheme"
|
||||
mode="horizontal"
|
||||
class="header-silder--wrap"
|
||||
/>
|
||||
<div class="right-menu">
|
||||
<screenfull class="right-menu-item hover-effect screenfull-item" />
|
||||
|
||||
<user-info class="right-menu-item hover-effect" />
|
||||
</div>
|
||||
</a-layout-header>
|
||||
<a-layout class="layout-content">
|
||||
<a-layout-sider
|
||||
v-model:collapsed="collapsed"
|
||||
:trigger="null"
|
||||
collapsible
|
||||
:class="'ant-layout-sider--' + theme"
|
||||
class="sidebar-container-wrap"
|
||||
>
|
||||
<silder
|
||||
:collapsed="collapsed"
|
||||
:theme="theme"
|
||||
class="left-sider-wrap"
|
||||
/>
|
||||
</a-layout-sider>
|
||||
<a-layout>
|
||||
<div class="navbar-wrap">
|
||||
<hamburger :collapsed="collapsed" class="hamburger-container" @toggleClick="setCollapsed" />
|
||||
<breadcrumb class="breadcrumb-container" />
|
||||
</div>
|
||||
<a-layout-content :class="{'layout-content-has-tags':has_tags}">
|
||||
<scrollbar class="main-wrap">
|
||||
<tags-view :class="{'has-tags':has_tags, 'has-tags-collapsed': collapsed && has_tags}" />
|
||||
<app-main class="classic-module--wrap" />
|
||||
</scrollbar>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue'
|
||||
import Logo from '../components/Logo.vue'
|
||||
import Silder from '../components/Silder'
|
||||
import UserInfo from '../components/UserInfo.vue'
|
||||
import TagsView from '../components/TagsView.vue'
|
||||
import AppMain from '../components/AppMain.vue'
|
||||
import Screenfull from '_c/Screenfull/index.vue'
|
||||
import Scrollbar from '_c/Scrollbar/index.vue'
|
||||
import Hamburger from '_c/Hamburger/index.vue'
|
||||
import Breadcrumb from '_c/Breadcrumb/index.vue'
|
||||
import { appStore } from '_p/index/store/modules/app'
|
||||
import config from '_p/index/config'
|
||||
const { show_logo, has_tags, theme } = config
|
||||
export default defineComponent({
|
||||
name: 'LeftTop',
|
||||
components: {
|
||||
Logo,
|
||||
Silder,
|
||||
UserInfo,
|
||||
TagsView,
|
||||
AppMain,
|
||||
Screenfull,
|
||||
Scrollbar,
|
||||
Hamburger,
|
||||
Breadcrumb
|
||||
},
|
||||
props: {
|
||||
theme: {
|
||||
type: String as PropType<'light' | 'dark'>,
|
||||
default: theme
|
||||
},
|
||||
silderTheme: {
|
||||
type: String as PropType<'light' | 'dark'>,
|
||||
default: 'light'
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const collapsed = computed(() => appStore.collapsed)
|
||||
function setCollapsed(collapsed: boolean): void {
|
||||
appStore.SetCollapsed(collapsed)
|
||||
}
|
||||
return {
|
||||
collapsed,
|
||||
show_logo,
|
||||
has_tags,
|
||||
setCollapsed
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@{deep}(.ant-layout-header) {
|
||||
height: @topSilderHeight;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
&--light {
|
||||
background: @menuLightBg;
|
||||
}
|
||||
&--dark {
|
||||
background: @menuBg;
|
||||
.screenfull-item,
|
||||
.name-item {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.header-silder--wrap {
|
||||
flex: 1;
|
||||
margin: 0 50px;
|
||||
height: 100% !important;
|
||||
.ant-menu-horizontal {
|
||||
height: @topSilderHeight;
|
||||
line-height: @topSilderHeight;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.ant-menu-light {
|
||||
height: calc(~"@{topSilderHeight} - 4px");
|
||||
line-height: calc(~"@{topSilderHeight} - 2px");
|
||||
}
|
||||
}
|
||||
.right-menu {
|
||||
display: flex;
|
||||
.screenfull-item {
|
||||
line-height: @topSilderHeight;
|
||||
}
|
||||
.avatar-container {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-layout-sider--light {
|
||||
background: @menuLightBg;
|
||||
}
|
||||
.ant-layout-sider--dark {
|
||||
background: @menuBg;
|
||||
}
|
||||
.layout-content {
|
||||
margin-top: @topSilderHeight;
|
||||
height: calc(~"100vh - @{topSilderHeight}");
|
||||
.navbar-wrap {
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
height: @navbarHeight;
|
||||
line-height: @navbarHeight;
|
||||
display: flex;
|
||||
}
|
||||
.layout-content-has-tags {
|
||||
margin-top: @tagsViewHeight;
|
||||
.mian-wrap {
|
||||
background-color: @contentBg;
|
||||
}
|
||||
}
|
||||
.left-sider-wrap {
|
||||
height: 100%;
|
||||
}
|
||||
.has-tags {
|
||||
position: fixed;
|
||||
top: @topSilderHeight + @navbarHeight;
|
||||
left: @menuWidth;
|
||||
width: calc(~"100% - @{menuWidth}");
|
||||
z-index: 20;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.has-tags-collapsed {
|
||||
left: 80px;
|
||||
width: calc(~"100% - 80px");
|
||||
}
|
||||
}
|
||||
</style>
|
||||
62
src/pages/index/layout/modules/Test.vue
Normal file
62
src/pages/index/layout/modules/Test.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="app__wrap">
|
||||
<div class="sidebar__wrap">
|
||||
<logo
|
||||
v-if="show_logo"
|
||||
:collapsed="collapsed"
|
||||
/>
|
||||
<sider /></div>
|
||||
<div class="main__wrap">
|
||||
<div class="navbar__wrap" />
|
||||
<div class="tags__wrap" />
|
||||
<div class="main__wrap" />
|
||||
</div>
|
||||
<!-- <sidebar class="sidebar-wrap" />
|
||||
<div :class="{hasTagsView: has_tags}" class="main-wrap">
|
||||
<div>
|
||||
<navbar />
|
||||
<tags-view v-if="has_tags" />
|
||||
</div>
|
||||
<app-main />
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { appStore } from '_p/index/store/modules/app'
|
||||
|
||||
import AppMain from '../components/AppMain.vue'
|
||||
import TagsView from '../components/TagsView.vue'
|
||||
import Logo from '_c/Logo/index.vue'
|
||||
import Scrollbar from '_c/Scrollbar/index.vue'
|
||||
import Sider from '_c/Sider/index.vue'
|
||||
import Navbar from '../components/Navbar.vue'
|
||||
|
||||
import config from '_p/index/config'
|
||||
const { show_logo, has_tags } = config
|
||||
export default defineComponent({
|
||||
name: 'Layout',
|
||||
components: {
|
||||
Sider,
|
||||
Navbar,
|
||||
AppMain,
|
||||
TagsView,
|
||||
Logo,
|
||||
Scrollbar
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
show_logo, has_tags
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-wrap {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
128
src/pages/index/layout/modules/Top.vue
Normal file
128
src/pages/index/layout/modules/Top.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<a-layout>
|
||||
<a-layout-header :class="'ant-layout-header--' + theme">
|
||||
<logo
|
||||
v-if="show_logo"
|
||||
:theme="theme"
|
||||
/>
|
||||
<silder
|
||||
:theme="theme"
|
||||
mode="horizontal"
|
||||
class="header-silder--wrap"
|
||||
/>
|
||||
<div class="right-menu">
|
||||
<screenfull class="right-menu-item hover-effect screenfull-item" />
|
||||
|
||||
<user-info class="right-menu-item hover-effect" />
|
||||
</div>
|
||||
</a-layout-header>
|
||||
<a-layout-content :class="{'layout-content-has-tags':has_tags}">
|
||||
<scrollbar class="main-wrap">
|
||||
<tags-view :class="{'has-tags':has_tags}" />
|
||||
<app-main class="top-module--wrap" />
|
||||
</scrollbar>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import Logo from '../components/Logo.vue'
|
||||
import Silder from '../components/Silder'
|
||||
import UserInfo from '../components/UserInfo.vue'
|
||||
import TagsView from '../components/TagsView.vue'
|
||||
import AppMain from '../components/AppMain.vue'
|
||||
import Screenfull from '_c/Screenfull/index.vue'
|
||||
import Scrollbar from '_c/Scrollbar/index.vue'
|
||||
import config from '_p/index/config'
|
||||
const { show_logo, has_tags, theme } = config
|
||||
export default defineComponent({
|
||||
name: 'Top',
|
||||
components: {
|
||||
Logo,
|
||||
Silder,
|
||||
UserInfo,
|
||||
TagsView,
|
||||
AppMain,
|
||||
Screenfull,
|
||||
Scrollbar
|
||||
},
|
||||
props: {
|
||||
theme: {
|
||||
type: String as PropType<'light' | 'dark'>,
|
||||
default: theme
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
show_logo,
|
||||
has_tags
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@{deep}(.ant-layout-header) {
|
||||
height: @topSilderHeight;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
&--light {
|
||||
background: @menuLightBg;
|
||||
}
|
||||
&--dark {
|
||||
background: @menuBg;
|
||||
.screenfull-item,
|
||||
.name-item {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.header-silder--wrap {
|
||||
flex: 1;
|
||||
margin: 0 50px;
|
||||
height: 100% !important;
|
||||
.ant-menu-horizontal {
|
||||
height: @topSilderHeight;
|
||||
line-height: @topSilderHeight;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.ant-menu-light {
|
||||
height: calc(~"@{topSilderHeight} - 4px");
|
||||
line-height: calc(~"@{topSilderHeight} - 4px");
|
||||
}
|
||||
}
|
||||
.right-menu {
|
||||
display: flex;
|
||||
.screenfull-item {
|
||||
line-height: @topSilderHeight;
|
||||
}
|
||||
.avatar-container {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-layout-content {
|
||||
margin-top: @topSilderHeight;
|
||||
height: calc(~"100vh - @{topSilderHeight}");
|
||||
.main-wrap {
|
||||
background-color: @contentBg;
|
||||
}
|
||||
}
|
||||
.layout-content-has-tags {
|
||||
margin-top: @topSilderHeight + @tagsViewHeight;
|
||||
height: calc(~"100vh - @{topSilderHeight} - @{tagsViewHeight}");
|
||||
}
|
||||
.has-tags {
|
||||
position: fixed;
|
||||
top: @topSilderHeight;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 20;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user