80 changed files with 1335 additions and 969 deletions
@ -1,96 +0,0 @@ |
|||
import type { Menu as MenuType } from '/@/router/types'; |
|||
import type { PropType } from 'vue'; |
|||
import { computed, unref } from 'vue'; |
|||
|
|||
import { defineComponent } from 'vue'; |
|||
import Icon from '/@/components/Icon/index'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
|
|||
const { t } = useI18n(); |
|||
|
|||
export default defineComponent({ |
|||
name: 'MenuContent', |
|||
props: { |
|||
item: { |
|||
type: Object as PropType<MenuType>, |
|||
default: null, |
|||
}, |
|||
showTitle: { |
|||
type: Boolean as PropType<boolean>, |
|||
default: true, |
|||
}, |
|||
level: { |
|||
type: Number as PropType<number>, |
|||
default: 0, |
|||
}, |
|||
isHorizontal: { |
|||
type: Boolean as PropType<boolean>, |
|||
default: true, |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('basic-menu'); |
|||
|
|||
const getI18nName = computed(() => t(props.item?.name)); |
|||
|
|||
const getTagClass = computed(() => { |
|||
const { item } = props; |
|||
const { tag = {} } = item || {}; |
|||
const { dot, type = 'error' } = tag; |
|||
return [ |
|||
`${prefixCls}__tag`, |
|||
type, |
|||
{ |
|||
dot, |
|||
}, |
|||
]; |
|||
}); |
|||
|
|||
const getNameClass = computed(() => { |
|||
const { showTitle } = props; |
|||
return { [`${prefixCls}--show-title`]: showTitle, [`${prefixCls}__name`]: !showTitle }; |
|||
}); |
|||
|
|||
/** |
|||
* @description: 渲染图标 |
|||
*/ |
|||
function renderIcon(icon?: string) { |
|||
return icon ? <Icon icon={icon} size={18} class="menu-item-icon" /> : null; |
|||
} |
|||
|
|||
function renderTag() { |
|||
const { item, showTitle, isHorizontal } = props; |
|||
if (!item || showTitle || isHorizontal) return null; |
|||
|
|||
const { tag } = item; |
|||
if (!tag) return null; |
|||
|
|||
const { dot, content } = tag; |
|||
if (!dot && !content) return null; |
|||
|
|||
return <span class={unref(getTagClass)}>{dot ? '' : content}</span>; |
|||
} |
|||
|
|||
return () => { |
|||
const { item } = props; |
|||
if (!item) { |
|||
return null; |
|||
} |
|||
const { icon } = item; |
|||
const name = unref(getI18nName); |
|||
|
|||
return ( |
|||
<span class={`${prefixCls}__content-wrapper`}> |
|||
{renderIcon(icon)} |
|||
{ |
|||
<span class={unref(getNameClass)}> |
|||
{name} |
|||
{renderTag()} |
|||
</span> |
|||
} |
|||
</span> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,41 @@ |
|||
<template> |
|||
<span :class="`${prefixCls}-wrapper`"> |
|||
<Icon v-if="getIcon" :icon="getIcon" :size="18" :class="`${prefixCls}-wrapper__icon`" /> |
|||
<span :class="getNameClass"> |
|||
{{ getI18nName }} |
|||
<MenuItemTag v-bind="$props" /> |
|||
</span> |
|||
</span> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { computed, defineComponent } from 'vue'; |
|||
|
|||
import Icon from '/@/components/Icon/index'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { contentProps } from '../props'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
const { t } = useI18n(); |
|||
|
|||
export default defineComponent({ |
|||
name: 'MenuItemContent', |
|||
components: { Icon, MenuItemTag: createAsyncComponent(() => import('./MenuItemTag.vue')) }, |
|||
props: contentProps, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('basic-menu-item-content'); |
|||
const getI18nName = computed(() => t(props.item?.name)); |
|||
const getIcon = computed(() => props.item?.icon); |
|||
|
|||
const getNameClass = computed(() => { |
|||
const { showTitle } = props; |
|||
return { [`${prefixCls}--show-title`]: showTitle, [`${prefixCls}__name`]: !showTitle }; |
|||
}); |
|||
return { |
|||
prefixCls, |
|||
getNameClass, |
|||
getI18nName, |
|||
getIcon, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,56 @@ |
|||
<template> |
|||
<span :class="getTagClass" v-if="getShowTag">{{ getContent }}</span> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, computed } from 'vue'; |
|||
|
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { contentProps } from '../props'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'MenuItemTag', |
|||
props: contentProps, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('basic-menu-item-tag'); |
|||
|
|||
const getShowTag = computed(() => { |
|||
const { item, showTitle, isHorizontal } = props; |
|||
if (!item || showTitle || isHorizontal) return false; |
|||
|
|||
const { tag } = item; |
|||
if (!tag) return false; |
|||
|
|||
const { dot, content } = tag; |
|||
if (!dot && !content) return false; |
|||
return true; |
|||
}); |
|||
|
|||
const getContent = computed(() => { |
|||
if (!getShowTag.value) return ''; |
|||
const { item } = props; |
|||
const { tag } = item; |
|||
const { dot, content } = tag!; |
|||
return dot ? '' : content; |
|||
}); |
|||
|
|||
const getTagClass = computed(() => { |
|||
const { item } = props; |
|||
const { tag = {} } = item || {}; |
|||
const { dot, type = 'error' } = tag; |
|||
return [ |
|||
prefixCls, |
|||
[`${prefixCls}--${type}`], |
|||
{ |
|||
[`${prefixCls}--dot`]: dot, |
|||
}, |
|||
]; |
|||
}); |
|||
return { |
|||
prefixCls, |
|||
getTagClass, |
|||
getShowTag, |
|||
getContent, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,10 @@ |
|||
import { useAppProviderContext } from '/@/components/Application'; |
|||
import { computed, unref } from 'vue'; |
|||
|
|||
export function useAppInject() { |
|||
const values = useAppProviderContext(); |
|||
|
|||
return { |
|||
getIsMobile: computed(() => unref(values.isMobile)), |
|||
}; |
|||
} |
|||
@ -1,125 +0,0 @@ |
|||
// components
|
|||
import { Dropdown, Menu } from 'ant-design-vue'; |
|||
|
|||
import { defineComponent, computed, unref } from 'vue'; |
|||
|
|||
// res
|
|||
import headerImg from '/@/assets/images/header.jpg'; |
|||
|
|||
import Icon from '/@/components/Icon/index'; |
|||
|
|||
import { userStore } from '/@/store/modules/user'; |
|||
|
|||
import { DOC_URL } from '/@/settings/siteSetting'; |
|||
|
|||
import { openWindow } from '/@/utils'; |
|||
|
|||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
|||
import { FunctionalComponent } from 'vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
type MenuEvent = 'loginOut' | 'doc'; |
|||
interface MenuItemProps { |
|||
icon: string; |
|||
text: string; |
|||
key: MenuEvent; |
|||
} |
|||
|
|||
const prefixCls = 'user-dropdown'; |
|||
|
|||
const MenuItem: FunctionalComponent<MenuItemProps> = (props) => { |
|||
const { key, icon, text } = props; |
|||
return ( |
|||
<Menu.Item key={key}> |
|||
{() => ( |
|||
<span class="flex items-center"> |
|||
<Icon icon={icon} class="mr-1" /> |
|||
<span>{text}</span> |
|||
</span> |
|||
)} |
|||
</Menu.Item> |
|||
); |
|||
}; |
|||
|
|||
export default defineComponent({ |
|||
name: 'UserDropdown', |
|||
setup() { |
|||
const { t } = useI18n(); |
|||
const { getShowDoc } = useHeaderSetting(); |
|||
|
|||
const getUserInfo = computed(() => { |
|||
const { realName = '', desc } = userStore.getUserInfoState || {}; |
|||
return { realName, desc }; |
|||
}); |
|||
|
|||
// login out
|
|||
function handleLoginOut() { |
|||
userStore.confirmLoginOut(); |
|||
} |
|||
|
|||
// open doc
|
|||
function openDoc() { |
|||
openWindow(DOC_URL); |
|||
} |
|||
|
|||
function handleMenuClick(e: { key: MenuEvent }) { |
|||
switch (e.key) { |
|||
case 'loginOut': |
|||
handleLoginOut(); |
|||
break; |
|||
case 'doc': |
|||
openDoc(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
function renderSlotsDefault() { |
|||
const { realName } = unref(getUserInfo); |
|||
return ( |
|||
<section class={prefixCls}> |
|||
<img class={`${prefixCls}__header`} src={headerImg} /> |
|||
<section class={`${prefixCls}__info`}> |
|||
<section class={`${prefixCls}__name`}>{realName}</section> |
|||
</section> |
|||
</section> |
|||
); |
|||
} |
|||
|
|||
function renderSlotOverlay() { |
|||
const showDoc = unref(getShowDoc); |
|||
return ( |
|||
<Menu onClick={handleMenuClick}> |
|||
{() => ( |
|||
<> |
|||
{showDoc && ( |
|||
<MenuItem |
|||
key="doc" |
|||
text={t('layout.header.dropdownItemDoc')} |
|||
icon="gg:loadbar-doc" |
|||
/> |
|||
)} |
|||
{/* @ts-ignore */} |
|||
{showDoc && <Menu.Divider />} |
|||
<MenuItem |
|||
key="loginOut" |
|||
text={t('layout.header.dropdownItemLoginOut')} |
|||
icon="carbon:power" |
|||
/> |
|||
</> |
|||
)} |
|||
</Menu> |
|||
); |
|||
} |
|||
|
|||
return () => { |
|||
return ( |
|||
<Dropdown placement="bottomLeft" overlayClassName="app-layout-header-user-dropdown-overlay"> |
|||
{{ |
|||
default: () => renderSlotsDefault(), |
|||
overlay: () => renderSlotOverlay(), |
|||
}} |
|||
</Dropdown> |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,47 @@ |
|||
<template> |
|||
<Tooltip |
|||
:title="t('layout.header.tooltipErrorLog')" |
|||
placement="bottom" |
|||
:mouseEnterDelay="0.5" |
|||
@click="handleToErrorList" |
|||
> |
|||
<Badge :count="getCount" :offset="[0, 10]" dot :overflowCount="99"> |
|||
<BugOutlined /> |
|||
</Badge> |
|||
</Tooltip> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, computed } from 'vue'; |
|||
import { Tooltip, Badge } from 'ant-design-vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { BugOutlined } from '@ant-design/icons-vue'; |
|||
import { errorStore } from '/@/store/modules/error'; |
|||
import { PageEnum } from '/@/enums/pageEnum'; |
|||
import { useRouter } from 'vue-router'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'ErrorAction', |
|||
components: { BugOutlined, Tooltip, Badge }, |
|||
|
|||
setup() { |
|||
const { t } = useI18n(); |
|||
const { push } = useRouter(); |
|||
|
|||
const getCount = computed(() => { |
|||
return errorStore.getErrorListCountState; |
|||
}); |
|||
|
|||
function handleToErrorList() { |
|||
push(PageEnum.ERROR_LOG_PAGE).then(() => { |
|||
errorStore.commitErrorListCountState(0); |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
t, |
|||
getCount, |
|||
handleToErrorList, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,36 @@ |
|||
<template> |
|||
<Tooltip :title="getTitle" placement="bottom" :mouseEnterDelay="0.5"> |
|||
<span @click="toggleFullscreen"> |
|||
<FullscreenOutlined v-if="!isFullscreen" /> |
|||
<FullscreenExitOutlined v-else /> |
|||
</span> |
|||
</Tooltip> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, computed, unref } from 'vue'; |
|||
import { Tooltip } from 'ant-design-vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { useFullscreen } from '/@/hooks/web/useFullScreen'; |
|||
import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons-vue'; |
|||
export default defineComponent({ |
|||
name: 'FullScreen', |
|||
components: { FullscreenExitOutlined, FullscreenOutlined, Tooltip }, |
|||
|
|||
setup() { |
|||
const { t } = useI18n(); |
|||
const { toggleFullscreen, isFullscreenRef } = useFullscreen(); |
|||
|
|||
const getTitle = computed(() => { |
|||
return unref(isFullscreenRef) |
|||
? t('layout.header.tooltipExitFull') |
|||
: t('layout.header.tooltipEntryFull'); |
|||
}); |
|||
|
|||
return { |
|||
getTitle, |
|||
isFullscreen: isFullscreenRef, |
|||
toggleFullscreen, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,15 @@ |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
|
|||
export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { |
|||
loading: true, |
|||
}); |
|||
|
|||
export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')); |
|||
|
|||
export const FullScreen = createAsyncComponent(() => import('./FullScreen.vue')); |
|||
|
|||
export const Notify = createAsyncComponent(() => import('./notify/index.vue')); |
|||
|
|||
export const LockItem = createAsyncComponent(() => import('./lock/index.vue')); |
|||
|
|||
export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue')); |
|||
@ -0,0 +1,116 @@ |
|||
<template> |
|||
<BasicModal |
|||
:footer="null" |
|||
:title="t('layout.header.lockScreen')" |
|||
v-bind="$attrs" |
|||
:class="prefixCls" |
|||
@register="register" |
|||
> |
|||
<div :class="`${prefixCls}__entry`"> |
|||
<div :class="`${prefixCls}__header`"> |
|||
<img src="/@/assets/images/header.jpg" :class="`${prefixCls}__header-img`" /> |
|||
<p :class="`${prefixCls}__header-name`">{{ getRealName }}</p> |
|||
</div> |
|||
|
|||
<BasicForm @register="registerForm" layout="vertical" /> |
|||
|
|||
<div :class="`${prefixCls}__footer`"> |
|||
<a-button type="primary" block class="mt-2" @click="handleLock"> |
|||
{{ t('layout.header.lockScreenBtn') }} |
|||
</a-button> |
|||
</div> |
|||
</div> |
|||
</BasicModal> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, computed } from 'vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { BasicModal, useModalInner } from '/@/components/Modal/index'; |
|||
import { BasicForm, useForm } from '/@/components/Form/index'; |
|||
|
|||
import { userStore } from '/@/store/modules/user'; |
|||
import { lockStore } from '/@/store/modules/lock'; |
|||
export default defineComponent({ |
|||
name: 'LockModal', |
|||
components: { BasicModal, BasicForm }, |
|||
|
|||
setup() { |
|||
const { t } = useI18n(); |
|||
const { prefixCls } = useDesign('header-lock-modal'); |
|||
|
|||
const getRealName = computed(() => { |
|||
return userStore.getUserInfoState?.realName; |
|||
}); |
|||
const [register, { closeModal }] = useModalInner(); |
|||
|
|||
const [registerForm, { validateFields, resetFields }] = useForm({ |
|||
showActionButtonGroup: false, |
|||
schemas: [ |
|||
{ |
|||
field: 'password', |
|||
label: t('layout.header.lockScreenPassword'), |
|||
component: 'InputPassword', |
|||
required: true, |
|||
}, |
|||
], |
|||
}); |
|||
|
|||
async function handleLock() { |
|||
const values = (await validateFields()) as any; |
|||
const password: string | undefined = values.password; |
|||
closeModal(); |
|||
|
|||
lockStore.commitLockInfoState({ |
|||
isLock: true, |
|||
pwd: password, |
|||
}); |
|||
await resetFields(); |
|||
} |
|||
|
|||
return { |
|||
t, |
|||
prefixCls, |
|||
getRealName, |
|||
register, |
|||
registerForm, |
|||
handleLock, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@import (reference) '../../../../../design/index.less'; |
|||
@prefix-cls: ~'@{namespace}-header-lock-modal'; |
|||
|
|||
.@{prefix-cls} { |
|||
&__entry { |
|||
position: relative; |
|||
height: 240px; |
|||
padding: 130px 30px 60px 30px; |
|||
background: #fff; |
|||
border-radius: 10px; |
|||
} |
|||
|
|||
&__header { |
|||
position: absolute; |
|||
top: 0; |
|||
left: calc(50% - 45px); |
|||
width: auto; |
|||
text-align: center; |
|||
|
|||
&-img { |
|||
width: 70px; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
&-name { |
|||
margin-top: 5px; |
|||
} |
|||
} |
|||
|
|||
&__footer { |
|||
text-align: center; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,38 @@ |
|||
<template> |
|||
<span @click="handleLock"> |
|||
<Tooltip :title="t('layout.header.tooltipLock')" placement="bottom" :mouseEnterDelay="0.5"> |
|||
<LockOutlined /> |
|||
</Tooltip> |
|||
<LockAction @register="register" /> |
|||
</span> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import { Tooltip } from 'ant-design-vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { LockOutlined } from '@ant-design/icons-vue'; |
|||
import { useModal } from '/@/components/Modal'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
export default defineComponent({ |
|||
name: 'FullScreen', |
|||
components: { |
|||
LockOutlined, |
|||
Tooltip, |
|||
LockAction: createAsyncComponent(() => import('./LockModal.vue')), |
|||
}, |
|||
|
|||
setup() { |
|||
const { t } = useI18n(); |
|||
const [register, { openModal }] = useModal(); |
|||
|
|||
function handleLock() { |
|||
openModal(true); |
|||
} |
|||
return { |
|||
t, |
|||
register, |
|||
handleLock, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,27 @@ |
|||
<template> |
|||
<MenuItem :key="key"> |
|||
<span class="flex items-center"> |
|||
<Icon :icon="icon" class="mr-1" /> |
|||
<span>{{ text }}</span> |
|||
</span> |
|||
</MenuItem> |
|||
</template> |
|||
<script lang="ts"> |
|||
// components |
|||
import { Menu } from 'ant-design-vue'; |
|||
|
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import Icon from '/@/components/Icon/index'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'DropdownMenuItem', |
|||
components: { MenuItem: Menu.Item, Icon }, |
|||
props: { |
|||
key: propTypes.string, |
|||
text: propTypes.string, |
|||
icon: propTypes.string, |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,156 @@ |
|||
<template> |
|||
<Dropdown placement="bottomLeft" :overlayClassName="`${prefixCls}-dropdown-overlay`"> |
|||
<span :class="[prefixCls, `${prefixCls}--${theme}`]"> |
|||
<img :class="`${prefixCls}__header`" src="/@/assets/images/header.jpg" /> |
|||
<span :class="`${prefixCls}__info`"> |
|||
<span :class="`${prefixCls}__name anticon`">{{ getUserInfo.realName }}</span> |
|||
</span> |
|||
</span> |
|||
|
|||
<template #overlay> |
|||
<Menu @click="handleMenuClick"> |
|||
<MenuItem key="doc" :text="t('layout.header.dropdownItemDoc')" icon="gg:loadbar-doc" /> |
|||
<MenuDivider v-if="getShowDoc" /> |
|||
<MenuItem |
|||
key="loginOut" |
|||
:text="t('layout.header.dropdownItemLoginOut')" |
|||
icon="carbon:power" |
|||
/> |
|||
</Menu> |
|||
</template> |
|||
</Dropdown> |
|||
</template> |
|||
<script lang="ts"> |
|||
// components |
|||
import { Dropdown, Menu } from 'ant-design-vue'; |
|||
|
|||
import { defineComponent, computed } from 'vue'; |
|||
|
|||
// res |
|||
|
|||
import Icon from '/@/components/Icon/index'; |
|||
|
|||
import { userStore } from '/@/store/modules/user'; |
|||
|
|||
import { DOC_URL } from '/@/settings/siteSetting'; |
|||
|
|||
import { openWindow } from '/@/utils'; |
|||
|
|||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
|
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; |
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
|
|||
type MenuEvent = 'loginOut' | 'doc'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'UserDropdown', |
|||
components: { |
|||
Dropdown, |
|||
Menu, |
|||
MenuItem: createAsyncComponent(() => import('./DropMenuItem.vue')), |
|||
MenuDivider: Menu.Divider, |
|||
Icon, |
|||
}, |
|||
props: { |
|||
theme: propTypes.oneOf(['dark', 'light']), |
|||
}, |
|||
setup() { |
|||
const { prefixCls } = useDesign('header-user-dropdown'); |
|||
const { t } = useI18n(); |
|||
const { getShowDoc } = useHeaderSetting(); |
|||
|
|||
const getUserInfo = computed(() => { |
|||
const { realName = '', desc } = userStore.getUserInfoState || {}; |
|||
return { realName, desc }; |
|||
}); |
|||
|
|||
// login out |
|||
function handleLoginOut() { |
|||
userStore.confirmLoginOut(); |
|||
} |
|||
|
|||
// open doc |
|||
function openDoc() { |
|||
openWindow(DOC_URL); |
|||
} |
|||
|
|||
function handleMenuClick(e: { key: MenuEvent }) { |
|||
switch (e.key) { |
|||
case 'loginOut': |
|||
handleLoginOut(); |
|||
break; |
|||
case 'doc': |
|||
openDoc(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return { |
|||
prefixCls, |
|||
t, |
|||
getUserInfo, |
|||
handleMenuClick, |
|||
getShowDoc, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@import (reference) '../../../../../design/index.less'; |
|||
@prefix-cls: ~'@{namespace}-header-user-dropdown'; |
|||
|
|||
.@{prefix-cls} { |
|||
display: flex; |
|||
height: @header-height; |
|||
min-width: 100px; |
|||
padding: 0 0 0 10px; |
|||
padding-right: 10px; |
|||
overflow: hidden; |
|||
font-size: 12px; |
|||
cursor: pointer; |
|||
align-items: center; |
|||
|
|||
&:hover { |
|||
background: @header-light-bg-hover-color; |
|||
} |
|||
|
|||
img { |
|||
width: 26px; |
|||
height: 26px; |
|||
margin-right: 12px; |
|||
} |
|||
|
|||
&__header { |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
&__name { |
|||
font-size: 14px; |
|||
} |
|||
|
|||
&--dark { |
|||
&:hover { |
|||
background: @header-dark-bg-hover-color; |
|||
} |
|||
} |
|||
|
|||
&--light { |
|||
.@{prefix-cls}__name { |
|||
color: @text-color-base; |
|||
} |
|||
|
|||
.@{prefix-cls}__desc { |
|||
color: @header-light-desc-color; |
|||
} |
|||
} |
|||
|
|||
&-dropdown-overlay { |
|||
.ant-dropdown-menu-item { |
|||
min-width: 160px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,161 @@ |
|||
<template> |
|||
<Header :class="getHeaderClass"> |
|||
<!-- left start --> |
|||
<div :class="`${prefixCls}-left`"> |
|||
<!-- logo --> |
|||
<AppLogo v-if="getShowHeaderLogo" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" /> |
|||
|
|||
<LayoutTrigger |
|||
v-if="getShowContent && getShowHeaderTrigger" |
|||
:theme="getHeaderTheme" |
|||
:sider="false" |
|||
/> |
|||
<LayoutBreadcrumb |
|||
v-if="getShowContent && getShowBread && !getIsMobile" |
|||
:theme="getHeaderTheme" |
|||
/> |
|||
</div> |
|||
<!-- left end --> |
|||
|
|||
<!-- menu start --> |
|||
<div :class="`${prefixCls}-menu`" v-if="getShowTopMenu && !getIsMobile"> |
|||
<LayoutMenu |
|||
:isHorizontal="true" |
|||
:theme="getHeaderTheme" |
|||
:splitType="getSplitType" |
|||
:menuMode="getMenuMode" |
|||
/> |
|||
</div> |
|||
<!-- menu-end --> |
|||
|
|||
<!-- action --> |
|||
<div :class="`${prefixCls}-action`"> |
|||
<AppSearch v-if="!getIsMobile" :class="`${prefixCls}-action__item`" /> |
|||
|
|||
<ErrorAction v-if="getUseErrorHandle && !getIsMobile" :class="`${prefixCls}-action__item`" /> |
|||
|
|||
<LockItem v-if="getUseLockPage && !getIsMobile" :class="`${prefixCls}-action__item`" /> |
|||
|
|||
<Notify v-if="getShowNotice && !getIsMobile" :class="`${prefixCls}-action__item`" /> |
|||
|
|||
<FullScreen v-if="getShowFullScreen && !getIsMobile" :class="`${prefixCls}-action__item`" /> |
|||
|
|||
<UserDropDown :theme="getHeaderTheme" /> |
|||
|
|||
<AppLocalePicker |
|||
v-if="getShowLocale" |
|||
:reload="true" |
|||
:showText="false" |
|||
:class="`${prefixCls}-action__item`" |
|||
/> |
|||
</div> |
|||
</Header> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, unref, computed } from 'vue'; |
|||
|
|||
import { propTypes } from '/@/utils/propTypes'; |
|||
|
|||
import { Layout } from 'ant-design-vue'; |
|||
import { AppLogo } from '/@/components/Application'; |
|||
import LayoutMenu from '../menu'; |
|||
import LayoutTrigger from '../trigger/index.vue'; |
|||
|
|||
import { AppSearch } from '/@/components/Application'; |
|||
|
|||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
|||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|||
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
|||
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting'; |
|||
|
|||
import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum'; |
|||
import { AppLocalePicker } from '/@/components/Application'; |
|||
|
|||
import { |
|||
UserDropDown, |
|||
LayoutBreadcrumb, |
|||
FullScreen, |
|||
Notify, |
|||
LockItem, |
|||
ErrorAction, |
|||
} from './components'; |
|||
import { useAppInject } from '/@/hooks/web/useAppInject'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'LayoutHeader', |
|||
components: { |
|||
Header: Layout.Header, |
|||
AppLogo, |
|||
LayoutTrigger, |
|||
LayoutBreadcrumb, |
|||
LayoutMenu, |
|||
UserDropDown, |
|||
AppLocalePicker, |
|||
FullScreen, |
|||
Notify, |
|||
LockItem, |
|||
AppSearch, |
|||
ErrorAction, |
|||
}, |
|||
props: { |
|||
fixed: propTypes.bool, |
|||
}, |
|||
setup(props) { |
|||
const { prefixCls } = useDesign('layout-header'); |
|||
const { getShowTopMenu, getShowHeaderTrigger, getSplit } = useMenuSetting(); |
|||
const { getShowLocale } = useLocaleSetting(); |
|||
const { getUseErrorHandle } = useRootSetting(); |
|||
|
|||
const { |
|||
getHeaderTheme, |
|||
getUseLockPage, |
|||
getShowFullScreen, |
|||
getShowNotice, |
|||
getShowContent, |
|||
getShowBread, |
|||
getShowHeaderLogo, |
|||
} = useHeaderSetting(); |
|||
|
|||
const { getIsMobile } = useAppInject(); |
|||
|
|||
const getHeaderClass = computed(() => { |
|||
const theme = unref(getHeaderTheme); |
|||
return [ |
|||
prefixCls, |
|||
{ [`${prefixCls}--fixed`]: props.fixed, [`${prefixCls}--${theme}`]: theme }, |
|||
]; |
|||
}); |
|||
|
|||
const getSplitType = computed(() => { |
|||
return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE; |
|||
}); |
|||
|
|||
const getMenuMode = computed(() => { |
|||
return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null; |
|||
}); |
|||
|
|||
return { |
|||
prefixCls, |
|||
getHeaderClass, |
|||
getShowHeaderLogo, |
|||
getHeaderTheme, |
|||
getShowHeaderTrigger, |
|||
getIsMobile, |
|||
getShowBread, |
|||
getShowContent, |
|||
getSplitType, |
|||
getMenuMode, |
|||
getShowTopMenu, |
|||
getShowLocale, |
|||
getShowFullScreen, |
|||
getShowNotice, |
|||
getUseLockPage, |
|||
getUseErrorHandle, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@import './index.less'; |
|||
</style> |
|||
@ -0,0 +1,37 @@ |
|||
<template> |
|||
<Tooltip :title="t('layout.multipleTab.tooltipRedo')" placement="bottom" :mouseEnterDelay="0.5"> |
|||
<span :class="`${prefixCls}__extra-redo`" @click="handleRedo"> |
|||
<RedoOutlined :spin="loading" /> |
|||
</span> |
|||
</Tooltip> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, ref } from 'vue'; |
|||
import { RedoOutlined } from '@ant-design/icons-vue'; |
|||
import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { Tooltip } from 'ant-design-vue'; |
|||
import { useI18n } from '/@/hooks/web/useI18n'; |
|||
import { useTabs } from '/@/hooks/web/useTabs'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'TabContent', |
|||
components: { RedoOutlined, Tooltip }, |
|||
|
|||
setup() { |
|||
const loading = ref(false); |
|||
const { prefixCls } = useDesign('multiple-tabs-content'); |
|||
const { t } = useI18n(); |
|||
const { refreshPage } = useTabs(); |
|||
|
|||
async function handleRedo() { |
|||
loading.value = true; |
|||
await refreshPage(); |
|||
setTimeout(() => { |
|||
loading.value = false; |
|||
// Animation execution time |
|||
}, 1000); |
|||
} |
|||
return { prefixCls, t, handleRedo, loading }; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Used to monitor routing changes to change the status of menus and tabs. There is no need to monitor the route, because the route status change is affected by the page rendering time, which will be slow |
|||
*/ |
|||
|
|||
import Mitt from '/@/utils/mitt'; |
|||
import type { RouteLocationNormalized } from 'vue-router'; |
|||
import { getRoute } from '/@/router/helper/routeHelper'; |
|||
|
|||
const mitt = new Mitt(); |
|||
|
|||
const key = Symbol(); |
|||
|
|||
let lastChangeTab: RouteLocationNormalized; |
|||
|
|||
export function setLastChangeTab(lastChangeRoute: RouteLocationNormalized) { |
|||
mitt.emit(key, getRoute(lastChangeRoute)); |
|||
lastChangeTab = getRoute(lastChangeRoute); |
|||
} |
|||
|
|||
export function listenerLastChangeTab( |
|||
callback: (route: RouteLocationNormalized) => void, |
|||
immediate = true |
|||
) { |
|||
mitt.on(key, callback); |
|||
if (immediate) { |
|||
callback(lastChangeTab); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue