优化驾驶舱页面

This commit is contained in:
guanj
2025-12-20 23:44:46 +08:00
parent 7e4db9d4cd
commit cc0f8bc8b6
10 changed files with 770 additions and 456 deletions

View File

@@ -32,7 +32,7 @@ const config = useConfig()
const tree = ref() const tree = ref()
const treRef = ref() const treRef = ref()
const changeDeviceType = (val: any, obj: any) => { const changeDeviceType = (val: any, obj: any) => {
console.log("🚀 ~ changeDeviceType ~ val:", val,obj) console.log('🚀 ~ changeDeviceType ~ val:', val, obj)
emit('deviceTypeChange', val, obj) emit('deviceTypeChange', val, obj)
} }
getDeviceTree().then(res => { getDeviceTree().then(res => {
@@ -50,7 +50,7 @@ getDeviceTree().then(res => {
item2.color = config.getColorVal('elementUiPrimary') item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => { item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform' item3.icon = 'el-icon-Platform'
item3.level = 2 item3.level = 2
item3.color = config.getColorVal('elementUiPrimary') item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) { if (item3.comFlag === 1) {
item3.color = '#e26257 !important' item3.color = '#e26257 !important'
@@ -83,8 +83,8 @@ getDeviceTree().then(res => {
// }) // })
}) })
}) })
}else if (item.name == '在线设备') { } else if (item.name == '在线设备') {
item.children.forEach((item: any) => { item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled' item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary') item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => { item.children.forEach((item2: any) => {
@@ -102,41 +102,40 @@ getDeviceTree().then(res => {
}) })
} }
}) })
console.log("🚀 ~ file: deviceTree.vue ~ line 18 ~ getDeviceTree ~ tree:", arr,arr2,arr3) console.log('🚀 ~ file: deviceTree.vue ~ line 18 ~ getDeviceTree ~ tree:', arr, arr2, arr3)
tree.value = res.data tree.value = res.data
nextTick(() => { nextTick(() => {
if (arr.length) { setTimeout(() => {
treRef.value.treeRef1.setCurrentKey(arr[0].id) if (arr.length > 0) {
// 注册父组件事件 treRef.value.treeRef1.setCurrentKey(arr[0].id)
emit('init', { // 注册父组件事件
level: 2, emit('init', {
...arr[0] level: 2,
}) ...arr[0]
return })
} return
if (arr2.length) { } else if (arr2.length > 0) {
treRef.value.treeRef2.setCurrentKey(arr2[0].id) treRef.value.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件 // 注册父组件事件
emit('init', { emit('init', {
level: 2, level: 2,
...arr2[0] ...arr2[0]
}) })
return return
} } else if (arr3.length > 0) {
if (arr3.length) { treRef.value.treeRef3.setCurrentKey(arr3[0].id)
treRef.value.treeRef3.setCurrentKey(arr3[0].id) // 注册父组件事件
// 注册父组件事件 emit('init', {
emit('init', { level: 2,
level: 2, ...arr3[0]
...arr3[0] })
}) return
return } else {
} emit('init')
else { return
emit('init') }
return }, 500)
}
}) })
}) })
const handleCheckChange = (data: any, checked: any, indeterminate: any) => { const handleCheckChange = (data: any, checked: any, indeterminate: any) => {

View File

@@ -99,38 +99,37 @@ const info = () => {
}) })
tree.value = res.data tree.value = res.data
nextTick(() => { nextTick(() => {
if (arr1.length) { setTimeout(() => {
//初始化选中 if (arr1.length > 0) {
treRef.value.treeRef1.setCurrentKey(arr1[0].id) //初始化选中
// 注册父组件事件 treRef.value.treeRef1.setCurrentKey(arr1[0].id)
emit('init', { // 注册父组件事件
level: 2, emit('init', {
...arr1[0] level: 2,
}) ...arr1[0]
return })
} return
if (arr2.length) { } else if (arr2.length > 0) {
//初始化选中 //初始化选中
treRef.value.treeRef2.setCurrentKey(arr2[0].id) treRef.value.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件 // 注册父组件事件
emit('init', { emit('init', {
level: 2, level: 2,
...arr2[0] ...arr2[0]
}) })
return return
} } else if (arr3.length > 0) {
if(arr3.length){ treRef.value.treeRef3.setCurrentKey(arr3[0].id)
treRef.value.treeRef3.setCurrentKey(arr3[0].id) emit('init', {
emit('init', { level: 2,
level: 2, ...arr3[0]
...arr3[0] })
}) return
return } else {
} emit('init')
else { return
emit('init') }
return }, 500)
}
}) })
}) })
} }

View File

