de/core/frontend/src/layout/components/Topbar.vue

565 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="top-nav">
<div
v-loading="!axiosFinished"
class="log"
>
<svg-icon
v-if="!logoUrl && axiosFinished"
icon-class="DataEase"
custom-class="top-nav-logo-icon"
/>
<img
v-if="logoUrl && axiosFinished"
:src="logoUrl"
width="140"
alt=""
style="padding-top: 10px;"
>
</div>
<el-menu
class="de-top-menu"
mode="horizontal"
:style="{'margin-left': '260px', 'position': 'absolute'}"
active-text-color="#FFFFFF"
:default-active="activeMenu"
@select="handleSelect"
>
<div
v-for="item in permission_routes"
:key="item.path"
class="nav-item"
>
<app-link :to="resolvePath(item)">
<el-menu-item
v-if="!item.hidden"
:index="item.path"
>
{{ item.meta ? item.meta.title : item.children[0].meta.title }}
</el-menu-item>
</app-link>
</div>
</el-menu>
<div
class="right-menu"
style="color: var(--TopTextColor)"
>
<div class="download-export">
<svg-icon
icon-class="icon_download_outlined"
@click="downloadClick"
/>
</div>
<div
v-if="aiBaseUrl"
style="height: 100%;padding: 0 8px;"
class="right-menu-item hover-effect"
>
<a style="font-size:24px;display: flex;height: 100%;width: 100%;justify-content: center;align-items: center;">
<svg-icon
icon-class="dv-ai"
@click="handleAiClick"
/>
</a>
</div>
<notification class="right-menu-item hover-effect" />
<lang-select class="right-menu-item hover-effect" />
<div
style="height: 100%;padding: 0 8px;"
class="right-menu-item hover-effect"
>
<a
:href="helpLink"
target="_blank"
style="display: flex;height: 100%;width: 100%;justify-content: center;align-items: center;"
>
<svg-icon icon-class="docs" />
</a>
</div>
<el-dropdown
ref="my-drop"
class="top-dropdown"
style="display: flex;align-items: center; width:100px;"
trigger="click"
>
<div
class="el-dropdown-link"
style="display: flex;color: var(--TopTextColor);font-size: 14px; width:100%;"
>
<span style="max-width:80px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;">{{ name }}</span>
<span><i class="el-icon-arrow-down el-icon--right" /></span>
</div>
<el-dropdown-menu slot="dropdown">
<router-link to="/person-info/index">
<el-dropdown-item>{{ $t('commons.personal_info') }}</el-dropdown-item>
</router-link>
<router-link
v-if="$store.getters.validate"
to="/ukey/index"
>
<el-dropdown-item>{{ $t('commons.ukey_title') }}</el-dropdown-item>
</router-link>
<router-link
v-if="!isOtherPlatform"
to="/person-pwd/index"
>
<el-dropdown-item>{{ $t('user.change_password') }}</el-dropdown-item>
</router-link>
<router-link to="/about/index">
<el-dropdown-item>{{ $t('commons.about_us') }}</el-dropdown-item>
</router-link>
<el-dropdown-item
v-if="!isOtherPlatform"
divided
@click.native="logout"
>
<span style="display:block;">{{ $t('commons.exit_system') }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<ExportExcel ref="ExportExcelRef" />
<ai-tips
v-if="showOverlay"
class="ai-icon-tips"
@confirm="aiTipsConfirm"
/>
<div
v-if="showOverlay"
class="overlay"
/>
<ai-component
v-if="aiBaseUrl"
:base-url="aiBaseUrl"
/>
<!--模板市场全屏显示框-->
<el-dialog
:visible="templateMarketShow"
:show-close="false"
class="dialog-css"
:fullscreen="true"
append-to-body
>
<template-market
v-if="templateMarketShow"
style="text-align: center"
@closeDialog="changeTemplateMarketShow(false)"
/>
</el-dialog>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import ExportExcel from '@/views/dataset/exportExcel/index.vue'
import AppLink from './Sidebar/Link'
import variables from '@/styles/variables.scss'
import { isExternal } from '@/utils/validate'
import Notification from '@/components/notification'
import bus from '@/utils/bus'
import LangSelect from '@/components/langSelect'
import { getSysUI } from '@/utils/auth'
import { pluginLoaded } from '@/api/user'
import { initTheme } from '@/utils/ThemeUtil'
import TemplateMarket from '@/views/panel/templateMarket'
import { changeFavicon, inOtherPlatform } from '@/utils/index'
import AiComponent from '@/layout/components/AiComponent'
import { findBaseParams } from '@/api/ai/aiComponent'
import AiTips from '@/layout/components/AiTips.vue'
export default {
name: 'Topbar',
components: {
AiTips,
AiComponent,
TemplateMarket,
AppLink,
Notification,
LangSelect,
ExportExcel
},
props: {
showTips: {
type: Boolean,
default: false
}
},
data() {
return {
showOverlay: false,
aiBaseUrl: null,
uiInfo: null,
logoUrl: null,
axiosFinished: false,
isPluginLoaded: false,
templateMarketShow: false
}
},
computed: {
theme() {
return this.$store.state.settings.theme
},
topMenuColor() {
if (this.$store.getters.uiInfo && this.$store.getters.uiInfo['ui.topMenuColor'] && this.$store.getters.uiInfo[
'ui.topMenuColor'].paramValue) {
return this.$store.getters.uiInfo['ui.topMenuColor'].paramValue
}
return this.variables.topBarBg
},
topMenuActiveColor() {
if (this.$store.getters.uiInfo && this.$store.getters.uiInfo['ui.topMenuActiveColor'] && this.$store.getters
.uiInfo['ui.topMenuActiveColor'].paramValue) {
return this.$store.getters.uiInfo['ui.topMenuActiveColor'].paramValue
}
return this.variables.topBarMenuActive
},
topMenuTextColor() {
if (this.$store.getters.uiInfo && this.$store.getters.uiInfo['ui.topMenuTextColor'] && this.$store.getters
.uiInfo['ui.topMenuTextColor'].paramValue) {
return this.$store.getters.uiInfo['ui.topMenuTextColor'].paramValue
}
return this.variables.topBarMenuText
},
topMenuTextActiveColor() {
if (this.$store.getters.uiInfo && this.$store.getters.uiInfo['ui.topMenuTextActiveColor'] && this.$store.getters
.uiInfo['ui.topMenuTextActiveColor'].paramValue) {
return this.$store.getters.uiInfo['ui.topMenuTextActiveColor'].paramValue
}
return this.variables.topBarMenuTextActive
},
helpLink() {
if (this.$store.getters.uiInfo && this.$store.getters.uiInfo['ui.helpLink'] && this.$store.getters.uiInfo['ui.helpLink'].paramValue) {
return this.$store.getters.uiInfo['ui.helpLink'].paramValue
}
return 'https://dataease.io/docs/v1/'
},
/* topMenuColor() {
return this.$store.getters.uiInfo.topMenuColor
}, */
activeMenu() {
const route = this.$route
const {
meta,
path
} = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
// return meta.activeMenu
}
// 如果是首页,首页高亮
if (path === '/panel') {
return '/'
}
// 如果不是首页,高亮一级菜单
const activeMenu = '/' + path.split('/')[1]
return activeMenu
},
variables() {
return variables
},
sidebar() {
return this.$store.state.app.sidebar
},
isOtherPlatform() {
return inOtherPlatform()
},
...mapGetters([
'avatar',
'permission_routes',
'name'
])
},
mounted() {
window.addEventListener('beforeunload', (e) => this.beforeunloadHandler(e))
window.addEventListener('unload', (e) => this.unloadHandler(e))
this.initCurrentRoutes()
bus.$on('set-top-menu-info', this.setTopMenuInfo)
bus.$on('set-top-menu-active-info', this.setTopMenuActiveInfo)
bus.$on('set-top-text-info', this.setTopTextInfo)
bus.$on('set-top-text-active-info', this.setTopTextActiveInfo)
bus.$on('sys-logout', this.logout)
bus.$on('data-export-center', this.dataExportCenter)
this.showTips && this.$nextTick(() => {
const drop = this.$refs['my-drop']
drop && drop.show && drop.show()
})
this.initAiBase()
},
beforeDestroy() {
window.removeEventListener('beforeunload', (e) => this.beforeunloadHandler(e))
window.removeEventListener('unload', (e) => this.unloadHandler(e))
bus.$off('data-export-center', this.dataExportCenter)
bus.$off('set-top-menu-info', this.setTopMenuInfo)
bus.$off('set-top-menu-active-info', this.setTopMenuActiveInfo)
bus.$off('set-top-text-info', this.setTopTextInfo)
bus.$off('set-top-text-active-info', this.setTopTextActiveInfo)
bus.$off('sys-logout', this.logout)
},
created() {
this.loadUiInfo()
},
beforeCreate() {
pluginLoaded().then(res => {
this.isPluginLoaded = res.success && res.data
if (this.isPluginLoaded) {
initTheme()
}
})
},
methods: {
aiTipsConfirm() {
localStorage.setItem('DE1.0-AI-TIPS-CHECK', 'CHECKED')
this.showOverlay = false
},
dataExportCenter() {
this.downloadClick()
},
async initAiBase() {
const aiTipsCheck = localStorage.getItem('DE1.0-AI-TIPS-CHECK')
await findBaseParams().then(rsp => {
const params = rsp.data
if (params && params['ai.baseUrl']) {
this.aiBaseUrl = params['ai.baseUrl']
}
})
},
handleAiClick() {
bus.$emit('aiComponentChange')
},
beforeunloadHandler() {
this.beforeUnload_time = new Date().getTime()
},
downloadClick() {
this.$refs.ExportExcelRef.init()
},
unloadHandler(e) {
this.gap_time = new Date().getTime() - this.beforeUnload_time
if (this.gap_time <= 5) {
// this.logout().then(res => {})
}
},
// 通过当前路径找到二级菜单对应项存到store用来渲染左侧菜单
initCurrentRoutes() {
const {
path
} = this.$route
let route = this.permission_routes.find(
item => item.path === '/' + path.split('/')[1]
)
// 如果找不到这个路由,说明是首页
if (!route) {
route = this.permission_routes.find(item => item.path === '/')
}
this.$store.commit('permission/SET_CURRENT_ROUTES', route)
this.setSidebarHide(route)
},
// 判断该路由是否只有一个子项或者没有子项,如果是,则在一级菜单添加跳转路由
isOnlyOneChild(item) {
if (item.children && item.children.length === 1) {
return true
}
return false
},
resolvePath(item) {
// 如果是个完成的url直接返回
if (isExternal(item.path)) {
return item.path
}
// 如果是首页,就返回重定向路由
if (item.path === '/') {
const path = item.redirect
return path
}
// 如果有子项,默认跳转第一个子项路由
let path = ''
/**
* item 路由子项
* parent 路由父项
*/
const getDefaultPath = (item, parent) => {
// 如果path是个外部链接不建议直接返回链接存在个问题如果是外部链接点击跳转后当前页内容还是上一个路由内容
if (isExternal(item.path)) {
path = item.path
return
}
// 第一次需要父项路由拼接所以只是第一个传parent
if (parent) {
path += (parent.path + '/' + item.path)
} else {
path += ('/' + item.path)
}
// 如果还有子项,继续递归
if (item.children) {
getDefaultPath(item.children[0])
}
}
if (item.children) {
getDefaultPath(item.children[0], item)
return path
}
return item.path
},
handleSelect(key, keyPath) {
// 把选中路由的子路由保存store
const route = this.permission_routes.find(item => item.path === key)
this.$store.commit('permission/SET_CURRENT_ROUTES', route)
this.setSidebarHide(route)
},
// 设置侧边栏的显示和隐藏
setSidebarHide(route) {
const hidePaths = ['/person-info', '/person-pwd', '/about']
if (hidePaths.includes(route.path)) {
this.$store.dispatch('app/toggleSideBarHide', true)
return
}
// if (!route.children || route.children.length === 1) {
if (route.name !== 'system' && (!route.children || this.showChildLength(route) === 1)) {
this.$store.dispatch('app/toggleSideBarHide', true)
} else {
this.$store.dispatch('app/toggleSideBarHide', false)
}
},
// 获取非隐藏子路由的个数
showChildLength(route) {
if (!route || !route.children) {
return 0
}
return route.children.filter(kid => !kid.hidden).length
},
async logout(param) {
const result = await this.$store.dispatch('user/logout', param)
if (result !== 'success' && result !== 'fail') {
window.location.href = result
} else {
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}
},
loadUiInfo() {
this.$store.dispatch('user/getUI').then((res) => {
this.uiInfo = getSysUI()
if (!this.uiInfo || Object.keys(this.uiInfo).length === 0) {
this.uiInfo = res
}
if (this.uiInfo['ui.logo'] && this.uiInfo['ui.logo'].paramValue) {
this.logoUrl = '/system/ui/image/' + this.uiInfo['ui.logo'].paramValue
}
if (this.uiInfo['ui.theme'] && this.uiInfo['ui.theme'].paramValue) {
const val = this.uiInfo['ui.theme'].paramValue
this.$store.dispatch('settings/changeSetting', {
key: 'theme',
value: val
})
}
if (this.uiInfo['ui.favicon'] && this.uiInfo['ui.favicon'].paramValue) {
const faviconUrl = '/system/ui/image/' + this.uiInfo['ui.favicon'].paramValue
changeFavicon(faviconUrl)
}
this.axiosFinished = true
})
},
setTopMenuInfo(val) {
this.loadUiInfo()
},
setTopMenuActiveInfo(val) {
this.loadUiInfo()
},
setTopTextInfo(val) {
this.loadUiInfo()
},
setTopTextActiveInfo(val) {
this.loadUiInfo()
},
changeTemplateMarketShow(isShow) {
this.templateMarketShow = isShow
}
}
}
</script>
<style lang="scss" scoped>
.download-export {
font-size: 20px;
cursor: pointer;
padding: 4px;
color: #646A73;
width: 28px;
height: 28px;
border-radius: 4px;
display: flex;
&:hover {
background: #1F23291A;
}
}
.el-dropdown-link {
cursor: pointer;
color: #1e212a;
}
.el-icon-arrow-down {
font-size: 12px;
}
.top-dropdown {
display: inline-block;
padding: 10px 8px;
height: 100%;
font-size: 16px;
color: #1e212a;
vertical-align: text-bottom;
margin-right: 10px;
}
.de-top-menu {
background-color: var(--MainBG);
}
.template-market-item {
display: flex;
color: var(--MenuActiveBG, #409EFF);
font-size: 14px !important;
line-height: 38px !important;
}
.dialog-css ::v-deep .el-dialog__header {
display: none;
}
.ai-icon-tips {
font-size: 24px !important;
z-index: 10001;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* 半透明黑色 */
z-index: 10000;
}
</style>