feat(TagsView): Add TagsView component
feat(ContextMenu): Add ContextMenu component feat(store): Add tagsView store
This commit is contained in:
parent
4612e5544b
commit
349ac9d398
|
@ -84,6 +84,7 @@
|
|||
"vite-plugin-purge-icons": "^0.7.0",
|
||||
"vite-plugin-style-import": "^1.4.1",
|
||||
"vite-plugin-svg-icons": "^1.1.0",
|
||||
"vite-plugin-vue-setup-extend": "^0.3.0",
|
||||
"vite-plugin-windicss": "^1.6.2",
|
||||
"vue-tsc": "^0.30.2",
|
||||
"windicss": "^3.4.2",
|
||||
|
|
|
@ -33,7 +33,6 @@ specifiers:
|
|||
lodash-es: ^4.17.21
|
||||
mockjs: ^1.1.0
|
||||
nprogress: ^0.2.0
|
||||
path-to-regexp: ^6.2.0
|
||||
pinia: ^2.0.9
|
||||
postcss: ^8.4.5
|
||||
postcss-html: ^1.3.0
|
||||
|
@ -54,6 +53,7 @@ specifiers:
|
|||
vite-plugin-purge-icons: ^0.7.0
|
||||
vite-plugin-style-import: ^1.4.1
|
||||
vite-plugin-svg-icons: ^1.1.0
|
||||
vite-plugin-vue-setup-extend: ^0.3.0
|
||||
vite-plugin-windicss: ^1.6.2
|
||||
vue: 3.2.26
|
||||
vue-i18n: 9.1.9
|
||||
|
@ -74,7 +74,6 @@ dependencies:
|
|||
lodash-es: registry.nlark.com/lodash-es/4.17.21
|
||||
mockjs: registry.npmmirror.com/mockjs/1.1.0
|
||||
nprogress: registry.npmmirror.com/nprogress/0.2.0
|
||||
path-to-regexp: registry.npmmirror.com/path-to-regexp/6.2.0
|
||||
pinia: registry.npmmirror.com/pinia/2.0.9_typescript@4.5.4+vue@3.2.26
|
||||
qs: registry.npmmirror.com/qs/6.10.3
|
||||
vue: registry.npmmirror.com/vue/3.2.26
|
||||
|
@ -125,6 +124,7 @@ devDependencies:
|
|||
vite-plugin-purge-icons: registry.nlark.com/vite-plugin-purge-icons/0.7.0_vite@2.7.10
|
||||
vite-plugin-style-import: registry.npmmirror.com/vite-plugin-style-import/1.4.1_vite@2.7.10
|
||||
vite-plugin-svg-icons: registry.npmmirror.com/vite-plugin-svg-icons/1.1.0_vite@2.7.10
|
||||
vite-plugin-vue-setup-extend: registry.npmmirror.com/vite-plugin-vue-setup-extend/0.3.0_vite@2.7.10
|
||||
vite-plugin-windicss: registry.npmmirror.com/vite-plugin-windicss/1.6.2_vite@2.7.10
|
||||
vue-tsc: registry.npmmirror.com/vue-tsc/0.30.2_typescript@4.5.4
|
||||
windicss: registry.npmmirror.com/windicss/3.4.2
|
||||
|
@ -1732,7 +1732,7 @@ packages:
|
|||
{
|
||||
integrity: sha1-0t5eA0JOcH3BDHQGjd7a5wh0Gyc=,
|
||||
registry: https://registry.npm.taobao.org/,
|
||||
tarball: https://registry.nlark.com/eslint-utils/download/eslint-utils-2.1.0.tgz?cache=0&sync_timestamp=1631600361784&other_urls=https%3A%2F%2Fregistry.nlark.com%2Feslint-utils%2Fdownload%2Feslint-utils-2.1.0.tgz
|
||||
tarball: https://registry.nlark.com/eslint-utils/download/eslint-utils-2.1.0.tgz
|
||||
}
|
||||
name: eslint-utils
|
||||
version: 2.1.0
|
||||
|
@ -4948,7 +4948,7 @@ packages:
|
|||
{
|
||||
integrity: sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=,
|
||||
registry: https://registry.npm.taobao.org/,
|
||||
tarball: https://registry.nlark.com/semver/download/semver-5.7.1.tgz
|
||||
tarball: https://registry.nlark.com/semver/download/semver-5.7.1.tgz?cache=0&sync_timestamp=1631500167672&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz
|
||||
}
|
||||
name: semver
|
||||
version: 5.7.1
|
||||
|
@ -4960,7 +4960,7 @@ packages:
|
|||
{
|
||||
integrity: sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=,
|
||||
registry: https://registry.npm.taobao.org/,
|
||||
tarball: https://registry.nlark.com/semver/download/semver-6.3.0.tgz
|
||||
tarball: https://registry.nlark.com/semver/download/semver-6.3.0.tgz?cache=0&sync_timestamp=1631500167672&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-6.3.0.tgz
|
||||
}
|
||||
name: semver
|
||||
version: 6.3.0
|
||||
|
@ -7909,7 +7909,6 @@ packages:
|
|||
magic-string: registry.nlark.com/magic-string/0.25.7
|
||||
postcss: registry.npmmirror.com/postcss/8.4.5
|
||||
source-map: registry.nlark.com/source-map/0.6.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@vue/compiler-ssr/3.2.26:
|
||||
resolution:
|
||||
|
@ -7923,7 +7922,6 @@ packages:
|
|||
dependencies:
|
||||
'@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.26
|
||||
'@vue/shared': registry.npmmirror.com/@vue/shared/3.2.26
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@vue/devtools-api/6.0.0-beta.21.1:
|
||||
resolution:
|
||||
|
@ -7951,7 +7949,6 @@ packages:
|
|||
'@vue/shared': registry.npmmirror.com/@vue/shared/3.2.26
|
||||
estree-walker: registry.npmmirror.com/estree-walker/2.0.2
|
||||
magic-string: registry.nlark.com/magic-string/0.25.7
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@vue/reactivity/3.2.26:
|
||||
resolution:
|
||||
|
@ -9678,7 +9675,7 @@ packages:
|
|||
{
|
||||
integrity: sha1-MOvR73wv3/AcOk8VEESvJfqwUj4=,
|
||||
registry: https://registry.npm.taobao.org/,
|
||||
tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-1.3.0.tgz?cache=0&sync_timestamp=1636378650851&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-1.3.0.tgz
|
||||
tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-1.3.0.tgz
|
||||
}
|
||||
name: eslint-visitor-keys
|
||||
version: 1.3.0
|
||||
|
@ -9690,7 +9687,7 @@ packages:
|
|||
{
|
||||
integrity: sha1-9lMoJZMFknOSyTjtROsKXJsr0wM=,
|
||||
registry: https://registry.npm.taobao.org/,
|
||||
tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-2.1.0.tgz?cache=0&sync_timestamp=1636378650851&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-2.1.0.tgz
|
||||
tarball: https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-2.1.0.tgz
|
||||
}
|
||||
name: eslint-visitor-keys
|
||||
version: 2.1.0
|
||||
|
@ -11132,6 +11129,7 @@ packages:
|
|||
}
|
||||
name: path-to-regexp
|
||||
version: 6.2.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/picocolors/1.0.0:
|
||||
resolution:
|
||||
|
@ -12350,6 +12348,24 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/vite-plugin-vue-setup-extend/0.3.0_vite@2.7.10:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-9Nd7Bj4TftB2CoOAD2ZI4cHLW5zjKMF3LNihWbrnAPx3nuGBn33tM9SVUGBVjBB6uv1mGAPavwKCTU0xAD8qhw==,
|
||||
registry: https://registry.npm.taobao.org/,
|
||||
tarball: https://registry.npmmirror.com/vite-plugin-vue-setup-extend/download/vite-plugin-vue-setup-extend-0.3.0.tgz
|
||||
}
|
||||
id: registry.npmmirror.com/vite-plugin-vue-setup-extend/0.3.0
|
||||
name: vite-plugin-vue-setup-extend
|
||||
version: 0.3.0
|
||||
peerDependencies:
|
||||
vite: '>=2.0.0'
|
||||
dependencies:
|
||||
'@vue/compiler-sfc': registry.npmmirror.com/@vue/compiler-sfc/3.2.26
|
||||
magic-string: registry.nlark.com/magic-string/0.25.7
|
||||
vite: registry.npmmirror.com/vite/2.7.10_less@4.1.2
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/vite-plugin-windicss/1.6.2_vite@2.7.10:
|
||||
resolution:
|
||||
{
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import ContextMenu from './src/ContextMenu.vue'
|
||||
|
||||
export { ContextMenu }
|
|
@ -0,0 +1,52 @@
|
|||
<script setup lang="ts">
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
||||
import { PropType } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
defineProps({
|
||||
schema: {
|
||||
type: Array as PropType<contextMenuSchema[]>,
|
||||
default: () => []
|
||||
},
|
||||
trigger: {
|
||||
type: String as PropType<'click' | 'hover' | 'focus' | 'contextmenu'>,
|
||||
default: 'contextmenu'
|
||||
}
|
||||
})
|
||||
|
||||
const command = (item: contextMenuSchema) => {
|
||||
item.command && item.command(item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDropdown
|
||||
:trigger="trigger"
|
||||
placement="bottom-start"
|
||||
@command="command"
|
||||
popper-class="v-context-menu-popper"
|
||||
>
|
||||
<slot></slot>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem
|
||||
v-for="(item, index) in schema"
|
||||
:key="`dropdown${index}`"
|
||||
:divided="item.divided"
|
||||
:disabled="item.disabled"
|
||||
:command="item"
|
||||
>
|
||||
<Icon :icon="item.icon" /> {{ t(item.label) }}
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.v-context-menu-popper {
|
||||
min-width: 150px;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="tsx">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { computed, defineComponent, unref } from 'vue'
|
||||
import { ElMenu, ElScrollbar } from 'element-plus'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
|
@ -33,7 +33,7 @@ export default defineComponent({
|
|||
const collapse = computed(() => appStore.getCollapse)
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = currentRoute.value
|
||||
const { meta, path } = unref(currentRoute)
|
||||
// if set path, the sidebar will highlight the path you set
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu as string
|
||||
|
@ -62,9 +62,9 @@ export default defineComponent({
|
|||
>
|
||||
<ElScrollbar>
|
||||
<ElMenu
|
||||
defaultActive={activeMenu.value}
|
||||
mode={menuMode.value}
|
||||
collapse={collapse.value}
|
||||
defaultActive={unref(activeMenu)}
|
||||
mode={unref(menuMode)}
|
||||
collapse={unref(collapse)}
|
||||
backgroundColor="var(--left-menu-bg-color)"
|
||||
textColor="var(--left-menu-text-color)"
|
||||
activeTextColor="var(--left-menu-text-active-color)"
|
||||
|
@ -72,7 +72,7 @@ export default defineComponent({
|
|||
>
|
||||
{{
|
||||
default: () => {
|
||||
const { renderMenuItem } = useRenderMenuItem(routers.value, menuMode.value)
|
||||
const { renderMenuItem } = useRenderMenuItem(unref(routers), unref(menuMode))
|
||||
return renderMenuItem()
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -1,5 +1,356 @@
|
|||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, watch, computed, unref, ref, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { filterAffixTags } from './helper'
|
||||
import { ContextMenu } from '@/components/ContextMenu'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { currentRoute, push, replace } = useRouter()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const routers = computed(() => permissionStore.getRouters)
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const visitedViews = computed(() => tagsViewStore.getVisitedViews)
|
||||
|
||||
const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
|
||||
|
||||
// 初始化tag
|
||||
const initTags = () => {
|
||||
affixTagArr.value = filterAffixTags(unref(routers))
|
||||
for (const tag of unref(affixTagArr)) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
tagsViewStore.addVisitedView(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedTag = ref<RouteLocationNormalizedLoaded>()
|
||||
|
||||
// 新增tag
|
||||
const addTags = () => {
|
||||
const { name } = unref(currentRoute)
|
||||
if (name) {
|
||||
selectedTag.value = unref(currentRoute)
|
||||
tagsViewStore.addView(unref(currentRoute))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 关闭选中的tag
|
||||
const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
|
||||
if (view?.meta?.affix) return
|
||||
tagsViewStore.delView(view)
|
||||
if (isActive(view)) {
|
||||
toLastView()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭全部
|
||||
const closeAllTags = () => {
|
||||
tagsViewStore.delAllViews()
|
||||
toLastView()
|
||||
}
|
||||
|
||||
// 关闭其他
|
||||
const closeOthersTags = () => {
|
||||
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
|
||||
if (!view) return
|
||||
tagsViewStore.delCachedView()
|
||||
const { fullPath } = view
|
||||
await nextTick()
|
||||
replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭左侧
|
||||
const closeLeftTags = () => {
|
||||
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 关闭右侧
|
||||
const closeRightTags = () => {
|
||||
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
const toLastView = () => {
|
||||
const visitedViews = tagsViewStore.getVisitedViews
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
if (latestView) {
|
||||
push(latestView)
|
||||
} else {
|
||||
if (
|
||||
unref(currentRoute).path === permissionStore.getAddRouters[0].path ||
|
||||
unref(currentRoute).path === permissionStore.getAddRouters[0].redirect
|
||||
) {
|
||||
addTags()
|
||||
return
|
||||
}
|
||||
// You can set another route
|
||||
push(permissionStore.getAddRouters[0].path)
|
||||
}
|
||||
}
|
||||
|
||||
const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
|
||||
return route.path === unref(currentRoute).path
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initTags()
|
||||
addTags()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
addTags()
|
||||
// moveToCurrentTag()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[var(--tags-view-height)]">tagsView</div>
|
||||
<div class="v-tags-view h-[var(--tags-view-height)] flex w-full">
|
||||
<span class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer">
|
||||
<Icon icon="ant-design:left-outlined" color="#333" />
|
||||
</span>
|
||||
<div class="overflow-hidden flex-1">
|
||||
<ElScrollbar>
|
||||
<div class="flex h-[var(--tags-view-height)]">
|
||||
<ContextMenu
|
||||
:schema="[
|
||||
{
|
||||
icon: 'ant-design:sync-outlined',
|
||||
label: t('common.reload'),
|
||||
disabled: selectedTag?.fullPath !== item.fullPath,
|
||||
command: () => {
|
||||
refreshSelectedTag(item)
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:close-outlined',
|
||||
label: t('common.closeTab'),
|
||||
command: () => {
|
||||
closeSelectedTag(item)
|
||||
}
|
||||
},
|
||||
{
|
||||
divided: true,
|
||||
icon: 'ant-design:vertical-right-outlined',
|
||||
label: t('common.closeTheLeftTab'),
|
||||
disabled:
|
||||
!!visitedViews?.length &&
|
||||
(item.fullPath === visitedViews[0].fullPath ||
|
||||
selectedTag?.fullPath !== item.fullPath),
|
||||
command: () => {
|
||||
closeLeftTags()
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:vertical-left-outlined',
|
||||
label: t('common.closeTheRightTab'),
|
||||
disabled:
|
||||
!!visitedViews?.length &&
|
||||
(item.fullPath === visitedViews[visitedViews.length - 1].fullPath ||
|
||||
selectedTag?.fullPath !== item.fullPath),
|
||||
command: () => {
|
||||
closeRightTags()
|
||||
}
|
||||
},
|
||||
{
|
||||
divided: true,
|
||||
icon: 'ant-design:tag-outlined',
|
||||
label: t('common.closeOther'),
|
||||
disabled: selectedTag?.fullPath !== item.fullPath,
|
||||
command: () => {
|
||||
closeOthersTags()
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:line-outlined',
|
||||
label: t('common.closeAll'),
|
||||
command: () => {
|
||||
closeAllTags()
|
||||
}
|
||||
}
|
||||
]"
|
||||
v-for="item in visitedViews"
|
||||
:key="item.fullPath"
|
||||
:class="[
|
||||
'v-tags-view__item',
|
||||
{
|
||||
'v-tags-view__item--affix': item?.meta?.affix,
|
||||
'is-active': isActive(item)
|
||||
}
|
||||
]"
|
||||
>
|
||||
<router-link :to="{ ...item }" custom #default="{ navigate }">
|
||||
<div @click="navigate" class="h-full">
|
||||
{{ t(item?.meta?.title as string) }}
|
||||
<Icon
|
||||
class="v-tags-view__item--close"
|
||||
color="#333"
|
||||
icon="ant-design:close-outlined"
|
||||
:size="12"
|
||||
@click.prevent.stop="closeSelectedTag(item)"
|
||||
/>
|
||||
</div>
|
||||
</router-link>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
<span class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer">
|
||||
<Icon icon="ant-design:right-outlined" color="#333" />
|
||||
</span>
|
||||
<span
|
||||
class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer"
|
||||
@click="refreshSelectedTag(selectedTag)"
|
||||
>
|
||||
<Icon icon="ant-design:reload-outlined" color="#333" />
|
||||
</span>
|
||||
<ContextMenu
|
||||
trigger="click"
|
||||
:schema="[
|
||||
{
|
||||
icon: 'ant-design:sync-outlined',
|
||||
label: t('common.reload'),
|
||||
command: () => {
|
||||
refreshSelectedTag(selectedTag)
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:close-outlined',
|
||||
label: t('common.closeTab')
|
||||
},
|
||||
{
|
||||
divided: true,
|
||||
icon: 'ant-design:vertical-right-outlined',
|
||||
label: t('common.closeTheLeftTab'),
|
||||
disabled: !!visitedViews?.length && selectedTag?.fullPath === visitedViews[0].fullPath,
|
||||
command: () => {
|
||||
closeLeftTags()
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:vertical-left-outlined',
|
||||
label: t('common.closeTheRightTab'),
|
||||
disabled:
|
||||
!!visitedViews?.length &&
|
||||
selectedTag?.fullPath === visitedViews[visitedViews.length - 1].fullPath,
|
||||
command: () => {
|
||||
closeRightTags()
|
||||
}
|
||||
},
|
||||
{
|
||||
divided: true,
|
||||
icon: 'ant-design:tag-outlined',
|
||||
label: t('common.closeOther'),
|
||||
command: () => {
|
||||
closeOthersTags()
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:line-outlined',
|
||||
label: t('common.closeAll'),
|
||||
command: () => {
|
||||
closeAllTags()
|
||||
}
|
||||
}
|
||||
]"
|
||||
>
|
||||
<span
|
||||
class="v-tags-view__tool w-[40px] h-[40px] text-center leading-[40px] cursor-pointer block"
|
||||
>
|
||||
<Icon icon="ant-design:down-outlined" color="#333" />
|
||||
</span>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-tags-view';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&__tool {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
:deep(span) {
|
||||
color: var(--el-color-black) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--top-tool-border-color);
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
&__item + &__item {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
height: calc(~'100% - 4px');
|
||||
padding: 0 15px;
|
||||
font-size: 12px;
|
||||
line-height: calc(~'var( - -tags-view-height) - 4px');
|
||||
cursor: pointer;
|
||||
border: 1px solid #d9d9d9;
|
||||
|
||||
&--close {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
display: none;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
&:not(.@{prefix-cls}__item--affix):hover {
|
||||
.@{prefix-cls}__item--close {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__item:not(.@{prefix-cls}__item--affix) {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
&__item:not(.is-active) {
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&__item.is-active {
|
||||
color: var(--el-color-white);
|
||||
background-color: var(--el-color-primary);
|
||||
.@{prefix-cls}__item--close {
|
||||
:deep(span) {
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import type { RouteMeta, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
|
||||
export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') => {
|
||||
let tags: RouteLocationNormalizedLoaded[] = []
|
||||
routes.forEach((route) => {
|
||||
const meta = route.meta as RouteMeta
|
||||
const tagPath = pathResolve(parentPath, route.path)
|
||||
if (meta?.affix) {
|
||||
tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
if (route.children) {
|
||||
const tempTags: RouteLocationNormalizedLoaded[] = filterAffixTags(route.children, tagPath)
|
||||
if (tempTags.length >= 1) {
|
||||
tags = [...tags, ...tempTags]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return tags
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="tsx">
|
||||
import { computed, defineComponent, KeepAlive } from 'vue'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { Menu } from '@/components/Menu'
|
||||
import { Collapse } from '@/components/Collapse'
|
||||
|
@ -10,12 +9,7 @@ import { UserInfo } from '@/components/UserInfo'
|
|||
import { Screenfull } from '@/components/Screenfull'
|
||||
import { Breadcrumb } from '@/components/Breadcrumb'
|
||||
import { TagsView } from '@/components/TagsView'
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const getCaches = computed((): string[] => {
|
||||
return tagsViewStore.getCachedViews
|
||||
})
|
||||
import AppView from './components/AppView.vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
|
@ -71,18 +65,10 @@ export default defineComponent({
|
|||
<UserInfo class="header__tigger"></UserInfo>
|
||||
</div>
|
||||
</div>
|
||||
<div class="v-app-right__tags relative">
|
||||
<div class="v-app-right__tags-view relative">
|
||||
<TagsView></TagsView>
|
||||
</div>
|
||||
<router-view>
|
||||
{{
|
||||
default: ({ Component, route }) => (
|
||||
<KeepAlive include={getCaches.value}>
|
||||
<Component is={Component} key={route.fullPath}></Component>
|
||||
</KeepAlive>
|
||||
)
|
||||
}}
|
||||
</router-view>
|
||||
<AppView></AppView>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
@ -111,7 +97,7 @@ export default defineComponent({
|
|||
transition: left var(--transition-time-02);
|
||||
|
||||
&__tool,
|
||||
&__tags {
|
||||
&__tags-view {
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const getCaches = computed((): string[] => {
|
||||
return tagsViewStore.getCachedViews
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<keep-alive :include="getCaches">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</template>
|
||||
</router-view>
|
||||
</template>
|
|
@ -11,7 +11,13 @@ export default {
|
|||
reminder: 'Reminder',
|
||||
loginOutMessage: 'Exit the system?',
|
||||
ok: 'OK',
|
||||
cancel: 'Cancel'
|
||||
cancel: 'Cancel',
|
||||
reload: 'Reload current',
|
||||
closeTab: 'Close current',
|
||||
closeTheLeftTab: 'Close left',
|
||||
closeTheRightTab: 'Close right',
|
||||
closeOther: 'Close other',
|
||||
closeAll: 'Close all'
|
||||
},
|
||||
size: {
|
||||
default: 'Default',
|
||||
|
|
|
@ -11,7 +11,13 @@ export default {
|
|||
reminder: '温馨提示',
|
||||
loginOutMessage: '是否退出本系统?',
|
||||
ok: '确定',
|
||||
cancel: '取消'
|
||||
cancel: '取消',
|
||||
reload: '重新加载',
|
||||
closeTab: '关闭标签页',
|
||||
closeTheLeftTab: '关闭左侧标签页',
|
||||
closeTheRightTab: '关闭右侧标签页',
|
||||
closeOther: '关闭其他标签页',
|
||||
closeAll: '关闭全部标签页'
|
||||
},
|
||||
size: {
|
||||
default: '默认',
|
||||
|
|
|
@ -20,7 +20,8 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
|||
}
|
||||
],
|
||||
meta: {
|
||||
hidden: true
|
||||
hidden: true,
|
||||
noTagsView: true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -107,7 +108,8 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
name: 'Icons',
|
||||
meta: {
|
||||
title: '图标',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
icon: 'carbon:skill-level-advanced',
|
||||
affix: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// import router from '@/router'
|
||||
import router from '@/router'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { getRawRoute } from '@/utils/routerHelper'
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { findIndex } from '@/utils'
|
||||
|
||||
export interface TagsViewState {
|
||||
visitedViews: RouteLocationNormalizedLoaded[]
|
||||
|
@ -24,18 +25,24 @@ export const useTagsViewStore = defineStore({
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
ADD_VISITED_VIEW(view: RouteLocationNormalizedLoaded): void {
|
||||
if (this.visitedViews.some((v: RouteLocationNormalizedLoaded) => v.path === view.path)) return
|
||||
// 新增缓存和tag
|
||||
addView(view: RouteLocationNormalizedLoaded): void {
|
||||
this.addVisitedView(view)
|
||||
this.addCachedView()
|
||||
},
|
||||
// 新增tag
|
||||
addVisitedView(view: RouteLocationNormalizedLoaded) {
|
||||
if (this.visitedViews.some((v) => v.path === view.path)) return
|
||||
if (view.meta?.noTagsView) return
|
||||
this.visitedViews.push(
|
||||
Object.assign({}, view, {
|
||||
title: view.meta.title || 'no-name'
|
||||
title: view.meta?.title || 'no-name'
|
||||
})
|
||||
)
|
||||
},
|
||||
SET_CACHED_VIEW(): void {
|
||||
// 新增缓存
|
||||
addCachedView() {
|
||||
const cacheMap: Set<string> = new Set()
|
||||
|
||||
for (const v of this.visitedViews) {
|
||||
const item = getRawRoute(v)
|
||||
const needCache = !item.meta?.noCache
|
||||
|
@ -45,9 +52,17 @@ export const useTagsViewStore = defineStore({
|
|||
const name = item.name as string
|
||||
cacheMap.add(name)
|
||||
}
|
||||
if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString())
|
||||
return
|
||||
this.cachedViews = cacheMap
|
||||
},
|
||||
DEL_VISITED_VIEW(view: RouteLocationNormalizedLoaded): void {
|
||||
// 删除某个
|
||||
delView(view: RouteLocationNormalizedLoaded) {
|
||||
this.delVisitedView(view)
|
||||
this.addCachedView()
|
||||
},
|
||||
// 删除tag
|
||||
delVisitedView(view: RouteLocationNormalizedLoaded) {
|
||||
for (const [i, v] of this.visitedViews.entries()) {
|
||||
if (v.path === view.path) {
|
||||
this.visitedViews.splice(i, 1)
|
||||
|
@ -55,117 +70,60 @@ export const useTagsViewStore = defineStore({
|
|||
}
|
||||
}
|
||||
},
|
||||
DEL_CACHED_VIEW(): void {
|
||||
// const route = router.currentRoute.value
|
||||
// for (const [key, value] of this.cachedViews) {
|
||||
// const index = value.findIndex((item: string) => item === (route.name as string))
|
||||
// if (index === -1) {
|
||||
// continue
|
||||
// }
|
||||
// if (value.length === 1) {
|
||||
// this.cachedViews.delete(key)
|
||||
// continue
|
||||
// }
|
||||
// value.splice(index, 1)
|
||||
// this.cachedViews.set(key, value)
|
||||
// }
|
||||
// 删除缓存
|
||||
delCachedView() {
|
||||
const route = router.currentRoute.value
|
||||
const index = findIndex<string>(this.getCachedViews, (v) => v === route.name)
|
||||
if (index > -1) {
|
||||
this.cachedViews.delete(this.getCachedViews[index])
|
||||
}
|
||||
},
|
||||
DEL_OTHERS_VISITED_VIEWS(view: RouteLocationNormalizedLoaded): void {
|
||||
this.visitedViews = this.visitedViews.filter((v) => {
|
||||
return v.meta.affix || v.path === view.path
|
||||
})
|
||||
// 删除所有缓存和tag
|
||||
delAllViews() {
|
||||
this.delAllVisitedViews()
|
||||
this.addCachedView()
|
||||
},
|
||||
DEL_ALL_VISITED_VIEWS(): void {
|
||||
// keep affix tags
|
||||
// 删除所有tag
|
||||
delAllVisitedViews() {
|
||||
const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
|
||||
this.visitedViews = affixTags
|
||||
},
|
||||
UPDATE_VISITED_VIEW(view: RouteLocationNormalizedLoaded): void {
|
||||
for (let v of this.visitedViews) {
|
||||
if (v.path === view.path) {
|
||||
v = Object.assign(v, view)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
addView(view: RouteLocationNormalizedLoaded): void {
|
||||
this.addVisitedView(view)
|
||||
// 删除其他
|
||||
delOthersViews(view: RouteLocationNormalizedLoaded) {
|
||||
this.delOthersVisitedViews(view)
|
||||
this.addCachedView()
|
||||
},
|
||||
addVisitedView(view: RouteLocationNormalizedLoaded): void {
|
||||
this.ADD_VISITED_VIEW(view)
|
||||
// 删除其他tag
|
||||
delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {
|
||||
this.visitedViews = this.visitedViews.filter((v) => {
|
||||
return v?.meta?.affix || v.path === view.path
|
||||
})
|
||||
},
|
||||
addCachedView(): void {
|
||||
this.SET_CACHED_VIEW()
|
||||
},
|
||||
delView(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.delVisitedView(view)
|
||||
this.SET_CACHED_VIEW()
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews],
|
||||
cachedViews: [...this.cachedViews]
|
||||
// 删除左侧
|
||||
delLeftViews(view: RouteLocationNormalizedLoaded) {
|
||||
const index = findIndex<RouteLocationNormalizedLoaded>(
|
||||
this.visitedViews,
|
||||
(v) => v.path === view.path
|
||||
)
|
||||
if (index > -1) {
|
||||
this.visitedViews = this.visitedViews.filter((v, i) => {
|
||||
return v?.meta?.affix || v.path === view.path || i > index
|
||||
})
|
||||
})
|
||||
this.addCachedView()
|
||||
}
|
||||
},
|
||||
delVisitedView(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.DEL_VISITED_VIEW(view)
|
||||
resolve([...this.visitedViews])
|
||||
})
|
||||
},
|
||||
delCachedView(): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.DEL_CACHED_VIEW()
|
||||
resolve([...this.cachedViews])
|
||||
})
|
||||
},
|
||||
delOthersViews(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.delOthersVisitedViews(view)
|
||||
this.SET_CACHED_VIEW()
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews],
|
||||
cachedViews: [...this.cachedViews]
|
||||
// 删除右侧
|
||||
delRightViews(view: RouteLocationNormalizedLoaded) {
|
||||
const index = findIndex<RouteLocationNormalizedLoaded>(
|
||||
this.visitedViews,
|
||||
(v) => v.path === view.path
|
||||
)
|
||||
if (index > -1) {
|
||||
this.visitedViews = this.visitedViews.filter((v, i) => {
|
||||
return v?.meta?.affix || v.path === view.path || i < index
|
||||
})
|
||||
})
|
||||
},
|
||||
delOthersVisitedViews(view: RouteLocationNormalizedLoaded): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.DEL_OTHERS_VISITED_VIEWS(view)
|
||||
resolve([...this.visitedViews])
|
||||
})
|
||||
},
|
||||
delOthersCachedViews(): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.SET_CACHED_VIEW()
|
||||
resolve([...this.cachedViews])
|
||||
})
|
||||
},
|
||||
delAllViews(): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.delAllVisitedViews()
|
||||
this.SET_CACHED_VIEW()
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews],
|
||||
cachedViews: [...this.cachedViews]
|
||||
})
|
||||
})
|
||||
},
|
||||
delAllVisitedViews(): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.DEL_ALL_VISITED_VIEWS()
|
||||
resolve([...this.visitedViews])
|
||||
})
|
||||
},
|
||||
delAllCachedViews(): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
this.SET_CACHED_VIEW()
|
||||
resolve([...this.cachedViews])
|
||||
})
|
||||
},
|
||||
updateVisitedView(view: RouteLocationNormalizedLoaded): void {
|
||||
this.UPDATE_VISITED_VIEW(view)
|
||||
this.addCachedView()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
import { isServer } from './is'
|
||||
const ieVersion = isServer ? 0 : Number((document as any).documentMode)
|
||||
const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g
|
||||
const MOZ_HACK_REGEXP = /^moz([A-Z])/
|
||||
|
||||
export interface ViewportOffsetResult {
|
||||
left: number
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
rightIncludeBody: number
|
||||
bottomIncludeBody: number
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const trim = function (string: string) {
|
||||
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const camelCase = function (name: string) {
|
||||
return name
|
||||
.replace(SPECIAL_CHARS_REGEXP, function (_, __, letter, offset) {
|
||||
return offset ? letter.toUpperCase() : letter
|
||||
})
|
||||
.replace(MOZ_HACK_REGEXP, 'Moz$1')
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function hasClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return false
|
||||
if (cls.indexOf(' ') !== -1) {
|
||||
throw new Error('className should not contain space.')
|
||||
}
|
||||
if (el.classList) {
|
||||
return el.classList.contains(cls)
|
||||
} else {
|
||||
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function addClass(el: Element, cls: string) {
|
||||
if (!el) return
|
||||
let curClass = el.className
|
||||
const classes = (cls || '').split(' ')
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i]
|
||||
if (!clsName) continue
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.add(clsName)
|
||||
} else if (!hasClass(el, clsName)) {
|
||||
curClass += ' ' + clsName
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = curClass
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function removeClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return
|
||||
const classes = cls.split(' ')
|
||||
let curClass = ' ' + el.className + ' '
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i]
|
||||
if (!clsName) continue
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.remove(clsName)
|
||||
} else if (hasClass(el, clsName)) {
|
||||
curClass = curClass.replace(' ' + clsName + ' ', ' ')
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = trim(curClass)
|
||||
}
|
||||
}
|
||||
|
||||
export function getBoundingClientRect(element: Element): DOMRect | number {
|
||||
if (!element || !element.getBoundingClientRect) {
|
||||
return 0
|
||||
}
|
||||
return element.getBoundingClientRect()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前元素的left、top偏移
|
||||
* left:元素最左侧距离文档左侧的距离
|
||||
* top:元素最顶端距离文档顶端的距离
|
||||
* right:元素最右侧距离文档右侧的距离
|
||||
* bottom:元素最底端距离文档底端的距离
|
||||
* rightIncludeBody:元素最左侧距离文档右侧的距离
|
||||
* bottomIncludeBody:元素最底端距离文档最底部的距离
|
||||
*
|
||||
* @description:
|
||||
*/
|
||||
export function getViewportOffset(element: Element): ViewportOffsetResult {
|
||||
const doc = document.documentElement
|
||||
|
||||
const docScrollLeft = doc.scrollLeft
|
||||
const docScrollTop = doc.scrollTop
|
||||
const docClientLeft = doc.clientLeft
|
||||
const docClientTop = doc.clientTop
|
||||
|
||||
const pageXOffset = window.pageXOffset
|
||||
const pageYOffset = window.pageYOffset
|
||||
|
||||
const box = getBoundingClientRect(element)
|
||||
|
||||
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect
|
||||
|
||||
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0)
|
||||
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0)
|
||||
const offsetLeft = retLeft + pageXOffset
|
||||
const offsetTop = rectTop + pageYOffset
|
||||
|
||||
const left = offsetLeft - scrollLeft
|
||||
const top = offsetTop - scrollTop
|
||||
|
||||
const clientWidth = window.document.documentElement.clientWidth
|
||||
const clientHeight = window.document.documentElement.clientHeight
|
||||
return {
|
||||
left: left,
|
||||
top: top,
|
||||
right: clientWidth - rectWidth - left,
|
||||
bottom: clientHeight - rectHeight - top,
|
||||
rightIncludeBody: clientWidth - left,
|
||||
bottomIncludeBody: clientHeight - top
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const on = function (
|
||||
element: HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false)
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const off = function (
|
||||
element: HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: any
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.removeEventListener(event, handler, false)
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const once = function (el: HTMLElement, event: string, fn: EventListener): void {
|
||||
const listener = function (this: any, ...args: unknown[]) {
|
||||
if (fn) {
|
||||
// @ts-ignore
|
||||
fn.apply(this, args)
|
||||
}
|
||||
off(el, event, listener)
|
||||
}
|
||||
on(el, event, listener)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const getStyle =
|
||||
ieVersion < 9
|
||||
? function (element: Element | any, styleName: string) {
|
||||
if (isServer) return
|
||||
if (!element || !styleName) return null
|
||||
styleName = camelCase(styleName)
|
||||
if (styleName === 'float') {
|
||||
styleName = 'styleFloat'
|
||||
}
|
||||
try {
|
||||
switch (styleName) {
|
||||
case 'opacity':
|
||||
try {
|
||||
return element.filters.item('alpha').opacity / 100
|
||||
} catch (e) {
|
||||
return 1.0
|
||||
}
|
||||
default:
|
||||
return element.style[styleName] || element.currentStyle
|
||||
? element.currentStyle[styleName]
|
||||
: null
|
||||
}
|
||||
} catch (e) {
|
||||
return element.style[styleName]
|
||||
}
|
||||
}
|
||||
: function (element: Element | any, styleName: string) {
|
||||
if (isServer) return
|
||||
if (!element || !styleName) return null
|
||||
styleName = camelCase(styleName)
|
||||
if (styleName === 'float') {
|
||||
styleName = 'cssFloat'
|
||||
}
|
||||
try {
|
||||
const computed = (document as any).defaultView.getComputedStyle(element, '')
|
||||
return element.style[styleName] || computed ? computed[styleName] : null
|
||||
} catch (e) {
|
||||
return element.style[styleName]
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function setStyle(element: Element | any, styleName: any, value: any) {
|
||||
if (!element || !styleName) return
|
||||
|
||||
if (typeof styleName === 'object') {
|
||||
for (const prop in styleName) {
|
||||
if (Object.prototype.hasOwnProperty.call(styleName, prop)) {
|
||||
setStyle(element, prop, styleName[prop])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
styleName = camelCase(styleName)
|
||||
if (styleName === 'opacity' && ieVersion < 9) {
|
||||
element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')'
|
||||
} else {
|
||||
element.style[styleName] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isScroll = (el: Element, vertical: any) => {
|
||||
if (isServer) return
|
||||
|
||||
const determinedDirection = vertical !== null || vertical !== undefined
|
||||
const overflow = determinedDirection
|
||||
? vertical
|
||||
? getStyle(el, 'overflow-y')
|
||||
: getStyle(el, 'overflow-x')
|
||||
: getStyle(el, 'overflow')
|
||||
|
||||
return overflow.match(/(scroll|auto)/)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const getScrollContainer = (el: Element, vertical?: any) => {
|
||||
if (isServer) return
|
||||
|
||||
let parent: any = el
|
||||
while (parent) {
|
||||
if ([window, document, document.documentElement].includes(parent)) {
|
||||
return window
|
||||
}
|
||||
if (isScroll(parent, vertical)) {
|
||||
return parent
|
||||
}
|
||||
parent = parent.parentNode
|
||||
}
|
||||
|
||||
return parent
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isInContainer = (el: Element, container: any) => {
|
||||
if (isServer || !el || !container) return false
|
||||
|
||||
const elRect = el.getBoundingClientRect()
|
||||
let containerRect
|
||||
|
||||
if ([window, document, document.documentElement, null, undefined].includes(container)) {
|
||||
containerRect = {
|
||||
top: 0,
|
||||
right: window.innerWidth,
|
||||
bottom: window.innerHeight,
|
||||
left: 0
|
||||
}
|
||||
} else {
|
||||
containerRect = container.getBoundingClientRect()
|
||||
}
|
||||
|
||||
return (
|
||||
elRect.top < containerRect.bottom &&
|
||||
elRect.bottom > containerRect.top &&
|
||||
elRect.right > containerRect.left &&
|
||||
elRect.left < containerRect.right
|
||||
)
|
||||
}
|
|
@ -38,3 +38,24 @@ export const underlineToHump = (str: string): string => {
|
|||
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
|
||||
dom.style.setProperty(prop, val)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找数组对象的某个下标
|
||||
* @param {Array} ary 查找的数组
|
||||
* @param {Functon} fn 判断的方法
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
export const findIndex = <T = Recordable>(ary: Array<T>, fn: Fn): number => {
|
||||
if (ary.findIndex) {
|
||||
return ary.findIndex(fn)
|
||||
}
|
||||
let index = -1
|
||||
ary.some((item: T, i: number, ary: Array<T>) => {
|
||||
const ret: T = fn(item, i, ary)
|
||||
if (ret) {
|
||||
index = i
|
||||
return ret
|
||||
}
|
||||
})
|
||||
return index
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts" name="Menu111">
|
||||
import { onMounted } from 'vue'
|
||||
onMounted(() => {
|
||||
console.log('????')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Menu111 <input type="text" /></div>
|
||||
<div class="h-[100000px]">Menu111 <input type="text" /></div>
|
||||
</template>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { LoginForm } from './components'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/components/LocaleDropdown'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { underlineToHump } from '@/utils'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
@ -10,22 +9,14 @@ import { useAppStore } from '@/store/modules/app'
|
|||
const appStore = useAppStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('login')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="h-[100%] relative overflow-hidden <xl:bg-v-dark <sm:px-10px <xl:px-10px <md:px-10px"
|
||||
class="v-login h-[100%] relative overflow-hidden <xl:bg-v-dark <sm:px-10px <xl:px-10px <md:px-10px"
|
||||
>
|
||||
<div class="relative h-full flex mx-auto">
|
||||
<div
|
||||
:class="`${prefixCls}__left`"
|
||||
class="flex-1 bg-gray-500 bg-opacity-20 relative p-30px <xl:hidden"
|
||||
>
|
||||
<div class="v-login__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px <xl:hidden">
|
||||
<div class="flex items-center relative text-white">
|
||||
<img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
|
|
|
@ -129,11 +129,10 @@ const signIn = async () => {
|
|||
await permissionStore.generateRoutes().catch(() => {})
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
console.log(route)
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || '/level' })
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
declare type contextMenuSchema = {
|
||||
disabled?: boolean
|
||||
divided?: boolean
|
||||
icon?: string
|
||||
label: string
|
||||
command?: (item: contextMenuSchema) => viod
|
||||
}
|
|
@ -10,6 +10,7 @@ import StyleImport, { ElementPlusResolve } from 'vite-plugin-style-import'
|
|||
import ViteSvgIcons from 'vite-plugin-svg-icons'
|
||||
import PurgeIcons from 'vite-plugin-purge-icons'
|
||||
import { viteMockServe } from 'vite-plugin-mock'
|
||||
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
const root = process.cwd()
|
||||
|
@ -68,7 +69,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
|
||||
setupProdMockServer()
|
||||
`
|
||||
})
|
||||
}),
|
||||
VueSetupExtend()
|
||||
],
|
||||
|
||||
css: {
|
||||
|
|
Loading…
Reference in New Issue