@@ -1,242 +1,246 @@
<template> <template>
<div class="nav-tabs" ref="tabScrollbarRef"> <div class="nav-tabs" ref="tabScrollbarRef">
<div
v-for="(item, idx) in navTabs.state.tabsView" <div
@click="onTab(item)" v-for="(item, idx) in navTabs.state.tabsView"
@contextmenu.prevent="onContextmenu(item, $event)" @click="onTab(item)"
class="ba-nav-tab" @contextmenu.prevent="onContextmenu(item, $event)"
:class="navTabs.state.activeIndex == idx ? 'active' : ''" class="ba-nav-tab"
:ref="tabsRefs.set" :class="navTabs.state.activeIndex == idx ? 'active' : ''"
:key="idx" :ref="tabsRefs.set"
> :key="idx"
{{ item.meta.title }} >
<transition @after-leave="selectNavTab(tabsRefs[navTabs.state.activeIndex])" name="el-fade-in"> {{ item.meta.title }}
<Icon <transition @after-leave="selectNavTab(tabsRefs[navTabs.state.activeIndex])" name="el-fade-in">
v-if="navTabs.state.tabsView.length > 1" <Icon
class="close-icon" v-if="navTabs.state.tabsView.length > 1"
@click.stop="closeTab(item)" class="close-icon"
size="15" @click.stop="closeTab(item)"
name="el-icon-Close" size="15"
/> name="el-icon-Close"
</transition> />
</div> </transition>
<!-- <div :style='activeBoxStyle' class='nav-tabs-active-box'></div>--> </div>
</div> <!-- <div :style='activeBoxStyle' class='nav-tabs-active-box'></div>-->
<Contextmenu ref="contextmenuRef" :items="state.contextmenuItems" @contextmenuItemClick="onContextmenuItem" /> </div>
</template> <Contextmenu ref="contextmenuRef" :items="state.contextmenuItems" @contextmenuItemClick="onContextmenuItem" />
</template>
<script setup lang="ts">
import { nextTick, onMounted, reactive, ref } from 'vue' <script setup lang="ts">
import { useRoute, useRouter, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router' import { nextTick, onMounted, reactive, ref } from 'vue'
import { useConfig } from '@/stores/config' import { useRoute, useRouter, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'
import { useNavTabs } from '@/stores/navTabs' import { useConfig } from '@/stores/config'
import { useTemplateRefsList } from '@vueuse/core' import { useNavTabs } from '@/stores/navTabs'
import type { ContextMenuItem, ContextmenuItemClickEmitArg } from '@/components/contextmenu/interface' import { useTemplateRefsList } from '@vueuse/core'
import useCurrentInstance from '@/utils/useCurrentInstance' import type { ContextMenuItem, ContextmenuItemClickEmitArg } from '@/components/contextmenu/interface'
import Contextmenu from '@/components/contextmenu/index.vue' import useCurrentInstance from '@/utils/useCurrentInstance'
import horizontalScroll from '@/utils/horizontalScroll' import Contextmenu from '@/components/contextmenu/index.vue'
import { getFirstRoute, routePush } from '@/utils/router' import horizontalScroll from '@/utils/horizontalScroll'
import { adminBaseRoutePath } from '@/router/static' import { getFirstRoute, routePush } from '@/utils/router'
import { adminBaseRoutePath } from '@/router/static'
const route = useRoute()
const router = useRouter() const route = useRoute()
const config = useConfig() const router = useRouter()
const navTabs = useNavTabs() const config = useConfig()
const navTabs = useNavTabs()
const { proxy } = useCurrentInstance()
const tabScrollbarRef = ref() const { proxy } = useCurrentInstance()
const tabsRefs = useTemplateRefsList<HTMLDivElement>() const tabScrollbarRef = ref()
const tabsRefs = useTemplateRefsList<HTMLDivElement>()
const contextmenuRef = ref()
const contextmenuRef = ref()
const state: {
contextmenuItems: ContextMenuItem[] const state: {
} = reactive({ contextmenuItems: ContextMenuItem[]
contextmenuItems: [ } = reactive({
{ name: 'refresh', label: '重新加载', icon: 'fa fa-refresh' }, contextmenuItems: [
{ name: 'close', label: '关闭标签', icon: 'fa fa-times' }, { name: 'refresh', label: '重新加载', icon: 'fa fa-refresh' },
{ name: 'fullScreen', label: '当前标签全屏', icon: 'el-icon-FullScreen' }, { name: 'close', label: '关闭标签', icon: 'fa fa-times' },
{ name: 'closeOther', label: '关闭其他标签', icon: 'fa fa-minus' }, { name: 'fullScreen', label: '当前标签全屏', icon: 'el-icon-FullScreen' },
{ name: 'closeAll', label: '关闭全部标签', icon: 'fa fa-stop' } { name: 'closeOther', label: '关闭其他标签', icon: 'fa fa-minus' },
] { name: 'closeAll', label: '关闭全部标签', icon: 'fa fa-stop' }
}) ]
})
const activeBoxStyle = reactive({
width: '0', const activeBoxStyle = reactive({
transform: 'translateX(0px)' width: '0',
}) transform: 'translateX(0px)'
})
const onTab = (menu: RouteLocationNormalized) => {
router.push(menu) const onTab = (menu: RouteLocationNormalized) => {
} router.push(menu)
}
const onContextmenu = (menu: RouteLocationNormalized, el: MouseEvent) => {
// 禁用刷新 const onContextmenu = (menu: RouteLocationNormalized, el: MouseEvent) => {
state.contextmenuItems[0].disabled = route.path !== menu.path
// 禁用关闭其他和关闭全部 // 禁用刷新
state.contextmenuItems[4].disabled = state.contextmenuItems[3].disabled = state.contextmenuItems[0].disabled = route.path !== menu.path
navTabs.state.tabsView.length == 1 ? true : false
const { clientX, clientY } = el // 禁用关闭其他和关闭全部
contextmenuRef.value.onShowContextmenu(menu, { state.contextmenuItems[4].disabled = state.contextmenuItems[3].disabled =
x: clientX, navTabs.state.tabsView.length == 1 ? true : false
y: clientY
}) const { clientX, clientY } = el
} contextmenuRef.value.onShowContextmenu(menu, {
x: clientX,
// tab 激活状态切换 y: clientY
const selectNavTab = function (dom: HTMLDivElement) { })
if (!dom) { }
return false
} // tab 激活状态切换
activeBoxStyle.width = dom.clientWidth + 'px' const selectNavTab = function (dom: HTMLDivElement) {
activeBoxStyle.transform = `translateX(${dom.offsetLeft}px)` if (!dom) {
return false
let scrollLeft = dom.offsetLeft + dom.clientWidth - tabScrollbarRef.value.clientWidth }
if (dom.offsetLeft < tabScrollbarRef.value.scrollLeft) { activeBoxStyle.width = dom.clientWidth + 'px'
tabScrollbarRef.value.scrollTo(dom.offsetLeft, 0) activeBoxStyle.transform = `translateX(${dom.offsetLeft}px)`
} else if (scrollLeft > tabScrollbarRef.value.scrollLeft) {
tabScrollbarRef.value.scrollTo(scrollLeft, 0) let scrollLeft = dom.offsetLeft + dom.clientWidth - tabScrollbarRef.value.clientWidth
} if (dom.offsetLeft < tabScrollbarRef.value.scrollLeft) {
} tabScrollbarRef.value.scrollTo(dom.offsetLeft, 0)
} else if (scrollLeft > tabScrollbarRef.value.scrollLeft) {
const toLastTab = () => { tabScrollbarRef.value.scrollTo(scrollLeft, 0)
const lastTab = navTabs.state.tabsView.slice(-1)[0] }
if (lastTab) { }
router.push(lastTab)
} else { const toLastTab = () => {
router.push(adminBaseRoutePath) const lastTab = navTabs.state.tabsView.slice(-1)[0]
} if (lastTab) {
} router.push(lastTab)
} else {
const closeTab = (route: RouteLocationNormalized) => { router.push(adminBaseRoutePath)
navTabs.closeTab(route) }
proxy.eventBus.emit('onTabViewClose', route) }
if (navTabs.state.activeRoute?.path === route.path) {
toLastTab() const closeTab = (route: RouteLocationNormalized) => {
} else { navTabs.closeTab(route)
navTabs.setActiveRoute(navTabs.state.activeRoute!) proxy.eventBus.emit('onTabViewClose', route)
nextTick(() => { if (navTabs.state.activeRoute?.path === route.path) {
selectNavTab(tabsRefs.value[navTabs.state.activeIndex]) toLastTab()
}) } else {
} navTabs.setActiveRoute(navTabs.state.activeRoute!)
nextTick(() => {
contextmenuRef.value.onHideContextmenu() selectNavTab(tabsRefs.value[navTabs.state.activeIndex])
} })
}
const closeOtherTab = (menu: RouteLocationNormalized) => {
navTabs.closeTabs(menu) contextmenuRef.value.onHideContextmenu()
navTabs.setActiveRoute(menu) }
if (navTabs.state.activeRoute?.path !== route.path) {
router.push(menu!.path) const closeOtherTab = (menu: RouteLocationNormalized) => {
} navTabs.closeTabs(menu)
} navTabs.setActiveRoute(menu)
if (navTabs.state.activeRoute?.path !== route.path) {
const closeAllTab = (menu: RouteLocationNormalized) => { router.push(menu!.path)
let firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes) }
if (firstRoute && firstRoute.path == menu.path) { }
return closeOtherTab(menu)
} const closeAllTab = (menu: RouteLocationNormalized) => {
if (firstRoute && firstRoute.path == navTabs.state.activeRoute?.path) { let firstRoute = getFirstRoute(navTabs.state.tabsViewRoutes)
return closeOtherTab(navTabs.state.activeRoute) if (firstRoute && firstRoute.path == menu.path) {
} return closeOtherTab(menu)
navTabs.closeTabs(false) }
if (firstRoute) routePush(firstRoute.path) if (firstRoute && firstRoute.path == navTabs.state.activeRoute?.path) {
} return closeOtherTab(navTabs.state.activeRoute)
}
const onContextmenuItem = async (item: ContextmenuItemClickEmitArg) => { navTabs.closeTabs(false)
const { name, menu } = item if (firstRoute) routePush(firstRoute.path)
if (!menu) return }
switch (name) {
case 'refresh': const onContextmenuItem = async (item: ContextmenuItemClickEmitArg) => {
proxy.eventBus.emit('onTabViewRefresh', menu) const { name, menu } = item
break if (!menu) return
case 'close': switch (name) {
closeTab(menu) case 'refresh':
break proxy.eventBus.emit('onTabViewRefresh', menu)
case 'closeOther': break
closeOtherTab(menu) case 'close':
break closeTab(menu)
case 'closeAll': break
closeAllTab(menu) case 'closeOther':
break closeOtherTab(menu)
case 'fullScreen': break
if (route.path !== menu?.path) { case 'closeAll':
router.push(menu?.path as string) closeAllTab(menu)
} break
navTabs.setFullScreen(true) case 'fullScreen':
break if (route.path !== menu?.path) {
} router.push(menu?.path as string)
} }
navTabs.setFullScreen(true)
const updateTab = function (newRoute: RouteLocationNormalized) { break
// 添加tab }
navTabs.addTab(newRoute) }
// 激活当前tab
navTabs.setActiveRoute(newRoute) const updateTab = function (newRoute: RouteLocationNormalized) {
// 添加tab
nextTick(() => { navTabs.addTab(newRoute)
selectNavTab(tabsRefs.value[navTabs.state.activeIndex]) // 激活当前tab
}) navTabs.setActiveRoute(newRoute)
}
nextTick(() => {
onBeforeRouteUpdate(async to => { selectNavTab(tabsRefs.value[navTabs.state.activeIndex])
})
}
updateTab(to)
onBeforeRouteUpdate(async to => {
})
onMounted(() => { updateTab(to)
updateTab(router.currentRoute.value)
new horizontalScroll(tabScrollbarRef.value) })
})
</script> onMounted(() => {
updateTab(router.currentRoute.value)
<style scoped lang="scss"> new horizontalScroll(tabScrollbarRef.value)
.dark { })
.close-icon { </script>
color: v-bind('config.getColorVal("headerBarTabColor")') !important;
} <style scoped lang="scss">
.dark {
.ba-nav-tab.active { .close-icon {
.close-icon { color: v-bind('config.getColorVal("headerBarTabColor")') !important;
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important; }
}
} .ba-nav-tab.active {
} .close-icon {
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important;
.nav-tabs { }
overflow-x: auto; }
overflow-y: hidden; }
margin-right: var(--ba-main-space);
scrollbar-width: none; .nav-tabs {
overflow-x: auto;
&::-webkit-scrollbar { overflow-y: hidden;
height: 5px; margin-right: var(--ba-main-space);
} scrollbar-width: none;
// &::-webkit-scrollbar {
//&::-webkit-scrollbar-thumb { height: 5px;
// background: #eaeaea; }
// border-radius: var(--el-border-radius-base);
// box-shadow: none; //
// -webkit-box-shadow: none; //&::-webkit-scrollbar-thumb {
//} // background: #eaeaea;
// // border-radius: var(--el-border-radius-base);
//&::-webkit-scrollbar-track { // box-shadow: none;
// background: v-bind('config.layout.layoutMode == "Default" ? "none":config.getColorVal("headerBarBackground")'); // -webkit-box-shadow: none;
//} //}
// //
//&:hover { //&::-webkit-scrollbar-track {
// &::-webkit-scrollbar-thumb:hover { // background: v-bind('config.layout.layoutMode == "Default" ? "none":config.getColorVal("headerBarBackground")');
// background: #c8c9cc; //}
// } //
//} //&:hover {
} // &::-webkit-scrollbar-thumb:hover {
// background: #c8c9cc;
.ba-nav-tab { // }
white-space: nowrap; //}
height: 40px; }
}
</style> .ba-nav-tab {
white-space: nowrap;
height: 40px;
}
</style>

View File

@@ -1,108 +1,169 @@
import { reactive } from 'vue' import { reactive } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { STORE_TAB_VIEW_CONFIG } from '@/stores/constant/cacheKey' import { STORE_TAB_VIEW_CONFIG } from '@/stores/constant/cacheKey'
import type { NavTabs } from '@/stores/interface/index' import type { NavTabs } from '@/stores/interface/index'
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router' import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
import { adminBaseRoutePath } from '@/router/static' import { adminBaseRoutePath } from '@/router/static'
import { set } from 'lodash'
export const useNavTabs = defineStore(
'navTabs', export const useNavTabs = defineStore(
() => { 'navTabs',
const state: NavTabs = reactive({ () => {
// 激活tab的index const state: NavTabs = reactive({
activeIndex: 0, // 激活tab的index
// 激活的tab activeIndex: 0,
activeRoute: null, // 激活的tab
// tab列表 activeRoute: null,
tabsView: [], // tab列表
// 当前tab是否全屏 tabsView: [],
tabFullScreen: false, // 当前tab是否全屏
// 从后台加载到的菜单路由列表 tabFullScreen: false,
tabsViewRoutes: [], // 从后台加载到的菜单路由列表
// 按钮权限节点 tabsViewRoutes: [],
authNode: new Map(), // 按钮权限节点
}) authNode: new Map()
})
function addTab(route: RouteLocationNormalized) {
if (!route.meta.addtab) return function addTab(route: RouteLocationNormalized) {
for (const key in state.tabsView) { console.log('🚀 ~ addTab ~ route:', route)
if (state.tabsView[key].path === route.path) { if (!route.meta.addtab) return
state.tabsView[key].params = route.params ? route.params : state.tabsView[key].params for (const key in state.tabsView) {
state.tabsView[key].query = route.query ? route.query : state.tabsView[key].query if (state.tabsView[key].path === route.path) {
return state.tabsView[key].params = route.params ? route.params : state.tabsView[key].params
} state.tabsView[key].query = route.query ? route.query : state.tabsView[key].query
} state.tabsView[key].meta = route.query ? route.meta : state.tabsView[key].meta
state.tabsView.push(route) return
} }
}
function closeTab(route: RouteLocationNormalized) { state.tabsView.push(route)
state.tabsView.map((v, k) => { }
if (v.path == route.path) {
state.tabsView.splice(k, 1) function closeTab(route: RouteLocationNormalized) {
return state.tabsView.map((v, k) => {
} if (v.path == route.path) {
}) state.tabsView.splice(k, 1)
} return
}
/** })
* 关闭多个标签 }
* @param retainMenu 需要保留的标签,否则关闭全部标签
*/ /**
const closeTabs = (retainMenu: RouteLocationNormalized | false = false) => { * 关闭多个标签
if (retainMenu) { * @param retainMenu 需要保留的标签,否则关闭全部标签
state.tabsView = [retainMenu] */
} else { const closeTabs = (retainMenu: RouteLocationNormalized | false = false) => {
state.tabsView = [] if (retainMenu) {
} state.tabsView = [retainMenu]
} } else {
state.tabsView = []
const setActiveRoute = (route: RouteLocationNormalized): void => { }
const currentRouteIndex: number = state.tabsView.findIndex((item: RouteLocationNormalized) => { }
return item.path === route.path
}) const setActiveRoute = (route: RouteLocationNormalized): void => {
if (currentRouteIndex === -1) return const currentRouteIndex: number = state.tabsView.findIndex((item: RouteLocationNormalized) => {
state.activeRoute = route return item.path === route.path
state.activeIndex = currentRouteIndex })
} if (currentRouteIndex === -1) return
state.activeRoute = route
const setTabsViewRoutes = (data: RouteRecordRaw[]): void => { state.activeIndex = currentRouteIndex
state.tabsViewRoutes = encodeRoutesURI(data) }
}
const setTabsViewRoutes = (data: RouteRecordRaw[]): void => {
const setAuthNode = (key: string, data: string[]) => { state.tabsViewRoutes = encodeRoutesURI(data)
state.authNode.set(key, data) }
}
const setAuthNode = (key: string, data: string[]) => {
const fillAuthNode = (data: Map<string, string[]>) => { state.authNode.set(key, data)
state.authNode = data }
}
const fillAuthNode = (data: Map<string, string[]>) => {
const setFullScreen = (fullScreen: boolean): void => { state.authNode = data
state.tabFullScreen = fullScreen }
}
const setFullScreen = (fullScreen: boolean): void => {
return { state, addTab, closeTab, closeTabs, setActiveRoute, setTabsViewRoutes, setAuthNode, fillAuthNode, setFullScreen } state.tabFullScreen = fullScreen
}, }
{
persist: { const refresh = () => {
key: STORE_TAB_VIEW_CONFIG, // setTimeout(() => {
paths: ['state.tabFullScreen'], // console.log(123, state.tabsViewRoutes)
},
} let list = matchAndReturnRouteData(state.tabsViewRoutes, state.tabsView)
) state.tabsView = []
list.forEach(item => {
/** addTab(item)
* 对iframe的url进行编码 })
*/ // }, 1000)
function encodeRoutesURI(data: RouteRecordRaw[]) { }
data.forEach((item) => { return {
if (item.meta?.menu_type == 'iframe') { state,
item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path) addTab,
} closeTab,
closeTabs,
if (item.children && item.children.length) { setActiveRoute,
item.children = encodeRoutesURI(item.children) setTabsViewRoutes,
} setAuthNode,
}) fillAuthNode,
return data setFullScreen,
} refresh
}
},
{
persist: {
key: STORE_TAB_VIEW_CONFIG,
paths: ['state.tabFullScreen']
}
}
)
/**
* 核心逻辑:
* 1. 递归遍历树形菜单筛选出与routeList中name匹配的节点
* 2. 将匹配到的节点格式转换为routeList的结构并返回
*/
function matchAndReturnRouteData(tree, routeList) {
// 1. 构建路由name映射name -> 完整路由对象)
const routeMap = new Map()
routeList.forEach(route => {
if (route.name) {
routeMap.set(route.name, route)
}
})
// 2. 递归遍历树形菜单,收集匹配的节点
const matchedNodes = []
function recursion(node) {
// 匹配当前节点
if (routeMap.has(node.name)) {
// 深度克隆路由对象,避免修改原数据
const matchedRoute = JSON.parse(JSON.stringify(routeMap.get(node.name)))
matchedNodes.push(matchedRoute)
}
// 递归处理子节点
if (node.children && node.children.length) {
node.children.forEach(child => recursion(child))
}
}
// 遍历所有顶级节点
tree.forEach(node => recursion(node))
// 3. 返回匹配后的第二个数据格式和routeList结构一致
return matchedNodes
}
/**
* 对iframe的url进行编码
*/
function encodeRoutesURI(data: RouteRecordRaw[]) {
data.forEach(item => {
if (item.meta?.menu_type == 'iframe') {
item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path)
}
if (item.children && item.children.length) {
item.children = encodeRoutesURI(item.children)
}
})
return data
}

View File

@@ -839,6 +839,7 @@ const lineId: any = ref('')
const dataLevel: any = ref('') const dataLevel: any = ref('')
const dataSource = ref([]) const dataSource = ref([])
const nodeClick = async (e: anyObj) => { const nodeClick = async (e: anyObj) => {
console.log("🚀 ~ nodeClick ~ e:", e)
if (e == undefined || e.level == 2) { if (e == undefined || e.level == 2) {
return (loading.value = false) return (loading.value = false)
} }

View File

@@ -0,0 +1,167 @@
<template>
<div class="default-main" :style="height">
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="size">
<!-- <pointTreeWx :default-expand-all="false" template @node-click="handleNodeClick" @init="handleNodeClick"
@Policy="stencil">
</pointTreeWx> -->
<CloudDeviceEntryTree
ref="TerminalRef"
template
@Policy="stencil"
@node-click="handleNodeClick"
@init="handleNodeClick"
></CloudDeviceEntryTree>
</pane>
<pane :size="(100 - size)" style="background: #fff" :style="height">
<TableHeader ref="TableHeaderRef" :showReset="false">
<template v-slot:select>
<el-form-item label="时间:">
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="模板策略">
<el-select v-model.trim="Template" @change="changetype" placeholder="请选择模版" value-key="id">
<el-option v-for="item in templatePolicy" :key="item.id" :label="item.name"
:value="item"></el-option>
</el-select>
</el-form-item>
</template>
<template #operation>
<el-button icon="el-icon-Download" type="primary" @click="exportEvent">导出excel</el-button>
</template>
</TableHeader>
<div class="box" v-loading="tableStore.table.loading">
<div id="luckysheet"
:style="`height: calc(${tableStore.table.height} + 45px)`"
v-if="tableStore.table.data.length > 0"></div>
<el-empty :style="`height: calc(${tableStore.table.height} + 45px)`" v-else description="暂无数据" />
</div>
</pane>
</splitpanes>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import TableStore from '@/utils/tableStore'
import pointTreeWx from '@/components/tree/govern/pointTreeWx.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel'
import { exportExcel } from '@/views/system/reportForms/export.js'
import 'splitpanes/dist/splitpanes.css'
import DatePicker from '@/components/form/datePicker/time.vue'
import { Splitpanes, Pane } from 'splitpanes'
import CloudDeviceEntryTree from '@/components/tree/govern/cloudDeviceEntryTree.vue'
// import data from './123.json'
defineOptions({
name: 'govern/reportCore/statisticsWx/index'
})
const height = mainHeight(20)
const size = ref(0)
const dictData = useDictData()
const TableHeaderRef = ref()
const dotList: any = ref({})
const Template: any = ref({})
const reportForm: any = ref('')
const datePickerRef = ref()
const templatePolicy: any = ref([])
const tableStore = new TableStore({
url: '/cs-harmonic-boot/customReport/getSensitiveUserReport',
method: 'POST',
column: [],
beforeSearchFun: () => {
tableStore.table.params.tempId = Template.value.id
tableStore.table.params.lineId = dotList.value.id
tableStore.table.params.startTime = datePickerRef.value.timeValue[0],
tableStore.table.params.endTime = datePickerRef.value.timeValue[1],
delete tableStore.table.params.searchBeginTime
delete tableStore.table.params.searchEndTime
delete tableStore.table.params.timeFlag
},
loadCallback: () => {
console.log("🚀 ~ tableStore.table:", tableStore.table)
// tableStore.table.data.forEach((item: any) => {
// item.data1 ? (item.data = JSON.parse(item.data1)) : ''
// item.celldata.forEach((k: any) => {
// item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
// })
// })
console.log("🚀 ~ tableStore.table:", tableStore.table)
setTimeout(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data
// tableStore.table.data
})
}, 10)
}
})
provide('tableStore', tableStore)
tableStore.table.params.resourceType = 1
tableStore.table.params.customType = 1
const flag = ref(true)
onMounted(() => {
nextTick(() => {
const dom = document.getElementById('navigation-splitpanes')
if (dom && dom.offsetHeight > 0) {
size.value = ((280 / (dom.offsetWidth - 7)) * 100)
} else {
// 设置默认值
size.value = 20
}
})
})
// getTemplateByDept({ id: dictData.state.area[0].id }).then((res: any) => {
// templatePolicy.value = res.data
// })
const stencil = (val: any) => {
templatePolicy.value = val.filter((item: any) => item.name != '稳态治理报表')
Template.value = templatePolicy.value[0]
reportForm.value = templatePolicy.value[0]?.reportForm
}
const changetype = (val: any) => {
reportForm.value = val.reportForm
}
const handleNodeClick = (data: any, node: any) => {
if (data?.level==4) {
dotList.value = data
setTimeout(() => {
tableStore.index()
}, 500)
} else {
tableStore.table.loading = false
}
}
const exportEvent = () => {
exportExcel(luckysheet.getAllSheets(), '稳态报表')
}
</script>
<style lang="scss">
.splitpanes.default-theme .splitpanes__pane {
background: #fff;
}
.box {
padding: 10px;
}
</style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<el-dialog v-model="dialogVisible" title="设置" width="600"> <el-dialog v-model="dialogVisible" title="自定义功能管理" width="600">
<div style="display: flex; justify-content: end" class="mb10"> <div style="display: flex; justify-content: end" class="mb10">
<el-button icon="el-icon-Plus" type="primary" @click="add">新增</el-button> <el-button icon="el-icon-Plus" type="primary" @click="add">新增</el-button>
</div> </div>
@@ -52,12 +52,13 @@ import { defaultAttribute } from '@/components/table/defaultAttribute'
import { getDashboardPageByUserId, deleteDashboard, activatePage } from '@/api/system-boot/csstatisticalset' import { getDashboardPageByUserId, deleteDashboard, activatePage } from '@/api/system-boot/csstatisticalset'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { useNavTabs } from '@/stores/navTabs'
import { getMenu } from '@/utils/router' import { getMenu } from '@/utils/router'
const { push } = useRouter() const { push } = useRouter()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const route = useRouter() const route = useRouter()
const navTabs = useNavTabs()
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
const pageList: any = ref([]) const pageList: any = ref([])
@@ -92,7 +93,7 @@ const beforeChange = (row: any): Promise<boolean> => {
type: 'warning' type: 'warning'
}) })
.then(() => { .then(() => {
activatePage({ id: row.id, state: row.state == 0 ? 1 : 0 }).then((res: any) => { activatePage({ id: row.id, state: row.state == 0 ? 1 : 0 }).then( async(res: any) => {
if (res.code == 'A0000') { if (res.code == 'A0000') {
ElMessage({ ElMessage({
type: 'success', type: 'success',
@@ -101,7 +102,11 @@ const beforeChange = (row: any): Promise<boolean> => {
} }
init() init()
resolve(true) resolve(true)
getMenu() await getMenu()
await setTimeout(() => {
navTabs.refresh()
}, 1000)
}) })
}) })
.catch(() => { .catch(() => {

View File

@@ -2,7 +2,7 @@
<div class="default-main"> <div class="default-main">
<TableHeader :showSearch="false" v-show="flag"> <TableHeader :showSearch="false" v-show="flag">
<template v-slot:select> <template v-slot:select>
<el-form-item label="日期"> <el-form-item label="日期" v-show="layout?.length != 1">
<DatePicker <DatePicker
ref="datePickerRef" ref="datePickerRef"
:nextFlag="false" :nextFlag="false"
@@ -13,7 +13,14 @@
</template> </template>
<template v-slot:operation> <template v-slot:operation>
<el-button type="primary" icon="el-icon-Edit" @click="editd">编辑</el-button> <el-button type="primary" icon="el-icon-Edit" @click="editd">编辑</el-button>
<el-button type="primary" icon="el-icon-Tools" @click="settings">设置</el-button> <el-button
type="primary"
icon="el-icon-Tools"
@click="settings"
v-if="router.currentRoute.value.name == 'dashboard/index'"
>
自定义功能管理
</el-button>
</template> </template>
</TableHeader> </TableHeader>
<GridLayout <GridLayout
@@ -221,6 +228,10 @@ const fetchLayoutData = async () => {
component: registerComponent(item.path) component: registerComponent(item.path)
})) }))
layoutCopy.value = JSON.parse(JSON.stringify(layout.value)) layoutCopy.value = JSON.parse(JSON.stringify(layout.value))
if (layout.value.length == 1) {
setZoom(layout.value[0])
}
initRowHeight() initRowHeight()
} catch (error) { } catch (error) {
console.error('获取布局数据失败:', error) console.error('获取布局数据失败:', error)

View File

@@ -21,21 +21,30 @@
@load="onIframeLoad" @load="onIframeLoad"
></iframe> ></iframe>
</div> </div>
</div>
<el-card class="bottom-container" style="min-height: 230px"> <div class="bottom-container">
<!-- <div class="buttonBox"> <el-button
type="primary"
:icon="show ? 'el-icon-ArrowDownBold' : 'el-icon-ArrowUpBold'"
@click="show = !show"
style="width: 100%"
>
事件列表
</el-button>
<!-- <div class="buttonBox">
<el-button type="primary" icon="el-icon-Aim" @click="reset">复位</el-button> <el-button type="primary" icon="el-icon-Aim" @click="reset">复位</el-button>
</div> --> </div> -->
<div class="tableBox">
<!-- <Table ref="tableRef" height="100%"></Table> --> <transition name="table-fade">
<vxe-table border auto-resize height="100%" :data="tableData" ref="tableRef"> <div class="tableBox" v-if="show">
<vxe-table border auto-resize height="230px" :data="tableData" ref="tableRef">
<vxe-column type="seq" title="序号" align="center" width="80px"></vxe-column> <vxe-column type="seq" title="序号" align="center" width="80px"></vxe-column>
<vxe-column field="date" align="center" title="时间" width="200px"></vxe-column> <vxe-column field="date" align="center" title="时间" width="200px"></vxe-column>
<vxe-column field="name" align="center" title="监测点名" width="200px"></vxe-column> <vxe-column field="name" align="center" title="监测点名" width="200px"></vxe-column>
<vxe-column field="address" align="center" title="事件描述"></vxe-column> <vxe-column field="address" align="center" title="事件描述"></vxe-column>
</vxe-table> </vxe-table>
</div> </div>
</el-card> </transition>
</div> </div>
</div> </div>
</template> </template>
@@ -49,7 +58,7 @@ import { mainHeight } from '@/utils/layout'
// const props = defineProps<{ // const props = defineProps<{
// project: { id: string; name: string } | null // project: { id: string; name: string } | null
// }>() // }>()
const show = ref(false)
const prop = defineProps({ const prop = defineProps({
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
@@ -57,7 +66,9 @@ const prop = defineProps({
timeValue: { type: Object } timeValue: { type: Object }
}) })
const tableData = ref() const tableData = ref([
])
// 在父页面中添加事件监听器 // 在父页面中添加事件监听器
window.addEventListener('message', function (event) { window.addEventListener('message', function (event) {
@@ -195,10 +206,10 @@ const sendKeysToIframe = (keyList: string[]) => {
flex: 3.5; flex: 3.5;
} }
:deep(.el-card__body) { .bottom-container {
display: flex; position: absolute;
padding: 10px; width: 100%;
height: 100%; bottom: 0px;
.buttonBox { .buttonBox {
display: flex; display: flex;
@@ -212,4 +223,39 @@ const sendKeysToIframe = (keyList: string[]) => {
width: 100%; width: 100%;
} }
} }
.tableBox {
/* 必须:初始高度/overflow 配合动画 */
overflow: hidden;
/* 与表格高度一致,保证展开后高度正确 */
height: 230px;
// margin-top: 10px;
}
/* 过渡动画核心样式 */
.table-fade-enter-from,
.table-fade-leave-to {
/* 关闭时高度收为0 + 透明度0 */
height: 0;
opacity: 0;
}
.table-fade-enter-to,
.table-fade-leave-from {
/* 展开时:高度恢复 + 透明度1 */
height: 230px;
opacity: 1;
}
/* 动画过渡时长 + 曲线(可自定义) */
.table-fade-enter-active,
.table-fade-leave-active {
transition: all 0.3s ease;
/* 防止动画过程中出现滚动条 */
overflow: hidden;
}
/* 解决动画结束后瞬间闪回的问题 */
.table-fade-enter-active {
transition-delay: 0.05s;
}
</style> </style>

View File

@@ -17,11 +17,20 @@
<el-form-item label="页面排序" prop="sort"> <el-form-item label="页面排序" prop="sort">
<el-input-number style="width: 100%" v-model.trim="form.sort" :min="0" :max="10000" :step="1" /> <el-input-number style="width: 100%" v-model.trim="form.sort" :min="0" :max="10000" :step="1" />
</el-form-item> </el-form-item>
<!-- <el-form-item label="绑定页面">
<el-select v-model="form.pagePath" filterable placeholder="请选择绑定页面" style="width: 100%" clearable> <el-form-item label="是否激活">
<el-option v-for="item in pageList" :key="item.path" :label="item.name" :value="item.path" />
</el-select> <el-switch
</el-form-item> --> v-model="form.state"
inline-prompt
:disabled="form.pagePath == 'dashboard/index'"
:active-value="1"
:inactive-value="0"
active-text=""
inactive-text=""
/>
</el-form-item>
<el-form-item label="备注" class="top"> <el-form-item label="备注" class="top">
<el-input <el-input
@@ -33,6 +42,7 @@
v-model.trim="form.remark" v-model.trim="form.remark"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div style="width: 100%; display: flex; justify-content: end"> <div style="width: 100%; display: flex; justify-content: end">
<el-button type="primary" icon="el-icon-Check" @click="onSubmit">保存</el-button> <el-button type="primary" icon="el-icon-Check" @click="onSubmit">保存</el-button>
@@ -135,14 +145,17 @@ import { addDashboard, updateDashboard, queryById } from '@/api/system-boot/csst
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { getMenu } from '@/utils/router' import { getMenu } from '@/utils/router'
import { useNavTabs } from '@/stores/navTabs'
// defineOptions({ // defineOptions({
// name: 'cockpit/popup' // name: 'cockpit/popup'
// }) // })
const { go } = useRouter() const { go } = useRouter()
const navTabs = useNavTabs()
const { query } = useRoute() const { query } = useRoute()
const router = useRouter() const router = useRouter()
const height = mainHeight(108) const height = mainHeight(148)
const indicatorHeight = mainHeight(128) const indicatorHeight = mainHeight(168)
const rowHeight = ref(0) const rowHeight = ref(0)
const pageList: any = ref([]) const pageList: any = ref([])
const GridHeight = ref(0) const GridHeight = ref(0)
@@ -153,7 +166,7 @@ const form: any = reactive({
containerConfig: [], containerConfig: [],
sort: '100', sort: '100',
id: '', id: '',
state: 1,
icon: '', icon: '',
pagePath: '', pagePath: '',
remark: '', remark: '',
@@ -209,6 +222,7 @@ const info = () => {
form.sort = res.data.sort form.sort = res.data.sort
form.remark = res.data.remark form.remark = res.data.remark
form.id = res.data.id form.id = res.data.id
form.state = res.data.state
form.icon = res.data.icon form.icon = res.data.icon
}) })
} else { } else {
@@ -367,22 +381,28 @@ const onSubmit = () => {
if (valid) { if (valid) {
if (form.id == '') { if (form.id == '') {
addDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then( await addDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then(
(res: any) => { async (res: any) => {
ElMessage.success('新增页面成功!') ElMessage.success('新增页面成功!')
go(-1) // go(-1)
getMenu() await getMenu()
} }
) )
} else { } else {
updateDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then( await updateDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then(
(res: any) => { async (res: any) => {
ElMessage.success('修改页面成功!') ElMessage.success('修改页面成功!')
go(-1) // go(-1)
getMenu() await getMenu()
} }
) )
} }
await setTimeout(() => {
router.push({
name: form.state == 1 ? form.pagePath : 'dashboard/index'
})
navTabs.refresh()
}, 500)
} }
}) })
} }
@@ -414,8 +434,9 @@ onBeforeUnmount(() => {
justify-content: space-between; justify-content: space-between;
.el-form-item { .el-form-item {
display: flex; display: flex;
flex: 1; // flex: 1;
align-items: center; // align-items: center;
width: 24%;
.el-form-item__content { .el-form-item__content {
width: 100%; width: 100%;
flex: 1; flex: 1;